Show top artists in stats page
This commit is contained in:
@@ -240,6 +240,40 @@ defmodule MusicLibrary.ScrobbleActivity do
|
|||||||
Repo.all(query)
|
Repo.all(query)
|
||||||
end
|
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 """
|
@doc """
|
||||||
Gets top albums for multiple time periods (30, 90, 365 days).
|
Gets top albums for multiple time periods (30, 90, 365 days).
|
||||||
Returns a map with the results for each period, along with collected and
|
Returns a map with the results for each period, along with collected and
|
||||||
@@ -268,6 +302,22 @@ defmodule MusicLibrary.ScrobbleActivity do
|
|||||||
}
|
}
|
||||||
end
|
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
|
defp resolve_timezone! do
|
||||||
Application.get_env(:music_library, MusicLibraryWeb)
|
Application.get_env(:music_library, MusicLibraryWeb)
|
||||||
|> Keyword.fetch!(:timezone)
|
|> Keyword.fetch!(:timezone)
|
||||||
|
|||||||
@@ -150,6 +150,54 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
"""
|
"""
|
||||||
end
|
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
|
defp refresh_lastfm_feed_button(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<button
|
<button
|
||||||
@@ -185,6 +233,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
|> assign_counts()
|
|> assign_counts()
|
||||||
|> assign_scrobble_activity(recent_tracks)
|
|> assign_scrobble_activity(recent_tracks)
|
||||||
|> assign_top_albums()
|
|> assign_top_albums()
|
||||||
|
|> assign_top_artists()
|
||||||
|> assign(
|
|> assign(
|
||||||
scrobble_activity_mode: :albums,
|
scrobble_activity_mode: :albums,
|
||||||
latest_record: latest_record,
|
latest_record: latest_record,
|
||||||
@@ -246,6 +295,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign_top_albums()
|
|> assign_top_albums()
|
||||||
|
|> assign_top_artists()
|
||||||
|> assign_scrobble_activity(recent_tracks)}
|
|> assign_scrobble_activity(recent_tracks)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -300,6 +350,20 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
assign(socket, top_albums: top_albums)
|
assign(socket, top_albums: top_albums)
|
||||||
end
|
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
|
defp tracked_record?(tracked_releases, release_id) do
|
||||||
Enum.find_value(tracked_releases, fn tracked_release ->
|
Enum.find_value(tracked_releases, fn tracked_release ->
|
||||||
if tracked_release.release_id == release_id, do: tracked_release.record_id
|
if tracked_release.release_id == release_id, do: tracked_release.record_id
|
||||||
|
|||||||
@@ -98,6 +98,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</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="flow-root">
|
||||||
<div class="mt-5 flex justify-between items-center">
|
<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">
|
<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
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
msgstr ""
|
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
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
msgstr ""
|
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