ML-199: extract shared record show logic
This commit is contained in:
@@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
id: ML-199
|
||||||
|
title: Extract shared record show LiveView logic
|
||||||
|
status: Done
|
||||||
|
assignee:
|
||||||
|
- pi
|
||||||
|
created_date: "2026-05-29 05:29"
|
||||||
|
updated_date: "2026-05-29 05:39"
|
||||||
|
labels:
|
||||||
|
- refactor
|
||||||
|
- liveview
|
||||||
|
dependencies: []
|
||||||
|
references:
|
||||||
|
- lib/music_library_web/live/collection_live/show.ex
|
||||||
|
- lib/music_library_web/live/wishlist_live/show.ex
|
||||||
|
- lib/music_library_web/live_helpers/record_actions.ex
|
||||||
|
- lib/music_library_web/components/record_components.ex
|
||||||
|
documentation:
|
||||||
|
- docs/architecture.md
|
||||||
|
- docs/project-conventions.md
|
||||||
|
- .agents/skills/ui-framework/SKILL.md
|
||||||
|
modified_files:
|
||||||
|
- lib/music_library_web/live/collection_live/show.ex
|
||||||
|
- lib/music_library_web/live/wishlist_live/show.ex
|
||||||
|
- lib/music_library_web/live_helpers/record_show.ex
|
||||||
|
- lib/music_library_web/components/record_components.ex
|
||||||
|
- docs/architecture.md
|
||||||
|
- priv/gettext/default.pot
|
||||||
|
- priv/gettext/en/LC_MESSAGES/default.po
|
||||||
|
priority: medium
|
||||||
|
ordinal: 32000
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
|
||||||
|
Refactor the duplicated Collection and Wishlist record show LiveViews so common record detail rendering and event handling live in shared web-layer helpers/components while preserving page-specific collection and wishlist behavior.
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
|
||||||
|
- [x] #1 Collection and Wishlist show pages reuse shared rendering for the common action bar, selected release row, release sheet, chat component, and edit modal.
|
||||||
|
- [x] #2 Collection and Wishlist show pages reuse shared handlers for common record actions, scrobble async handling, saved-record handling, chat-count refresh, and background record updates.
|
||||||
|
- [x] #3 Collection-specific behavior remains intact: notes, regenerate embeddings, purchased/listening details, similar records, print-enabled release sheet, and editable purchased_at field.
|
||||||
|
- [x] #4 Wishlist-specific behavior remains intact: purchase action, online store links, unreleased metadata, print-disabled release sheet, and hidden purchased_at field.
|
||||||
|
- [x] #5 Relevant focused tests or compile checks pass after the refactor.
|
||||||
|
- [x] #6 Architecture documentation reflects any new shared helper module added by the refactor.
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
<!-- SECTION:PLAN:BEGIN -->
|
||||||
|
|
||||||
|
1. Add `MusicLibraryWeb.LiveHelpers.RecordShow` for shared Collection/Wishlist show-page logic: common record assigns, delete navigation, scrobble release start/async result handling, saved-record assignment, chat-count refresh, background-update guard/toast, and shared page-title construction.
|
||||||
|
2. Extend `MusicLibraryWeb.RecordComponents` with focused shared components for the common show-page action bar, selected-release row, release sheet, chat component, and edit modal. Use slots for page-specific dropdown actions so collection and wishlist behavior stays explicit.
|
||||||
|
3. Refactor `CollectionLive.Show` and `WishlistLive.Show` to call the shared helper/components while keeping local page-specific sections and handlers: collection notes/regenerate/similar/listening details and wishlist purchase/online-store/current-date behavior.
|
||||||
|
4. Update `docs/architecture.md` to mention the new shared `LiveHelpers.RecordShow` module in the web utility modules table.
|
||||||
|
5. Run focused formatting/compile/tests for the touched LiveViews/components, then mark acceptance criteria based on the results.
|
||||||
|
<!-- SECTION:PLAN:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
|
||||||
|
Implemented shared `LiveHelpers.RecordShow` for common record loading, delete navigation, scrobble async handling, saved-record handling, chat refresh, and background update behavior. Added shared record show components for the action bar, selected release row, release sheet, chat component, and edit modal. Preserved collection-only notes/regenerate/listening/similar-record behavior and wishlist-only purchase/online-store/unreleased behavior. Verification: `mix compile --warnings-as-errors`, `mix test test/music_library_web/live/collection_live/show_test.exs test/music_library_web/live/wishlist_live/show_test.exs`, `mix gettext.extract --check-up-to-date`, `mix format --check-formatted ...`, and `mix credo --strict` all passed.
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
|
|
||||||
|
## Final Summary
|
||||||
|
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||||
|
|
||||||
|
Refactored the Collection and Wishlist record show LiveViews to remove duplicated show-page rendering and lifecycle logic.
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- Added `MusicLibraryWeb.LiveHelpers.RecordShow` for shared record loading, page title assignment, delete navigation, common action dispatch, scrobble async handling, saved-record handling, chat refreshes, and guarded background record updates.
|
||||||
|
- Added shared `RecordComponents` functions for the common show action bar, selected release row, release sheet, record chat component, and edit modal.
|
||||||
|
- Updated `CollectionLive.Show` and `WishlistLive.Show` to compose the shared helpers/components while keeping collection-only and wishlist-only sections explicit.
|
||||||
|
- Updated gettext references after moving user-facing strings and documented `LiveHelpers.RecordShow` in `docs/architecture.md`.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- `mix compile --warnings-as-errors`
|
||||||
|
- `mix test test/music_library_web/live/collection_live/show_test.exs test/music_library_web/live/wishlist_live/show_test.exs`
|
||||||
|
- `mix gettext.extract --check-up-to-date`
|
||||||
|
- `mix format --check-formatted lib/music_library_web/live_helpers/record_show.ex lib/music_library_web/components/record_components.ex lib/music_library_web/live/collection_live/show.ex lib/music_library_web/live/wishlist_live/show.ex`
|
||||||
|
- `mix credo --strict`
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||||
@@ -314,10 +314,10 @@ All authenticated routes live inside a single `live_session` with three `on_moun
|
|||||||
### Shared Component Modules (lib/music_library_web/components/)
|
### Shared Component Modules (lib/music_library_web/components/)
|
||||||
|
|
||||||
| Module | Purpose |
|
| Module | Purpose |
|
||||||
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `CoreComponents` | Forms, buttons, icons, tables, flash messages |
|
| `CoreComponents` | Forms, buttons, icons, tables, flash messages |
|
||||||
| `Layouts` | Application layout templates, navigation components (`dropdown_nav/1`) |
|
| `Layouts` | Application layout templates, navigation components (`dropdown_nav/1`) |
|
||||||
| `RecordComponents` | Record cards, cover images, artist images, labels, grids, release status icon badge, shared show-page sections (title, external links, genres, releases, timestamps, debug) |
|
| `RecordComponents` | Record cards, cover images, artist images, labels, grids, release status badges, and shared record show-page sections/actions/sheets |
|
||||||
| `ChartComponents` | Charts for stats dashboard |
|
| `ChartComponents` | Charts for stats dashboard |
|
||||||
| `StatsComponents` | Stats dashboard widgets (`section/1` layout, counters, album preview, records on this day) |
|
| `StatsComponents` | Stats dashboard widgets (`section/1` layout, counters, album preview, records on this day) |
|
||||||
| `ScrobbleComponents` | Scrobble activity displays: status badges, import dropdowns, metadata tooltips, and record-matching UI |
|
| `ScrobbleComponents` | Scrobble activity displays: status badges, import dropdowns, metadata tooltips, and record-matching UI |
|
||||||
@@ -328,7 +328,7 @@ All authenticated routes live inside a single `live_session` with three `on_moun
|
|||||||
### Web Utility Modules (lib/music_library_web/)
|
### Web Utility Modules (lib/music_library_web/)
|
||||||
|
|
||||||
| Module | Purpose |
|
| Module | Purpose |
|
||||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `ErrorMessages` | Maps internal error terms (atoms, structs) to user-friendly gettext strings via `friendly_message/1` |
|
| `ErrorMessages` | Maps internal error terms (atoms, structs) to user-friendly gettext strings via `friendly_message/1` |
|
||||||
| `Markdown` | Markdown-to-HTML conversion (MDEx with ammonia sanitization) with `[[double bracket]]` link syntax and streaming document support for chat |
|
| `Markdown` | Markdown-to-HTML conversion (MDEx with ammonia sanitization) with `[[double bracket]]` link syntax and streaming document support for chat |
|
||||||
| `Duration` | Milliseconds to human-readable duration formatting |
|
| `Duration` | Milliseconds to human-readable duration formatting |
|
||||||
@@ -337,6 +337,7 @@ All authenticated routes live inside a single `live_session` with three `on_moun
|
|||||||
| `LiveHelpers.Params` | URL query param parsing: pagination, search query, sort order, display mode, fallback index |
|
| `LiveHelpers.Params` | URL query param parsing: pagination, search query, sort order, display mode, fallback index |
|
||||||
| `LiveHelpers.IndexActions` | Shared index page logic (search, pagination, import, delete, display mode) for Collection/Wishlist index pages, parameterized by config map |
|
| `LiveHelpers.IndexActions` | Shared index page logic (search, pagination, import, delete, display mode) for Collection/Wishlist index pages, parameterized by config map |
|
||||||
| `LiveHelpers.RecordActions` | Shared record action handlers (refresh cover, genres, embeddings, MusicBrainz data) for Collection/Wishlist show pages |
|
| `LiveHelpers.RecordActions` | Shared record action handlers (refresh cover, genres, embeddings, MusicBrainz data) for Collection/Wishlist show pages |
|
||||||
|
| `LiveHelpers.RecordShow` | Shared Collection/Wishlist show-page loading, page titles, delete navigation, scrobble async handling, and background record update handling |
|
||||||
| — | Logster v2 handles `[:phoenix, :socket_connected]` telemetry — not a custom module |
|
| — | Logster v2 handles `[:phoenix, :socket_connected]` telemetry — not a custom module |
|
||||||
|
|
||||||
### Controllers
|
### Controllers
|
||||||
|
|||||||
@@ -553,6 +553,281 @@ defmodule MusicLibraryWeb.RecordComponents do
|
|||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attr :record, Records.Record, required: true
|
||||||
|
attr :can_scrobble?, :boolean, required: true
|
||||||
|
attr :chat_count, :integer, required: true
|
||||||
|
attr :edit_path, :string, required: true
|
||||||
|
slot :dropdown_start
|
||||||
|
slot :dropdown_extra
|
||||||
|
|
||||||
|
def record_show_action_bar(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div class="mt-4 flex items-center justify-between md:mt-0">
|
||||||
|
<h1 class="text-base/6 font-medium text-zinc-700">
|
||||||
|
<.artist_links joinphrase_class="text-sm" artists={@record.artists} />
|
||||||
|
</h1>
|
||||||
|
<div class="min-w-12">
|
||||||
|
<.button_group>
|
||||||
|
<.button
|
||||||
|
:if={@can_scrobble? and @record.selected_release_id}
|
||||||
|
variant="soft"
|
||||||
|
phx-click="scrobble_release"
|
||||||
|
>
|
||||||
|
<span class="sr-only">{gettext("Scrobble release")}</span>
|
||||||
|
<.icon
|
||||||
|
name="hero-play"
|
||||||
|
class="icon"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
</.button>
|
||||||
|
<.button
|
||||||
|
variant="soft"
|
||||||
|
phx-click={MusicLibraryWeb.Components.Chat.open("record-chat-sheet")}
|
||||||
|
>
|
||||||
|
<span class="sr-only">{gettext("Chat about album")}</span>
|
||||||
|
<.icon
|
||||||
|
name="hero-chat-bubble-left-right"
|
||||||
|
class="icon"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
<span :if={@chat_count > 0} class="text-xs font-medium">{@chat_count}</span>
|
||||||
|
</.button>
|
||||||
|
<.button
|
||||||
|
:if={@record.selected_release_id}
|
||||||
|
variant="soft"
|
||||||
|
phx-click={MusicLibraryWeb.Components.Release.open("release-with-tracks-sheet")}
|
||||||
|
>
|
||||||
|
<span class="sr-only">{gettext("Show Tracks")}</span>
|
||||||
|
<.icon
|
||||||
|
name="hero-numbered-list"
|
||||||
|
class="icon"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
</.button>
|
||||||
|
<.dropdown id={"actions-#{@record.id}"} placement="bottom-end">
|
||||||
|
<:toggle>
|
||||||
|
<.button variant="soft">
|
||||||
|
<span class="sr-only">{gettext("Actions")}</span>
|
||||||
|
<.icon
|
||||||
|
name="hero-ellipsis-vertical"
|
||||||
|
class="icon cursor-pointer text-zinc-500 dark:text-zinc-400"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
</.button>
|
||||||
|
</:toggle>
|
||||||
|
<.focus_wrap id={"actions-#{@record.id}-focus-wrap"}>
|
||||||
|
{render_slot(@dropdown_start)}
|
||||||
|
|
||||||
|
<.dropdown_link
|
||||||
|
id={"actions-#{@record.id}-debug"}
|
||||||
|
phx-click={Fluxon.open_dialog("debug-data")}
|
||||||
|
>
|
||||||
|
<.icon
|
||||||
|
name="hero-code-bracket"
|
||||||
|
class="mr-1 size-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
{gettext("Debug data")}
|
||||||
|
</.dropdown_link>
|
||||||
|
|
||||||
|
<.dropdown_separator />
|
||||||
|
|
||||||
|
<.dropdown_link id={"actions-#{@record.id}-edit"} patch={@edit_path}>
|
||||||
|
<.icon
|
||||||
|
name="hero-pencil-square"
|
||||||
|
class="phx-click-loading:animate-bounce mr-1 size-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
{gettext("Edit")}
|
||||||
|
</.dropdown_link>
|
||||||
|
|
||||||
|
<.dropdown_link
|
||||||
|
id={"actions-#{@record.id}-refresh-cover"}
|
||||||
|
phx-click="refresh_cover"
|
||||||
|
>
|
||||||
|
<.icon
|
||||||
|
name="hero-photo"
|
||||||
|
class="phx-click-loading:animate-bounce mr-1 size-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
{gettext("Refresh cover")}
|
||||||
|
</.dropdown_link>
|
||||||
|
|
||||||
|
<.dropdown_link
|
||||||
|
id={"actions-#{@record.id}-refresh-mb-data"}
|
||||||
|
phx-click="refresh_musicbrainz_data"
|
||||||
|
>
|
||||||
|
<.icon
|
||||||
|
name="hero-cloud-arrow-down"
|
||||||
|
class="phx-click-loading:animate-bounce mr-1 size-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
{gettext("Refresh MB data")}
|
||||||
|
</.dropdown_link>
|
||||||
|
|
||||||
|
<.dropdown_link
|
||||||
|
id={"actions-#{@record.id}-populate-genres"}
|
||||||
|
phx-click="populate_genres"
|
||||||
|
>
|
||||||
|
<.icon
|
||||||
|
name="hero-sparkles"
|
||||||
|
class="phx-click-loading:animate-shake mr-1 size-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
{gettext("Populate genres")}
|
||||||
|
</.dropdown_link>
|
||||||
|
|
||||||
|
{render_slot(@dropdown_extra)}
|
||||||
|
|
||||||
|
<.dropdown_link
|
||||||
|
id={"actions-#{@record.id}-extract-colors"}
|
||||||
|
phx-click="extract_colors"
|
||||||
|
>
|
||||||
|
<.icon
|
||||||
|
name="hero-paint-brush"
|
||||||
|
class="phx-click-loading:animate-shake mr-1 size-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
{gettext("Extract colors")}
|
||||||
|
</.dropdown_link>
|
||||||
|
|
||||||
|
<.dropdown_separator />
|
||||||
|
<.dropdown_link
|
||||||
|
id={"actions-#{@record.id}-delete"}
|
||||||
|
phx-click="delete"
|
||||||
|
data-confirm={gettext("Are you sure?")}
|
||||||
|
class="text-red-900! hover:bg-red-50! dark:text-red-500! dark:hover:bg-red-900/30! dark:hover:text-red-600!"
|
||||||
|
>
|
||||||
|
<.icon
|
||||||
|
name="hero-trash"
|
||||||
|
class="phx-click-loading:animate-spin mr-1 size-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
{gettext("Delete")}
|
||||||
|
</.dropdown_link>
|
||||||
|
</.focus_wrap>
|
||||||
|
</.dropdown>
|
||||||
|
</.button_group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
attr :record, Records.Record, required: true
|
||||||
|
attr :label, :string, required: true
|
||||||
|
attr :copy_selected_release_id?, :boolean, default: false
|
||||||
|
|
||||||
|
def record_show_selected_release_row(assigns) do
|
||||||
|
~H"""
|
||||||
|
<.dl_row label={@label}>
|
||||||
|
<div class="flex justify-between space-x-2">
|
||||||
|
<span
|
||||||
|
:if={!@record.selected_release_id}
|
||||||
|
class="text-xs text-zinc-700 md:text-sm dark:text-zinc-300"
|
||||||
|
>
|
||||||
|
{gettext("No release selected")}
|
||||||
|
</span>
|
||||||
|
<.release_summary
|
||||||
|
:if={@record.selected_release_id}
|
||||||
|
release={Records.Record.selected_release(@record)}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
:if={@copy_selected_release_id? and @record.selected_release_id}
|
||||||
|
id={"record-selected-release-" <> @record.id}
|
||||||
|
class="hidden"
|
||||||
|
>
|
||||||
|
{@record.selected_release_id}
|
||||||
|
</span>
|
||||||
|
<.copy_to_clipboard
|
||||||
|
:if={@copy_selected_release_id? and @record.selected_release_id}
|
||||||
|
target_id={"record-selected-release-" <> @record.id}
|
||||||
|
label={gettext("Copy record selected release ID to clipboard")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</.dl_row>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
attr :record, Records.Record, required: true
|
||||||
|
attr :show_print?, :boolean, required: true
|
||||||
|
attr :timezone, :string, required: true
|
||||||
|
|
||||||
|
def record_show_release_sheet(assigns) do
|
||||||
|
~H"""
|
||||||
|
<.sheet
|
||||||
|
:if={@record.selected_release_id}
|
||||||
|
id="release-with-tracks-sheet"
|
||||||
|
placement="right"
|
||||||
|
class="flex min-w-xs flex-col overflow-hidden p-0 sm:min-w-sm"
|
||||||
|
>
|
||||||
|
<.live_component
|
||||||
|
id="release-with-tracks"
|
||||||
|
sheet_id="release-with-tracks-sheet"
|
||||||
|
module={MusicLibraryWeb.Components.Release}
|
||||||
|
release_id={@record.selected_release_id}
|
||||||
|
show_print?={@show_print?}
|
||||||
|
timezone={@timezone}
|
||||||
|
/>
|
||||||
|
</.sheet>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
attr :record, Records.Record, required: true
|
||||||
|
attr :embedding_text, :string, required: true
|
||||||
|
|
||||||
|
def record_show_chat(assigns) do
|
||||||
|
~H"""
|
||||||
|
<.live_component
|
||||||
|
id="record-chat"
|
||||||
|
sheet_id="record-chat-sheet"
|
||||||
|
module={MusicLibraryWeb.Components.Chat}
|
||||||
|
title={@record.title}
|
||||||
|
entity={:record}
|
||||||
|
musicbrainz_id={@record.musicbrainz_id}
|
||||||
|
chat_module={MusicLibrary.Chats.RecordChat}
|
||||||
|
chat_context={{@record, @embedding_text}}
|
||||||
|
placeholder={gettext("Ask about this album...")}
|
||||||
|
empty_prompt={gettext("Ask anything about this album")}
|
||||||
|
/>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
attr :record, Records.Record, required: true
|
||||||
|
attr :live_action, :atom, required: true
|
||||||
|
attr :show_purchased_at, :boolean, required: true
|
||||||
|
attr :close_path, :string, required: true
|
||||||
|
attr :patch_path, :string, required: true
|
||||||
|
|
||||||
|
def record_show_edit_modal(assigns) do
|
||||||
|
~H"""
|
||||||
|
<.structured_modal
|
||||||
|
:if={@live_action == :edit}
|
||||||
|
id="record-modal"
|
||||||
|
on_close={JS.patch(@close_path)}
|
||||||
|
>
|
||||||
|
<.live_component
|
||||||
|
module={MusicLibraryWeb.Components.RecordForm}
|
||||||
|
id={@record.id}
|
||||||
|
action={@live_action}
|
||||||
|
show_purchased_at={@show_purchased_at}
|
||||||
|
record={@record}
|
||||||
|
patch={@patch_path}
|
||||||
|
/>
|
||||||
|
</.structured_modal>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
attr :record, Records.Record, required: true
|
attr :record, Records.Record, required: true
|
||||||
attr :section, :atom, values: [:collection, :wishlist], required: true
|
attr :section, :atom, values: [:collection, :wishlist], required: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
defmodule MusicLibraryWeb.CollectionLive.Show do
|
defmodule MusicLibraryWeb.CollectionLive.Show do
|
||||||
use MusicLibraryWeb, :live_view
|
use MusicLibraryWeb, :live_view
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
import MusicLibrary.ListeningStats, only: [localize_scrobbled_at: 2]
|
import MusicLibrary.ListeningStats, only: [localize_scrobbled_at: 2]
|
||||||
|
|
||||||
import MusicLibraryWeb.RecordComponents,
|
import MusicLibraryWeb.RecordComponents,
|
||||||
only: [
|
only: [
|
||||||
artist_links: 1,
|
|
||||||
record_cover: 1,
|
record_cover: 1,
|
||||||
record_debug_sheet: 1,
|
record_debug_sheet: 1,
|
||||||
record_external_links: 1,
|
record_external_links: 1,
|
||||||
@@ -15,18 +12,26 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
|
|||||||
record_includes: 1,
|
record_includes: 1,
|
||||||
record_published_releases: 1,
|
record_published_releases: 1,
|
||||||
record_sets_list: 1,
|
record_sets_list: 1,
|
||||||
|
record_show_action_bar: 1,
|
||||||
|
record_show_chat: 1,
|
||||||
|
record_show_edit_modal: 1,
|
||||||
|
record_show_release_sheet: 1,
|
||||||
|
record_show_selected_release_row: 1,
|
||||||
record_timestamps: 1,
|
record_timestamps: 1,
|
||||||
record_title_and_metadata: 1,
|
record_title_and_metadata: 1
|
||||||
release_summary: 1
|
|
||||||
]
|
]
|
||||||
|
|
||||||
alias MusicLibrary.{Chats, ListeningStats, Records, RecordSets, ScrobbleActivity}
|
alias MusicLibrary.{ListeningStats, Records, ScrobbleActivity}
|
||||||
alias MusicLibrary.Records.Similarity
|
alias MusicLibrary.Records.Similarity
|
||||||
alias MusicLibraryWeb.ErrorMessages
|
alias MusicLibraryWeb.ErrorMessages
|
||||||
alias MusicLibraryWeb.LiveHelpers.RecordActions
|
alias MusicLibraryWeb.LiveHelpers.RecordShow
|
||||||
alias Phoenix.LiveView.JS
|
|
||||||
|
|
||||||
alias MusicBrainz
|
@common_record_events [
|
||||||
|
"refresh_musicbrainz_data",
|
||||||
|
"refresh_cover",
|
||||||
|
"populate_genres",
|
||||||
|
"extract_colors"
|
||||||
|
]
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
@@ -46,64 +51,13 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="xl:col-span-3">
|
<div class="xl:col-span-3">
|
||||||
<div class="mt-4 flex items-center justify-between md:mt-0">
|
<.record_show_action_bar
|
||||||
<h1 class="text-base/6 font-medium text-zinc-700">
|
record={@record}
|
||||||
<.artist_links joinphrase_class="text-sm" artists={@record.artists} />
|
can_scrobble?={@can_scrobble?}
|
||||||
</h1>
|
chat_count={@chat_count}
|
||||||
<div class="min-w-12">
|
edit_path={~p"/collection/#{@record}/show/edit"}
|
||||||
<.button_group>
|
|
||||||
<.button
|
|
||||||
:if={@can_scrobble? and @record.selected_release_id}
|
|
||||||
variant="soft"
|
|
||||||
phx-click="scrobble_release"
|
|
||||||
>
|
>
|
||||||
<span class="sr-only">{gettext("Scrobble release")}</span>
|
<:dropdown_start>
|
||||||
<.icon
|
|
||||||
name="hero-play"
|
|
||||||
class="icon"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
</.button>
|
|
||||||
<.button
|
|
||||||
variant="soft"
|
|
||||||
phx-click={MusicLibraryWeb.Components.Chat.open("record-chat-sheet")}
|
|
||||||
>
|
|
||||||
<span class="sr-only">{gettext("Chat about album")}</span>
|
|
||||||
<.icon
|
|
||||||
name="hero-chat-bubble-left-right"
|
|
||||||
class="icon"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
<span :if={@chat_count > 0} class="text-xs font-medium">{@chat_count}</span>
|
|
||||||
</.button>
|
|
||||||
<.button
|
|
||||||
:if={@record.selected_release_id}
|
|
||||||
variant="soft"
|
|
||||||
phx-click={MusicLibraryWeb.Components.Release.open("release-with-tracks-sheet")}
|
|
||||||
>
|
|
||||||
<span class="sr-only">{gettext("Show Tracks")}</span>
|
|
||||||
<.icon
|
|
||||||
name="hero-numbered-list"
|
|
||||||
class="icon"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
</.button>
|
|
||||||
<.dropdown id={"actions-#{@record.id}"} placement="bottom-end">
|
|
||||||
<:toggle>
|
|
||||||
<.button variant="soft">
|
|
||||||
<span class="sr-only">{gettext("Actions")}</span>
|
|
||||||
<.icon
|
|
||||||
name="hero-ellipsis-vertical"
|
|
||||||
class="icon cursor-pointer text-zinc-500 dark:text-zinc-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
</.button>
|
|
||||||
</:toggle>
|
|
||||||
<.focus_wrap id={"actions-#{@record.id}-focus-wrap"}>
|
|
||||||
<.dropdown_link
|
<.dropdown_link
|
||||||
id={"actions-#{@record.id}-notes"}
|
id={"actions-#{@record.id}-notes"}
|
||||||
phx-click={MusicLibraryWeb.Components.Notes.open("record-notes-sheet")}
|
phx-click={MusicLibraryWeb.Components.Notes.open("record-notes-sheet")}
|
||||||
@@ -116,74 +70,9 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
|
|||||||
/>
|
/>
|
||||||
{gettext("Notes")}
|
{gettext("Notes")}
|
||||||
</.dropdown_link>
|
</.dropdown_link>
|
||||||
|
</:dropdown_start>
|
||||||
|
|
||||||
<.dropdown_link
|
<:dropdown_extra>
|
||||||
id={"actions-#{@record.id}-debug"}
|
|
||||||
phx-click={Fluxon.open_dialog("debug-data")}
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-code-bracket"
|
|
||||||
class="mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Debug data")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_separator />
|
|
||||||
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-edit"}
|
|
||||||
patch={~p"/collection/#{@record}/show/edit"}
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-pencil-square"
|
|
||||||
class="phx-click-loading:animate-bounce mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Edit")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-refresh-cover"}
|
|
||||||
phx-click="refresh_cover"
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-photo"
|
|
||||||
class="phx-click-loading:animate-bounce mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Refresh cover")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-refresh-mb-data"}
|
|
||||||
phx-click="refresh_musicbrainz_data"
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-cloud-arrow-down"
|
|
||||||
class="phx-click-loading:animate-bounce mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Refresh MB data")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-populate-genres"}
|
|
||||||
phx-click="populate_genres"
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-sparkles"
|
|
||||||
class="phx-click-loading:animate-shake mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Populate genres")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_link
|
<.dropdown_link
|
||||||
id={"actions-#{@record.id}-regenerate-embeddings"}
|
id={"actions-#{@record.id}-regenerate-embeddings"}
|
||||||
phx-click="regenerate_embeddings"
|
phx-click="regenerate_embeddings"
|
||||||
@@ -196,40 +85,9 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
|
|||||||
/>
|
/>
|
||||||
{gettext("Regenerate embeddings")}
|
{gettext("Regenerate embeddings")}
|
||||||
</.dropdown_link>
|
</.dropdown_link>
|
||||||
|
</:dropdown_extra>
|
||||||
|
</.record_show_action_bar>
|
||||||
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-extract-colors"}
|
|
||||||
phx-click="extract_colors"
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-paint-brush"
|
|
||||||
class="phx-click-loading:animate-shake mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Extract colors")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_separator />
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-delete"}
|
|
||||||
phx-click="delete"
|
|
||||||
data-confirm={gettext("Are you sure?")}
|
|
||||||
class="text-red-900! hover:bg-red-50! dark:text-red-500! dark:hover:bg-red-900/30! dark:hover:text-red-600!"
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-trash"
|
|
||||||
class="phx-click-loading:animate-spin mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Delete")}
|
|
||||||
</.dropdown_link>
|
|
||||||
</.focus_wrap>
|
|
||||||
</.dropdown>
|
|
||||||
</.button_group>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<.record_title_and_metadata record={@record} />
|
<.record_title_and_metadata record={@record} />
|
||||||
<.record_external_links record={@record} />
|
<.record_external_links record={@record} />
|
||||||
<div class="mt-4 md:mt-8">
|
<div class="mt-4 md:mt-8">
|
||||||
@@ -254,32 +112,11 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
|
|||||||
</.link>
|
</.link>
|
||||||
</.dl_row>
|
</.dl_row>
|
||||||
<.record_published_releases record={@record} />
|
<.record_published_releases record={@record} />
|
||||||
<.dl_row label={gettext("Collected release")}>
|
<.record_show_selected_release_row
|
||||||
<div class="flex justify-between space-x-2">
|
record={@record}
|
||||||
<span
|
label={gettext("Collected release")}
|
||||||
:if={!@record.selected_release_id}
|
copy_selected_release_id?={true}
|
||||||
class="text-xs text-zinc-700 md:text-sm dark:text-zinc-300"
|
|
||||||
>
|
|
||||||
{gettext("No release selected")}
|
|
||||||
</span>
|
|
||||||
<.release_summary
|
|
||||||
:if={@record.selected_release_id}
|
|
||||||
release={Records.Record.selected_release(@record)}
|
|
||||||
/>
|
/>
|
||||||
<span
|
|
||||||
:if={@record.selected_release_id}
|
|
||||||
id={"record-selected-release-" <> @record.id}
|
|
||||||
class="hidden"
|
|
||||||
>
|
|
||||||
{@record.selected_release_id}
|
|
||||||
</span>
|
|
||||||
<.copy_to_clipboard
|
|
||||||
:if={@record.selected_release_id}
|
|
||||||
target_id={"record-selected-release-" <> @record.id}
|
|
||||||
label={gettext("Copy record selected release ID to clipboard")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</.dl_row>
|
|
||||||
<.record_includes record={@record} />
|
<.record_includes record={@record} />
|
||||||
<.record_sets_list record_sets={@record_sets} />
|
<.record_sets_list record_sets={@record_sets} />
|
||||||
</dl>
|
</dl>
|
||||||
@@ -292,21 +129,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
|
|||||||
|
|
||||||
<.record_debug_sheet record={@record} embedding_text={@embedding_text} />
|
<.record_debug_sheet record={@record} embedding_text={@embedding_text} />
|
||||||
|
|
||||||
<.sheet
|
<.record_show_release_sheet record={@record} show_print?={true} timezone={@timezone} />
|
||||||
:if={@record.selected_release_id}
|
|
||||||
id="release-with-tracks-sheet"
|
|
||||||
placement="right"
|
|
||||||
class="flex min-w-xs flex-col overflow-hidden p-0 sm:min-w-sm"
|
|
||||||
>
|
|
||||||
<.live_component
|
|
||||||
id="release-with-tracks"
|
|
||||||
sheet_id="release-with-tracks-sheet"
|
|
||||||
module={MusicLibraryWeb.Components.Release}
|
|
||||||
release_id={@record.selected_release_id}
|
|
||||||
show_print?={true}
|
|
||||||
timezone={@timezone}
|
|
||||||
/>
|
|
||||||
</.sheet>
|
|
||||||
|
|
||||||
<.live_component
|
<.live_component
|
||||||
id="record-notes"
|
id="record-notes"
|
||||||
@@ -316,33 +139,15 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
|
|||||||
musicbrainz_id={@record.musicbrainz_id}
|
musicbrainz_id={@record.musicbrainz_id}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<.live_component
|
<.record_show_chat record={@record} embedding_text={@embedding_text} />
|
||||||
id="record-chat"
|
|
||||||
sheet_id="record-chat-sheet"
|
|
||||||
module={MusicLibraryWeb.Components.Chat}
|
|
||||||
title={@record.title}
|
|
||||||
entity={:record}
|
|
||||||
musicbrainz_id={@record.musicbrainz_id}
|
|
||||||
chat_module={MusicLibrary.Chats.RecordChat}
|
|
||||||
chat_context={{@record, @embedding_text}}
|
|
||||||
placeholder={gettext("Ask about this album...")}
|
|
||||||
empty_prompt={gettext("Ask anything about this album")}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<.structured_modal
|
<.record_show_edit_modal
|
||||||
:if={@live_action == :edit}
|
|
||||||
id="record-modal"
|
|
||||||
on_close={JS.patch(~p"/collection/#{@record}")}
|
|
||||||
>
|
|
||||||
<.live_component
|
|
||||||
module={MusicLibraryWeb.Components.RecordForm}
|
|
||||||
id={@record.id}
|
|
||||||
action={@live_action}
|
|
||||||
show_purchased_at={true}
|
|
||||||
record={@record}
|
record={@record}
|
||||||
patch={~p"/collection/#{@record}"}
|
live_action={@live_action}
|
||||||
|
show_purchased_at={true}
|
||||||
|
close_path={~p"/collection/#{@record}"}
|
||||||
|
patch_path={~p"/collection/#{@record}"}
|
||||||
/>
|
/>
|
||||||
</.structured_modal>
|
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
@@ -352,52 +157,24 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
|
|||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(:current_section, :collection)
|
|> assign(:current_section, :collection)
|
||||||
|> assign(:can_scrobble?, ScrobbleActivity.can_scrobble?())
|
|> assign(:can_scrobble?, ScrobbleActivity.can_scrobble?())}
|
||||||
|> assign(:release_with_tracks, nil)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_params(%{"id" => id}, _, socket) do
|
def handle_params(%{"id" => id}, _, socket) do
|
||||||
RecordActions.manage_subscription(socket, id)
|
|
||||||
|
|
||||||
record = Records.get_record!(id)
|
|
||||||
last_listened_track = ListeningStats.get_last_listened_track(record)
|
|
||||||
play_count = ListeningStats.play_count(record)
|
|
||||||
record_sets = RecordSets.list_record_sets_for_record(record.id)
|
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, page_title(socket.assigns.live_action, record))
|
|> RecordShow.assign_common_record(id, gettext("Collection"))
|
||||||
|> assign(:record, record)
|
|> assign_collection_record_context()}
|
||||||
|> assign(:last_listened_track, last_listened_track)
|
|
||||||
|> assign(:play_count, play_count)
|
|
||||||
|> assign(:record_sets, record_sets)
|
|
||||||
|> assign(:chat_count, Chats.count_chats(:record, record.musicbrainz_id))
|
|
||||||
|> RecordActions.assign_embedding_text()
|
|
||||||
|> assign_similar_records()}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("delete", _params, socket) do
|
def handle_event("delete", _params, socket) do
|
||||||
{:ok, _} = Records.delete_record(socket.assigns.record)
|
RecordShow.delete_record(socket, ~p"/collection")
|
||||||
|
|
||||||
{:noreply, push_navigate(socket, to: ~p"/collection")}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("refresh_musicbrainz_data", _params, socket) do
|
def handle_event(event, _params, socket) when event in @common_record_events do
|
||||||
RecordActions.refresh_musicbrainz_data(socket)
|
RecordShow.handle_common_event(event, socket)
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("populate_genres", _params, socket) do
|
|
||||||
RecordActions.populate_genres(socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("refresh_cover", _params, socket) do
|
|
||||||
RecordActions.refresh_cover(socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("extract_colors", _params, socket) do
|
|
||||||
RecordActions.extract_colors(socket)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("regenerate_embeddings", _params, socket) do
|
def handle_event("regenerate_embeddings", _params, socket) do
|
||||||
@@ -420,101 +197,30 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("scrobble_release", _params, socket) do
|
def handle_event("scrobble_release", _params, socket) do
|
||||||
record = socket.assigns.record
|
RecordShow.scrobble_release(socket)
|
||||||
|
|
||||||
{:noreply,
|
|
||||||
start_async(socket, :scrobble_release, fn ->
|
|
||||||
with {:ok, release} <- MusicBrainz.get_release(record.selected_release_id) do
|
|
||||||
release_with_tracks = MusicBrainz.Release.from_api_response(release)
|
|
||||||
|
|
||||||
ScrobbleActivity.scrobble_release(release_with_tracks, :finished_at, DateTime.utc_now())
|
|
||||||
end
|
|
||||||
end)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_async(:scrobble_release, {:ok, {:ok, _result}}, socket) do
|
def handle_async(:scrobble_release, result, socket) do
|
||||||
{:noreply, put_toast(socket, :info, gettext("Release scrobbled successfully"))}
|
RecordShow.handle_scrobble_release(result, socket)
|
||||||
end
|
|
||||||
|
|
||||||
def handle_async(:scrobble_release, {:ok, {:error, reason}}, socket) do
|
|
||||||
{:noreply,
|
|
||||||
put_toast(
|
|
||||||
socket,
|
|
||||||
:error,
|
|
||||||
gettext("Error scrobbling release") <> ": " <> ErrorMessages.friendly_message(reason)
|
|
||||||
)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_async(:scrobble_release, {:exit, reason}, socket) do
|
|
||||||
Logger.error("Scrobble release failed: #{inspect(reason)}")
|
|
||||||
|
|
||||||
{:noreply,
|
|
||||||
put_toast(
|
|
||||||
socket,
|
|
||||||
:error,
|
|
||||||
gettext("Error scrobbling release") <> ": " <> ErrorMessages.friendly_message(reason)
|
|
||||||
)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({MusicLibraryWeb.Components.RecordForm, {:saved, record}}, socket) do
|
def handle_info({MusicLibraryWeb.Components.RecordForm, {:saved, record}}, socket) do
|
||||||
{:noreply,
|
RecordShow.handle_saved_record(socket, record, &assign_similar_records/1)
|
||||||
socket
|
|
||||||
|> assign(:record, record)
|
|
||||||
|> assign_similar_records()
|
|
||||||
|> RecordActions.assign_embedding_text()}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({MusicLibraryWeb.Components.Chat, :chats_changed}, socket) do
|
def handle_info({MusicLibraryWeb.Components.Chat, :chats_changed}, socket) do
|
||||||
RecordActions.handle_chats_changed(socket)
|
RecordShow.handle_chats_changed(socket)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({MusicLibraryWeb.Components.Release, {:loaded, _release}}, socket) do
|
def handle_info({MusicLibraryWeb.Components.Release, {:loaded, _release}}, socket) do
|
||||||
{:noreply, socket}
|
RecordShow.handle_release_loaded(socket)
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_info({:update, record}, socket) do
|
def handle_info({:update, record}, socket) do
|
||||||
cond do
|
RecordShow.handle_record_update(socket, record, &assign_similar_records/1)
|
||||||
record.id != socket.assigns.record.id ->
|
|
||||||
{:noreply, socket}
|
|
||||||
|
|
||||||
socket.assigns.live_action == :edit ->
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> put_toast(
|
|
||||||
:warning,
|
|
||||||
gettext(
|
|
||||||
"Record was updated in the background. Your edits may be stale — save and re-open to see the latest data."
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
true ->
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> RecordActions.handle_record_updated(record)
|
|
||||||
|> assign_similar_records()}
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
defp page_title(action, record) do
|
|
||||||
Enum.join(
|
|
||||||
[
|
|
||||||
Records.Record.artist_names(record),
|
|
||||||
"-",
|
|
||||||
record.title,
|
|
||||||
"·",
|
|
||||||
title_segment(action),
|
|
||||||
"·",
|
|
||||||
gettext("Collection")
|
|
||||||
],
|
|
||||||
" "
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp title_segment(:show), do: gettext("Details")
|
|
||||||
defp title_segment(:edit), do: gettext("Edit")
|
|
||||||
|
|
||||||
attr :similar_records, :list, required: true
|
attr :similar_records, :list, required: true
|
||||||
|
|
||||||
@@ -562,6 +268,15 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
|
|||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp assign_collection_record_context(socket) do
|
||||||
|
record = socket.assigns.record
|
||||||
|
|
||||||
|
socket
|
||||||
|
|> assign(:last_listened_track, ListeningStats.get_last_listened_track(record))
|
||||||
|
|> assign(:play_count, ListeningStats.play_count(record))
|
||||||
|
|> assign_similar_records()
|
||||||
|
end
|
||||||
|
|
||||||
defp assign_similar_records(socket) do
|
defp assign_similar_records(socket) do
|
||||||
similar_records =
|
similar_records =
|
||||||
Similarity.find_similar(socket.assigns.record.id, limit: 6, scope: :collection)
|
Similarity.find_similar(socket.assigns.record.id, limit: 6, scope: :collection)
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
defmodule MusicLibraryWeb.WishlistLive.Show do
|
defmodule MusicLibraryWeb.WishlistLive.Show do
|
||||||
use MusicLibraryWeb, :live_view
|
use MusicLibraryWeb, :live_view
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
import MusicLibraryWeb.RecordComponents,
|
import MusicLibraryWeb.RecordComponents,
|
||||||
only: [
|
only: [
|
||||||
artist_links: 1,
|
|
||||||
record_cover: 1,
|
record_cover: 1,
|
||||||
record_debug_sheet: 1,
|
record_debug_sheet: 1,
|
||||||
record_external_links: 1,
|
record_external_links: 1,
|
||||||
@@ -13,16 +10,25 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
|
|||||||
record_includes: 1,
|
record_includes: 1,
|
||||||
record_published_releases: 1,
|
record_published_releases: 1,
|
||||||
record_sets_list: 1,
|
record_sets_list: 1,
|
||||||
|
record_show_action_bar: 1,
|
||||||
|
record_show_chat: 1,
|
||||||
|
record_show_edit_modal: 1,
|
||||||
|
record_show_release_sheet: 1,
|
||||||
|
record_show_selected_release_row: 1,
|
||||||
record_timestamps: 1,
|
record_timestamps: 1,
|
||||||
record_title_and_metadata: 1,
|
record_title_and_metadata: 1
|
||||||
release_summary: 1
|
|
||||||
]
|
]
|
||||||
|
|
||||||
alias MusicLibrary.Chats
|
|
||||||
alias MusicLibrary.OnlineStoreTemplates
|
alias MusicLibrary.OnlineStoreTemplates
|
||||||
alias MusicLibrary.{Records, RecordSets, ScrobbleActivity}
|
alias MusicLibrary.{Records, ScrobbleActivity}
|
||||||
alias MusicLibraryWeb.ErrorMessages
|
alias MusicLibraryWeb.LiveHelpers.RecordShow
|
||||||
alias MusicLibraryWeb.LiveHelpers.RecordActions
|
|
||||||
|
@common_record_events [
|
||||||
|
"refresh_musicbrainz_data",
|
||||||
|
"refresh_cover",
|
||||||
|
"populate_genres",
|
||||||
|
"extract_colors"
|
||||||
|
]
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
@@ -42,131 +48,13 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="xl:col-span-3">
|
<div class="xl:col-span-3">
|
||||||
<div class="mt-4 flex items-center justify-between md:mt-0">
|
<.record_show_action_bar
|
||||||
<h1 class="text-base/6 font-medium text-zinc-700">
|
record={@record}
|
||||||
<.artist_links joinphrase_class="text-sm" artists={@record.artists} />
|
can_scrobble?={@can_scrobble?}
|
||||||
</h1>
|
chat_count={@chat_count}
|
||||||
<div class="min-w-12">
|
edit_path={~p"/wishlist/#{@record}/show/edit"}
|
||||||
<.button_group>
|
|
||||||
<.button
|
|
||||||
:if={@can_scrobble? and @record.selected_release_id}
|
|
||||||
variant="soft"
|
|
||||||
phx-click="scrobble_release"
|
|
||||||
>
|
>
|
||||||
<span class="sr-only">{gettext("Scrobble release")}</span>
|
<:dropdown_extra>
|
||||||
<.icon
|
|
||||||
name="hero-play"
|
|
||||||
class="icon"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
</.button>
|
|
||||||
<.button
|
|
||||||
variant="soft"
|
|
||||||
phx-click={MusicLibraryWeb.Components.Chat.open("record-chat-sheet")}
|
|
||||||
>
|
|
||||||
<span class="sr-only">{gettext("Chat about album")}</span>
|
|
||||||
<.icon
|
|
||||||
name="hero-chat-bubble-left-right"
|
|
||||||
class="icon"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
<span :if={@chat_count > 0} class="text-xs font-medium">{@chat_count}</span>
|
|
||||||
</.button>
|
|
||||||
<.button
|
|
||||||
:if={@record.selected_release_id}
|
|
||||||
variant="soft"
|
|
||||||
phx-click={MusicLibraryWeb.Components.Release.open("release-with-tracks-sheet")}
|
|
||||||
>
|
|
||||||
<span class="sr-only">{gettext("Show Tracks")}</span>
|
|
||||||
<.icon
|
|
||||||
name="hero-numbered-list"
|
|
||||||
class="icon"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
</.button>
|
|
||||||
<.dropdown id={"actions-#{@record.id}"} placement="bottom-end">
|
|
||||||
<:toggle>
|
|
||||||
<.button variant="soft">
|
|
||||||
<span class="sr-only">{gettext("Actions")}</span>
|
|
||||||
<.icon
|
|
||||||
name="hero-ellipsis-vertical"
|
|
||||||
class="icon cursor-pointer text-zinc-500 dark:text-zinc-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
</.button>
|
|
||||||
</:toggle>
|
|
||||||
<.focus_wrap id={"actions-#{@record.id}-focus-wrap"}>
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-debug"}
|
|
||||||
phx-click={Fluxon.open_dialog("debug-data")}
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-code-bracket"
|
|
||||||
class="mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Debug data")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_separator />
|
|
||||||
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-edit"}
|
|
||||||
patch={~p"/wishlist/#{@record}/show/edit"}
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-pencil-square"
|
|
||||||
class="phx-click-loading:animate-bounce mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Edit")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-refresh-cover"}
|
|
||||||
phx-click="refresh_cover"
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-photo"
|
|
||||||
class="phx-click-loading:animate-bounce mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Refresh cover")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-refresh-mb-data"}
|
|
||||||
phx-click="refresh_musicbrainz_data"
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-cloud-arrow-down"
|
|
||||||
class="phx-click-loading:animate-bounce mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Refresh MB data")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-populate-genres"}
|
|
||||||
phx-click="populate_genres"
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-sparkles"
|
|
||||||
class="phx-click-loading:animate-shake mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Populate genres")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_link
|
<.dropdown_link
|
||||||
:if={!@record.purchased_at}
|
:if={!@record.purchased_at}
|
||||||
id={"actions-#{@record.id}-purchase"}
|
id={"actions-#{@record.id}-purchase"}
|
||||||
@@ -183,60 +71,19 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
|
|||||||
/>
|
/>
|
||||||
{gettext("Purchased")}
|
{gettext("Purchased")}
|
||||||
</.dropdown_link>
|
</.dropdown_link>
|
||||||
|
</:dropdown_extra>
|
||||||
|
</.record_show_action_bar>
|
||||||
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-extract-colors"}
|
|
||||||
phx-click="extract_colors"
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-paint-brush"
|
|
||||||
class="phx-click-loading:animate-shake mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Extract colors")}
|
|
||||||
</.dropdown_link>
|
|
||||||
|
|
||||||
<.dropdown_separator />
|
|
||||||
<.dropdown_link
|
|
||||||
id={"actions-#{@record.id}-delete"}
|
|
||||||
phx-click="delete"
|
|
||||||
data-confirm={gettext("Are you sure?")}
|
|
||||||
class="text-red-900! hover:bg-red-50! dark:text-red-500! dark:hover:bg-red-900/30! dark:hover:text-red-600!"
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-trash"
|
|
||||||
class="phx-click-loading:animate-spin mr-1 size-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
{gettext("Delete")}
|
|
||||||
</.dropdown_link>
|
|
||||||
</.focus_wrap>
|
|
||||||
</.dropdown>
|
|
||||||
</.button_group>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<.record_title_and_metadata record={@record} current_date={@current_date} />
|
<.record_title_and_metadata record={@record} current_date={@current_date} />
|
||||||
<.record_external_links record={@record} />
|
<.record_external_links record={@record} />
|
||||||
<div class="mt-4 md:mt-8">
|
<div class="mt-4 md:mt-8">
|
||||||
<dl class="divide-y divide-zinc-100 dark:divide-slate-300/30">
|
<dl class="divide-y divide-zinc-100 dark:divide-slate-300/30">
|
||||||
<.record_genres record={@record} section={:wishlist} />
|
<.record_genres record={@record} section={:wishlist} />
|
||||||
<.record_published_releases record={@record} />
|
<.record_published_releases record={@record} />
|
||||||
<.dl_row label={gettext("Wishlisted release")}>
|
<.record_show_selected_release_row
|
||||||
<div class="flex justify-between space-x-2">
|
record={@record}
|
||||||
<span
|
label={gettext("Wishlisted release")}
|
||||||
:if={!@record.selected_release_id}
|
|
||||||
class="text-xs text-zinc-700 md:text-sm dark:text-zinc-300"
|
|
||||||
>
|
|
||||||
{gettext("No release selected")}
|
|
||||||
</span>
|
|
||||||
<.release_summary
|
|
||||||
:if={@record.selected_release_id}
|
|
||||||
release={Records.Record.selected_release(@record)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</.dl_row>
|
|
||||||
<.record_includes record={@record} />
|
<.record_includes record={@record} />
|
||||||
<.record_sets_list record_sets={@record_sets} />
|
<.record_sets_list record_sets={@record_sets} />
|
||||||
</dl>
|
</dl>
|
||||||
@@ -281,49 +128,17 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
|
|||||||
|
|
||||||
<.record_debug_sheet record={@record} embedding_text={@embedding_text} />
|
<.record_debug_sheet record={@record} embedding_text={@embedding_text} />
|
||||||
|
|
||||||
<.sheet
|
<.record_show_release_sheet record={@record} show_print?={false} timezone={@timezone} />
|
||||||
:if={@record.selected_release_id}
|
|
||||||
id="release-with-tracks-sheet"
|
|
||||||
placement="right"
|
|
||||||
class="flex min-w-xs flex-col overflow-hidden p-0 sm:min-w-sm"
|
|
||||||
>
|
|
||||||
<.live_component
|
|
||||||
id="release-with-tracks"
|
|
||||||
sheet_id="release-with-tracks-sheet"
|
|
||||||
module={MusicLibraryWeb.Components.Release}
|
|
||||||
release_id={@record.selected_release_id}
|
|
||||||
show_print?={false}
|
|
||||||
timezone={@timezone}
|
|
||||||
/>
|
|
||||||
</.sheet>
|
|
||||||
|
|
||||||
<.live_component
|
<.record_show_chat record={@record} embedding_text={@embedding_text} />
|
||||||
id="record-chat"
|
|
||||||
sheet_id="record-chat-sheet"
|
|
||||||
module={MusicLibraryWeb.Components.Chat}
|
|
||||||
title={@record.title}
|
|
||||||
entity={:record}
|
|
||||||
musicbrainz_id={@record.musicbrainz_id}
|
|
||||||
chat_module={MusicLibrary.Chats.RecordChat}
|
|
||||||
chat_context={{@record, @embedding_text}}
|
|
||||||
placeholder={gettext("Ask about this album...")}
|
|
||||||
empty_prompt={gettext("Ask anything about this album")}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<.structured_modal
|
<.record_show_edit_modal
|
||||||
:if={@live_action == :edit}
|
|
||||||
id="record-modal"
|
|
||||||
on_close={JS.patch(~p"/wishlist/#{@record}")}
|
|
||||||
>
|
|
||||||
<.live_component
|
|
||||||
module={MusicLibraryWeb.Components.RecordForm}
|
|
||||||
id={@record.id}
|
|
||||||
action={@live_action}
|
|
||||||
show_purchased_at={false}
|
|
||||||
record={@record}
|
record={@record}
|
||||||
patch={~p"/wishlist/#{@record}"}
|
live_action={@live_action}
|
||||||
|
show_purchased_at={false}
|
||||||
|
close_path={~p"/wishlist/#{@record}"}
|
||||||
|
patch_path={~p"/wishlist/#{@record}"}
|
||||||
/>
|
/>
|
||||||
</.structured_modal>
|
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
@@ -341,40 +156,19 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_params(%{"id" => id}, _, socket) do
|
def handle_params(%{"id" => id}, _, socket) do
|
||||||
RecordActions.manage_subscription(socket, id)
|
|
||||||
|
|
||||||
record = Records.get_record!(id)
|
|
||||||
online_store_templates = OnlineStoreTemplates.list_enabled_templates()
|
|
||||||
|
|
||||||
record_sets = RecordSets.list_record_sets_for_record(record.id)
|
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, page_title(socket.assigns.live_action, record))
|
|> RecordShow.assign_common_record(id, gettext("Wishlist"))
|
||||||
|> assign(:record, record)
|
|> assign(:online_store_templates, OnlineStoreTemplates.list_enabled_templates())}
|
||||||
|> assign(:online_store_templates, online_store_templates)
|
|
||||||
|> assign(:record_sets, record_sets)
|
|
||||||
|> assign(:chat_count, Chats.count_chats(:record, record.musicbrainz_id))
|
|
||||||
|> RecordActions.assign_embedding_text()}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("delete", _params, socket) do
|
def handle_event("delete", _params, socket) do
|
||||||
{:ok, _} = Records.delete_record(socket.assigns.record)
|
RecordShow.delete_record(socket, ~p"/wishlist")
|
||||||
|
|
||||||
{:noreply, push_navigate(socket, to: ~p"/wishlist")}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("refresh_musicbrainz_data", _params, socket) do
|
def handle_event(event, _params, socket) when event in @common_record_events do
|
||||||
RecordActions.refresh_musicbrainz_data(socket)
|
RecordShow.handle_common_event(event, socket)
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("refresh_cover", _params, socket) do
|
|
||||||
RecordActions.refresh_cover(socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("populate_genres", _params, socket) do
|
|
||||||
RecordActions.populate_genres(socket)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("add-to-collection", _params, socket) do
|
def handle_event("add-to-collection", _params, socket) do
|
||||||
@@ -393,100 +187,29 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("extract_colors", _params, socket) do
|
|
||||||
RecordActions.extract_colors(socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("scrobble_release", _params, socket) do
|
def handle_event("scrobble_release", _params, socket) do
|
||||||
record = socket.assigns.record
|
RecordShow.scrobble_release(socket)
|
||||||
|
|
||||||
{:noreply,
|
|
||||||
start_async(socket, :scrobble_release, fn ->
|
|
||||||
with {:ok, release} <- MusicBrainz.get_release(record.selected_release_id) do
|
|
||||||
release_with_tracks = MusicBrainz.Release.from_api_response(release)
|
|
||||||
|
|
||||||
ScrobbleActivity.scrobble_release(release_with_tracks, :finished_at, DateTime.utc_now())
|
|
||||||
end
|
|
||||||
end)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_async(:scrobble_release, {:ok, {:ok, _result}}, socket) do
|
def handle_async(:scrobble_release, result, socket) do
|
||||||
{:noreply, put_toast(socket, :info, gettext("Release scrobbled successfully"))}
|
RecordShow.handle_scrobble_release(result, socket)
|
||||||
end
|
|
||||||
|
|
||||||
def handle_async(:scrobble_release, {:ok, {:error, reason}}, socket) do
|
|
||||||
{:noreply,
|
|
||||||
put_toast(
|
|
||||||
socket,
|
|
||||||
:error,
|
|
||||||
gettext("Error scrobbling release") <> ": " <> ErrorMessages.friendly_message(reason)
|
|
||||||
)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_async(:scrobble_release, {:exit, reason}, socket) do
|
|
||||||
Logger.error("Scrobble release failed: #{inspect(reason)}")
|
|
||||||
|
|
||||||
{:noreply,
|
|
||||||
put_toast(
|
|
||||||
socket,
|
|
||||||
:error,
|
|
||||||
gettext("Error scrobbling release") <> ": " <> ErrorMessages.friendly_message(reason)
|
|
||||||
)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({MusicLibraryWeb.Components.RecordForm, {:saved, record}}, socket) do
|
def handle_info({MusicLibraryWeb.Components.RecordForm, {:saved, record}}, socket) do
|
||||||
{:noreply,
|
RecordShow.handle_saved_record(socket, record)
|
||||||
socket
|
|
||||||
|> assign(:record, record)
|
|
||||||
|> RecordActions.assign_embedding_text()}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({MusicLibraryWeb.Components.Chat, :chats_changed}, socket) do
|
def handle_info({MusicLibraryWeb.Components.Chat, :chats_changed}, socket) do
|
||||||
RecordActions.handle_chats_changed(socket)
|
RecordShow.handle_chats_changed(socket)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({MusicLibraryWeb.Components.Release, {:loaded, _release}}, socket) do
|
def handle_info({MusicLibraryWeb.Components.Release, {:loaded, _release}}, socket) do
|
||||||
{:noreply, socket}
|
RecordShow.handle_release_loaded(socket)
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_info({:update, record}, socket) do
|
def handle_info({:update, record}, socket) do
|
||||||
cond do
|
RecordShow.handle_record_update(socket, record)
|
||||||
record.id != socket.assigns.record.id ->
|
|
||||||
{:noreply, socket}
|
|
||||||
|
|
||||||
socket.assigns.live_action == :edit ->
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> put_toast(
|
|
||||||
:warning,
|
|
||||||
gettext(
|
|
||||||
"Record was updated in the background. Your edits may be stale — save and re-open to see the latest data."
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
true ->
|
|
||||||
{:noreply, RecordActions.handle_record_updated(socket, record)}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp page_title(action, record) do
|
|
||||||
Enum.join(
|
|
||||||
[
|
|
||||||
Records.Record.artist_names(record),
|
|
||||||
"-",
|
|
||||||
record.title,
|
|
||||||
"·",
|
|
||||||
title_segment(action),
|
|
||||||
"·",
|
|
||||||
gettext("Wishlist")
|
|
||||||
],
|
|
||||||
" "
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp title_segment(:show), do: gettext("Details")
|
|
||||||
defp title_segment(:edit), do: gettext("Edit")
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
defmodule MusicLibraryWeb.LiveHelpers.RecordShow do
|
||||||
|
@moduledoc """
|
||||||
|
Shared show-page helpers for collection and wishlist record LiveViews.
|
||||||
|
|
||||||
|
The helpers keep common record loading, event handling, async scrobble
|
||||||
|
handling, and background-update behaviour in one place while allowing each
|
||||||
|
LiveView to compose page-specific assigns and UI around the shared flow.
|
||||||
|
"""
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
import LiveToast, only: [put_toast: 3]
|
||||||
|
import Phoenix.Component, only: [assign: 3]
|
||||||
|
import Phoenix.LiveView, only: [push_navigate: 2, start_async: 3]
|
||||||
|
|
||||||
|
use Gettext, backend: MusicLibraryWeb.Gettext
|
||||||
|
|
||||||
|
alias MusicLibrary.{Chats, Records, RecordSets, ScrobbleActivity}
|
||||||
|
alias MusicLibraryWeb.ErrorMessages
|
||||||
|
alias MusicLibraryWeb.LiveHelpers.RecordActions
|
||||||
|
|
||||||
|
def assign_common_record(socket, id, section_title) do
|
||||||
|
RecordActions.manage_subscription(socket, id)
|
||||||
|
|
||||||
|
record = Records.get_record!(id)
|
||||||
|
record_sets = RecordSets.list_record_sets_for_record(record.id)
|
||||||
|
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, page_title(socket.assigns.live_action, record, section_title))
|
||||||
|
|> assign(:record, record)
|
||||||
|
|> assign(:record_sets, record_sets)
|
||||||
|
|> assign(:chat_count, Chats.count_chats(:record, record.musicbrainz_id))
|
||||||
|
|> RecordActions.assign_embedding_text()
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_common_event("refresh_musicbrainz_data", socket) do
|
||||||
|
RecordActions.refresh_musicbrainz_data(socket)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_common_event("refresh_cover", socket) do
|
||||||
|
RecordActions.refresh_cover(socket)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_common_event("populate_genres", socket) do
|
||||||
|
RecordActions.populate_genres(socket)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_common_event("extract_colors", socket) do
|
||||||
|
RecordActions.extract_colors(socket)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_record(socket, path) do
|
||||||
|
{:ok, _} = Records.delete_record(socket.assigns.record)
|
||||||
|
|
||||||
|
{:noreply, push_navigate(socket, to: path)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def scrobble_release(socket) do
|
||||||
|
record = socket.assigns.record
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
start_async(socket, :scrobble_release, fn ->
|
||||||
|
with {:ok, release} <- MusicBrainz.get_release(record.selected_release_id) do
|
||||||
|
release_with_tracks = MusicBrainz.Release.from_api_response(release)
|
||||||
|
|
||||||
|
ScrobbleActivity.scrobble_release(release_with_tracks, :finished_at, DateTime.utc_now())
|
||||||
|
end
|
||||||
|
end)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_scrobble_release({:ok, {:ok, _result}}, socket) do
|
||||||
|
{:noreply, put_toast(socket, :info, gettext("Release scrobbled successfully"))}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_scrobble_release({:ok, {:error, reason}}, socket) do
|
||||||
|
{:noreply,
|
||||||
|
put_toast(
|
||||||
|
socket,
|
||||||
|
:error,
|
||||||
|
gettext("Error scrobbling release") <> ": " <> ErrorMessages.friendly_message(reason)
|
||||||
|
)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_scrobble_release({:exit, reason}, socket) do
|
||||||
|
Logger.error("Scrobble release failed: #{inspect(reason)}")
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
put_toast(
|
||||||
|
socket,
|
||||||
|
:error,
|
||||||
|
gettext("Error scrobbling release") <> ": " <> ErrorMessages.friendly_message(reason)
|
||||||
|
)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_saved_record(socket, record, after_assign \\ &Function.identity/1) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:record, record)
|
||||||
|
|> after_assign.()
|
||||||
|
|> RecordActions.assign_embedding_text()}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_chats_changed(socket) do
|
||||||
|
RecordActions.handle_chats_changed(socket)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_release_loaded(socket) do
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_record_update(socket, record, after_update \\ &Function.identity/1) do
|
||||||
|
cond do
|
||||||
|
record.id != socket.assigns.record.id ->
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
|
socket.assigns.live_action == :edit ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_toast(
|
||||||
|
:warning,
|
||||||
|
gettext(
|
||||||
|
"Record was updated in the background. Your edits may be stale — save and re-open to see the latest data."
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
|
true ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> RecordActions.handle_record_updated(record)
|
||||||
|
|> after_update.()}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp page_title(action, record, section_title) do
|
||||||
|
Enum.join(
|
||||||
|
[
|
||||||
|
Records.Record.artist_names(record),
|
||||||
|
"-",
|
||||||
|
record.title,
|
||||||
|
"·",
|
||||||
|
title_segment(action),
|
||||||
|
"·",
|
||||||
|
section_title
|
||||||
|
],
|
||||||
|
" "
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp title_segment(:show), do: gettext("Details")
|
||||||
|
defp title_segment(:edit), do: gettext("Edit")
|
||||||
|
end
|
||||||
+17
-39
@@ -12,13 +12,11 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/components/record_components.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/online_store_template_live/index.ex
|
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/index.ex
|
#: lib/music_library_web/live/record_set_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/show.ex
|
#: lib/music_library_web/live/record_set_live/show.ex
|
||||||
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
||||||
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Are you sure?"
|
msgid "Are you sure?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -38,13 +36,11 @@ msgid "Cover art"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/components/record_components.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/online_store_template_live/index.ex
|
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/index.ex
|
#: lib/music_library_web/live/record_set_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/show.ex
|
#: lib/music_library_web/live/record_set_live/show.ex
|
||||||
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
||||||
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -52,14 +48,13 @@ msgstr ""
|
|||||||
#: lib/music_library_web/components/notes.ex
|
#: lib/music_library_web/components/notes.ex
|
||||||
#: lib/music_library_web/components/record_components.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/artist_live/show.ex
|
#: lib/music_library_web/live/artist_live/show.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/online_store_template_live/index.ex
|
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/index.ex
|
#: lib/music_library_web/live/record_set_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/show.ex
|
#: lib/music_library_web/live/record_set_live/show.ex
|
||||||
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
||||||
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#: lib/music_library_web/live_helpers/index_actions.ex
|
#: lib/music_library_web/live_helpers/index_actions.ex
|
||||||
|
#: lib/music_library_web/live_helpers/record_show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -280,8 +275,7 @@ msgid "No MB ID"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/artist_live/show.ex
|
#: lib/music_library_web/live/artist_live/show.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/live_helpers/record_show.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Details"
|
msgid "Details"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -296,20 +290,17 @@ msgstr ""
|
|||||||
msgid "Loading biography"
|
msgid "Loading biography"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Populate genres"
|
msgid "Populate genres"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Refresh MB data"
|
msgid "Refresh MB data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Refresh cover"
|
msgid "Refresh cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -601,8 +592,7 @@ msgstr ""
|
|||||||
msgid "Collected release"
|
msgid "Collected release"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "No release selected"
|
msgid "No release selected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -622,8 +612,7 @@ msgstr ""
|
|||||||
msgid "Loading release with tracks"
|
msgid "Loading release with tracks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Show Tracks"
|
msgid "Show Tracks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -639,15 +628,13 @@ msgid "Successfully connected your Last.fm account"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/components/release.ex
|
#: lib/music_library_web/components/release.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/live_helpers/record_show.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Error scrobbling release"
|
msgid "Error scrobbling release"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/components/release.ex
|
#: lib/music_library_web/components/release.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/live_helpers/record_show.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Release scrobbled successfully"
|
msgid "Release scrobbled successfully"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -662,9 +649,8 @@ msgstr ""
|
|||||||
msgid "Scrobbling..."
|
msgid "Scrobbling..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/components/release.ex
|
#: lib/music_library_web/components/release.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Scrobble release"
|
msgid "Scrobble release"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -795,13 +781,11 @@ msgstr ""
|
|||||||
#: lib/music_library_web/components/layouts/app.html.heex
|
#: lib/music_library_web/components/layouts/app.html.heex
|
||||||
#: lib/music_library_web/components/record_components.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/artist_live/show.ex
|
#: lib/music_library_web/live/artist_live/show.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/online_store_template_live/index.ex
|
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/index.ex
|
#: lib/music_library_web/live/record_set_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/show.ex
|
#: lib/music_library_web/live/record_set_live/show.ex
|
||||||
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
||||||
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1013,7 +997,7 @@ msgstr ""
|
|||||||
msgid "to open this search"
|
msgid "to open this search"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Copy record selected release ID to clipboard"
|
msgid "Copy record selected release ID to clipboard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1736,14 +1720,12 @@ msgstr ""
|
|||||||
msgid "Based on genres, artists, and musical style"
|
msgid "Based on genres, artists, and musical style"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Ask about this album..."
|
msgid "Ask about this album..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Ask anything about this album"
|
msgid "Ask anything about this album"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1753,8 +1735,7 @@ msgstr ""
|
|||||||
msgid "Chat about %{title}"
|
msgid "Chat about %{title}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Chat about album"
|
msgid "Chat about album"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1822,9 +1803,8 @@ msgid "Copy MB ID"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/components/core_components.ex
|
#: lib/music_library_web/components/core_components.ex
|
||||||
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/artist_live/show.ex
|
#: lib/music_library_web/live/artist_live/show.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Debug data"
|
msgid "Debug data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1910,8 +1890,7 @@ msgstr ""
|
|||||||
msgid "Error extracting colors"
|
msgid "Error extracting colors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Extract colors"
|
msgid "Extract colors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2561,8 +2540,7 @@ msgstr ""
|
|||||||
msgid "ISO format"
|
msgid "ISO format"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/live_helpers/record_show.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Record was updated in the background. Your edits may be stale — save and re-open to see the latest data."
|
msgid "Record was updated in the background. Your edits may be stale — save and re-open to see the latest data."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -12,13 +12,11 @@ msgstr ""
|
|||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
#: lib/music_library_web/components/record_components.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/online_store_template_live/index.ex
|
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/index.ex
|
#: lib/music_library_web/live/record_set_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/show.ex
|
#: lib/music_library_web/live/record_set_live/show.ex
|
||||||
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
||||||
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Are you sure?"
|
msgid "Are you sure?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -38,13 +36,11 @@ msgid "Cover art"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/components/record_components.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/online_store_template_live/index.ex
|
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/index.ex
|
#: lib/music_library_web/live/record_set_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/show.ex
|
#: lib/music_library_web/live/record_set_live/show.ex
|
||||||
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
||||||
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -52,14 +48,13 @@ msgstr ""
|
|||||||
#: lib/music_library_web/components/notes.ex
|
#: lib/music_library_web/components/notes.ex
|
||||||
#: lib/music_library_web/components/record_components.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/artist_live/show.ex
|
#: lib/music_library_web/live/artist_live/show.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/online_store_template_live/index.ex
|
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/index.ex
|
#: lib/music_library_web/live/record_set_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/show.ex
|
#: lib/music_library_web/live/record_set_live/show.ex
|
||||||
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
||||||
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#: lib/music_library_web/live_helpers/index_actions.ex
|
#: lib/music_library_web/live_helpers/index_actions.ex
|
||||||
|
#: lib/music_library_web/live_helpers/record_show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -280,8 +275,7 @@ msgid "No MB ID"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/artist_live/show.ex
|
#: lib/music_library_web/live/artist_live/show.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/live_helpers/record_show.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Details"
|
msgid "Details"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -296,20 +290,17 @@ msgstr ""
|
|||||||
msgid "Loading biography"
|
msgid "Loading biography"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Populate genres"
|
msgid "Populate genres"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Refresh MB data"
|
msgid "Refresh MB data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Refresh cover"
|
msgid "Refresh cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -601,8 +592,7 @@ msgstr ""
|
|||||||
msgid "Collected release"
|
msgid "Collected release"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "No release selected"
|
msgid "No release selected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -622,8 +612,7 @@ msgstr ""
|
|||||||
msgid "Loading release with tracks"
|
msgid "Loading release with tracks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Show Tracks"
|
msgid "Show Tracks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -639,15 +628,13 @@ msgid "Successfully connected your Last.fm account"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/components/release.ex
|
#: lib/music_library_web/components/release.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/live_helpers/record_show.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Error scrobbling release"
|
msgid "Error scrobbling release"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/components/release.ex
|
#: lib/music_library_web/components/release.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/live_helpers/record_show.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Release scrobbled successfully"
|
msgid "Release scrobbled successfully"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -662,9 +649,8 @@ msgstr ""
|
|||||||
msgid "Scrobbling..."
|
msgid "Scrobbling..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/components/release.ex
|
#: lib/music_library_web/components/release.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Scrobble release"
|
msgid "Scrobble release"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -795,13 +781,11 @@ msgstr ""
|
|||||||
#: lib/music_library_web/components/layouts/app.html.heex
|
#: lib/music_library_web/components/layouts/app.html.heex
|
||||||
#: lib/music_library_web/components/record_components.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/artist_live/show.ex
|
#: lib/music_library_web/live/artist_live/show.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/online_store_template_live/index.ex
|
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/index.ex
|
#: lib/music_library_web/live/record_set_live/index.ex
|
||||||
#: lib/music_library_web/live/record_set_live/show.ex
|
#: lib/music_library_web/live/record_set_live/show.ex
|
||||||
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
#: lib/music_library_web/live/scrobble_rules_live/index.ex
|
||||||
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
#: lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1013,7 +997,7 @@ msgstr ""
|
|||||||
msgid "to open this search"
|
msgid "to open this search"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Copy record selected release ID to clipboard"
|
msgid "Copy record selected release ID to clipboard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1736,14 +1720,12 @@ msgstr ""
|
|||||||
msgid "Based on genres, artists, and musical style"
|
msgid "Based on genres, artists, and musical style"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Ask about this album..."
|
msgid "Ask about this album..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Ask anything about this album"
|
msgid "Ask anything about this album"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1753,8 +1735,7 @@ msgstr ""
|
|||||||
msgid "Chat about %{title}"
|
msgid "Chat about %{title}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Chat about album"
|
msgid "Chat about album"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1822,9 +1803,8 @@ msgid "Copy MB ID"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/components/core_components.ex
|
#: lib/music_library_web/components/core_components.ex
|
||||||
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/artist_live/show.ex
|
#: lib/music_library_web/live/artist_live/show.ex
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Debug data"
|
msgid "Debug data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1910,8 +1890,7 @@ msgstr ""
|
|||||||
msgid "Error extracting colors"
|
msgid "Error extracting colors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/components/record_components.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Extract colors"
|
msgid "Extract colors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2561,8 +2540,7 @@ msgstr ""
|
|||||||
msgid "ISO format"
|
msgid "ISO format"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/live/collection_live/show.ex
|
#: lib/music_library_web/live_helpers/record_show.ex
|
||||||
#: lib/music_library_web/live/wishlist_live/show.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Record was updated in the background. Your edits may be stale — save and re-open to see the latest data."
|
msgid "Record was updated in the background. Your edits may be stale — save and re-open to see the latest data."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
Reference in New Issue
Block a user