Add /scrobble/:rg_id release-group page
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
defmodule MusicLibraryWeb.ScrobbleLive.ReleaseGroupShow do
|
||||
@moduledoc false
|
||||
use MusicLibraryWeb, :live_view
|
||||
|
||||
require Logger
|
||||
|
||||
import MusicLibraryWeb.RecordComponents, only: [type_label: 1, country_label: 1]
|
||||
|
||||
alias MusicBrainz.{Release, ReleaseGroupSearchResult}
|
||||
alias MusicLibrary.Records
|
||||
alias MusicLibraryWeb.ErrorMessages
|
||||
alias Phoenix.LiveView.AsyncResult
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<Layouts.app
|
||||
flash={@flash}
|
||||
current_section={@current_section}
|
||||
socket={@socket}
|
||||
toasts_sync={assigns[:toasts_sync]}
|
||||
>
|
||||
<div class="my-4">
|
||||
<.button variant="ghost" size="sm" navigate={~p"/scrobble"}>
|
||||
<.icon name="hero-arrow-left" class="icon" aria-hidden="true" data-slot="icon" />
|
||||
{gettext("Back to search")}
|
||||
</.button>
|
||||
</div>
|
||||
|
||||
<.async_result :let={data} assign={@release_group_data}>
|
||||
<:loading>
|
||||
<div class="py-8 text-center">
|
||||
<.loading class="mx-auto size-8 text-zinc-400" />
|
||||
</div>
|
||||
</:loading>
|
||||
<:failed :let={_failure}>
|
||||
<div class="py-8 text-center text-zinc-500">
|
||||
{gettext("Could not load release group")}
|
||||
</div>
|
||||
</:failed>
|
||||
|
||||
<div class="flex items-start gap-4">
|
||||
<img
|
||||
class="w-32 flex-none rounded-lg drop-shadow-sm"
|
||||
alt={data.release_group.title}
|
||||
src={ReleaseGroupSearchResult.thumb_url(data.release_group)}
|
||||
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
|
||||
/>
|
||||
<div class="min-w-0 flex-auto">
|
||||
<h1 class="text-xl font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
{data.release_group.title}
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
{data.release_group.artists}
|
||||
</p>
|
||||
<div class="mt-2 flex flex-wrap items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400">
|
||||
<.badge variant="soft" size="xs">{type_label(data.release_group.type)}</.badge>
|
||||
<span>{Records.Record.format_release_date(data.release_group.release_date)}</span>
|
||||
<span>·</span>
|
||||
<span>
|
||||
{ngettext("%{count} release", "%{count} releases", length(data.releases),
|
||||
count: length(data.releases)
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="mt-6 divide-y divide-zinc-100 dark:divide-slate-300/30">
|
||||
<li :for={release <- data.releases}>
|
||||
<.link
|
||||
navigate={"/scrobble/#{@rg_id}/releases/#{release.id}"}
|
||||
class="flex items-center gap-x-4 px-4 py-5 transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-700"
|
||||
>
|
||||
<img
|
||||
class="w-20 flex-none rounded-lg"
|
||||
alt={release.title}
|
||||
src={Release.thumb_url(release)}
|
||||
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
|
||||
/>
|
||||
<div class="min-w-0 flex-auto">
|
||||
<p class="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{release.title}
|
||||
</p>
|
||||
<div class="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
<span :if={release.date}>{release.date}</span>
|
||||
<span :if={release.country}>{country_label(release.country)}</span>
|
||||
<.badge :if={release.catalog_number} variant="soft" size="xs">
|
||||
{release.catalog_number}
|
||||
</.badge>
|
||||
<span :if={release.media != []}>
|
||||
{ngettext("1 disc", "%{count} discs", Release.media_count(release))}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</.link>
|
||||
</li>
|
||||
</ul>
|
||||
</.async_result>
|
||||
</Layouts.app>
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok,
|
||||
assign(socket,
|
||||
current_section: :scrobble,
|
||||
release_group_data: AsyncResult.loading(),
|
||||
rg_id: nil,
|
||||
page_title: gettext("Scrobble")
|
||||
)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(%{"rg_id" => rg_id}, _url, socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:rg_id, rg_id)
|
||||
|> assign(:release_group_data, AsyncResult.loading())
|
||||
|> start_async(:release_group_data, fn -> load(rg_id) end)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_async(:release_group_data, {:ok, {:ok, data}}, socket) do
|
||||
{:noreply,
|
||||
assign(
|
||||
socket,
|
||||
:release_group_data,
|
||||
AsyncResult.ok(socket.assigns.release_group_data, data)
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_async(:release_group_data, {:ok, {:error, reason}}, socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_toast(
|
||||
:error,
|
||||
gettext("Error loading release group") <> ": " <> ErrorMessages.friendly_message(reason)
|
||||
)
|
||||
|> push_navigate(to: ~p"/scrobble")}
|
||||
end
|
||||
|
||||
def handle_async(:release_group_data, {:exit, reason}, socket) do
|
||||
Logger.error("Release-group show exited: #{inspect(reason)}")
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_toast(:error, gettext("Error loading release group"))
|
||||
|> push_navigate(to: ~p"/scrobble")}
|
||||
end
|
||||
|
||||
defp load(rg_id) do
|
||||
with {:ok, raw_rg} <- MusicBrainz.get_release_group(rg_id),
|
||||
release_group = ReleaseGroupSearchResult.from_api_response(raw_rg),
|
||||
{:ok, %{"releases" => raw_releases}} <- MusicBrainz.get_releases(rg_id, limit: 50) do
|
||||
releases = Enum.map(raw_releases, &Release.from_api_response/1)
|
||||
{:ok, %{release_group: release_group, releases: releases}}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -111,7 +111,7 @@ defmodule MusicLibraryWeb.Router do
|
||||
live "/scrobbled-tracks/:scrobbled_at_uts/edit", ScrobbledTracksLive.Index, :edit
|
||||
|
||||
live "/scrobble", ScrobbleLive.Index, :index
|
||||
live "/scrobble/:release_id", ScrobbleLive.Show, :show
|
||||
live "/scrobble/:rg_id", ScrobbleLive.ReleaseGroupShow, :show
|
||||
|
||||
live "/maintenance", MaintenanceLive.Index, :index
|
||||
end
|
||||
|
||||
@@ -1266,6 +1266,7 @@ msgid "90d"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/index.ex
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Scrobble"
|
||||
msgstr ""
|
||||
@@ -1286,6 +1287,7 @@ msgid "Release Groups"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/index.ex
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#: lib/music_library_web/live/scrobble_live/show.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "1 disc"
|
||||
@@ -1719,6 +1721,7 @@ msgstr ""
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#: lib/music_library_web/live/scrobble_live/show.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Back to search"
|
||||
@@ -2560,3 +2563,20 @@ msgid "across %{count} disc"
|
||||
msgid_plural "across %{count} discs"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} release"
|
||||
msgid_plural "%{count} releases"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not load release group"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Error loading release group"
|
||||
msgstr ""
|
||||
|
||||
@@ -666,11 +666,12 @@ msgstr ""
|
||||
|
||||
#: lib/music_library_web/components/release.ex
|
||||
#: lib/music_library_web/live/collection_live/show.ex
|
||||
#: lib/music_library_web/live/scrobble_live/show.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Scrobble release"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/components/release.ex
|
||||
#: lib/music_library_web/live/scrobble_live/show.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Scrobble selected tracks"
|
||||
msgstr ""
|
||||
@@ -1265,6 +1266,7 @@ msgid "90d"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/index.ex
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "Scrobble"
|
||||
msgstr ""
|
||||
@@ -1285,6 +1287,7 @@ msgid "Release Groups"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/index.ex
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#: lib/music_library_web/live/scrobble_live/show.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "1 disc"
|
||||
@@ -1718,6 +1721,7 @@ msgstr ""
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#: lib/music_library_web/live/scrobble_live/show.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Back to search"
|
||||
@@ -2559,3 +2563,20 @@ msgid "across %{count} disc"
|
||||
msgid_plural "across %{count} discs"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "%{count} release"
|
||||
msgid_plural "%{count} releases"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not load release group"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/scrobble_live/release_group_show.ex
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "Error loading release group"
|
||||
msgstr ""
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
defmodule MusicLibraryWeb.ScrobbleLive.ReleaseGroupShowTest do
|
||||
use MusicLibraryWeb.ConnCase
|
||||
|
||||
alias MusicBrainz.Fixtures.ReleaseGroup
|
||||
alias Req.Test
|
||||
|
||||
@rg_id ReleaseGroup.release_group_id(:marbles)
|
||||
|
||||
defp stub_release_group(_) do
|
||||
Test.stub(MusicBrainz.API, fn conn ->
|
||||
case conn.request_path do
|
||||
"/ws/2/release-group/" <> _id ->
|
||||
Test.json(conn, ReleaseGroup.release_group(:marbles))
|
||||
|
||||
"/ws/2/release" ->
|
||||
Test.json(conn, ReleaseGroup.release_group_releases(:marbles))
|
||||
|
||||
_ ->
|
||||
Test.json(conn, %{})
|
||||
end
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp stub_release_group_error(_) do
|
||||
Test.stub(MusicBrainz.API, fn conn ->
|
||||
Plug.Conn.send_resp(conn, 500, "Internal Server Error")
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "Show" do
|
||||
setup [:stub_release_group]
|
||||
|
||||
test "renders release-group title", %{conn: conn} do
|
||||
conn
|
||||
|> visit(~p"/scrobble/#{@rg_id}")
|
||||
|> assert_has("h1", text: "Marbles", timeout: 200)
|
||||
end
|
||||
|
||||
test "renders list of releases with link targets", %{conn: conn} do
|
||||
conn
|
||||
|> visit(~p"/scrobble/#{@rg_id}")
|
||||
|> assert_has("a[href^='/scrobble/#{@rg_id}/releases/']", timeout: 200)
|
||||
end
|
||||
|
||||
test "back link targets /scrobble", %{conn: conn} do
|
||||
conn
|
||||
|> visit(~p"/scrobble/#{@rg_id}")
|
||||
|> assert_has("a[href='/scrobble']", text: "Back to search")
|
||||
end
|
||||
end
|
||||
|
||||
describe "fetch failure" do
|
||||
setup [:stub_release_group_error]
|
||||
|
||||
@tag :capture_log
|
||||
test "shows toast and redirects to /scrobble", %{conn: conn} do
|
||||
conn
|
||||
|> visit(~p"/scrobble/#{@rg_id}")
|
||||
|> assert_has("#toast-group", text: "Error loading release group", timeout: 200)
|
||||
|> assert_path(~p"/scrobble")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,9 @@
|
||||
defmodule MusicLibraryWeb.ScrobbleLive.ShowTest do
|
||||
use MusicLibraryWeb.ConnCase
|
||||
|
||||
# Route /scrobble/:release_id is removed in Phase 5 — skip until then
|
||||
@moduletag :skip
|
||||
|
||||
import Phoenix.LiveViewTest, only: [element: 2, render_change: 2, render_click: 3]
|
||||
|
||||
alias MusicBrainz.Fixtures.Release, as: ReleaseFixtures
|
||||
|
||||
Reference in New Issue
Block a user