Switch the release component to a form to enable recovery
This commit is contained in:
@@ -19,7 +19,9 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
|> assign(:can_scrobble?, ScrobbleActivity.can_scrobble?())
|
|> assign(:can_scrobble?, ScrobbleActivity.can_scrobble?())
|
||||||
|> assign(:release_with_tracks, AsyncResult.loading())
|
|> assign(:release_with_tracks, AsyncResult.loading())
|
||||||
|> assign(:already_scrobbled, false)
|
|> assign(:already_scrobbled, false)
|
||||||
|> assign(:selected_tracks, MapSet.new())}
|
|> assign(:selected_tracks, MapSet.new())
|
||||||
|
|> assign(:form, to_form(%{}, as: :release))
|
||||||
|
|> assign(:pending_form_params, nil)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -27,10 +29,9 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(assigns)
|
|> assign(assigns)
|
||||||
|> assign_async(:release_with_tracks, fn ->
|
|> assign(:release_with_tracks, AsyncResult.loading())
|
||||||
with {:ok, release} <- load_release_with_tracks(record.selected_release_id) do
|
|> start_async(:release_with_tracks, fn ->
|
||||||
{:ok, %{release_with_tracks: release}}
|
load_release_with_tracks(record.selected_release_id)
|
||||||
end
|
|
||||||
end)}
|
end)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -38,6 +39,52 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
{:ok, assign(socket, assigns)}
|
{:ok, assign(socket, assigns)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_async(:release_with_tracks, {:ok, {:ok, release}}, socket) do
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(
|
||||||
|
:release_with_tracks,
|
||||||
|
AsyncResult.ok(socket.assigns.release_with_tracks, release)
|
||||||
|
)
|
||||||
|
|> apply_pending_form_params()
|
||||||
|
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_async(:release_with_tracks, {:ok, {:error, reason}}, socket) do
|
||||||
|
{:noreply,
|
||||||
|
assign(
|
||||||
|
socket,
|
||||||
|
:release_with_tracks,
|
||||||
|
AsyncResult.failed(socket.assigns.release_with_tracks, {:error, reason})
|
||||||
|
)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_async(:release_with_tracks, {:exit, reason}, socket) do
|
||||||
|
{:noreply,
|
||||||
|
assign(
|
||||||
|
socket,
|
||||||
|
:release_with_tracks,
|
||||||
|
AsyncResult.failed(socket.assigns.release_with_tracks, {:exit, reason})
|
||||||
|
)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_pending_form_params(socket) do
|
||||||
|
case socket.assigns.pending_form_params do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|
||||||
|
params ->
|
||||||
|
release = socket.assigns.release_with_tracks.result
|
||||||
|
new_selected = apply_form_params(release, params, socket.assigns.selected_tracks)
|
||||||
|
|
||||||
|
socket
|
||||||
|
|> assign(:selected_tracks, new_selected)
|
||||||
|
|> assign(:pending_form_params, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
@@ -48,102 +95,115 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
placement="right"
|
placement="right"
|
||||||
class="min-w-xs sm:min-w-sm"
|
class="min-w-xs sm:min-w-sm"
|
||||||
>
|
>
|
||||||
<div class="mt-6 flex items-center justify-between gap-4">
|
<.form
|
||||||
<label class="cursor-pointer text-lg font-semibold text-zinc-700 dark:text-zinc-300">
|
for={@form}
|
||||||
<input
|
id={"#{@sheet_id}-form"}
|
||||||
:if={
|
phx-target={@myself}
|
||||||
@can_scrobble? && @release_with_tracks.ok? &&
|
phx-change="validate"
|
||||||
Release.media_count(@release_with_tracks.result) == 1
|
phx-auto-recover="recover_form"
|
||||||
}
|
>
|
||||||
type="checkbox"
|
<input type="hidden" name="release[selected_tracks][]" value="" />
|
||||||
id="medium-checkbox-1"
|
<input type="hidden" name="release[toggle_medium][]" value="" />
|
||||||
checked={
|
|
||||||
medium_selected?(
|
|
||||||
Release.get_medium(@release_with_tracks.result, 1),
|
|
||||||
@selected_tracks
|
|
||||||
)
|
|
||||||
}
|
|
||||||
phx-click="toggle_medium"
|
|
||||||
phx-value-medium-number={1}
|
|
||||||
phx-target={@myself}
|
|
||||||
class="mr-2 size-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600"
|
|
||||||
/>
|
|
||||||
{gettext("Tracks")}
|
|
||||||
</label>
|
|
||||||
<.button_group>
|
|
||||||
<.button
|
|
||||||
:if={@release_with_tracks.ok?}
|
|
||||||
variant="soft"
|
|
||||||
size="sm"
|
|
||||||
phx-click="print_tracklist"
|
|
||||||
phx-target={@myself}
|
|
||||||
>
|
|
||||||
<span class="sr-only">{gettext("Print tracklist")}</span>
|
|
||||||
<.icon name="hero-printer" class="icon" aria-hidden="true" data-slot="icon" />
|
|
||||||
</.button>
|
|
||||||
<.button
|
|
||||||
:if={@can_scrobble? && @release_with_tracks.ok?}
|
|
||||||
variant="soft"
|
|
||||||
size="sm"
|
|
||||||
disabled={@already_scrobbled}
|
|
||||||
phx-click={
|
|
||||||
if MapSet.size(@selected_tracks) > 0,
|
|
||||||
do: "scrobble_selected_tracks",
|
|
||||||
else: "scrobble_release"
|
|
||||||
}
|
|
||||||
phx-target={@myself}
|
|
||||||
phx-disable-with={gettext("Scrobbling...")}
|
|
||||||
>
|
|
||||||
<span class="sr-only">{scrobble_button_label(@selected_tracks)}</span>
|
|
||||||
<.icon name="hero-play" class="icon" aria-hidden="true" data-slot="icon" />
|
|
||||||
</.button>
|
|
||||||
<.button :if={!@can_scrobble?} variant="soft" size="sm" href={LastFm.auth_url()}>
|
|
||||||
<span class="sr-only">{gettext("Connect your Last.fm account")}</span>
|
|
||||||
<.icon name="hero-link" class="icon" aria-hidden="true" data-slot="icon" />
|
|
||||||
</.button>
|
|
||||||
</.button_group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :if={@release_with_tracks} class="mt-4 space-y-4">
|
<div class="mt-6 flex items-center justify-between gap-4">
|
||||||
<.async_result :let={release_with_tracks} assign={@release_with_tracks}>
|
<label class="cursor-pointer text-lg font-semibold text-zinc-700 dark:text-zinc-300">
|
||||||
<:loading>
|
<input
|
||||||
<div class="mt-48 flex items-center justify-center">
|
:if={
|
||||||
<span class="sr-only">{gettext("Loading release with tracks")}</span>
|
@can_scrobble? && @release_with_tracks.ok? &&
|
||||||
<.loading />
|
Release.media_count(@release_with_tracks.result) == 1
|
||||||
</div>
|
}
|
||||||
</:loading>
|
type="checkbox"
|
||||||
<:failed :let={_failure}>
|
id="medium-checkbox-1"
|
||||||
<div class="mt-4 text-sm/5 text-zinc-500 dark:text-zinc-400">
|
name="release[toggle_medium][]"
|
||||||
<.icon
|
value="1"
|
||||||
name="hero-exclamation-triangle"
|
checked={
|
||||||
class="size-5"
|
medium_selected?(
|
||||||
aria-hidden="true"
|
Release.get_medium(@release_with_tracks.result, 1),
|
||||||
data-slot="icon"
|
@selected_tracks
|
||||||
/>
|
)
|
||||||
{gettext("Error loading tracks")}
|
}
|
||||||
<.button
|
class="mr-2 size-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600"
|
||||||
variant="ghost"
|
/>
|
||||||
size="xs"
|
{gettext("Tracks")}
|
||||||
phx-click={JS.push("load_release_tracks", target: @myself)}
|
</label>
|
||||||
class="ml-2 cursor-pointer"
|
<.button_group>
|
||||||
>
|
<.button
|
||||||
{gettext("Retry")}
|
:if={@release_with_tracks.ok?}
|
||||||
</.button>
|
type="button"
|
||||||
</div>
|
variant="soft"
|
||||||
</:failed>
|
size="sm"
|
||||||
<.medium
|
phx-click="print_tracklist"
|
||||||
:for={medium <- release_with_tracks.media}
|
phx-target={@myself}
|
||||||
can_scrobble?={@can_scrobble?}
|
>
|
||||||
already_scrobbled={@already_scrobbled}
|
<span class="sr-only">{gettext("Print tracklist")}</span>
|
||||||
medium={medium}
|
<.icon name="hero-printer" class="icon" aria-hidden="true" data-slot="icon" />
|
||||||
release_artists={release_with_tracks.artists}
|
</.button>
|
||||||
media_count={MusicBrainz.Release.media_count(release_with_tracks)}
|
<.button
|
||||||
selected_tracks={@selected_tracks}
|
:if={@can_scrobble? && @release_with_tracks.ok?}
|
||||||
myself={@myself}
|
type="button"
|
||||||
record={@record}
|
variant="soft"
|
||||||
/>
|
size="sm"
|
||||||
</.async_result>
|
disabled={@already_scrobbled}
|
||||||
</div>
|
phx-click={
|
||||||
|
if MapSet.size(@selected_tracks) > 0,
|
||||||
|
do: "scrobble_selected_tracks",
|
||||||
|
else: "scrobble_release"
|
||||||
|
}
|
||||||
|
phx-target={@myself}
|
||||||
|
phx-disable-with={gettext("Scrobbling...")}
|
||||||
|
>
|
||||||
|
<span class="sr-only">{scrobble_button_label(@selected_tracks)}</span>
|
||||||
|
<.icon name="hero-play" class="icon" aria-hidden="true" data-slot="icon" />
|
||||||
|
</.button>
|
||||||
|
<.button :if={!@can_scrobble?} variant="soft" size="sm" href={LastFm.auth_url()}>
|
||||||
|
<span class="sr-only">{gettext("Connect your Last.fm account")}</span>
|
||||||
|
<.icon name="hero-link" class="icon" aria-hidden="true" data-slot="icon" />
|
||||||
|
</.button>
|
||||||
|
</.button_group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :if={@release_with_tracks} class="mt-4 space-y-4">
|
||||||
|
<.async_result :let={release_with_tracks} assign={@release_with_tracks}>
|
||||||
|
<:loading>
|
||||||
|
<div class="mt-48 flex items-center justify-center">
|
||||||
|
<span class="sr-only">{gettext("Loading release with tracks")}</span>
|
||||||
|
<.loading />
|
||||||
|
</div>
|
||||||
|
</:loading>
|
||||||
|
<:failed :let={_failure}>
|
||||||
|
<div class="mt-4 text-sm/5 text-zinc-500 dark:text-zinc-400">
|
||||||
|
<.icon
|
||||||
|
name="hero-exclamation-triangle"
|
||||||
|
class="size-5"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
{gettext("Error loading tracks")}
|
||||||
|
<.button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="xs"
|
||||||
|
phx-click={JS.push("load_release_tracks", target: @myself)}
|
||||||
|
class="ml-2 cursor-pointer"
|
||||||
|
>
|
||||||
|
{gettext("Retry")}
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
|
</:failed>
|
||||||
|
<.medium
|
||||||
|
:for={medium <- release_with_tracks.media}
|
||||||
|
can_scrobble?={@can_scrobble?}
|
||||||
|
already_scrobbled={@already_scrobbled}
|
||||||
|
medium={medium}
|
||||||
|
release_artists={release_with_tracks.artists}
|
||||||
|
media_count={MusicBrainz.Release.media_count(release_with_tracks)}
|
||||||
|
selected_tracks={@selected_tracks}
|
||||||
|
myself={@myself}
|
||||||
|
record={@record}
|
||||||
|
/>
|
||||||
|
</.async_result>
|
||||||
|
</div>
|
||||||
|
</.form>
|
||||||
</.sheet>
|
</.sheet>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
@@ -169,10 +229,9 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
:if={@can_scrobble?}
|
:if={@can_scrobble?}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={"medium-checkbox-#{@medium.number}"}
|
id={"medium-checkbox-#{@medium.number}"}
|
||||||
|
name="release[toggle_medium][]"
|
||||||
|
value={@medium.number}
|
||||||
checked={medium_selected?(@medium, @selected_tracks)}
|
checked={medium_selected?(@medium, @selected_tracks)}
|
||||||
phx-click="toggle_medium"
|
|
||||||
phx-value-medium-number={@medium.number}
|
|
||||||
phx-target={@myself}
|
|
||||||
class="mr-2 size-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600"
|
class="mr-2 size-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600"
|
||||||
/>
|
/>
|
||||||
{medium_title(@medium)}
|
{medium_title(@medium)}
|
||||||
@@ -183,6 +242,7 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
<.button_group>
|
<.button_group>
|
||||||
<.button
|
<.button
|
||||||
:if={@record}
|
:if={@record}
|
||||||
|
type="button"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
size="sm"
|
size="sm"
|
||||||
phx-click="print_medium_tracklist"
|
phx-click="print_medium_tracklist"
|
||||||
@@ -194,6 +254,7 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
</.button>
|
</.button>
|
||||||
<.button
|
<.button
|
||||||
:if={@can_scrobble?}
|
:if={@can_scrobble?}
|
||||||
|
type="button"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={@already_scrobbled || MapSet.size(@selected_tracks) > 0}
|
disabled={@already_scrobbled || MapSet.size(@selected_tracks) > 0}
|
||||||
@@ -213,7 +274,6 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
release_artists={@release_artists}
|
release_artists={@release_artists}
|
||||||
selected_tracks={@selected_tracks}
|
selected_tracks={@selected_tracks}
|
||||||
can_scrobble?={@can_scrobble?}
|
can_scrobble?={@can_scrobble?}
|
||||||
myself={@myself}
|
|
||||||
/>
|
/>
|
||||||
<.separator />
|
<.separator />
|
||||||
<p class="text-right text-xs text-zinc-700 md:text-sm dark:text-zinc-300">
|
<p class="text-right text-xs text-zinc-700 md:text-sm dark:text-zinc-300">
|
||||||
@@ -235,7 +295,6 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
attr :release_artists, :list, required: true
|
attr :release_artists, :list, required: true
|
||||||
attr :selected_tracks, :any, required: true
|
attr :selected_tracks, :any, required: true
|
||||||
attr :can_scrobble?, :boolean, required: true
|
attr :can_scrobble?, :boolean, required: true
|
||||||
attr :myself, :any, required: true
|
|
||||||
|
|
||||||
def track_list(assigns) do
|
def track_list(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
@@ -250,10 +309,9 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={"track-checkbox-#{track.id}"}
|
id={"track-checkbox-#{track.id}"}
|
||||||
|
name="release[selected_tracks][]"
|
||||||
|
value={track.id}
|
||||||
checked={MapSet.member?(@selected_tracks, track.id)}
|
checked={MapSet.member?(@selected_tracks, track.id)}
|
||||||
phx-click="toggle_track"
|
|
||||||
phx-value-track-id={track.id}
|
|
||||||
phx-target={@myself}
|
|
||||||
class="size-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600"
|
class="size-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -347,38 +405,25 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("toggle_track", %{"track-id" => track_id}, socket) do
|
def handle_event("validate", %{"release" => params}, socket)
|
||||||
selected_tracks = socket.assigns.selected_tracks
|
when release_loaded?(socket.assigns) do
|
||||||
|
release = socket.assigns.release_with_tracks.result
|
||||||
updated_tracks =
|
new_selected = apply_form_params(release, params, socket.assigns.selected_tracks)
|
||||||
if MapSet.member?(selected_tracks, track_id) do
|
{:noreply, assign(socket, :selected_tracks, new_selected)}
|
||||||
MapSet.delete(selected_tracks, track_id)
|
|
||||||
else
|
|
||||||
MapSet.put(selected_tracks, track_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply, assign(socket, :selected_tracks, updated_tracks)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("toggle_medium", %{"medium-number" => number}, socket) do
|
def handle_event("validate", %{"release" => params}, socket) do
|
||||||
{number, ""} = Integer.parse(number)
|
# Release not yet loaded (e.g., phx-auto-recover fires before async completes).
|
||||||
selected_tracks = socket.assigns.selected_tracks
|
# Stash the params; handle_async/3 will apply them once the release is available.
|
||||||
release_with_tracks = socket.assigns.release_with_tracks.result
|
{:noreply, assign(socket, :pending_form_params, params)}
|
||||||
|
end
|
||||||
|
|
||||||
medium_tracks =
|
def handle_event("validate", _params, socket) do
|
||||||
release_with_tracks
|
{:noreply, socket}
|
||||||
|> Release.medium_tracks(number)
|
end
|
||||||
|> Enum.map(& &1.id)
|
|
||||||
|> MapSet.new()
|
|
||||||
|
|
||||||
updated_tracks =
|
def handle_event("recover_form", params, socket) do
|
||||||
if MapSet.subset?(medium_tracks, selected_tracks) do
|
handle_event("validate", params, socket)
|
||||||
MapSet.difference(selected_tracks, medium_tracks)
|
|
||||||
else
|
|
||||||
MapSet.union(selected_tracks, medium_tracks)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply, assign(socket, :selected_tracks, updated_tracks)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("scrobble_selected_tracks", _params, socket)
|
def handle_event("scrobble_selected_tracks", _params, socket)
|
||||||
@@ -495,10 +540,9 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign_async(:release_with_tracks, fn ->
|
|> assign(:release_with_tracks, AsyncResult.loading())
|
||||||
with {:ok, release} <- load_release_with_tracks(selected_release_id) do
|
|> start_async(:release_with_tracks, fn ->
|
||||||
{:ok, %{release_with_tracks: release}}
|
load_release_with_tracks(selected_release_id)
|
||||||
end
|
|
||||||
end)}
|
end)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -513,6 +557,54 @@ defmodule MusicLibraryWeb.Components.Release do
|
|||||||
MapSet.subset?(medium_track_ids, selected_tracks)
|
MapSet.subset?(medium_track_ids, selected_tracks)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Applies form params from a track-selection form to produce a new `selected_tracks` MapSet.
|
||||||
|
|
||||||
|
Detects master-toggle transitions (the `release[toggle_medium][]` field) by diffing the
|
||||||
|
incoming params against the current server-side `current_selected` MapSet. When a medium's
|
||||||
|
master toggle transitions from unchecked to checked, all of its tracks are added; when it
|
||||||
|
transitions the other way, all of its tracks are removed.
|
||||||
|
|
||||||
|
Used by both `Release` (as a LiveComponent) and `MusicLibraryWeb.ScrobbleLive.Show` to
|
||||||
|
share identical form-driven selection logic.
|
||||||
|
"""
|
||||||
|
@spec apply_form_params(MusicBrainz.Release.t(), map(), MapSet.t()) :: MapSet.t()
|
||||||
|
def apply_form_params(release, params, current_selected) do
|
||||||
|
form_selected =
|
||||||
|
(params["selected_tracks"] || [])
|
||||||
|
|> Enum.reject(&(&1 == ""))
|
||||||
|
|> MapSet.new()
|
||||||
|
|
||||||
|
toggled_medium_numbers =
|
||||||
|
(params["toggle_medium"] || [])
|
||||||
|
|> Enum.reject(&(&1 == ""))
|
||||||
|
|> Enum.map(&String.to_integer/1)
|
||||||
|
|> MapSet.new()
|
||||||
|
|
||||||
|
current_fully_selected =
|
||||||
|
for medium <- release.media,
|
||||||
|
medium_selected?(medium, current_selected),
|
||||||
|
into: MapSet.new(),
|
||||||
|
do: medium.number
|
||||||
|
|
||||||
|
Enum.reduce(release.media, form_selected, fn medium, acc ->
|
||||||
|
was_fully_selected? = MapSet.member?(current_fully_selected, medium.number)
|
||||||
|
is_toggled_now? = MapSet.member?(toggled_medium_numbers, medium.number)
|
||||||
|
medium_track_ids = medium.tracks |> Enum.map(& &1.id) |> MapSet.new()
|
||||||
|
|
||||||
|
cond do
|
||||||
|
not was_fully_selected? and is_toggled_now? ->
|
||||||
|
MapSet.union(acc, medium_track_ids)
|
||||||
|
|
||||||
|
was_fully_selected? and not is_toggled_now? ->
|
||||||
|
MapSet.difference(acc, medium_track_ids)
|
||||||
|
|
||||||
|
true ->
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
def scrobble_button_label(selected_tracks) do
|
def scrobble_button_label(selected_tracks) do
|
||||||
if MapSet.size(selected_tracks) > 0 do
|
if MapSet.size(selected_tracks) > 0 do
|
||||||
gettext("Scrobble selected tracks")
|
gettext("Scrobble selected tracks")
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do
|
|||||||
import(MusicLibraryWeb.Components.Release, only: [medium: 1, scrobble_button_label: 1])
|
import(MusicLibraryWeb.Components.Release, only: [medium: 1, scrobble_button_label: 1])
|
||||||
import MusicLibraryWeb.RecordComponents, only: [country_label: 1]
|
import MusicLibraryWeb.RecordComponents, only: [country_label: 1]
|
||||||
|
|
||||||
alias MusicBrainz.Release
|
|
||||||
alias MusicLibrary.ScrobbleActivity
|
alias MusicLibrary.ScrobbleActivity
|
||||||
alias MusicLibraryWeb.ErrorMessages
|
alias MusicLibraryWeb.ErrorMessages
|
||||||
|
|
||||||
@@ -77,7 +76,17 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do
|
|||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :if={@release.media != []} class="mt-6 space-y-4">
|
<.form
|
||||||
|
:if={@release.media != []}
|
||||||
|
for={@form}
|
||||||
|
id="scrobble-release-form"
|
||||||
|
class="mt-6 space-y-4"
|
||||||
|
phx-change="validate"
|
||||||
|
phx-auto-recover="recover_form"
|
||||||
|
>
|
||||||
|
<input type="hidden" name="release[selected_tracks][]" value="" />
|
||||||
|
<input type="hidden" name="release[toggle_medium][]" value="" />
|
||||||
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h3 class="text-lg font-semibold text-zinc-900 dark:text-zinc-200">
|
<h3 class="text-lg font-semibold text-zinc-900 dark:text-zinc-200">
|
||||||
{gettext("Tracks")}
|
{gettext("Tracks")}
|
||||||
@@ -85,6 +94,7 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do
|
|||||||
|
|
||||||
<.button
|
<.button
|
||||||
:if={@can_scrobble}
|
:if={@can_scrobble}
|
||||||
|
type="button"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
size="sm"
|
size="sm"
|
||||||
phx-click={
|
phx-click={
|
||||||
@@ -108,7 +118,7 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do
|
|||||||
media_count={MusicBrainz.Release.media_count(@release)}
|
media_count={MusicBrainz.Release.media_count(@release)}
|
||||||
myself={nil}
|
myself={nil}
|
||||||
/>
|
/>
|
||||||
</div>
|
</.form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,7 +134,8 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do
|
|||||||
release: nil,
|
release: nil,
|
||||||
can_scrobble: ScrobbleActivity.can_scrobble?(),
|
can_scrobble: ScrobbleActivity.can_scrobble?(),
|
||||||
page_title: "Scrobble Release",
|
page_title: "Scrobble Release",
|
||||||
selected_tracks: MapSet.new()
|
selected_tracks: MapSet.new(),
|
||||||
|
form: to_form(%{}, as: :release)
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -229,37 +240,22 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("toggle_medium", %{"medium-number" => number}, socket) do
|
def handle_event("validate", %{"release" => params}, socket) do
|
||||||
{number, ""} = Integer.parse(number)
|
if socket.assigns.release do
|
||||||
selected_tracks = socket.assigns.selected_tracks
|
new_selected =
|
||||||
release = socket.assigns.release
|
MusicLibraryWeb.Components.Release.apply_form_params(
|
||||||
|
socket.assigns.release,
|
||||||
|
params,
|
||||||
|
socket.assigns.selected_tracks
|
||||||
|
)
|
||||||
|
|
||||||
medium_tracks =
|
{:noreply, assign(socket, :selected_tracks, new_selected)}
|
||||||
release
|
else
|
||||||
|> Release.medium_tracks(number)
|
{:noreply, socket}
|
||||||
|> Enum.map(& &1.id)
|
end
|
||||||
|> MapSet.new()
|
|
||||||
|
|
||||||
updated_tracks =
|
|
||||||
if MapSet.subset?(medium_tracks, selected_tracks) do
|
|
||||||
MapSet.difference(selected_tracks, medium_tracks)
|
|
||||||
else
|
|
||||||
MapSet.union(selected_tracks, medium_tracks)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply, assign(socket, :selected_tracks, updated_tracks)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("toggle_track", %{"track-id" => track_id}, socket) do
|
def handle_event("recover_form", params, socket) do
|
||||||
selected_tracks = socket.assigns.selected_tracks
|
handle_event("validate", params, socket)
|
||||||
|
|
||||||
updated_tracks =
|
|
||||||
if MapSet.member?(selected_tracks, track_id) do
|
|
||||||
MapSet.delete(selected_tracks, track_id)
|
|
||||||
else
|
|
||||||
MapSet.put(selected_tracks, track_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply, assign(socket, :selected_tracks, updated_tracks)}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
defmodule MusicLibraryWeb.ScrobbleLive.ShowTest do
|
defmodule MusicLibraryWeb.ScrobbleLive.ShowTest do
|
||||||
use MusicLibraryWeb.ConnCase
|
use MusicLibraryWeb.ConnCase
|
||||||
|
|
||||||
import Phoenix.LiveViewTest, only: [render_click: 3]
|
import Phoenix.LiveViewTest, only: [element: 2, render_change: 2, render_click: 3]
|
||||||
|
|
||||||
alias MusicBrainz.Fixtures.Release, as: ReleaseFixtures
|
alias MusicBrainz.Fixtures.Release, as: ReleaseFixtures
|
||||||
alias MusicLibrary.Secrets
|
alias MusicLibrary.Secrets
|
||||||
@@ -120,14 +120,18 @@ defmodule MusicLibraryWeb.ScrobbleLive.ShowTest do
|
|||||||
# Toggle track on — button label changes
|
# Toggle track on — button label changes
|
||||||
session
|
session
|
||||||
|> unwrap(fn view ->
|
|> unwrap(fn view ->
|
||||||
render_click(view, "toggle_track", %{"track-id" => track_id})
|
view
|
||||||
|
|> element("#scrobble-release-form")
|
||||||
|
|> render_change(%{"release" => %{"selected_tracks" => [track_id]}})
|
||||||
end)
|
end)
|
||||||
|> assert_has("button", "Scrobble selected tracks")
|
|> assert_has("button", "Scrobble selected tracks")
|
||||||
|
|
||||||
# Toggle track off — button label reverts
|
# Toggle track off — button label reverts
|
||||||
session
|
session
|
||||||
|> unwrap(fn view ->
|
|> unwrap(fn view ->
|
||||||
render_click(view, "toggle_track", %{"track-id" => track_id})
|
view
|
||||||
|
|> element("#scrobble-release-form")
|
||||||
|
|> render_change(%{"release" => %{"selected_tracks" => []}})
|
||||||
end)
|
end)
|
||||||
|> assert_has("button", "Scrobble release")
|
|> assert_has("button", "Scrobble release")
|
||||||
end
|
end
|
||||||
@@ -138,14 +142,18 @@ defmodule MusicLibraryWeb.ScrobbleLive.ShowTest do
|
|||||||
# Toggle medium 1 on — button label changes
|
# Toggle medium 1 on — button label changes
|
||||||
session
|
session
|
||||||
|> unwrap(fn view ->
|
|> unwrap(fn view ->
|
||||||
render_click(view, "toggle_medium", %{"medium-number" => "1"})
|
view
|
||||||
|
|> element("#scrobble-release-form")
|
||||||
|
|> render_change(%{"release" => %{"toggle_medium" => ["1"]}})
|
||||||
end)
|
end)
|
||||||
|> assert_has("button", "Scrobble selected tracks")
|
|> assert_has("button", "Scrobble selected tracks")
|
||||||
|
|
||||||
# Toggle medium 1 off — button label reverts
|
# Toggle medium 1 off — button label reverts
|
||||||
session
|
session
|
||||||
|> unwrap(fn view ->
|
|> unwrap(fn view ->
|
||||||
render_click(view, "toggle_medium", %{"medium-number" => "1"})
|
view
|
||||||
|
|> element("#scrobble-release-form")
|
||||||
|
|> render_change(%{"release" => %{"toggle_medium" => []}})
|
||||||
end)
|
end)
|
||||||
|> assert_has("button", "Scrobble release")
|
|> assert_has("button", "Scrobble release")
|
||||||
end
|
end
|
||||||
@@ -157,7 +165,10 @@ defmodule MusicLibraryWeb.ScrobbleLive.ShowTest do
|
|||||||
|
|
||||||
session
|
session
|
||||||
|> unwrap(fn view ->
|
|> unwrap(fn view ->
|
||||||
render_click(view, "toggle_track", %{"track-id" => track_id})
|
view
|
||||||
|
|> element("#scrobble-release-form")
|
||||||
|
|> render_change(%{"release" => %{"selected_tracks" => [track_id]}})
|
||||||
|
|
||||||
render_click(view, "scrobble_selected_tracks", %{})
|
render_click(view, "scrobble_selected_tracks", %{})
|
||||||
end)
|
end)
|
||||||
|> assert_has("#toast-group", "Selected tracks scrobbled successfully")
|
|> assert_has("#toast-group", "Selected tracks scrobbled successfully")
|
||||||
@@ -207,7 +218,10 @@ defmodule MusicLibraryWeb.ScrobbleLive.ShowTest do
|
|||||||
|
|
||||||
session
|
session
|
||||||
|> unwrap(fn view ->
|
|> unwrap(fn view ->
|
||||||
render_click(view, "toggle_track", %{"track-id" => track_id})
|
view
|
||||||
|
|> element("#scrobble-release-form")
|
||||||
|
|> render_change(%{"release" => %{"selected_tracks" => [track_id]}})
|
||||||
|
|
||||||
render_click(view, "scrobble_selected_tracks", %{})
|
render_click(view, "scrobble_selected_tracks", %{})
|
||||||
end)
|
end)
|
||||||
|> assert_has("#toast-group", "Error scrobbling selected tracks")
|
|> assert_has("#toast-group", "Error scrobbling selected tracks")
|
||||||
|
|||||||
Reference in New Issue
Block a user