From 0fd12ce44a5593ff7b8600f306a46e6a01d8ca4c Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Fri, 8 Nov 2024 19:36:05 +0000 Subject: [PATCH] Can wishlist a scrobbled record --- lib/music_brainz/api_behaviour.ex | 2 + lib/music_brainz/api_impl.ex | 71 +++++++++++++++++++ lib/music_library/records.ex | 11 +++ .../live/stats_live/index.ex | 38 ++++++++++ .../live/stats_live/index.html.heex | 59 ++++++++++++++- priv/gettext/default.pot | 4 ++ 6 files changed, 183 insertions(+), 2 deletions(-) diff --git a/lib/music_brainz/api_behaviour.ex b/lib/music_brainz/api_behaviour.ex index 117b622c..715007d2 100644 --- a/lib/music_brainz/api_behaviour.ex +++ b/lib/music_brainz/api_behaviour.ex @@ -3,6 +3,8 @@ defmodule MusicBrainz.APIBehaviour do @callback get_release_group(musicbrainz_id) :: {:ok, map()} | {:error, String.t()} + @callback get_release(musicbrainz_id) :: {:ok, map()} | {:error, String.t()} + @callback search_release_group(String.t(), Keyword.t()) :: {:ok, [map()]} | {:error, String.t()} @callback get_cover_art(musicbrainz_id) :: {:ok, binary()} | {:error, String.t()} diff --git a/lib/music_brainz/api_impl.ex b/lib/music_brainz/api_impl.ex index 62e34ac1..e5168636 100644 --- a/lib/music_brainz/api_impl.ex +++ b/lib/music_brainz/api_impl.ex @@ -85,6 +85,77 @@ defmodule MusicBrainz.APIImpl do json_get(url) end + @doc """ + Uses the [lookup](https://musicbrainz.org/doc/MusicBrainz_API#Lookups) endpoint with the release id and include the + release group. + + Example request: https://musicbrainz.org/ws/2/release/a444b9ca-865d-4f78-a7d9-7e68999e2ca9?fmt=json&inc=release-groups + + Example response: + { + "asin": null, + "barcode": null, + "country": "XW", + "cover-art-archive": { + "artwork": true, + "back": false, + "count": 1, + "darkened": false, + "front": true + }, + "date": "2022-05-05", + "disambiguation": "", + "id": "a444b9ca-865d-4f78-a7d9-7e68999e2ca9", + "packaging": null, + "packaging-id": null, + "quality": "normal", + "release-events": [ + { + "area": { + "disambiguation": "", + "id": "525d4e18-3d00-31b9-a58b-a146a916de8f", + "iso-3166-1-codes": [ + "XW" + ], + "name": "[Worldwide]", + "sort-name": "[Worldwide]", + "type": null, + "type-id": null + }, + "date": "2022-05-05" + } + ], + "release-group": { + "disambiguation": "", + "first-release-date": "2022-05-05", + "id": "6916dd75-e196-4d2f-986f-345579290043", + "primary-type": "Album", + "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "secondary-type-ids": [ + "22a628ad-c082-3c4f-b1b6-d41665107b88" + ], + "secondary-types": [ + "Soundtrack" + ], + "title": "Clark (A Dramatic Score From the Netflix Series)" + }, + "status": "Official", + "status-id": "4e304316-386d-3409-af2e-78857eec5cfe", + "text-representation": { + "language": null, + "script": null + }, + "title": "Clark (Soundtrack From the Netflix Series)" + } + """ + @impl true + def get_release(id) do + url = + "https://musicbrainz.org/ws/2/release/#{id}?fmt=json&inc=release-groups" + + json_get(url) + end + @doc """ Uses the [search](https://musicbrainz.org/doc/MusicBrainz_API/Search#Release_Group) endpoint with a search query string. diff --git a/lib/music_library/records.ex b/lib/music_library/records.ex index e79cb68b..fe39d80a 100644 --- a/lib/music_library/records.ex +++ b/lib/music_library/records.ex @@ -112,6 +112,17 @@ defmodule MusicLibrary.Records do musicbrainz().search_release_group(query, limit: limit, offset: offset) end + def import_from_musicbrainz_release(musicbrainz_id, opts \\ []) do + case musicbrainz().get_release(musicbrainz_id) do + {:ok, release} -> + release_group_id = release["release-group"]["id"] + import_from_musicbrainz(release_group_id, opts) + + error -> + error + end + end + def import_from_musicbrainz(musicbrainz_id, opts \\ []) do with format = Keyword.get(opts, :format, "cd"), purchased_at = Keyword.get(opts, :purchased_at), diff --git a/lib/music_library_web/live/stats_live/index.ex b/lib/music_library_web/live/stats_live/index.ex index ae287347..813f967e 100644 --- a/lib/music_library_web/live/stats_live/index.ex +++ b/lib/music_library_web/live/stats_live/index.ex @@ -41,6 +41,34 @@ defmodule MusicLibraryWeb.StatsLive.Index do )} end + def handle_event("import", %{"id" => musicbrainz_id, "format" => format}, socket) do + case Records.import_from_musicbrainz_release(musicbrainz_id, + format: format, + purchased_at: nil + ) do + {:ok, record} -> + {:noreply, + socket + |> put_flash(:info, gettext("Record imported successfully")) + |> push_navigate(to: ~p"/wishlist/#{record.id}")} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, + socket + |> put_flash( + :error, + gettext("Error importing record") <> "," <> inspect(changeset.errors) + ) + |> push_patch(to: ~p"/wishlist")} + + {:error, reason} -> + {:noreply, + socket + |> put_flash(:error, gettext("Error importing record") <> "," <> inspect(reason)) + |> push_patch(to: ~p"/wishlist")} + end + end + def handle_info(%{tracks: tracks}, socket) do {:noreply, stream(socket, :recent_tracks, tracks, reset: true)} end @@ -68,4 +96,14 @@ defmodule MusicLibraryWeb.StatsLive.Index do |> DateTime.from_unix!() |> DateTime.to_iso8601() end + + defp toggle_actions_menu(track_id) do + JS.toggle(to: "#actions-#{track_id}") + |> JS.toggle_class("pointer-events-none", to: "#scrobble-activity > li") + end + + def close_actions_menu(track_id) do + JS.hide(to: "#actions-#{track_id}") + |> JS.remove_class("pointer-events-none", to: "#scrobble-activity > li") + end end diff --git a/lib/music_library_web/live/stats_live/index.html.heex b/lib/music_library_web/live/stats_live/index.html.heex index c6e0707d..52f365d4 100644 --- a/lib/music_library_web/live/stats_live/index.html.heex +++ b/lib/music_library_web/live/stats_live/index.html.heex @@ -80,8 +80,8 @@
{track.title} -
-
+
+

<%= track.artist.name %>

@@ -95,6 +95,61 @@ > <%= track.scrobbled_at_label %> + +
+ + + <.focus_wrap + id={"actions-#{track.scrobbled_at_uts}"} + class={[ + "hidden pointer-events-auto absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white dark:bg-zinc-800 py-2 shadow-lg ring-1 ring-zinc-900/5 focus:outline-none" + ]} + role="menu" + aria-orientation="vertical" + aria-labelledby="options-menu-0-button" + tabindex="-1" + > + <.link + :for={format <- Records.Record.formats()} + class="block px-3 py-1 text-sm leading-6 text-zinc-900 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:text-zinc-300 dark:hover:bg-zinc-700" + role="menuitem" + tabindex="-1" + id={"actions-#{track.scrobbled_at_uts}-#{format}-import"} + phx-click={ + JS.push("import", + value: %{id: track.album.musicbrainz_id, format: format}, + page_loading: true + ) + } + > + <%= Records.Record.format_long_label(format) %> + + +
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 7b57ef9a..25a19bd8 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -82,6 +82,8 @@ msgid "Edit" msgstr "" #: lib/music_library_web/live/record_live/index.ex:118 +#: lib/music_library_web/live/stats_live/index.ex:60 +#: lib/music_library_web/live/stats_live/index.ex:67 #: lib/music_library_web/live/wishlist_live/index.ex:114 #: lib/music_library_web/live/wishlist_live/index.ex:121 #, elixir-autogen, elixir-format @@ -176,6 +178,7 @@ msgstr "" #: lib/music_library_web/live/record_live/import_component.ex:76 #: lib/music_library_web/live/record_live/index.html.heex:89 +#: lib/music_library_web/live/stats_live/index.html.heex:108 #: lib/music_library_web/live/wishlist_live/index.html.heex:89 #, elixir-autogen, elixir-format msgid "Open options" @@ -208,6 +211,7 @@ msgid "Purchased on" msgstr "" #: lib/music_library_web/live/record_live/index.ex:109 +#: lib/music_library_web/live/stats_live/index.ex:52 #: lib/music_library_web/live/wishlist_live/index.ex:106 #, elixir-autogen, elixir-format msgid "Record imported successfully"