Add top albums to the stats page
Authored with Claude. > I'd like to have a new section in the Stats page. In this section I want to see the top albums I listened to in the past 30, 90, 365 days. Use the data in `scrobbled_tracks` to determine which albums need to be displayed. > This all looks great as a starter. I'd like to make some modifications. 1. Invert the position of the artist name and the album title (so that it's consistent with the Scrobble Activity) 2. Remove the border and shadow from around each album 3. Rename the "Last 365 days" section to "Last Year"
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
defmodule MusicLibrary.ScrobbleActivity do
|
||||
alias LastFm.Scrobble
|
||||
import Ecto.Query
|
||||
|
||||
alias LastFm.{Scrobble, Track}
|
||||
alias MusicBrainz.Release
|
||||
alias MusicLibrary.{Artists, Collection, Secrets, Wishlist}
|
||||
alias MusicLibrary.{Artists, Collection, Repo, Secrets, Wishlist}
|
||||
|
||||
def can_scrobble? do
|
||||
Secrets.get("last_fm_session_key") !== nil
|
||||
@@ -200,4 +202,46 @@ defmodule MusicLibrary.ScrobbleActivity do
|
||||
|> Enum.reject(fn musicbrainz_id -> musicbrainz_id == "" end)
|
||||
|> MapSet.new()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the top albums by scrobble count for the given number of days.
|
||||
Returns a list of maps with album information and play counts.
|
||||
"""
|
||||
def get_top_albums_by_days(days, limit \\ 10) do
|
||||
cutoff_timestamp =
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(-days, :day)
|
||||
|> DateTime.to_unix()
|
||||
|
||||
query =
|
||||
from t in Track,
|
||||
where: t.scrobbled_at_uts >= ^cutoff_timestamp,
|
||||
group_by: [
|
||||
fragment("json_extract(album, '$.title')"),
|
||||
fragment("json_extract(artist, '$.name')")
|
||||
],
|
||||
select: %{
|
||||
album_title: fragment("json_extract(album, '$.title')"),
|
||||
artist_name: fragment("json_extract(artist, '$.name')"),
|
||||
play_count: count(t.scrobbled_at_uts),
|
||||
cover_url: fragment("max(?)", t.cover_url),
|
||||
album_mbid: fragment("json_extract(album, '$.musicbrainz_id')")
|
||||
},
|
||||
order_by: [desc: count(t.scrobbled_at_uts)],
|
||||
limit: ^limit
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets top albums for multiple time periods (30, 90, 365 days).
|
||||
Returns a map with the results for each period.
|
||||
"""
|
||||
def get_top_albums_by_periods(limit \\ 10) do
|
||||
%{
|
||||
last_30_days: get_top_albums_by_days(30, limit),
|
||||
last_90_days: get_top_albums_by_days(90, limit),
|
||||
last_365_days: get_top_albums_by_days(365, limit)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -76,6 +76,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
||||
recent_tracks = LastFm.get_scrobbled_tracks()
|
||||
records_by_artists = Collection.count_records_by_artist(limit: 20)
|
||||
records_by_genre = Collection.count_records_by_genre(limit: 20)
|
||||
top_albums = ScrobbleActivity.get_top_albums_by_periods(10)
|
||||
|
||||
if connected?(socket) do
|
||||
LastFm.subscribe_to_feed()
|
||||
@@ -98,7 +99,8 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
||||
page_title: gettext("Stats"),
|
||||
current_section: :stats,
|
||||
records_by_artist: records_by_artists,
|
||||
records_by_genre: records_by_genre
|
||||
records_by_genre: records_by_genre,
|
||||
top_albums: top_albums
|
||||
)}
|
||||
end
|
||||
|
||||
|
||||
@@ -112,6 +112,92 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 class="mt-5 text-base lg:text-2xl text-zinc-900 dark:text-zinc-200 font-semibold">
|
||||
{gettext("Top Albums")}
|
||||
</h1>
|
||||
|
||||
<div class="mt-5 grid grid-cols-1 lg:grid-cols-3 gap-5">
|
||||
<div>
|
||||
<h2 class="text-base font-semibold text-zinc-900 dark:text-zinc-200 mb-3">
|
||||
{gettext("Last 30 days")}
|
||||
</h2>
|
||||
<div class="space-y-2">
|
||||
<div :for={album <- @top_albums.last_30_days} class="flex items-center space-x-3 p-2">
|
||||
<img
|
||||
class="w-12 h-12 rounded-md object-cover"
|
||||
src={album.cover_url}
|
||||
alt={album.album_title}
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs text-zinc-600 dark:text-zinc-400 truncate">
|
||||
{album.artist_name}
|
||||
</p>
|
||||
<p class="text-sm font-medium text-zinc-900 dark:text-zinc-300 truncate">
|
||||
{album.album_title}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-sm font-semibold text-zinc-900 dark:text-zinc-300">
|
||||
{album.play_count}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-base font-semibold text-zinc-900 dark:text-zinc-200 mb-3">
|
||||
{gettext("Last 90 days")}
|
||||
</h2>
|
||||
<div class="space-y-2">
|
||||
<div :for={album <- @top_albums.last_90_days} class="flex items-center space-x-3 p-2">
|
||||
<img
|
||||
class="w-12 h-12 rounded-md object-cover"
|
||||
src={album.cover_url}
|
||||
alt={album.album_title}
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs text-zinc-600 dark:text-zinc-400 truncate">
|
||||
{album.artist_name}
|
||||
</p>
|
||||
<p class="text-sm font-medium text-zinc-900 dark:text-zinc-300 truncate">
|
||||
{album.album_title}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-sm font-semibold text-zinc-900 dark:text-zinc-300">
|
||||
{album.play_count}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-base font-semibold text-zinc-900 dark:text-zinc-200 mb-3">
|
||||
{gettext("Last Year")}
|
||||
</h2>
|
||||
<div class="space-y-2">
|
||||
<div :for={album <- @top_albums.last_365_days} class="flex items-center space-x-3 p-2">
|
||||
<img
|
||||
class="w-12 h-12 rounded-md object-cover"
|
||||
src={album.cover_url}
|
||||
alt={album.album_title}
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs text-zinc-600 dark:text-zinc-400 truncate">
|
||||
{album.artist_name}
|
||||
</p>
|
||||
<p class="text-sm font-medium text-zinc-900 dark:text-zinc-300 truncate">
|
||||
{album.album_title}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-sm font-semibold text-zinc-900 dark:text-zinc-300">
|
||||
{album.play_count}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flow-root">
|
||||
<div class="mt-5 flex justify-between items-center">
|
||||
<h1 class="text-base lg:text-2xl text-zinc-900 dark:text-zinc-200 font-semibold">
|
||||
|
||||
@@ -885,3 +885,23 @@ msgstr ""
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Record deleted"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/stats_live/index.html.heex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Last 30 days"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/stats_live/index.html.heex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Last 90 days"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/stats_live/index.html.heex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Last Year"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/stats_live/index.html.heex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Top Albums"
|
||||
msgstr ""
|
||||
|
||||
@@ -885,3 +885,23 @@ msgstr ""
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Record deleted"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/stats_live/index.html.heex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Last 30 days"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/stats_live/index.html.heex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Last 90 days"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/stats_live/index.html.heex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Last Year"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/stats_live/index.html.heex
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "Top Albums"
|
||||
msgstr ""
|
||||
|
||||
Reference in New Issue
Block a user