From a7056aa8d934c10611ea2b840415250b57884ec5 Mon Sep 17 00:00:00 2001
From: Claudio Ortolina
Date: Sat, 7 Feb 2026 17:23:13 +0000
Subject: [PATCH] Extract some reused components
---
.../components/core_components.ex | 31 +++
.../components/scrobble_components.ex | 168 +++++++-------
.../live/artist_live/show.html.heex | 16 +-
.../live/collection_live/show.html.heex | 206 ++++++------------
.../live/scrobbled_tracks_live/index.ex | 3 +-
.../scrobbled_tracks_live/index.html.heex | 52 +----
.../live/stats_live/index.html.heex | 112 ++--------
.../live/wishlist_live/show.html.heex | 146 ++++---------
priv/gettext/default.pot | 12 +-
priv/gettext/en/LC_MESSAGES/default.po | 12 +-
10 files changed, 258 insertions(+), 500 deletions(-)
diff --git a/lib/music_library_web/components/core_components.ex b/lib/music_library_web/components/core_components.ex
index 5787ef4e..f352d8b1 100644
--- a/lib/music_library_web/components/core_components.ex
+++ b/lib/music_library_web/components/core_components.ex
@@ -186,6 +186,37 @@ defmodule MusicLibraryWeb.CoreComponents do
"https://www.google.com/s2/favicons?domain=#{uri.host}&sz=16"
end
+ attr :target_id, :string, required: true
+ attr :label, :string, required: true
+
+ def copy_to_clipboard(assigns) do
+ ~H"""
+
+ """
+ end
+
+ attr :label, :string, required: true
+ slot :inner_block, required: true
+
+ def dl_row(assigns) do
+ ~H"""
+
+
+ {@label}
+
+
+ {render_slot(@inner_block)}
+
+
+ """
+ end
+
## JS Commands
def show(js \\ %JS{}, selector) do
diff --git a/lib/music_library_web/components/scrobble_components.ex b/lib/music_library_web/components/scrobble_components.ex
index e9ab7dd1..d11c2f3f 100644
--- a/lib/music_library_web/components/scrobble_components.ex
+++ b/lib/music_library_web/components/scrobble_components.ex
@@ -4,9 +4,12 @@ defmodule MusicLibraryWeb.ScrobbleComponents do
"""
alias LastFm.Track
+ alias MusicLibrary.Records
use MusicLibraryWeb, :html
+ import MusicLibraryWeb.RecordComponents, only: [format_label: 1]
+
def refresh_lastfm_feed_button(assigns) do
~H"""
- <%!-- TODO: replace with OSS version --%>
-
-
-
- {gettext("ID")}
-
-
-
+ <.dl_row label={gettext("ID")}>
+
{@record.id}
- @record.id)
- |> JS.transition("animate-shake")
- }>
- {gettext("Copy record ID to clipboard")}
- <.icon
- name="hero-clipboard-document"
- class="-mt-1 h-5 w-5"
- aria-hidden="true"
- data-slot="icon"
- />
-
-
-
-
-
-
- {gettext("Genres")}
-
- -
- <.link
- :for={genre <- @record.genres}
- class="mr-2 text-zinc-700 hover:text-zinc-500 dark:text-zinc-400 dark:hover:text-zinc-300"
- patch={~p"/collection?#{%{query: ~s(genre:"#{genre}")}}"}
- >
- {genre}
-
-
-
-
-
-
- {gettext("MusicBrainz ID")}
-
- -
+ <.copy_to_clipboard
+ target_id={"record-#{@record.id}"}
+ label={gettext("Copy record ID to clipboard")}
+ />
+
+
+ <.dl_row label={gettext("Genres")}>
+ <.link
+ :for={genre <- @record.genres}
+ class="mr-2 text-zinc-700 hover:text-zinc-500 dark:text-zinc-400 dark:hover:text-zinc-300"
+ patch={~p"/collection?#{%{query: ~s(genre:"#{genre}")}}"}
+ >
+ {genre}
+
+
+ <.dl_row label={gettext("MusicBrainz ID")}>
+
{@record.musicbrainz_id}
-
@record.musicbrainz_id)
- |> JS.transition("animate-shake")
- }>
- {gettext("Copy MusicBrainz ID to clipboard")}
- <.icon
- name="hero-clipboard-document"
- class="-mt-1 h-5 w-5"
- aria-hidden="true"
- data-slot="icon"
- />
-
-
-
-
-
-
- {gettext("Purchased on")}
-
- -
- {Records.Record.format_as_date(@record.purchased_at)}
-
-
-
-
-
- {gettext("Published releases")}
-
- -
+ <.copy_to_clipboard
+ target_id={"mb-#{@record.musicbrainz_id}"}
+ label={gettext("Copy MusicBrainz ID to clipboard")}
+ />
+
+
+ <.dl_row label={gettext("Purchased on")}>
+ {Records.Record.format_as_date(@record.purchased_at)}
+
+ <.dl_row label={gettext("Published releases")}>
+
{Records.Record.release_count(@record)}
<.release_list record={@record} />
@record.id)}>
@@ -261,13 +225,10 @@
data-slot="icon"
/>
-
-
-
-
-
- {gettext("Collected release")}
-
- -
+
+
+ <.dl_row label={gettext("Collected release")}>
+
{@record.selected_release_id}
-
- @record.id
- )
- |> JS.transition("animate-shake")
- }
- >
-
- {gettext("Copy record selected release ID to clipboard")}
-
- <.icon
- name="hero-clipboard-document"
- class="-mt-1 h-5 w-5"
- aria-hidden="true"
- data-slot="icon"
- />
-
-
-
- @record.id}
+ label={gettext("Copy record selected release ID to clipboard")}
+ />
+
+
+ <.dl_row
:if={Records.Record.included_release_groups_count(@record) > 0}
- class="py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"
+ label={gettext("Includes")}
>
- -
- {gettext("Includes")}
-
-
-
-
- -
- {included_release_group.artists} - {included_release_group.title}
-
-
-
-
-
-
-
- {gettext("Inserted at")}
-
- -
- {Records.Record.format_as_date(@record.inserted_at)}
-
-
-
-
-
- {gettext("Updated at")}
-
- -
- {Records.Record.format_as_date(@record.updated_at)}
-
-
-
-
-
- {gettext("Last listened at")}
-
- -
-
- {localize_scrobbled_at(@last_listened_track.scrobbled_at_uts, @timezone)}
-
-
- {gettext("Never")}
-
- 0}>
- {ngettext("(1 scrobble)", "(%{count} scrobbles)", @play_count)}
-
-
-
+
+ -
+ {included_release_group.artists} - {included_release_group.title}
+
+
+
+ <.dl_row label={gettext("Inserted at")}>
+ {Records.Record.format_as_date(@record.inserted_at)}
+
+ <.dl_row label={gettext("Updated at")}>
+ {Records.Record.format_as_date(@record.updated_at)}
+
+ <.dl_row label={gettext("Last listened at")}>
+
+ {localize_scrobbled_at(@last_listened_track.scrobbled_at_uts, @timezone)}
+
+
+ {gettext("Never")}
+
+ 0}>
+ {ngettext("(1 scrobble)", "(%{count} scrobbles)", @play_count)}
+
+
diff --git a/lib/music_library_web/live/scrobbled_tracks_live/index.ex b/lib/music_library_web/live/scrobbled_tracks_live/index.ex
index 8a41b522..7ab31a33 100644
--- a/lib/music_library_web/live/scrobbled_tracks_live/index.ex
+++ b/lib/music_library_web/live/scrobbled_tracks_live/index.ex
@@ -2,12 +2,11 @@ defmodule MusicLibraryWeb.ScrobbledTracksLive.Index do
use MusicLibraryWeb, :live_view
import MusicLibraryWeb.Components.Pagination
- import MusicLibraryWeb.RecordComponents, only: [format_label: 1]
import MusicLibraryWeb.ScrobbleComponents
alias LastFm.Track
alias MusicLibrary.Assets.Transform
- alias MusicLibrary.{Records, ScrobbleActivity}
+ alias MusicLibrary.ScrobbleActivity
@default_tracks_list_params %{
query: "",
diff --git a/lib/music_library_web/live/scrobbled_tracks_live/index.html.heex b/lib/music_library_web/live/scrobbled_tracks_live/index.html.heex
index 6deeec0e..a2ad6fa8 100644
--- a/lib/music_library_web/live/scrobbled_tracks_live/index.html.heex
+++ b/lib/music_library_web/live/scrobbled_tracks_live/index.html.heex
@@ -120,56 +120,20 @@
-
- <.badge :if={track.album.musicbrainz_id == ""}>
- {gettext("No MB ID")}
-
- <.link
- :if={collected_record_id}
- navigate={~p"/collection/#{collected_record_id}"}
- >
- <.badge color="success">{gettext("Collected")}
-
- <.link
- :if={wishlisted_record_id}
- navigate={~p"/wishlist/#{wishlisted_record_id}"}
- >
- <.badge color="warning">{gettext("Wishlisted")}
-
-
+ <.record_status_badges
+ musicbrainz_id={track.album.musicbrainz_id}
+ collected_record_id={collected_record_id}
+ wishlisted_record_id={wishlisted_record_id}
+ />
- <.dropdown
+ <.import_format_dropdown
:if={
track.album.musicbrainz_id !== "" and !collected_record_id and
!wishlisted_record_id
}
id={"actions-#{track.scrobbled_at_uts}-tracks"}
- placement="bottom-end"
- >
- <:toggle>
-
{gettext("Choose which format to import")}
- <.icon
- name="hero-star"
- class="-mt-1 h-5 w-5 text-zinc-500 dark:text-zinc-400 cursor-pointer"
- aria-hidden="true"
- data-slot="icon"
- />
-
- <.focus_wrap id={"actions-#{track.scrobbled_at_uts}-tracks-focus-wrap"}>
- <.dropdown_link
- :for={format <- Records.Record.formats()}
- id={"actions-#{track.scrobbled_at_uts}-#{format}-import"}
- phx-click={
- JS.push("import",
- value: %{id: track.album.musicbrainz_id, format: format},
- page_loading: true
- )
- }
- >
- {format_label(format)}
-
-
-
+ musicbrainz_id={track.album.musicbrainz_id}
+ />
<.dropdown id={"actions-#{track.scrobbled_at_uts}"} placement="bottom-end">
<:toggle>
<.button variant="ghost">
diff --git a/lib/music_library_web/live/stats_live/index.html.heex b/lib/music_library_web/live/stats_live/index.html.heex
index 214c4715..e09e6857 100644
--- a/lib/music_library_web/live/stats_live/index.html.heex
+++ b/lib/music_library_web/live/stats_live/index.html.heex
@@ -139,62 +139,20 @@
-
- <.badge :if={album.metadata.musicbrainz_id == ""}>
- {gettext("No MB ID")}
-
- <.link
- :if={collected_record_id}
- navigate={~p"/collection/#{collected_record_id}"}
- >
- <.badge color="success">
- {gettext("Collected")}
-
-
- <.link
- :if={wishlisted_record_id}
- navigate={~p"/wishlist/#{wishlisted_record_id}"}
- >
- <.badge color="warning">
- {gettext("Wishlisted")}
-
-
-
+ <.record_status_badges
+ musicbrainz_id={album.metadata.musicbrainz_id}
+ collected_record_id={collected_record_id}
+ wishlisted_record_id={wishlisted_record_id}
+ />
- <.dropdown
+ <.import_format_dropdown
:if={
album.metadata.musicbrainz_id !== "" and !collected_record_id and
!wishlisted_record_id
}
id={"actions-#{album.scrobbled_at_uts}-albums"}
- placement="bottom-end"
- >
- <:toggle>
- <.button variant="ghost">
- {gettext("Choose which format to import")}
- <.icon
- name="hero-star"
- class="h-5 w-5 text-zinc-500 dark:text-zinc-400 cursor-pointer"
- aria-hidden="true"
- data-slot="icon"
- />
-
-
- <.focus_wrap id={"actions-#{album.scrobbled_at_uts}-albums-focus-wrap"}>
- <.dropdown_link
- :for={format <- Records.Record.formats()}
- id={"actions-#{album.scrobbled_at_uts}-#{format}-import"}
- phx-click={
- JS.push("import",
- value: %{id: album.metadata.musicbrainz_id, format: format},
- page_loading: true
- )
- }
- >
- {format_label(format)}
-
-
-
+ musicbrainz_id={album.metadata.musicbrainz_id}
+ />
@@ -264,58 +222,20 @@
-
- <.badge :if={track.album.musicbrainz_id == ""}>
- {gettext("No MB ID")}
-
- <.link
- :if={collected_record_id}
- navigate={~p"/collection/#{collected_record_id}"}
- >
- <.badge color="success">{gettext("Collected")}
-
- <.link
- :if={wishlisted_record_id}
- navigate={~p"/wishlist/#{wishlisted_record_id}"}
- >
- <.badge color="warning">{gettext("Wishlisted")}
-
-
+ <.record_status_badges
+ musicbrainz_id={track.album.musicbrainz_id}
+ collected_record_id={collected_record_id}
+ wishlisted_record_id={wishlisted_record_id}
+ />
- <.dropdown
+ <.import_format_dropdown
:if={
track.album.musicbrainz_id !== "" and !collected_record_id and
!wishlisted_record_id
}
id={"actions-#{track.scrobbled_at_uts}-tracks"}
- placement="bottom-end"
- >
- <:toggle>
- <.button variant="ghost">
- {gettext("Choose which format to import")}
- <.icon
- name="hero-star"
- class="h-5 w-5 text-zinc-500 dark:text-zinc-400 cursor-pointer"
- aria-hidden="true"
- data-slot="icon"
- />
-
-
- <.focus_wrap id={"actions-#{track.scrobbled_at_uts}-tracks-focus-wrap"}>
- <.dropdown_link
- :for={format <- Records.Record.formats()}
- id={"actions-#{track.scrobbled_at_uts}-#{format}-import"}
- phx-click={
- JS.push("import",
- value: %{id: track.album.musicbrainz_id, format: format},
- page_loading: true
- )
- }
- >
- {format_label(format)}
-
-
-
+ musicbrainz_id={track.album.musicbrainz_id}
+ />
diff --git a/lib/music_library_web/live/wishlist_live/show.html.heex b/lib/music_library_web/live/wishlist_live/show.html.heex
index 64006257..b8c11507 100644
--- a/lib/music_library_web/live/wishlist_live/show.html.heex
+++ b/lib/music_library_web/live/wishlist_live/show.html.heex
@@ -153,69 +153,38 @@
- <%!-- TODO: replace with OSS version --%>
-
-
-
- {gettext("ID")}
-
-
-
+ <.dl_row label={gettext("ID")}>
+
{@record.id}
- @record.id)
- |> JS.transition("animate-shake")
- }>
- {gettext("Copy record ID to clipboard")}
- <.icon
- name="hero-clipboard-document"
- class="-mt-1 h-5 w-5"
- aria-hidden="true"
- data-slot="icon"
- />
-
-
-
-
-
-
- {gettext("Genres")}
-
- -
- <.link
- :for={genre <- @record.genres}
- class="mr-2 text-zinc-700 hover:text-zinc-500 dark:text-zinc-400 dark:hover:text-zinc-300"
- patch={~p"/wishlist?#{%{query: ~s(genre:"#{genre}")}}"}
- >
- {genre}
-
-
-
-
-
-
- {gettext("MusicBrainz ID")}
-
- -
+ <.copy_to_clipboard
+ target_id={"record-#{@record.id}"}
+ label={gettext("Copy record ID to clipboard")}
+ />
+
+
+ <.dl_row label={gettext("Genres")}>
+ <.link
+ :for={genre <- @record.genres}
+ class="mr-2 text-zinc-700 hover:text-zinc-500 dark:text-zinc-400 dark:hover:text-zinc-300"
+ patch={~p"/wishlist?#{%{query: ~s(genre:"#{genre}")}}"}
+ >
+ {genre}
+
+
+ <.dl_row label={gettext("MusicBrainz ID")}>
+
{@record.musicbrainz_id}
-
@record.musicbrainz_id)
- |> JS.transition("animate-shake")
- }>
- {gettext("Copy MusicBrainz ID to clipboard")}
- <.icon
- name="hero-clipboard-document"
- class="-mt-1 h-5 w-5"
- aria-hidden="true"
- data-slot="icon"
- />
-
-
-
-
-
-
- {gettext("Published releases")}
-
- -
+ <.copy_to_clipboard
+ target_id={"mb-#{@record.musicbrainz_id}"}
+ label={gettext("Copy MusicBrainz ID to clipboard")}
+ />
+
+
+ <.dl_row label={gettext("Published releases")}>
+
{Records.Record.release_count(@record)}
<.release_list record={@record} />
@record.id)}>
@@ -229,59 +198,38 @@
data-slot="icon"
/>
-
-
-
-
-
- {gettext("Wishlisted release")}
-
- -
+
+
+ <.dl_row label={gettext("Wishlisted release")}>
+
{gettext("No release selected")}
-
<.release_summary
:if={@record.selected_release_id}
release={Records.Record.selected_release(@record)}
/>
-
-
-
+
+ <.dl_row
:if={Records.Record.included_release_groups_count(@record) > 0}
- class="py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"
+ label={gettext("Includes")}
>
-
-
- {gettext("Includes")}
-
-
-
-
- -
- {included_release_group.artists} - {included_release_group.title}
-
-
-
-
-
-
-
- {gettext("Inserted at")}
-
- -
- {Records.Record.format_as_date(@record.inserted_at)}
-
-
-
-
-
- {gettext("Updated at")}
-
- -
- {Records.Record.format_as_date(@record.updated_at)}
-
-
+
+ -
+ {included_release_group.artists} - {included_release_group.title}
+
+
+
+ <.dl_row label={gettext("Inserted at")}>
+ {Records.Record.format_as_date(@record.inserted_at)}
+
+ <.dl_row label={gettext("Updated at")}>
+ {Records.Record.format_as_date(@record.updated_at)}
+
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
index 27a72e58..67daf110 100644
--- a/priv/gettext/default.pot
+++ b/priv/gettext/default.pot
@@ -267,24 +267,21 @@ msgid "Scrobble activity"
msgstr ""
#: lib/music_library_web/components/add_record.ex
-#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex
-#: lib/music_library_web/live/stats_live/index.html.heex
+#: lib/music_library_web/components/scrobble_components.ex
#, elixir-autogen, elixir-format
msgid "Choose which format to import"
msgstr ""
#: lib/music_library_web/components/barcode_scanner.ex
+#: lib/music_library_web/components/scrobble_components.ex
#: lib/music_library_web/live/record_set_live/record_picker.ex
-#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex
-#: lib/music_library_web/live/stats_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Collected"
msgstr ""
#: lib/music_library_web/components/barcode_scanner.ex
+#: lib/music_library_web/components/scrobble_components.ex
#: lib/music_library_web/live/record_set_live/record_picker.ex
-#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex
-#: lib/music_library_web/live/stats_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Wishlisted"
msgstr ""
@@ -328,8 +325,7 @@ msgstr ""
msgid "Error refreshing cover"
msgstr ""
-#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex
-#: lib/music_library_web/live/stats_live/index.html.heex
+#: lib/music_library_web/components/scrobble_components.ex
#, elixir-autogen, elixir-format
msgid "No MB ID"
msgstr ""
diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po
index 8f552c7d..f9d627d8 100644
--- a/priv/gettext/en/LC_MESSAGES/default.po
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -267,24 +267,21 @@ msgid "Scrobble activity"
msgstr ""
#: lib/music_library_web/components/add_record.ex
-#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex
-#: lib/music_library_web/live/stats_live/index.html.heex
+#: lib/music_library_web/components/scrobble_components.ex
#, elixir-autogen, elixir-format
msgid "Choose which format to import"
msgstr ""
#: lib/music_library_web/components/barcode_scanner.ex
+#: lib/music_library_web/components/scrobble_components.ex
#: lib/music_library_web/live/record_set_live/record_picker.ex
-#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex
-#: lib/music_library_web/live/stats_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Collected"
msgstr ""
#: lib/music_library_web/components/barcode_scanner.ex
+#: lib/music_library_web/components/scrobble_components.ex
#: lib/music_library_web/live/record_set_live/record_picker.ex
-#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex
-#: lib/music_library_web/live/stats_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Wishlisted"
msgstr ""
@@ -328,8 +325,7 @@ msgstr ""
msgid "Error refreshing cover"
msgstr ""
-#: lib/music_library_web/live/scrobbled_tracks_live/index.html.heex
-#: lib/music_library_web/live/stats_live/index.html.heex
+#: lib/music_library_web/components/scrobble_components.ex
#, elixir-autogen, elixir-format
msgid "No MB ID"
msgstr ""