Show top artists in stats page
This commit is contained in:
@@ -240,6 +240,40 @@ defmodule MusicLibrary.ScrobbleActivity do
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the top artists by scrobble count for the given number of days.
|
||||
Returns a list of maps with artist information and play counts.
|
||||
"""
|
||||
def get_top_artists_by_days(days, opts) do
|
||||
limit = Keyword.get(opts, :limit, 10)
|
||||
current_time = Keyword.get_lazy(opts, :current_time, &DateTime.utc_now/0)
|
||||
timezone = Keyword.get(opts, :timezone, &resolve_timezone!/0)
|
||||
|
||||
cutoff_timestamp =
|
||||
current_time
|
||||
|> DateTime.add(-days, :day)
|
||||
|> NaiveDateTime.beginning_of_day()
|
||||
|> DateTime.from_naive!(timezone)
|
||||
|> DateTime.to_unix()
|
||||
|
||||
query =
|
||||
from t in Track,
|
||||
where: t.scrobbled_at_uts >= ^cutoff_timestamp,
|
||||
group_by: [
|
||||
fragment("json_extract(artist, '$.name')"),
|
||||
fragment("json_extract(artist, '$.musicbrainz_id')")
|
||||
],
|
||||
select: %{
|
||||
artist_name: fragment("json_extract(artist, '$.name')"),
|
||||
artist_musicbrainz_id: fragment("json_extract(artist, '$.musicbrainz_id')"),
|
||||
play_count: count(t.scrobbled_at_uts)
|
||||
},
|
||||
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, along with collected and
|
||||
@@ -268,6 +302,22 @@ defmodule MusicLibrary.ScrobbleActivity do
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets top artists for multiple time periods (30, 90, 365 days).
|
||||
Returns a map with the results for each period.
|
||||
"""
|
||||
def get_top_artists_by_periods(opts) do
|
||||
last_30_days = get_top_artists_by_days(30, opts)
|
||||
last_90_days = get_top_artists_by_days(90, opts)
|
||||
last_365_days = get_top_artists_by_days(365, opts)
|
||||
|
||||
%{
|
||||
last_30_days: last_30_days,
|
||||
last_90_days: last_90_days,
|
||||
last_365_days: last_365_days
|
||||
}
|
||||
end
|
||||
|
||||
defp resolve_timezone! do
|
||||
Application.get_env(:music_library, MusicLibraryWeb)
|
||||
|> Keyword.fetch!(:timezone)
|
||||
|
||||
@@ -150,6 +150,54 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
||||
"""
|
||||
end
|
||||
|
||||
attr :artists, :list, required: true
|
||||
attr :title, :string, required: true
|
||||
|
||||
def top_artists_by_period(assigns) do
|
||||
~H"""
|
||||
<div>
|
||||
<h2 class="text-base font-semibold text-zinc-900 dark:text-zinc-200 mb-3">
|
||||
{@title}
|
||||
</h2>
|
||||
<div class="space-y-2">
|
||||
<div :for={artist <- @artists} class="flex items-center space-x-3 p-2">
|
||||
<img
|
||||
:if={artist.artist_musicbrainz_id != ""}
|
||||
class="w-12 h-12 rounded-md object-cover"
|
||||
src={~p"/artists/#{artist.artist_musicbrainz_id}/image"}
|
||||
alt={artist.artist_name}
|
||||
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
|
||||
/>
|
||||
<div
|
||||
:if={artist.artist_musicbrainz_id == ""}
|
||||
class="w-12 h-12 rounded-md bg-zinc-200 dark:bg-zinc-700 flex items-center justify-center"
|
||||
>
|
||||
<.icon name="hero-user" class="w-6 h-6 text-zinc-400" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<.link
|
||||
:if={artist.artist_musicbrainz_id != ""}
|
||||
class="text-sm font-medium text-zinc-900 dark:text-zinc-300 hover:text-zinc-700 dark:hover:text-zinc-400 truncate"
|
||||
navigate={~p"/artists/#{artist.artist_musicbrainz_id}"}
|
||||
>
|
||||
{artist.artist_name}
|
||||
</.link>
|
||||
<p
|
||||
:if={artist.artist_musicbrainz_id == ""}
|
||||
class="text-sm font-medium text-zinc-500 dark:text-zinc-400 truncate"
|
||||
>
|
||||
{artist.artist_name}
|
||||
</p>
|
||||
</div>
|
||||
<.badge>
|
||||
{artist.play_count}
|
||||
</.badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp refresh_lastfm_feed_button(assigns) do
|
||||
~H"""
|
||||
<button
|
||||
@@ -185,6 +233,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
||||
|> assign_counts()
|
||||
|> assign_scrobble_activity(recent_tracks)
|
||||
|> assign_top_albums()
|
||||
|> assign_top_artists()
|
||||
|> assign(
|
||||
scrobble_activity_mode: :albums,
|
||||
latest_record: latest_record,
|
||||
@@ -246,6 +295,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign_top_albums()
|
||||
|> assign_top_artists()
|
||||
|> assign_scrobble_activity(recent_tracks)}
|
||||
end
|
||||
|
||||
@@ -300,6 +350,20 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
||||
assign(socket, top_albums: top_albums)
|
||||
end
|
||||
|
||||
defp assign_top_artists(socket) do
|
||||
timezone = socket.assigns.timezone
|
||||
current_time = DateTime.utc_now()
|
||||
|
||||
top_artists =
|
||||
ScrobbleActivity.get_top_artists_by_periods(
|
||||
limit: 10,
|
||||
current_time: current_time,
|
||||
timezone: timezone
|
||||
)
|
||||
|
||||
assign(socket, top_artists: top_artists)
|
||||
end
|
||||
|
||||
defp tracked_record?(tracked_releases, release_id) do
|
||||
Enum.find_value(tracked_releases, fn tracked_release ->
|
||||
if tracked_release.release_id == release_id, do: tracked_release.record_id
|
||||
|
||||
@@ -98,6 +98,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<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">
|
||||
{gettext("Top Artists")}
|
||||
</h1>
|
||||
<.refresh_lastfm_feed_button />
|
||||
</div>
|
||||
|
||||
<div class="mt-5 grid grid-cols-1 lg:grid-cols-3 gap-5">
|
||||
<.top_artists_by_period artists={@top_artists.last_30_days} title={gettext("Last 30 days")} />
|
||||
<.top_artists_by_period artists={@top_artists.last_90_days} title={gettext("Last 90 days")} />
|
||||
<.top_artists_by_period artists={@top_artists.last_365_days} title={gettext("Last year")} />
|
||||
</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">
|
||||
|
||||
@@ -908,3 +908,8 @@ msgstr ""
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/stats_live/index.html.heex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Top Artists"
|
||||
msgstr ""
|
||||
|
||||
@@ -908,3 +908,8 @@ msgstr ""
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/stats_live/index.html.heex
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "Top Artists"
|
||||
msgstr ""
|
||||
|
||||
Reference in New Issue
Block a user