Add picker to create scrobble rules on the fly
This commit is contained in:
@@ -26,6 +26,7 @@ import { Hooks as FluxonHooks, DOM as FluxonDOM } from "fluxon";
|
|||||||
import FormatNumberHook from "./hooks/format-number";
|
import FormatNumberHook from "./hooks/format-number";
|
||||||
import UniversalSearchNavigationHook from "./hooks/universal-search-navigation";
|
import UniversalSearchNavigationHook from "./hooks/universal-search-navigation";
|
||||||
import RecordPickerNavigationHook from "./hooks/record-picker-navigation";
|
import RecordPickerNavigationHook from "./hooks/record-picker-navigation";
|
||||||
|
import RulePickerNavigationHook from "./hooks/rule-picker-navigation";
|
||||||
import SortableListHook from "./hooks/sortable-list";
|
import SortableListHook from "./hooks/sortable-list";
|
||||||
import confetti from "canvas-confetti";
|
import confetti from "canvas-confetti";
|
||||||
import { createLiveToastHook } from "live_toast";
|
import { createLiveToastHook } from "live_toast";
|
||||||
@@ -43,6 +44,7 @@ let Hooks = FluxonHooks;
|
|||||||
Hooks.FormatNumber = FormatNumberHook;
|
Hooks.FormatNumber = FormatNumberHook;
|
||||||
Hooks.UniversalSearchNavigation = UniversalSearchNavigationHook;
|
Hooks.UniversalSearchNavigation = UniversalSearchNavigationHook;
|
||||||
Hooks.RecordPickerNavigation = RecordPickerNavigationHook;
|
Hooks.RecordPickerNavigation = RecordPickerNavigationHook;
|
||||||
|
Hooks.RulePickerNavigation = RulePickerNavigationHook;
|
||||||
Hooks.SortableList = SortableListHook;
|
Hooks.SortableList = SortableListHook;
|
||||||
Hooks.LiveToast = liveToastHook;
|
Hooks.LiveToast = liveToastHook;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import createNavigationHook from "./create-navigation-hook";
|
||||||
|
|
||||||
|
export default createNavigationHook({
|
||||||
|
getContainer: (hook) => hook.el,
|
||||||
|
inputId: "rule-picker-search-input",
|
||||||
|
onSelect: (hook) => {
|
||||||
|
if (hook.selectedIndex === 0) {
|
||||||
|
hook.navigateDown();
|
||||||
|
} else {
|
||||||
|
hook.navigateUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -100,6 +100,7 @@ defmodule MusicLibraryWeb.ScrobbleComponents do
|
|||||||
attr :id, :string, required: true
|
attr :id, :string, required: true
|
||||||
attr :musicbrainz_id, :string, required: true
|
attr :musicbrainz_id, :string, required: true
|
||||||
attr :matching_records, :list, default: []
|
attr :matching_records, :list, default: []
|
||||||
|
attr :album_title, :string, default: nil
|
||||||
|
|
||||||
def record_status_badges(assigns) do
|
def record_status_badges(assigns) do
|
||||||
assigns =
|
assigns =
|
||||||
@@ -109,7 +110,16 @@ defmodule MusicLibraryWeb.ScrobbleComponents do
|
|||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="flex flex-col gap-1 text-right">
|
<div class="flex flex-col gap-1 text-right">
|
||||||
<.badge :if={@musicbrainz_id == ""}>
|
<.badge :if={@musicbrainz_id == "" and is_nil(@album_title)}>
|
||||||
|
{gettext("No MB ID")}
|
||||||
|
</.badge>
|
||||||
|
<.badge
|
||||||
|
:if={@musicbrainz_id == "" and @album_title}
|
||||||
|
class="cursor-pointer"
|
||||||
|
phx-click="open_rule_picker"
|
||||||
|
phx-value-album-title={@album_title}
|
||||||
|
>
|
||||||
|
<.icon name="hero-link" class="icon" />
|
||||||
{gettext("No MB ID")}
|
{gettext("No MB ID")}
|
||||||
</.badge>
|
</.badge>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
defmodule MusicLibraryWeb.ScrobbleRulePicker do
|
||||||
|
@moduledoc false
|
||||||
|
use MusicLibraryWeb, :live_component
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
alias MusicLibrary.Collection
|
||||||
|
alias MusicLibrary.Records.Record
|
||||||
|
alias MusicLibrary.ScrobbleRules
|
||||||
|
alias MusicLibrary.Wishlist
|
||||||
|
|
||||||
|
import MusicLibraryWeb.RecordComponents,
|
||||||
|
only: [format_label: 1, type_label: 1, release_status_tooltip: 1]
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div id="rule-picker-navigation" phx-hook="RulePickerNavigation">
|
||||||
|
<header class="mb-6">
|
||||||
|
<h1 class="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
|
||||||
|
{gettext("Create Scrobble Rule")}
|
||||||
|
</h1>
|
||||||
|
<p class="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
|
||||||
|
{gettext("Search your records to map \"%{album_title}\" to a release.",
|
||||||
|
album_title: @album_title
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<form
|
||||||
|
class="w-full"
|
||||||
|
for={@query}
|
||||||
|
phx-submit="search"
|
||||||
|
phx-change="search"
|
||||||
|
phx-target={@myself}
|
||||||
|
>
|
||||||
|
<.input
|
||||||
|
type="search"
|
||||||
|
size="sm"
|
||||||
|
id="rule-picker-search-input"
|
||||||
|
name={:query}
|
||||||
|
value={@query}
|
||||||
|
placeholder={gettext("Search")}
|
||||||
|
phx-debounce="500"
|
||||||
|
autocomplete="off"
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div
|
||||||
|
:if={@query != ""}
|
||||||
|
class="mt-4 max-h-96 overflow-y-auto"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
:if={@collected_results == [] and @wishlisted_results == []}
|
||||||
|
class="py-4 text-center text-sm text-zinc-500 dark:text-zinc-400"
|
||||||
|
>
|
||||||
|
{gettext("No records found")}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div :if={@collected_results != []}>
|
||||||
|
<h3 class="mb-1 text-xs font-medium tracking-wide text-zinc-500 uppercase dark:text-zinc-400">
|
||||||
|
{gettext("Collected")}
|
||||||
|
</h3>
|
||||||
|
<ul class="divide-y divide-zinc-100 dark:divide-zinc-700">
|
||||||
|
<.record_result
|
||||||
|
:for={record <- @collected_results}
|
||||||
|
record={record}
|
||||||
|
myself={@myself}
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :if={@wishlisted_results != []} class={[@collected_results != [] && "mt-4"]}>
|
||||||
|
<h3 class="mb-1 text-xs font-medium tracking-wide text-zinc-500 uppercase dark:text-zinc-400">
|
||||||
|
{gettext("Wishlisted")}
|
||||||
|
</h3>
|
||||||
|
<ul class="divide-y divide-zinc-100 dark:divide-zinc-700">
|
||||||
|
<.record_result
|
||||||
|
:for={record <- @wishlisted_results}
|
||||||
|
record={record}
|
||||||
|
myself={@myself}
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
defp record_result(assigns) do
|
||||||
|
~H"""
|
||||||
|
<li
|
||||||
|
role="option"
|
||||||
|
class={[
|
||||||
|
"flex cursor-pointer items-center gap-3 rounded-lg px-2 py-3 hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
||||||
|
"aria-selected:bg-zinc-100 dark:aria-selected:bg-zinc-700"
|
||||||
|
]}
|
||||||
|
phx-click="select_record"
|
||||||
|
phx-target={@myself}
|
||||||
|
phx-value-record-id={@record.id}
|
||||||
|
phx-value-selected-release-id={@record.selected_release_id}
|
||||||
|
>
|
||||||
|
<div class="w-12 flex-none">
|
||||||
|
<MusicLibraryWeb.RecordComponents.record_cover
|
||||||
|
record={@record}
|
||||||
|
class="aspect-square rounded object-cover"
|
||||||
|
width={96}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0 flex-auto">
|
||||||
|
<p class="truncate text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
||||||
|
{@record.title}
|
||||||
|
</p>
|
||||||
|
<p class="truncate text-xs text-zinc-500 dark:text-zinc-400">
|
||||||
|
{Record.artist_names(@record)}
|
||||||
|
</p>
|
||||||
|
<p class="flex items-center gap-1 mt-1 text-xs/5 text-zinc-500 dark:text-zinc-400">
|
||||||
|
<.release_status_tooltip record={@record} />
|
||||||
|
{Record.format_release_date(@record.release_date)} · {format_label(@record.format)} · {type_label(
|
||||||
|
@record.type
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<.icon
|
||||||
|
name="hero-link"
|
||||||
|
class="size-5 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def update(assigns, socket) do
|
||||||
|
{:ok,
|
||||||
|
socket
|
||||||
|
|> assign(assigns)
|
||||||
|
|> assign(:query, "")
|
||||||
|
|> assign(:collected_results, [])
|
||||||
|
|> assign(:wishlisted_results, [])}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("search", %{"query" => query}, socket) do
|
||||||
|
{collected, wishlisted} =
|
||||||
|
if String.trim(query) == "" do
|
||||||
|
{[], []}
|
||||||
|
else
|
||||||
|
collected =
|
||||||
|
query
|
||||||
|
|> Collection.search_records(limit: 20)
|
||||||
|
|> Enum.filter(&has_selected_release?/1)
|
||||||
|
|
||||||
|
wishlisted =
|
||||||
|
query
|
||||||
|
|> Wishlist.search_records(limit: 20)
|
||||||
|
|> Enum.filter(&has_selected_release?/1)
|
||||||
|
|
||||||
|
{collected, wishlisted}
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:query, query)
|
||||||
|
|> assign(:collected_results, collected)
|
||||||
|
|> assign(:wishlisted_results, wishlisted)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event(
|
||||||
|
"select_record",
|
||||||
|
%{"record-id" => _record_id, "selected-release-id" => selected_release_id},
|
||||||
|
socket
|
||||||
|
) do
|
||||||
|
album_title = socket.assigns.album_title
|
||||||
|
|
||||||
|
case ScrobbleRules.create_scrobble_rule(%{
|
||||||
|
type: :album,
|
||||||
|
match_value: album_title,
|
||||||
|
target_musicbrainz_id: selected_release_id
|
||||||
|
}) do
|
||||||
|
{:ok, rule} ->
|
||||||
|
case ScrobbleRules.apply_rule(rule) do
|
||||||
|
{:ok, _count} -> :ok
|
||||||
|
{:error, reason} -> Logger.warning("Failed to apply scrobble rule: #{inspect(reason)}")
|
||||||
|
end
|
||||||
|
|
||||||
|
notify_parent({:rule_created, rule})
|
||||||
|
put_toast!(:info, gettext("Scrobble rule created"))
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
message =
|
||||||
|
if Keyword.has_key?(changeset.errors, :match_value) do
|
||||||
|
gettext("A rule for this album already exists")
|
||||||
|
else
|
||||||
|
gettext("Could not create scrobble rule")
|
||||||
|
end
|
||||||
|
|
||||||
|
put_toast!(:error, message)
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp has_selected_release?(record) do
|
||||||
|
record.selected_release_id not in [nil, ""]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
|
||||||
|
end
|
||||||
@@ -149,6 +149,7 @@ defmodule MusicLibraryWeb.ScrobbledTracksLive.Index do
|
|||||||
id={"status-#{track.scrobbled_at_uts}"}
|
id={"status-#{track.scrobbled_at_uts}"}
|
||||||
musicbrainz_id={track.album.musicbrainz_id}
|
musicbrainz_id={track.album.musicbrainz_id}
|
||||||
matching_records={matching_records}
|
matching_records={matching_records}
|
||||||
|
album_title={track.album.title}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<.import_format_dropdown
|
<.import_format_dropdown
|
||||||
@@ -204,6 +205,18 @@ defmodule MusicLibraryWeb.ScrobbledTracksLive.Index do
|
|||||||
patch={back_path(@track_list_params)}
|
patch={back_path(@track_list_params)}
|
||||||
/>
|
/>
|
||||||
</.structured_modal>
|
</.structured_modal>
|
||||||
|
|
||||||
|
<.structured_modal
|
||||||
|
:if={@rule_picker_album_title}
|
||||||
|
id="rule-picker-modal"
|
||||||
|
on_close={JS.push("close_rule_picker")}
|
||||||
|
>
|
||||||
|
<.live_component
|
||||||
|
module={MusicLibraryWeb.ScrobbleRulePicker}
|
||||||
|
id="rule-picker"
|
||||||
|
album_title={@rule_picker_album_title}
|
||||||
|
/>
|
||||||
|
</.structured_modal>
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
@@ -213,6 +226,7 @@ defmodule MusicLibraryWeb.ScrobbledTracksLive.Index do
|
|||||||
socket =
|
socket =
|
||||||
socket
|
socket
|
||||||
|> assign(:current_section, :scrobble_activity)
|
|> assign(:current_section, :scrobble_activity)
|
||||||
|
|> assign(:rule_picker_album_title, nil)
|
||||||
|> stream_configure(:tracks,
|
|> stream_configure(:tracks,
|
||||||
dom_id: fn %{track: %Track{scrobbled_at_uts: id}} -> "tracks-#{id}" end
|
dom_id: fn %{track: %Track{scrobbled_at_uts: id}} -> "tracks-#{id}" end
|
||||||
)
|
)
|
||||||
@@ -266,7 +280,22 @@ defmodule MusicLibraryWeb.ScrobbledTracksLive.Index do
|
|||||||
{:noreply, load_and_assign_tracks(socket, socket.assigns.track_list_params)}
|
{:noreply, load_and_assign_tracks(socket, socket.assigns.track_list_params)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info({MusicLibraryWeb.ScrobbleRulePicker, {:rule_created, _rule}}, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:rule_picker_album_title, nil)
|
||||||
|
|> load_and_assign_tracks(socket.assigns.track_list_params)}
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
def handle_event("open_rule_picker", %{"album-title" => album_title}, socket) do
|
||||||
|
{:noreply, assign(socket, :rule_picker_album_title, album_title)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("close_rule_picker", _, socket) do
|
||||||
|
{:noreply, assign(socket, :rule_picker_album_title, nil)}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_event("delete", %{"scrobbled-at-uts" => scrobbled_at_uts}, socket) do
|
def handle_event("delete", %{"scrobbled-at-uts" => scrobbled_at_uts}, socket) do
|
||||||
track = ListeningStats.get_track!(scrobbled_at_uts)
|
track = ListeningStats.get_track!(scrobbled_at_uts)
|
||||||
{:ok, _} = ListeningStats.delete_track(track)
|
{:ok, _} = ListeningStats.delete_track(track)
|
||||||
|
|||||||
@@ -47,6 +47,18 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
<.top_collection_genres records_by_genre={@records_by_genre} />
|
<.top_collection_genres records_by_genre={@records_by_genre} />
|
||||||
<.top_release_years records_by_release_year={@records_by_release_year} />
|
<.top_release_years records_by_release_year={@records_by_release_year} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<.structured_modal
|
||||||
|
:if={@rule_picker_album_title}
|
||||||
|
id="rule-picker-modal"
|
||||||
|
on_close={JS.push("close_rule_picker")}
|
||||||
|
>
|
||||||
|
<.live_component
|
||||||
|
module={MusicLibraryWeb.ScrobbleRulePicker}
|
||||||
|
id="rule-picker"
|
||||||
|
album_title={@rule_picker_album_title}
|
||||||
|
/>
|
||||||
|
</.structured_modal>
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
@@ -84,6 +96,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
latest_record: latest_record,
|
latest_record: latest_record,
|
||||||
page_title: gettext("Stats"),
|
page_title: gettext("Stats"),
|
||||||
current_section: :stats,
|
current_section: :stats,
|
||||||
|
rule_picker_album_title: nil,
|
||||||
records_by_artist: records_by_artists,
|
records_by_artist: records_by_artists,
|
||||||
records_by_genre: records_by_genre,
|
records_by_genre: records_by_genre,
|
||||||
records_by_release_year: records_by_release_year,
|
records_by_release_year: records_by_release_year,
|
||||||
@@ -142,7 +155,22 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_event("open_rule_picker", %{"album-title" => album_title}, socket) do
|
||||||
|
{:noreply, assign(socket, :rule_picker_album_title, album_title)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("close_rule_picker", _, socket) do
|
||||||
|
{:noreply, assign(socket, :rule_picker_album_title, nil)}
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
def handle_info({MusicLibraryWeb.ScrobbleRulePicker, {:rule_created, _rule}}, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:rule_picker_album_title, nil)
|
||||||
|
|> assign_scrobble_activity()}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_info(%{track_count: 0}, socket) do
|
def handle_info(%{track_count: 0}, socket) do
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
@@ -318,6 +346,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
id={"status-#{album.scrobbled_at_uts}-albums"}
|
id={"status-#{album.scrobbled_at_uts}-albums"}
|
||||||
musicbrainz_id={album.metadata.musicbrainz_id}
|
musicbrainz_id={album.metadata.musicbrainz_id}
|
||||||
matching_records={matching_records}
|
matching_records={matching_records}
|
||||||
|
album_title={album.metadata.title}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<.import_format_dropdown
|
<.import_format_dropdown
|
||||||
@@ -398,6 +427,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
id={"status-#{track.scrobbled_at_uts}-tracks"}
|
id={"status-#{track.scrobbled_at_uts}-tracks"}
|
||||||
musicbrainz_id={track.album.musicbrainz_id}
|
musicbrainz_id={track.album.musicbrainz_id}
|
||||||
matching_records={matching_records}
|
matching_records={matching_records}
|
||||||
|
album_title={track.album.title}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<.import_format_dropdown
|
<.import_format_dropdown
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: lib/music_library_web/components/core_components.ex
|
#: lib/music_library_web/components/core_components.ex
|
||||||
#: lib/music_library_web/components/record_form.ex
|
#: lib/music_library_web/components/record_form.ex
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
#: lib/music_library_web/live/artist_live/form.ex
|
#: lib/music_library_web/live/artist_live/form.ex
|
||||||
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
@@ -234,6 +235,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: lib/music_library_web/components/barcode_scanner.ex
|
#: lib/music_library_web/components/barcode_scanner.ex
|
||||||
#: lib/music_library_web/components/scrobble_components.ex
|
#: lib/music_library_web/components/scrobble_components.ex
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Collected"
|
msgid "Collected"
|
||||||
@@ -241,6 +243,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: lib/music_library_web/components/barcode_scanner.ex
|
#: lib/music_library_web/components/barcode_scanner.ex
|
||||||
#: lib/music_library_web/components/scrobble_components.ex
|
#: lib/music_library_web/components/scrobble_components.ex
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Wishlisted"
|
msgid "Wishlisted"
|
||||||
@@ -1559,6 +1562,7 @@ msgstr ""
|
|||||||
msgid "No record sets yet"
|
msgid "No record sets yet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "No records found"
|
msgid "No records found"
|
||||||
@@ -2432,3 +2436,28 @@ msgstr ""
|
|||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "W"
|
msgid "W"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Could not create scrobble rule"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Create Scrobble Rule"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Scrobble rule created"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Search your records to map \"%{album_title}\" to a release."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "A rule for this album already exists"
|
||||||
|
msgstr ""
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: lib/music_library_web/components/core_components.ex
|
#: lib/music_library_web/components/core_components.ex
|
||||||
#: lib/music_library_web/components/record_form.ex
|
#: lib/music_library_web/components/record_form.ex
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
#: lib/music_library_web/live/artist_live/form.ex
|
#: lib/music_library_web/live/artist_live/form.ex
|
||||||
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
@@ -234,6 +235,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: lib/music_library_web/components/barcode_scanner.ex
|
#: lib/music_library_web/components/barcode_scanner.ex
|
||||||
#: lib/music_library_web/components/scrobble_components.ex
|
#: lib/music_library_web/components/scrobble_components.ex
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Collected"
|
msgid "Collected"
|
||||||
@@ -241,6 +243,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: lib/music_library_web/components/barcode_scanner.ex
|
#: lib/music_library_web/components/barcode_scanner.ex
|
||||||
#: lib/music_library_web/components/scrobble_components.ex
|
#: lib/music_library_web/components/scrobble_components.ex
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Wishlisted"
|
msgid "Wishlisted"
|
||||||
@@ -1559,6 +1562,7 @@ msgstr ""
|
|||||||
msgid "No record sets yet"
|
msgid "No record sets yet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
#: lib/music_library_web/live/record_set_live/record_picker.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "No records found"
|
msgid "No records found"
|
||||||
@@ -2432,3 +2436,28 @@ msgstr ""
|
|||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "W"
|
msgid "W"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Could not create scrobble rule"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Create Scrobble Rule"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Scrobble rule created"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Search your records to map \"%{album_title}\" to a release."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/scrobble_rule_picker.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "A rule for this album already exists"
|
||||||
|
msgstr ""
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
defmodule MusicLibraryWeb.ScrobbledTracksLive.RulePickerTest do
|
||||||
|
use MusicLibraryWeb.ConnCase
|
||||||
|
|
||||||
|
import MusicLibrary.Fixtures.Records
|
||||||
|
import MusicLibrary.ScrobbledTracksFixtures
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
alias MusicLibrary.ScrobbleRules
|
||||||
|
|
||||||
|
defp create_track_without_album_mbid(_) do
|
||||||
|
track =
|
||||||
|
track_fixture(%{
|
||||||
|
title: "Comfortably Numb",
|
||||||
|
artist_name: "Pink Floyd",
|
||||||
|
album_title: "The Wall",
|
||||||
|
album_musicbrainz_id: ""
|
||||||
|
})
|
||||||
|
|
||||||
|
%{track: track}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_collected_record(_) do
|
||||||
|
collected = record(%{title: "The Wall", purchased_at: DateTime.utc_now()})
|
||||||
|
%{collected: collected}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp open_rule_picker(view, album_title) do
|
||||||
|
render_click(view, "open_rule_picker", %{"album-title" => album_title})
|
||||||
|
view
|
||||||
|
end
|
||||||
|
|
||||||
|
defp search_picker(view, query) do
|
||||||
|
view
|
||||||
|
|> form("#rule-picker-navigation form", %{query: query})
|
||||||
|
|> render_submit()
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Rule picker modal" do
|
||||||
|
setup [:create_track_without_album_mbid]
|
||||||
|
|
||||||
|
test "opens and shows album title", %{conn: conn, track: track} do
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/scrobbled-tracks")
|
||||||
|
|
||||||
|
open_rule_picker(view, track.album.title)
|
||||||
|
|
||||||
|
assert has_element?(view, "#rule-picker-modal")
|
||||||
|
assert has_element?(view, "h1", "Create Scrobble Rule")
|
||||||
|
assert render(view) =~ track.album.title
|
||||||
|
end
|
||||||
|
|
||||||
|
test "closes when close event is triggered", %{conn: conn, track: track} do
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/scrobbled-tracks")
|
||||||
|
|
||||||
|
open_rule_picker(view, track.album.title)
|
||||||
|
assert has_element?(view, "#rule-picker-modal")
|
||||||
|
|
||||||
|
render_click(view, "close_rule_picker")
|
||||||
|
|
||||||
|
refute has_element?(view, "#rule-picker-modal")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Rule picker search" do
|
||||||
|
setup [:create_track_without_album_mbid, :create_collected_record]
|
||||||
|
|
||||||
|
test "searches and displays collected records", %{
|
||||||
|
conn: conn,
|
||||||
|
track: track,
|
||||||
|
collected: collected
|
||||||
|
} do
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/scrobbled-tracks")
|
||||||
|
|
||||||
|
open_rule_picker(view, track.album.title)
|
||||||
|
html = search_picker(view, collected.title)
|
||||||
|
|
||||||
|
assert html =~ collected.title
|
||||||
|
assert html =~ "Collected"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "filters out records without selected_release_id", %{conn: conn, track: track} do
|
||||||
|
no_release =
|
||||||
|
record(%{
|
||||||
|
title: "No Release Zzzzzz",
|
||||||
|
selected_release_id: nil,
|
||||||
|
musicbrainz_data: %{}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert is_nil(no_release.selected_release_id)
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/scrobbled-tracks")
|
||||||
|
|
||||||
|
open_rule_picker(view, track.album.title)
|
||||||
|
search_picker(view, "No Release Zzzzzz")
|
||||||
|
|
||||||
|
refute has_element?(
|
||||||
|
view,
|
||||||
|
"li[phx-click='select_record'][phx-value-record-id='#{no_release.id}']"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows wishlisted records", %{conn: conn, track: track} do
|
||||||
|
wishlisted = record(%{title: "Wish You Were Here", purchased_at: nil})
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/scrobbled-tracks")
|
||||||
|
|
||||||
|
open_rule_picker(view, track.album.title)
|
||||||
|
html = search_picker(view, wishlisted.title)
|
||||||
|
|
||||||
|
assert html =~ wishlisted.title
|
||||||
|
assert html =~ "Wishlisted"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "empty search returns no results", %{conn: conn, track: track} do
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/scrobbled-tracks")
|
||||||
|
|
||||||
|
open_rule_picker(view, track.album.title)
|
||||||
|
html = search_picker(view, " ")
|
||||||
|
|
||||||
|
refute html =~ "Collected"
|
||||||
|
refute html =~ "Wishlisted"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Rule creation" do
|
||||||
|
setup [:create_track_without_album_mbid, :create_collected_record]
|
||||||
|
|
||||||
|
test "creates scrobble rule when record is selected", %{
|
||||||
|
conn: conn,
|
||||||
|
track: track,
|
||||||
|
collected: collected
|
||||||
|
} do
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/scrobbled-tracks")
|
||||||
|
|
||||||
|
open_rule_picker(view, track.album.title)
|
||||||
|
search_picker(view, collected.title)
|
||||||
|
|
||||||
|
view
|
||||||
|
|> element("li[phx-click='select_record'][phx-value-record-id='#{collected.id}']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
refute has_element?(view, "#rule-picker-modal")
|
||||||
|
|
||||||
|
[rule] = ScrobbleRules.list_scrobble_rules(type: :album)
|
||||||
|
assert rule.match_value == track.album.title
|
||||||
|
assert rule.target_musicbrainz_id == collected.selected_release_id
|
||||||
|
assert rule.type == :album
|
||||||
|
assert rule.enabled == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows error for duplicate rule", %{conn: conn, track: track, collected: collected} do
|
||||||
|
{:ok, _rule} =
|
||||||
|
ScrobbleRules.create_scrobble_rule(%{
|
||||||
|
type: :album,
|
||||||
|
match_value: track.album.title,
|
||||||
|
target_musicbrainz_id: Ecto.UUID.generate()
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/scrobbled-tracks")
|
||||||
|
|
||||||
|
open_rule_picker(view, track.album.title)
|
||||||
|
search_picker(view, collected.title)
|
||||||
|
|
||||||
|
view
|
||||||
|
|> element("li[phx-click='select_record'][phx-value-record-id='#{collected.id}']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
# Modal stays open after error
|
||||||
|
assert has_element?(view, "#rule-picker-modal")
|
||||||
|
|
||||||
|
# Only the original rule exists, no duplicate was created
|
||||||
|
assert length(ScrobbleRules.list_scrobble_rules(type: :album)) == 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user