Can wishlist a scrobbled record
This commit is contained in:
@@ -3,6 +3,8 @@ defmodule MusicBrainz.APIBehaviour do
|
|||||||
|
|
||||||
@callback get_release_group(musicbrainz_id) :: {:ok, map()} | {:error, String.t()}
|
@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 search_release_group(String.t(), Keyword.t()) :: {:ok, [map()]} | {:error, String.t()}
|
||||||
|
|
||||||
@callback get_cover_art(musicbrainz_id) :: {:ok, binary()} | {:error, String.t()}
|
@callback get_cover_art(musicbrainz_id) :: {:ok, binary()} | {:error, String.t()}
|
||||||
|
|||||||
@@ -85,6 +85,77 @@ defmodule MusicBrainz.APIImpl do
|
|||||||
json_get(url)
|
json_get(url)
|
||||||
end
|
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 """
|
@doc """
|
||||||
Uses the [search](https://musicbrainz.org/doc/MusicBrainz_API/Search#Release_Group) endpoint with a search query string.
|
Uses the [search](https://musicbrainz.org/doc/MusicBrainz_API/Search#Release_Group) endpoint with a search query string.
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,17 @@ defmodule MusicLibrary.Records do
|
|||||||
musicbrainz().search_release_group(query, limit: limit, offset: offset)
|
musicbrainz().search_release_group(query, limit: limit, offset: offset)
|
||||||
end
|
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
|
def import_from_musicbrainz(musicbrainz_id, opts \\ []) do
|
||||||
with format = Keyword.get(opts, :format, "cd"),
|
with format = Keyword.get(opts, :format, "cd"),
|
||||||
purchased_at = Keyword.get(opts, :purchased_at),
|
purchased_at = Keyword.get(opts, :purchased_at),
|
||||||
|
|||||||
@@ -41,6 +41,34 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
)}
|
)}
|
||||||
end
|
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
|
def handle_info(%{tracks: tracks}, socket) do
|
||||||
{:noreply, stream(socket, :recent_tracks, tracks, reset: true)}
|
{:noreply, stream(socket, :recent_tracks, tracks, reset: true)}
|
||||||
end
|
end
|
||||||
@@ -68,4 +96,14 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
|> DateTime.from_unix!()
|
|> DateTime.from_unix!()
|
||||||
|> DateTime.to_iso8601()
|
|> DateTime.to_iso8601()
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -80,8 +80,8 @@
|
|||||||
</span>
|
</span>
|
||||||
<div class="relative flex space-x-3 items-center">
|
<div class="relative flex space-x-3 items-center">
|
||||||
<img class="h-12 w-12 rounded-md shadow" src={track.cover_url} alt={track.title} />
|
<img class="h-12 w-12 rounded-md shadow" src={track.cover_url} alt={track.title} />
|
||||||
<div class="flex min-w-0 flex-1 justify-between space-x-4">
|
<div class="flex min-w-0 flex-1 justify-between space-x-4 items-center">
|
||||||
<div class="font-semibold">
|
<div class="font-semibold grow">
|
||||||
<p class="text-sm md:text-base block cursor-pointer text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-300">
|
<p class="text-sm md:text-base block cursor-pointer text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-300">
|
||||||
<%= track.artist.name %>
|
<%= track.artist.name %>
|
||||||
</p>
|
</p>
|
||||||
@@ -95,6 +95,61 @@
|
|||||||
>
|
>
|
||||||
<%= track.scrobbled_at_label %>
|
<%= track.scrobbled_at_label %>
|
||||||
</time>
|
</time>
|
||||||
|
|
||||||
|
<div class="relative flex-none">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-300"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-haspopup="true"
|
||||||
|
phx-click={toggle_actions_menu(track.scrobbled_at_uts)}
|
||||||
|
phx-click-away={close_actions_menu(track.scrobbled_at_uts)}
|
||||||
|
>
|
||||||
|
<span class="sr-only"><%= gettext("Open options") %></span>
|
||||||
|
<.icon
|
||||||
|
name="hero-ellipsis-vertical"
|
||||||
|
class="-mt-1 h-5 w-5"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<!--
|
||||||
|
Dropdown menu, show/hide based on menu state.
|
||||||
|
|
||||||
|
Entering: "transition ease-out duration-100"
|
||||||
|
From: "transform opacity-0 scale-95"
|
||||||
|
To: "transform opacity-100 scale-100"
|
||||||
|
Leaving: "transition ease-in duration-75"
|
||||||
|
From: "transform opacity-100 scale-100"
|
||||||
|
To: "transform opacity-0 scale-95"
|
||||||
|
-->
|
||||||
|
<.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) %>
|
||||||
|
</.link>
|
||||||
|
</.focus_wrap>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ msgid "Edit"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/record_live/index.ex:118
|
#: 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:114
|
||||||
#: lib/music_library_web/live/wishlist_live/index.ex:121
|
#: lib/music_library_web/live/wishlist_live/index.ex:121
|
||||||
#, elixir-autogen, elixir-format
|
#, 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/import_component.ex:76
|
||||||
#: lib/music_library_web/live/record_live/index.html.heex:89
|
#: 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
|
#: lib/music_library_web/live/wishlist_live/index.html.heex:89
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Open options"
|
msgid "Open options"
|
||||||
@@ -208,6 +211,7 @@ msgid "Purchased on"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/record_live/index.ex:109
|
#: 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
|
#: lib/music_library_web/live/wishlist_live/index.ex:106
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Record imported successfully"
|
msgid "Record imported successfully"
|
||||||
|
|||||||
Reference in New Issue
Block a user