diff --git a/lib/music_library/scrobble_activity.ex b/lib/music_library/scrobble_activity.ex index 67a2db8c..8af0a126 100644 --- a/lib/music_library/scrobble_activity.ex +++ b/lib/music_library/scrobble_activity.ex @@ -382,7 +382,25 @@ defmodule MusicLibrary.ScrobbleActivity do page_size = Map.get(params, :page_size, 200) order = Map.get(params, :order, :scrobbled_at) - base_query = from(t in Track) + all_artists_query = + from ar in ArtistRecord, + distinct: true + + base_query = + from t in Track, + left_join: cr in subquery(Collection.collected_releases_query()), + on: cr.release_id == fragment("? ->> '$.musicbrainz_id'", t.album), + left_join: wr in subquery(Wishlist.wishlisted_releases_query()), + on: wr.release_id == fragment("? ->> '$.musicbrainz_id'", t.album), + left_join: ar in subquery(all_artists_query), + on: wr.record_id == ar.record_id or cr.record_id == ar.record_id, + select: %{ + track: t, + collected_record_id: cr.record_id, + wishlisted_record_id: wr.record_id, + artist_id: ar.musicbrainz_id, + cover_hash: coalesce(cr.cover_hash, wr.cover_hash) + } search_query = if query == "" do @@ -393,8 +411,8 @@ defmodule MusicLibrary.ScrobbleActivity do from t in base_query, where: like(fragment("lower(?)", t.title), ^query_term) or - like(fragment("lower(json_extract(artist, '$.name'))"), ^query_term) or - like(fragment("lower(json_extract(album, '$.title'))"), ^query_term) + like(fragment("lower(json_extract(?, '$.name'))", t.artist), ^query_term) or + like(fragment("lower(json_extract(?, '$.title'))", t.album), ^query_term) end ordered_query = @@ -406,10 +424,10 @@ defmodule MusicLibrary.ScrobbleActivity do from t in search_query, order_by: [asc: t.title] :artist -> - from t in search_query, order_by: [asc: fragment("json_extract(artist, '$.name')")] + from t in search_query, order_by: [asc: fragment("json_extract(?, '$.name')", t.artist)] :album -> - from t in search_query, order_by: [asc: fragment("json_extract(album, '$.title')")] + from t in search_query, order_by: [asc: fragment("json_extract(?, '$.title')", t.album)] end offset = (page - 1) * page_size diff --git a/lib/music_library_web/live/scrobbled_tracks_live/index.ex b/lib/music_library_web/live/scrobbled_tracks_live/index.ex index e74cac09..3738ba1e 100644 --- a/lib/music_library_web/live/scrobbled_tracks_live/index.ex +++ b/lib/music_library_web/live/scrobbled_tracks_live/index.ex @@ -2,10 +2,12 @@ defmodule MusicLibraryWeb.ScrobbledTracksLive.Index do use MusicLibraryWeb, :live_view import MusicLibraryWeb.Components.Pagination + import MusicLibraryWeb.RecordComponents, only: [format_label: 1] import MusicLibraryWeb.ScrobbleComponents, only: [refresh_lastfm_feed_button: 1] alias LastFm.Track - alias MusicLibrary.ScrobbleActivity + alias MusicLibrary.Assets.Transform + alias MusicLibrary.{Records, ScrobbleActivity} @default_tracks_list_params %{ query: "", @@ -19,7 +21,9 @@ defmodule MusicLibraryWeb.ScrobbledTracksLive.Index do socket = socket |> assign(:current_section, :scrobble_activity) - |> stream_configure(:tracks, dom_id: fn %Track{scrobbled_at_uts: id} -> "tracks-#{id}" end) + |> stream_configure(:tracks, + dom_id: fn %{track: %Track{scrobbled_at_uts: id}} -> "tracks-#{id}" end + ) if connected?(socket) do LastFm.subscribe_to_feed() @@ -174,4 +178,20 @@ defmodule MusicLibraryWeb.ScrobbledTracksLive.Index do ~p"/scrobbled-tracks?#{qs}" end + + defp track_cover_url(track, nil) do + track.cover_url + end + + defp track_cover_url(track, cover_hash) do + if LastFm.fallback_cover?(track.cover_url) do + payload = + Transform.new(hash: cover_hash, width: 96) + |> Transform.encode!() + + ~p"/assets/#{payload}" + else + track.cover_url + end + end end diff --git a/lib/music_library_web/live/scrobbled_tracks_live/index.html.heex b/lib/music_library_web/live/scrobbled_tracks_live/index.html.heex index 9d8258ba..4fab5784 100644 --- a/lib/music_library_web/live/scrobbled_tracks_live/index.html.heex +++ b/lib/music_library_web/live/scrobbled_tracks_live/index.html.heex @@ -76,24 +76,28 @@
  • -
    +
    {"Cover track.album.title} + class="h-12 w-12 rounded-md shadow-sm" + src={track_cover_url(track, cover_hash)} + alt={track.title} onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"} />
    -
    -
    - <.icon name="hero-musical-note" class="h-6 w-6 text-zinc-400" /> -
    -

    @@ -112,6 +116,56 @@

    +
    + <.badge :if={track.album.musicbrainz_id == ""}> + {gettext("No MB ID")} + + <.link + :if={collected_record_id} + navigate={~p"/collection/#{collected_record_id}"} + > + <.badge color="success">{gettext("Collected")} + + <.link + :if={wishlisted_record_id} + navigate={~p"/wishlist/#{wishlisted_record_id}"} + > + <.badge color="warning">{gettext("Wishlisted")} + +
    + + <.dropdown + :if={ + track.album.musicbrainz_id !== "" and !collected_record_id and + !wishlisted_record_id + } + id={"actions-#{track.scrobbled_at_uts}-tracks"} + placement="bottom-end" + > + <:toggle> + {gettext("Choose which format to import")} + <.icon + name="hero-star" + class="-mt-1 h-5 w-5 text-zinc-500 dark:text-zinc-400 cursor-pointer" + aria-hidden="true" + data-slot="icon" + /> + + <.focus_wrap id={"actions-#{track.scrobbled_at_uts}-tracks-focus-wrap"}> + <.dropdown_link + :for={format <- Records.Record.formats()} + id={"actions-#{track.scrobbled_at_uts}-#{format}-import"} + phx-click={ + JS.push("import", + value: %{id: track.album.musicbrainz_id, format: format}, + page_loading: true + ) + } + > + {format_label(format)} + + + <.dropdown id={"actions-#{track.scrobbled_at_uts}"} placement="bottom-end"> <:toggle>
    diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 8144466d..cdee17e7 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -258,18 +258,21 @@ msgid "Scrobble activity" msgstr "" #: lib/music_library_web/components/add_record.ex +#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex #: lib/music_library_web/live/stats_live/index.html.heex #, elixir-autogen, elixir-format msgid "Choose which format to import" msgstr "" #: lib/music_library_web/components/barcode_scanner.ex +#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex #: lib/music_library_web/live/stats_live/index.html.heex #, elixir-autogen, elixir-format msgid "Collected" msgstr "" #: lib/music_library_web/components/barcode_scanner.ex +#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex #: lib/music_library_web/live/stats_live/index.html.heex #, elixir-autogen, elixir-format msgid "Wishlisted" @@ -314,6 +317,7 @@ msgstr "" msgid "Error refreshing cover" msgstr "" +#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex #: lib/music_library_web/live/stats_live/index.html.heex #, elixir-autogen, elixir-format msgid "No MB ID" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index ac2d9c2c..d7fb58fa 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -258,18 +258,21 @@ msgid "Scrobble activity" msgstr "" #: lib/music_library_web/components/add_record.ex +#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex #: lib/music_library_web/live/stats_live/index.html.heex #, elixir-autogen, elixir-format msgid "Choose which format to import" msgstr "" #: lib/music_library_web/components/barcode_scanner.ex +#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex #: lib/music_library_web/live/stats_live/index.html.heex #, elixir-autogen, elixir-format msgid "Collected" msgstr "" #: lib/music_library_web/components/barcode_scanner.ex +#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex #: lib/music_library_web/live/stats_live/index.html.heex #, elixir-autogen, elixir-format msgid "Wishlisted" @@ -314,6 +317,7 @@ msgstr "" msgid "Error refreshing cover" msgstr "" +#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex #: lib/music_library_web/live/stats_live/index.html.heex #, elixir-autogen, elixir-format msgid "No MB ID" diff --git a/test/music_library/scrobble_activity_test.exs b/test/music_library/scrobble_activity_test.exs index ae42078a..ee754f21 100644 --- a/test/music_library/scrobble_activity_test.exs +++ b/test/music_library/scrobble_activity_test.exs @@ -11,7 +11,7 @@ defmodule MusicLibrary.ScrobbleActivityTest do _track1 = track_fixture(%{title: "First Track"}) _track2 = track_fixture(%{title: "Second Track"}) - tracks = ScrobbleActivity.list_tracks() + tracks = list_tracks() assert length(tracks) == 2 track_titles = Enum.map(tracks, & &1.title) @@ -32,7 +32,7 @@ defmodule MusicLibrary.ScrobbleActivityTest do scrobbled_at_uts: System.system_time(:second) }) - tracks = ScrobbleActivity.list_tracks(%{order: :scrobbled_at}) + tracks = list_tracks(%{order: :scrobbled_at}) assert length(tracks) == 2 # Should be ordered by scrobbled_at_uts descending (newest first) @@ -44,7 +44,7 @@ defmodule MusicLibrary.ScrobbleActivityTest do track_fixture(%{title: "Zebra Track"}) track_fixture(%{title: "Alpha Track"}) - tracks = ScrobbleActivity.list_tracks(%{order: :title}) + tracks = list_tracks(%{order: :title}) assert length(tracks) == 2 assert List.first(tracks).title == "Alpha Track" @@ -55,7 +55,7 @@ defmodule MusicLibrary.ScrobbleActivityTest do track_fixture(%{artist_name: "Zebra Artist", title: "Track 1"}) track_fixture(%{artist_name: "Alpha Artist", title: "Track 2"}) - tracks = ScrobbleActivity.list_tracks(%{order: :artist}) + tracks = list_tracks(%{order: :artist}) assert length(tracks) == 2 assert List.first(tracks).artist.name == "Alpha Artist" @@ -66,7 +66,7 @@ defmodule MusicLibrary.ScrobbleActivityTest do track_fixture(%{album_title: "Zebra Album", title: "Track 1"}) track_fixture(%{album_title: "Alpha Album", title: "Track 2"}) - tracks = ScrobbleActivity.list_tracks(%{order: :album}) + tracks = list_tracks(%{order: :album}) assert length(tracks) == 2 assert List.first(tracks).album.title == "Alpha Album" @@ -77,7 +77,7 @@ defmodule MusicLibrary.ScrobbleActivityTest do track_fixture(%{title: "Special Track"}) track_fixture(%{title: "Regular Track"}) - tracks = ScrobbleActivity.list_tracks(%{query: "Special"}) + tracks = list_tracks(%{query: "Special"}) assert length(tracks) == 1 assert List.first(tracks).title == "Special Track" @@ -87,7 +87,7 @@ defmodule MusicLibrary.ScrobbleActivityTest do track_fixture(%{artist_name: "Special Artist", title: "Track 1"}) track_fixture(%{artist_name: "Regular Artist", title: "Track 2"}) - tracks = ScrobbleActivity.list_tracks(%{query: "Special Artist"}) + tracks = list_tracks(%{query: "Special Artist"}) assert length(tracks) == 1 assert List.first(tracks).artist.name == "Special Artist" @@ -97,7 +97,7 @@ defmodule MusicLibrary.ScrobbleActivityTest do track_fixture(%{album_title: "Special Album", title: "Track 1"}) track_fixture(%{album_title: "Regular Album", title: "Track 2"}) - tracks = ScrobbleActivity.list_tracks(%{query: "Special Album"}) + tracks = list_tracks(%{query: "Special Album"}) assert length(tracks) == 1 assert List.first(tracks).album.title == "Special Album" @@ -107,11 +107,11 @@ defmodule MusicLibrary.ScrobbleActivityTest do create_test_tracks(5) # Get first 2 tracks - tracks_page_1 = ScrobbleActivity.list_tracks(%{page: 1, page_size: 2}) + tracks_page_1 = list_tracks(%{page: 1, page_size: 2}) assert length(tracks_page_1) == 2 # Get next 2 tracks - tracks_page_2 = ScrobbleActivity.list_tracks(%{page: 2, page_size: 2}) + tracks_page_2 = list_tracks(%{page: 2, page_size: 2}) assert length(tracks_page_2) == 2 # Ensure they're different tracks @@ -123,7 +123,7 @@ defmodule MusicLibrary.ScrobbleActivityTest do test "returns empty list when query matches no tracks" do track_fixture(%{title: "Test Track"}) - tracks = ScrobbleActivity.list_tracks(%{query: "NonexistentTrack"}) + tracks = list_tracks(%{query: "NonexistentTrack"}) assert tracks == [] end @@ -282,4 +282,14 @@ defmodule MusicLibrary.ScrobbleActivityTest do assert new_count == initial_count + 3 end end + + defp list_tracks do + ScrobbleActivity.list_tracks() + |> Enum.map(fn r -> r.track end) + end + + defp list_tracks(params) do + ScrobbleActivity.list_tracks(params) + |> Enum.map(fn r -> r.track end) + end end