diff --git a/lib/music_library_web/live/artist_live/show.ex b/lib/music_library_web/live/artist_live/show.ex index 0eca1b14..a011e592 100644 --- a/lib/music_library_web/live/artist_live/show.ex +++ b/lib/music_library_web/live/artist_live/show.ex @@ -82,6 +82,384 @@ defmodule MusicLibraryWeb.ArtistLive.Show do """ end + @impl true + def render(assigns) do + ~H""" + +
+
+
+

+ {@artist.name} + <.country_flag country={@country} /> +

+ +
+ <.button_group> + <.button + variant="soft" + phx-click={MusicLibraryWeb.Components.Notes.open("artist-notes-sheet")} + > + {gettext("Open Notes")} + <.icon + name="hero-pencil" + class="h-5 w-5" + aria-hidden="true" + data-slot="icon" + /> + + <.button + variant="soft" + phx-click={MusicLibraryWeb.Components.Chat.open("artist-chat-sheet")} + > + {gettext("Chat about artist")} + <.icon + name="hero-chat-bubble-left-right" + class="h-5 w-5" + aria-hidden="true" + data-slot="icon" + /> + + <.dropdown id={"actions-#{@artist.musicbrainz_id}"} placement="bottom-end"> + <:toggle> + <.button variant="soft"> + {gettext("Actions")} + <.icon + name="hero-ellipsis-vertical" + class="h-5 w-5 text-zinc-500 dark:text-zinc-400 cursor-pointer" + aria-hidden="true" + data-slot="icon" + /> + + + <.focus_wrap id={"actions-#{@artist.musicbrainz_id}-focus-wrap"}> + <.dropdown_link + id={"actions-#{@artist.musicbrainz_id}-edit"} + patch={~p"/artists/#{@artist.musicbrainz_id}/edit"} + > + <.icon + name="hero-pencil-square" + class="h-4 w-4 mr-1 phx-click-loading:animate-bounce" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Edit")} + + <.dropdown_link + id={"actions-#{@artist.musicbrainz_id}-refresh-image"} + phx-click={ + JS.push("refresh_artist_image", value: %{id: @artist.musicbrainz_id}) + } + > + <.icon + name="hero-photo" + class="h-4 w-4 mr-1 phx-click-loading:animate-bounce" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Refresh image")} + + + <.dropdown_link + id={"actions-#{@artist.musicbrainz_id}-refresh-artist-info"} + phx-click={JS.push("refresh_artist_info", value: %{id: @artist.musicbrainz_id})} + > + <.icon + name="hero-arrow-path" + class="h-4 w-4 mr-1 phx-click-loading:animate-spin" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Refresh info")} + + <.dropdown_link + id={"actions-#{@artist.musicbrainz_id}-refresh-wikipedia"} + phx-click={ + JS.push("refresh_wikipedia_data", value: %{id: @artist.musicbrainz_id}) + } + > + <.icon + name="hero-arrow-path" + class="h-4 w-4 mr-1 phx-click-loading:animate-spin" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Refresh Wikipedia")} + + + + +
+
+ +
+ <.async_result :let={lastfm_artist_info} assign={@lastfm_artist_info}> + <:loading> + {gettext("Loading play count")} + <.loading /> + + <:failed :let={_failure}> +
+ <.icon + name="hero-exclamation-triangle" + class="-mt-1 mr-1 ml-2 h-5 w-5" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Error loading play count")} +
+ + <.on_tour_link lastfm_artist_info={lastfm_artist_info} /> + <.play_count play_count={lastfm_artist_info.play_count} /> + +
+
+ +
+
+

+ {gettext("Meta")} +

+ <.artist_image + class="w-full rounded-md shadow-sm mt-4 cursor-pointer" + artist={@artist} + image_hash={@artist_info.image_data_hash} + phx-click={Fluxon.open_dialog("artist-image-modal")} + /> + <.modal + id="artist-image-modal" + class="mx-auto sm:min-w-2xl max-w-sm md:max-w-3xl lg:max-w-5xl mt-8" + placement="center" + open={false} + > + <.artist_image + class="w-full rounded-md shadow-sm mt-8" + artist={@artist} + image_hash={@artist_info.image_data_hash} + /> + +
+
+ {gettext("MusicBrainz ID")} +
+
+ + + {@artist.musicbrainz_id} + + + <.copy_to_clipboard + target_id={"mb-#{@artist.musicbrainz_id}"} + label={gettext("Copy MusicBrainz ID to clipboard")} + /> +
+
+ <%= if @biography do %> +
+ {gettext("Biography")} + <.badge variant="soft" class="ml-1">{@biography.source} +
+
+

+ {@biography.description} +

+

{@biography.summary_html}

+ <.link + class="block mt-2 text-sm font-medium text-zinc-900 dark:text-zinc-400" + phx-click={Fluxon.open_dialog("bio")} + > + <.icon + name="hero-arrow-right-end-on-rectangle" + class="-mt-1 mr-1 h-5 w-5" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Read more")} + +
+ <.sheet + id="bio" + class="max-w-2xl text-zinc-700 dark:text-zinc-300" + placement="left" + > +
+ {Phoenix.HTML.raw(@biography.bio_html)} +
+ + {gettext("Read full article on Wikipedia")} + <.icon + name="hero-arrow-top-right-on-square" + class="-mt-1 ml-1 h-4 w-4" + aria-hidden="true" + data-slot="icon" + /> + + + <% else %> + <.async_result :let={lastfm_artist_info} assign={@lastfm_artist_info}> + <:loading> +
+ {gettext("Loading biography")} +
+ + <:failed :let={_failure}> +
+ <.icon + name="hero-exclamation-triangle" + class="-mt-1 mr-1 h-5 w-5" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Error loading biography")} +
+ +
+ {gettext("Biography")} +
+
+ {remove_read_more_link(lastfm_artist_info.summary)} + <.link + class="block mt-2 text-sm font-medium text-zinc-900 dark:text-zinc-400" + phx-click={Fluxon.open_dialog("lastfm-bio")} + > + <.icon + name="hero-arrow-right-end-on-rectangle" + class="-mt-1 mr-1 h-5 w-5" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Read more")} + +
+ <.sheet + :if={lastfm_artist_info.bio not in [nil, ""]} + id="lastfm-bio" + class="max-w-2xl text-zinc-700 dark:text-zinc-300" + placement="left" + > + {render_bio(lastfm_artist_info.bio)} + + + <% end %> + <.external_links external_links={@external_links} /> +
+
+ <.record_grid + :if={@collection_records_count > 0} + title={gettext("Collection")} + id="collection" + records={@streams.collection_records} + records_count={@collection_records_count} + record_show_path={fn record -> ~p"/collection/#{record}" end} + record_edit_path={fn record -> ~p"/collection/#{record}/show/edit" end} + /> + <.separator + :if={@collection_records_count > 0 && @wishlist_records_count > 0} + class="mt-8 mb-8" + /> + <.record_grid + :if={@wishlist_records_count > 0} + title={gettext("Wishlist")} + id="wishlist" + records={@streams.wishlist_records} + records_count={@wishlist_records_count} + record_show_path={fn record -> ~p"/wishlist/#{record}" end} + record_edit_path={fn record -> ~p"/wishlist/#{record}/show/edit" end} + /> + + <.async_result :let={similar_artists} assign={@similar_artists}> + <:loading> +
+ {gettext("Loading similar artists")} +
+ + <:failed :let={_failure}> +
+ <.icon + name="hero-exclamation-triangle" + class="-mt-1 mr-1 h-5 w-5" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Error loading similar artists")} +
+ + <.separator class="mt-8 mb-8" /> + <.artist_grid title={gettext("Similar artists")} artists={similar_artists} /> + +
+
+ + <.json_viewer + :if={@artist_info.musicbrainz_data} + title={gettext("MusicBrainz data")} + data={@artist_info.musicbrainz_data} + /> + + <.json_viewer + :if={@artist_info.discogs_data} + title={gettext("Discogs data")} + data={@artist_info.discogs_data} + /> + + <.json_viewer + :if={@artist_info.wikipedia_data != %{}} + title={gettext("Wikipedia data")} + data={@artist_info.wikipedia_data} + /> +
+ + <.live_component + id="artist-notes" + sheet_id="artist-notes-sheet" + module={MusicLibraryWeb.Components.Notes} + entity={:artist} + musicbrainz_id={@artist.musicbrainz_id} + /> + + <.live_component + id="artist-chat" + sheet_id="artist-chat-sheet" + module={MusicLibraryWeb.Components.Chat} + title={@artist.name} + chat_module={MusicLibrary.ArtistChat} + chat_context={{@artist, @artist_info}} + placeholder={gettext("Ask about this artist...")} + empty_prompt={gettext("Ask anything about this artist")} + /> + + <.structured_modal + :if={@live_action == :edit} + id="artist-info-modal" + on_close={JS.patch(~p"/artists/#{@artist.musicbrainz_id}")} + > + <.live_component + module={MusicLibraryWeb.ArtistLive.Form} + id={@artist_info.id} + action={@live_action} + artist_info={@artist_info} + artist={@artist} + patch={~p"/artists/#{@artist.musicbrainz_id}"} + /> + +
+ """ + end + @impl true def mount(_params, _session, socket) do {:ok, socket} diff --git a/lib/music_library_web/live/artist_live/show.html.heex b/lib/music_library_web/live/artist_live/show.html.heex deleted file mode 100644 index bddec309..00000000 --- a/lib/music_library_web/live/artist_live/show.html.heex +++ /dev/null @@ -1,372 +0,0 @@ - -
-
-
-

- {@artist.name} - <.country_flag country={@country} /> -

- -
- <.button_group> - <.button - variant="soft" - phx-click={MusicLibraryWeb.Components.Notes.open("artist-notes-sheet")} - > - {gettext("Open Notes")} - <.icon - name="hero-pencil" - class="h-5 w-5" - aria-hidden="true" - data-slot="icon" - /> - - <.button - variant="soft" - phx-click={MusicLibraryWeb.Components.Chat.open("artist-chat-sheet")} - > - {gettext("Chat about artist")} - <.icon - name="hero-chat-bubble-left-right" - class="h-5 w-5" - aria-hidden="true" - data-slot="icon" - /> - - <.dropdown id={"actions-#{@artist.musicbrainz_id}"} placement="bottom-end"> - <:toggle> - <.button variant="soft"> - {gettext("Actions")} - <.icon - name="hero-ellipsis-vertical" - class="h-5 w-5 text-zinc-500 dark:text-zinc-400 cursor-pointer" - aria-hidden="true" - data-slot="icon" - /> - - - <.focus_wrap id={"actions-#{@artist.musicbrainz_id}-focus-wrap"}> - <.dropdown_link - id={"actions-#{@artist.musicbrainz_id}-edit"} - patch={~p"/artists/#{@artist.musicbrainz_id}/edit"} - > - <.icon - name="hero-pencil-square" - class="h-4 w-4 mr-1 phx-click-loading:animate-bounce" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Edit")} - - <.dropdown_link - id={"actions-#{@artist.musicbrainz_id}-refresh-image"} - phx-click={ - JS.push("refresh_artist_image", value: %{id: @artist.musicbrainz_id}) - } - > - <.icon - name="hero-photo" - class="h-4 w-4 mr-1 phx-click-loading:animate-bounce" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Refresh image")} - - - <.dropdown_link - id={"actions-#{@artist.musicbrainz_id}-refresh-artist-info"} - phx-click={JS.push("refresh_artist_info", value: %{id: @artist.musicbrainz_id})} - > - <.icon - name="hero-arrow-path" - class="h-4 w-4 mr-1 phx-click-loading:animate-spin" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Refresh info")} - - <.dropdown_link - id={"actions-#{@artist.musicbrainz_id}-refresh-wikipedia"} - phx-click={ - JS.push("refresh_wikipedia_data", value: %{id: @artist.musicbrainz_id}) - } - > - <.icon - name="hero-arrow-path" - class="h-4 w-4 mr-1 phx-click-loading:animate-spin" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Refresh Wikipedia")} - - - - -
-
- -
- <.async_result :let={lastfm_artist_info} assign={@lastfm_artist_info}> - <:loading> - {gettext("Loading play count")} - <.loading /> - - <:failed :let={_failure}> -
- <.icon - name="hero-exclamation-triangle" - class="-mt-1 mr-1 ml-2 h-5 w-5" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Error loading play count")} -
- - <.on_tour_link lastfm_artist_info={lastfm_artist_info} /> - <.play_count play_count={lastfm_artist_info.play_count} /> - -
-
- -
-
-

- {gettext("Meta")} -

- <.artist_image - class="w-full rounded-md shadow-sm mt-4 cursor-pointer" - artist={@artist} - image_hash={@artist_info.image_data_hash} - phx-click={Fluxon.open_dialog("artist-image-modal")} - /> - <.modal - id="artist-image-modal" - class="mx-auto sm:min-w-2xl max-w-sm md:max-w-3xl lg:max-w-5xl mt-8" - placement="center" - open={false} - > - <.artist_image - class="w-full rounded-md shadow-sm mt-8" - artist={@artist} - image_hash={@artist_info.image_data_hash} - /> - -
-
- {gettext("MusicBrainz ID")} -
-
- - - {@artist.musicbrainz_id} - - - <.copy_to_clipboard - target_id={"mb-#{@artist.musicbrainz_id}"} - label={gettext("Copy MusicBrainz ID to clipboard")} - /> -
-
- <%= if @biography do %> -
- {gettext("Biography")} - <.badge variant="soft" class="ml-1">{@biography.source} -
-
-

- {@biography.description} -

-

{@biography.summary_html}

- <.link - class="block mt-2 text-sm font-medium text-zinc-900 dark:text-zinc-400" - phx-click={Fluxon.open_dialog("bio")} - > - <.icon - name="hero-arrow-right-end-on-rectangle" - class="-mt-1 mr-1 h-5 w-5" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Read more")} - -
- <.sheet - id="bio" - class="max-w-2xl text-zinc-700 dark:text-zinc-300" - placement="left" - > -
- {Phoenix.HTML.raw(@biography.bio_html)} -
- - {gettext("Read full article on Wikipedia")} - <.icon - name="hero-arrow-top-right-on-square" - class="-mt-1 ml-1 h-4 w-4" - aria-hidden="true" - data-slot="icon" - /> - - - <% else %> - <.async_result :let={lastfm_artist_info} assign={@lastfm_artist_info}> - <:loading> -
- {gettext("Loading biography")} -
- - <:failed :let={_failure}> -
- <.icon - name="hero-exclamation-triangle" - class="-mt-1 mr-1 h-5 w-5" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Error loading biography")} -
- -
- {gettext("Biography")} -
-
- {remove_read_more_link(lastfm_artist_info.summary)} - <.link - class="block mt-2 text-sm font-medium text-zinc-900 dark:text-zinc-400" - phx-click={Fluxon.open_dialog("lastfm-bio")} - > - <.icon - name="hero-arrow-right-end-on-rectangle" - class="-mt-1 mr-1 h-5 w-5" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Read more")} - -
- <.sheet - :if={lastfm_artist_info.bio not in [nil, ""]} - id="lastfm-bio" - class="max-w-2xl text-zinc-700 dark:text-zinc-300" - placement="left" - > - {render_bio(lastfm_artist_info.bio)} - - - <% end %> - <.external_links external_links={@external_links} /> -
-
- <.record_grid - :if={@collection_records_count > 0} - title={gettext("Collection")} - id="collection" - records={@streams.collection_records} - records_count={@collection_records_count} - record_show_path={fn record -> ~p"/collection/#{record}" end} - record_edit_path={fn record -> ~p"/collection/#{record}/show/edit" end} - /> - <.separator - :if={@collection_records_count > 0 && @wishlist_records_count > 0} - class="mt-8 mb-8" - /> - <.record_grid - :if={@wishlist_records_count > 0} - title={gettext("Wishlist")} - id="wishlist" - records={@streams.wishlist_records} - records_count={@wishlist_records_count} - record_show_path={fn record -> ~p"/wishlist/#{record}" end} - record_edit_path={fn record -> ~p"/wishlist/#{record}/show/edit" end} - /> - - <.async_result :let={similar_artists} assign={@similar_artists}> - <:loading> -
- {gettext("Loading similar artists")} -
- - <:failed :let={_failure}> -
- <.icon - name="hero-exclamation-triangle" - class="-mt-1 mr-1 h-5 w-5" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Error loading similar artists")} -
- - <.separator class="mt-8 mb-8" /> - <.artist_grid title={gettext("Similar artists")} artists={similar_artists} /> - -
-
- - <.json_viewer - :if={@artist_info.musicbrainz_data} - title={gettext("MusicBrainz data")} - data={@artist_info.musicbrainz_data} - /> - - <.json_viewer - :if={@artist_info.discogs_data} - title={gettext("Discogs data")} - data={@artist_info.discogs_data} - /> - - <.json_viewer - :if={@artist_info.wikipedia_data != %{}} - title={gettext("Wikipedia data")} - data={@artist_info.wikipedia_data} - /> -
- - <.live_component - id="artist-notes" - sheet_id="artist-notes-sheet" - module={MusicLibraryWeb.Components.Notes} - entity={:artist} - musicbrainz_id={@artist.musicbrainz_id} - /> - - <.live_component - id="artist-chat" - sheet_id="artist-chat-sheet" - module={MusicLibraryWeb.Components.Chat} - title={@artist.name} - chat_module={MusicLibrary.ArtistChat} - chat_context={{@artist, @artist_info}} - placeholder={gettext("Ask about this artist...")} - empty_prompt={gettext("Ask anything about this artist")} - /> - - <.structured_modal - :if={@live_action == :edit} - id="artist-info-modal" - on_close={JS.patch(~p"/artists/#{@artist.musicbrainz_id}")} - > - <.live_component - module={MusicLibraryWeb.ArtistLive.Form} - id={@artist_info.id} - action={@live_action} - artist_info={@artist_info} - artist={@artist} - patch={~p"/artists/#{@artist.musicbrainz_id}"} - /> - -
diff --git a/lib/music_library_web/live/collection_live/index.ex b/lib/music_library_web/live/collection_live/index.ex index 7957ac5a..ffeed660 100644 --- a/lib/music_library_web/live/collection_live/index.ex +++ b/lib/music_library_web/live/collection_live/index.ex @@ -17,6 +17,176 @@ defmodule MusicLibraryWeb.CollectionLive.Index do order: :purchase } + @impl true + def render(assigns) do + ~H""" + +
+
+ <.search_form query={@record_list_params.query} /> + <.button_group> + <.button + variant="solid" + size="sm" + patch={~p"/collection/import"} + > + <.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("Add")} + + <.button + variant="solid" + size="sm" + patch={~p"/collection/scan"} + > + <.barcode_icon class="icon fill-current" /> + {gettext("Scan")} + + +
+
+ +
+ <.button_group> + <.button + patch={order_path(@record_list_params, :purchase)} + size="sm" + class={[ + @record_list_params.order == :purchase && "bg-zinc-100! dark:bg-zinc-700!" + ]} + > + <.icon + name="hero-banknotes-solid" + class="icon" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Purchase")} + + <.button + patch={order_path(@record_list_params, :alphabetical)} + size="sm" + class={[ + @record_list_params.order == :alphabetical && "bg-zinc-100! dark:bg-zinc-700!" + ]} + > + <.icon name="hero-user-solid" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("A->Z")} + + <.button + patch={order_path(@record_list_params, :release)} + size="sm" + class={[ + @record_list_params.order == :release && "bg-zinc-100! dark:bg-zinc-700!" + ]} + > + <.icon name="hero-calendar-days" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("Release")} + + + + <.button_group> + <.button + phx-click="set_display" + phx-value-mode="grid" + size="sm" + class={[ + @display == :grid && "bg-zinc-100! dark:bg-zinc-700!" + ]} + > + <.icon + name="hero-squares-2x2" + class="icon" + aria-hidden="true" + data-slot="icon" + /> + + {gettext("Grid")} + + + <.button + phx-click="set_display" + phx-value-mode="list" + size="sm" + class={[ + @display == :list && "bg-zinc-100! dark:bg-zinc-700!" + ]} + > + <.icon name="hero-list-bullet" class="icon" aria-hidden="true" data-slot="icon" /> + + {gettext("List")} + + + +
+ + <.record_grid + :if={@display == :grid} + id="collection" + records={@streams.records} + record_show_path={fn record -> ~p"/collection/#{record}" end} + record_edit_path={fn record -> ~p"/collection/#{record}/edit" end} + display_artist_names + density={:high} + /> + + <.record_list + :if={@display == :list} + records={@streams.records} + record_show_path={fn record -> ~p"/collection/#{record}" end} + record_edit_path={fn record -> ~p"/collection/#{record}/edit" end} + /> + + <.structured_modal + :if={@live_action == :edit} + id="record-modal" + on_close={JS.patch(back_path(@record_list_params))} + > + <.live_component + module={MusicLibraryWeb.Components.RecordForm} + id={@record.id} + action={@live_action} + show_purchased_at={true} + record={@record} + patch={back_path(@record_list_params)} + /> + + + <.structured_modal + :if={@live_action == :import} + id="record-modal" + on_close={JS.patch(back_path(@record_list_params))} + > + <.live_component + module={MusicLibraryWeb.Components.AddRecord} + id={:search} + title={@page_title} + action={@live_action} + record={@record} + patch={back_path(@record_list_params)} + initial_query="" + icon_name="hero-plus" + /> + + + <.structured_modal + :if={@live_action == :barcode_scan} + id="barcode-scanner-modal" + on_close={JS.patch(back_path(@record_list_params))} + > + <.live_component + module={MusicLibraryWeb.Components.BarcodeScanner} + id={:barcode_scanner} + title={@page_title} + action={@live_action} + patch={back_path(@record_list_params)} + /> + + + <.pagination id={:bottom_pagination} pagination_params={@record_list_params} /> +
+ """ + end + @impl true def mount(_params, _session, socket) do {:ok, diff --git a/lib/music_library_web/live/collection_live/index.html.heex b/lib/music_library_web/live/collection_live/index.html.heex deleted file mode 100644 index 69102149..00000000 --- a/lib/music_library_web/live/collection_live/index.html.heex +++ /dev/null @@ -1,164 +0,0 @@ - -
-
- <.search_form query={@record_list_params.query} /> - <.button_group> - <.button - variant="solid" - size="sm" - patch={~p"/collection/import"} - > - <.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("Add")} - - <.button - variant="solid" - size="sm" - patch={~p"/collection/scan"} - > - <.barcode_icon class="icon fill-current" /> - {gettext("Scan")} - - -
-
- -
- <.button_group> - <.button - patch={order_path(@record_list_params, :purchase)} - size="sm" - class={[ - @record_list_params.order == :purchase && "bg-zinc-100! dark:bg-zinc-700!" - ]} - > - <.icon - name="hero-banknotes-solid" - class="icon" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Purchase")} - - <.button - patch={order_path(@record_list_params, :alphabetical)} - size="sm" - class={[ - @record_list_params.order == :alphabetical && "bg-zinc-100! dark:bg-zinc-700!" - ]} - > - <.icon name="hero-user-solid" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("A->Z")} - - <.button - patch={order_path(@record_list_params, :release)} - size="sm" - class={[ - @record_list_params.order == :release && "bg-zinc-100! dark:bg-zinc-700!" - ]} - > - <.icon name="hero-calendar-days" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("Release")} - - - - <.button_group> - <.button - phx-click="set_display" - phx-value-mode="grid" - size="sm" - class={[ - @display == :grid && "bg-zinc-100! dark:bg-zinc-700!" - ]} - > - <.icon - name="hero-squares-2x2" - class="icon" - aria-hidden="true" - data-slot="icon" - /> - - {gettext("Grid")} - - - <.button - phx-click="set_display" - phx-value-mode="list" - size="sm" - class={[ - @display == :list && "bg-zinc-100! dark:bg-zinc-700!" - ]} - > - <.icon name="hero-list-bullet" class="icon" aria-hidden="true" data-slot="icon" /> - - {gettext("List")} - - - -
- - <.record_grid - :if={@display == :grid} - id="collection" - records={@streams.records} - record_show_path={fn record -> ~p"/collection/#{record}" end} - record_edit_path={fn record -> ~p"/collection/#{record}/edit" end} - display_artist_names - density={:high} - /> - - <.record_list - :if={@display == :list} - records={@streams.records} - record_show_path={fn record -> ~p"/collection/#{record}" end} - record_edit_path={fn record -> ~p"/collection/#{record}/edit" end} - /> - - <.structured_modal - :if={@live_action == :edit} - id="record-modal" - on_close={JS.patch(back_path(@record_list_params))} - > - <.live_component - module={MusicLibraryWeb.Components.RecordForm} - id={@record.id} - action={@live_action} - show_purchased_at={true} - record={@record} - patch={back_path(@record_list_params)} - /> - - - <.structured_modal - :if={@live_action == :import} - id="record-modal" - on_close={JS.patch(back_path(@record_list_params))} - > - <.live_component - module={MusicLibraryWeb.Components.AddRecord} - id={:search} - title={@page_title} - action={@live_action} - record={@record} - patch={back_path(@record_list_params)} - initial_query="" - icon_name="hero-plus" - /> - - - <.structured_modal - :if={@live_action == :barcode_scan} - id="barcode-scanner-modal" - on_close={JS.patch(back_path(@record_list_params))} - > - <.live_component - module={MusicLibraryWeb.Components.BarcodeScanner} - id={:barcode_scanner} - title={@page_title} - action={@live_action} - patch={back_path(@record_list_params)} - /> - - - <.pagination id={:bottom_pagination} pagination_params={@record_list_params} /> -
diff --git a/lib/music_library_web/live/collection_live/show.ex b/lib/music_library_web/live/collection_live/show.ex index a7f6e9f9..b1d632c9 100644 --- a/lib/music_library_web/live/collection_live/show.ex +++ b/lib/music_library_web/live/collection_live/show.ex @@ -20,6 +20,379 @@ defmodule MusicLibraryWeb.CollectionLive.Show do alias MusicLibrary.RecordSets.RecordSet alias Phoenix.LiveView.JS + @impl true + def render(assigns) do + ~H""" + +
+
+ <.record_cover + record={@record} + class="w-full rounded-lg drop-shadow-sm" + /> +
+ +
+
+

+ <.artist_links joinphrase_class="text-sm" artists={@record.artists} /> +

+
+ <.button_group> + <.button + variant="soft" + phx-click={MusicLibraryWeb.Components.Notes.open("record-notes-sheet")} + > + {gettext("Open Notes")} + <.icon + name="hero-pencil" + class="h-5 w-5" + aria-hidden="true" + data-slot="icon" + /> + + <.button + variant="soft" + phx-click={MusicLibraryWeb.Components.Chat.open("record-chat-sheet")} + > + {gettext("Chat about album")} + <.icon + name="hero-chat-bubble-left-right" + class="h-5 w-5" + aria-hidden="true" + data-slot="icon" + /> + + <.button + :if={@record.selected_release_id} + variant="soft" + phx-click={MusicLibraryWeb.Components.Release.open("release-with-tracks-sheet")} + > + {gettext("Show Tracks")} + <.icon + name="hero-numbered-list" + class="h-5 w-5" + aria-hidden="true" + data-slot="icon" + /> + + <.dropdown id={"actions-#{@record.id}"} placement="bottom-end"> + <:toggle> + <.button variant="soft"> + {gettext("Actions")} + <.icon + name="hero-ellipsis-vertical" + class="h-5 w-5 text-zinc-500 dark:text-zinc-400 cursor-pointer" + aria-hidden="true" + data-slot="icon" + /> + + + <.focus_wrap id={"actions-#{@record.id}-focus-wrap"}> + <.dropdown_link + id={"actions-#{@record.id}-edit"} + patch={~p"/collection/#{@record}/show/edit"} + > + <.icon + name="hero-pencil-square" + class="h-4 w-4 mr-1 phx-click-loading:animate-bounce" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Edit")} + + + <.dropdown_link + id={"actions-#{@record.id}-refresh-cover"} + phx-click={JS.push("refresh_cover", value: %{id: @record.id})} + > + <.icon + name="hero-photo" + class="h-4 w-4 mr-1 phx-click-loading:animate-bounce" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Refresh cover")} + + + <.dropdown_link + id={"actions-#{@record.id}-refresh-mb-data"} + phx-click={JS.push("refresh_musicbrainz_data", value: %{id: @record.id})} + > + <.icon + name="hero-arrow-path" + class="h-4 w-4 mr-1 phx-click-loading:animate-spin" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Refresh MB data")} + + + <.dropdown_link + id={"actions-#{@record.id}-populate-genres"} + phx-click={JS.push("populate_genres", value: %{id: @record.id})} + > + <.icon + name="hero-sparkles" + class="h-4 w-4 mr-1 phx-click-loading:animate-shake" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Populate genres")} + + + <.dropdown_link + id={"actions-#{@record.id}-regenerate-embeddings"} + phx-click={JS.push("regenerate_embeddings", value: %{id: @record.id})} + > + <.icon + name="hero-sparkles" + class="h-4 w-4 mr-1 phx-click-loading:animate-shake" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Regenerate embeddings")} + + + <.dropdown_link + id={"actions-#{@record.id}-extract-colors-fast"} + phx-click={JS.push("extract_colors", value: %{id: @record.id, method: :fast})} + > + <.icon + name="hero-paint-brush" + class="h-4 w-4 mr-1 phx-click-loading:animate-shake" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Extract colors (fast)")} + + + <.dropdown_link + id={"actions-#{@record.id}-extract-colors-slow"} + phx-click={JS.push("extract_colors", value: %{id: @record.id, method: :slow})} + > + <.icon + name="hero-paint-brush" + class="h-4 w-4 mr-1 phx-click-loading:animate-shake" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Extract colors (slow)")} + + + <.dropdown_separator /> + <.dropdown_link + id={"actions-#{@record.id}-delete"} + phx-click={JS.push("delete", value: %{id: @record.id})} + 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="h-4 w-4 mr-1 phx-click-loading:animate-spin" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Delete")} + + + + +
+
+
+

+ {@record.title} +

+

+ <.record_colors record={@record} /> + + {Records.Record.format_release_date(@record.release_date)} · {format_label( + @record.format + )} · {type_label(@record.type)} + +

+
+
+
+ <.dl_row label={gettext("ID")}> +
+ {@record.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} + + <.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} /> + +
+ + <.dl_row label={gettext("Collected release")}> +
+ + {gettext("No release selected")} + + <.release_summary + :if={@record.selected_release_id} + release={Records.Record.selected_release(@record)} + /> + @record.id} + class="hidden" + > + {@record.selected_release_id} + + <.copy_to_clipboard + :if={@record.selected_release_id} + target_id={"record-selected-release-" <> @record.id} + label={gettext("Copy record selected release ID to clipboard")} + /> +
+ + <.dl_row + :if={Records.Record.included_release_groups_count(@record) > 0} + label={gettext("Includes")} + > +
    +
  • + {included_release_group.artists} - {included_release_group.title} +
  • +
+ + <.dl_row :if={@record_sets != []} label={gettext("Record sets")}> +
    +
  • + <.link + navigate={~p"/record-sets/#{record_set}"} + class="text-zinc-700 hover:text-zinc-500 dark:text-zinc-400 dark:hover:text-zinc-300 hover:underline" + > + {record_set.name} + + + {gettext( + "%{collected}/%{total} collected", + RecordSet.count_by_status(record_set) + )} + +
  • +
+ + <.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)} + + +
+
+
+
+ + <.similar_records + similar_records={@similar_records} + record_show_path={fn record -> ~p"/collection/#{record}" end} + section={:collection} + /> + + <.json_viewer title={gettext("MusicBrainz data")} data={@record.musicbrainz_data} /> + <.text_viewer title={gettext("Record Embedding")} data={@embedding_text} /> + + <.live_component + id="release-with-tracks" + sheet_id="release-with-tracks-sheet" + module={MusicLibraryWeb.Components.Release} + record={@record} + /> + + <.live_component + id="record-notes" + sheet_id="record-notes-sheet" + module={MusicLibraryWeb.Components.Notes} + entity={:record} + musicbrainz_id={@record.musicbrainz_id} + /> + + <.live_component + id="record-chat" + sheet_id="record-chat-sheet" + module={MusicLibraryWeb.Components.Chat} + title={@record.title} + chat_module={MusicLibrary.RecordChat} + chat_context={{@record, @embedding_text}} + placeholder={gettext("Ask about this album...")} + empty_prompt={gettext("Ask anything about this album")} + /> + + <.structured_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} + patch={~p"/collection/#{@record}"} + /> + +
+ """ + end + @impl true def mount(%{"id" => record_id}, _session, socket) do if connected?(socket) do diff --git a/lib/music_library_web/live/collection_live/show.html.heex b/lib/music_library_web/live/collection_live/show.html.heex deleted file mode 100644 index 4efccc54..00000000 --- a/lib/music_library_web/live/collection_live/show.html.heex +++ /dev/null @@ -1,367 +0,0 @@ - -
-
- <.record_cover - record={@record} - class="w-full rounded-lg drop-shadow-sm" - /> -
- -
-
-

- <.artist_links joinphrase_class="text-sm" artists={@record.artists} /> -

-
- <.button_group> - <.button - variant="soft" - phx-click={MusicLibraryWeb.Components.Notes.open("record-notes-sheet")} - > - {gettext("Open Notes")} - <.icon - name="hero-pencil" - class="h-5 w-5" - aria-hidden="true" - data-slot="icon" - /> - - <.button - variant="soft" - phx-click={MusicLibraryWeb.Components.Chat.open("record-chat-sheet")} - > - {gettext("Chat about album")} - <.icon - name="hero-chat-bubble-left-right" - class="h-5 w-5" - aria-hidden="true" - data-slot="icon" - /> - - <.button - :if={@record.selected_release_id} - variant="soft" - phx-click={MusicLibraryWeb.Components.Release.open("release-with-tracks-sheet")} - > - {gettext("Show Tracks")} - <.icon - name="hero-numbered-list" - class="h-5 w-5" - aria-hidden="true" - data-slot="icon" - /> - - <.dropdown id={"actions-#{@record.id}"} placement="bottom-end"> - <:toggle> - <.button variant="soft"> - {gettext("Actions")} - <.icon - name="hero-ellipsis-vertical" - class="h-5 w-5 text-zinc-500 dark:text-zinc-400 cursor-pointer" - aria-hidden="true" - data-slot="icon" - /> - - - <.focus_wrap id={"actions-#{@record.id}-focus-wrap"}> - <.dropdown_link - id={"actions-#{@record.id}-edit"} - patch={~p"/collection/#{@record}/show/edit"} - > - <.icon - name="hero-pencil-square" - class="h-4 w-4 mr-1 phx-click-loading:animate-bounce" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Edit")} - - - <.dropdown_link - id={"actions-#{@record.id}-refresh-cover"} - phx-click={JS.push("refresh_cover", value: %{id: @record.id})} - > - <.icon - name="hero-photo" - class="h-4 w-4 mr-1 phx-click-loading:animate-bounce" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Refresh cover")} - - - <.dropdown_link - id={"actions-#{@record.id}-refresh-mb-data"} - phx-click={JS.push("refresh_musicbrainz_data", value: %{id: @record.id})} - > - <.icon - name="hero-arrow-path" - class="h-4 w-4 mr-1 phx-click-loading:animate-spin" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Refresh MB data")} - - - <.dropdown_link - id={"actions-#{@record.id}-populate-genres"} - phx-click={JS.push("populate_genres", value: %{id: @record.id})} - > - <.icon - name="hero-sparkles" - class="h-4 w-4 mr-1 phx-click-loading:animate-shake" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Populate genres")} - - - <.dropdown_link - id={"actions-#{@record.id}-regenerate-embeddings"} - phx-click={JS.push("regenerate_embeddings", value: %{id: @record.id})} - > - <.icon - name="hero-sparkles" - class="h-4 w-4 mr-1 phx-click-loading:animate-shake" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Regenerate embeddings")} - - - <.dropdown_link - id={"actions-#{@record.id}-extract-colors-fast"} - phx-click={JS.push("extract_colors", value: %{id: @record.id, method: :fast})} - > - <.icon - name="hero-paint-brush" - class="h-4 w-4 mr-1 phx-click-loading:animate-shake" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Extract colors (fast)")} - - - <.dropdown_link - id={"actions-#{@record.id}-extract-colors-slow"} - phx-click={JS.push("extract_colors", value: %{id: @record.id, method: :slow})} - > - <.icon - name="hero-paint-brush" - class="h-4 w-4 mr-1 phx-click-loading:animate-shake" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Extract colors (slow)")} - - - <.dropdown_separator /> - <.dropdown_link - id={"actions-#{@record.id}-delete"} - phx-click={JS.push("delete", value: %{id: @record.id})} - 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="h-4 w-4 mr-1 phx-click-loading:animate-spin" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Delete")} - - - - -
-
-
-

- {@record.title} -

-

- <.record_colors record={@record} /> - - {Records.Record.format_release_date(@record.release_date)} · {format_label( - @record.format - )} · {type_label(@record.type)} - -

-
-
-
- <.dl_row label={gettext("ID")}> -
- {@record.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} - - <.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} /> - -
- - <.dl_row label={gettext("Collected release")}> -
- - {gettext("No release selected")} - - <.release_summary - :if={@record.selected_release_id} - release={Records.Record.selected_release(@record)} - /> - @record.id} - class="hidden" - > - {@record.selected_release_id} - - <.copy_to_clipboard - :if={@record.selected_release_id} - target_id={"record-selected-release-" <> @record.id} - label={gettext("Copy record selected release ID to clipboard")} - /> -
- - <.dl_row - :if={Records.Record.included_release_groups_count(@record) > 0} - label={gettext("Includes")} - > -
    -
  • - {included_release_group.artists} - {included_release_group.title} -
  • -
- - <.dl_row :if={@record_sets != []} label={gettext("Record sets")}> -
    -
  • - <.link - navigate={~p"/record-sets/#{record_set}"} - class="text-zinc-700 hover:text-zinc-500 dark:text-zinc-400 dark:hover:text-zinc-300 hover:underline" - > - {record_set.name} - - - {gettext( - "%{collected}/%{total} collected", - RecordSet.count_by_status(record_set) - )} - -
  • -
- - <.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)} - - -
-
-
-
- - <.similar_records - similar_records={@similar_records} - record_show_path={fn record -> ~p"/collection/#{record}" end} - section={:collection} - /> - - <.json_viewer title={gettext("MusicBrainz data")} data={@record.musicbrainz_data} /> - <.text_viewer title={gettext("Record Embedding")} data={@embedding_text} /> - - <.live_component - id="release-with-tracks" - sheet_id="release-with-tracks-sheet" - module={MusicLibraryWeb.Components.Release} - record={@record} - /> - - <.live_component - id="record-notes" - sheet_id="record-notes-sheet" - module={MusicLibraryWeb.Components.Notes} - entity={:record} - musicbrainz_id={@record.musicbrainz_id} - /> - - <.live_component - id="record-chat" - sheet_id="record-chat-sheet" - module={MusicLibraryWeb.Components.Chat} - title={@record.title} - chat_module={MusicLibrary.RecordChat} - chat_context={{@record, @embedding_text}} - placeholder={gettext("Ask about this album...")} - empty_prompt={gettext("Ask anything about this album")} - /> - - <.structured_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} - patch={~p"/collection/#{@record}"} - /> - -
diff --git a/lib/music_library_web/live/maintenance_live/index.ex b/lib/music_library_web/live/maintenance_live/index.ex index 79fe03fa..a97d6ac6 100644 --- a/lib/music_library_web/live/maintenance_live/index.ex +++ b/lib/music_library_web/live/maintenance_live/index.ex @@ -12,6 +12,142 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do @poll_interval 2_000 + @impl true + def render(assigns) do + ~H""" + +
+

+ {gettext("Maintenance")} +

+

+ {gettext("Records")} +

+

+ {gettext( + "Run operations on the entire record database. Monitor execution via the Oban dashboard." + )} +

+ +

+ {gettext("Artists")} +

+

+ {gettext( + "Run operations on the entire artist database. Monitor execution via the Oban dashboard." + )} +

+ +

+ {gettext("Database")} +

+

+ {gettext("Run lower-level operations on the database")} +

+ +
+
+ """ + end + + @impl true def mount(_params, _session, socket) do if connected?(socket) do Process.send_after(self(), :update_job_counts, @poll_interval) @@ -26,6 +162,7 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do |> assign_job_counts()} end + @impl true def handle_info(:update_job_counts, socket) do Process.send_after(self(), :update_job_counts, @poll_interval) @@ -66,6 +203,7 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do BackgroundRepo.one(query) end + @impl true def handle_event("refresh_records_musicbrainz_data", _params, socket) do Records.Batch.refresh_musicbrainz_data() diff --git a/lib/music_library_web/live/maintenance_live/index.html.heex b/lib/music_library_web/live/maintenance_live/index.html.heex deleted file mode 100644 index 6e784796..00000000 --- a/lib/music_library_web/live/maintenance_live/index.html.heex +++ /dev/null @@ -1,129 +0,0 @@ - -
-

- {gettext("Maintenance")} -

-

- {gettext("Records")} -

-

- {gettext( - "Run operations on the entire record database. Monitor execution via the Oban dashboard." - )} -

- -

- {gettext("Artists")} -

-

- {gettext( - "Run operations on the entire artist database. Monitor execution via the Oban dashboard." - )} -

- -

- {gettext("Database")} -

-

- {gettext("Run lower-level operations on the database")} -

- -
-
diff --git a/lib/music_library_web/live/online_store_template_live/index.ex b/lib/music_library_web/live/online_store_template_live/index.ex index 6971c13d..6d02b865 100644 --- a/lib/music_library_web/live/online_store_template_live/index.ex +++ b/lib/music_library_web/live/online_store_template_live/index.ex @@ -4,6 +4,120 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do alias MusicLibrary.OnlineStoreTemplates alias MusicLibrary.OnlineStoreTemplates.OnlineStoreTemplate + @impl true + def render(assigns) do + ~H""" + +
+
+
+

+ {gettext("Online Store Templates")} +

+
+
+ <.button variant="solid" size="sm" patch={~p"/online-store-templates/new"}> + <.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("Add")} + +
+
+
+ +
+ +
+ + <.structured_modal + :if={@live_action in [:new, :edit]} + id="template-modal" + on_close={JS.patch(~p"/online-store-templates")} + > + <.live_component + module={MusicLibraryWeb.OnlineStoreTemplateLive.Form} + id={@template.id || :new} + title={@page_title} + action={@live_action} + template={@template} + patch={~p"/online-store-templates"} + /> + +
+ """ + end + @impl true def mount(_params, _session, socket) do {:ok, diff --git a/lib/music_library_web/live/online_store_template_live/index.html.heex b/lib/music_library_web/live/online_store_template_live/index.html.heex deleted file mode 100644 index 9c90a402..00000000 --- a/lib/music_library_web/live/online_store_template_live/index.html.heex +++ /dev/null @@ -1,108 +0,0 @@ - -
-
-
-

- {gettext("Online Store Templates")} -

-
-
- <.button variant="solid" size="sm" patch={~p"/online-store-templates/new"}> - <.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("Add")} - -
-
-
- -
- -
- - <.structured_modal - :if={@live_action in [:new, :edit]} - id="template-modal" - on_close={JS.patch(~p"/online-store-templates")} - > - <.live_component - module={MusicLibraryWeb.OnlineStoreTemplateLive.Form} - id={@template.id || :new} - title={@page_title} - action={@live_action} - template={@template} - patch={~p"/online-store-templates"} - /> - -
diff --git a/lib/music_library_web/live/record_set_live/index.ex b/lib/music_library_web/live/record_set_live/index.ex index 3cb5b1f7..ca0a258d 100644 --- a/lib/music_library_web/live/record_set_live/index.ex +++ b/lib/music_library_web/live/record_set_live/index.ex @@ -16,6 +16,285 @@ defmodule MusicLibraryWeb.RecordSetLive.Index do order: :updated_at } + @impl true + def render(assigns) do + ~H""" + +
+
+ <.search_form query={@list_params.query} /> + <.button + variant="solid" + size="sm" + patch={~p"/record-sets/new"} + > + <.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("New Set")} + +
+
+ +
+ <.button_group> + <.button + patch={order_path(@list_params, :updated_at)} + size="sm" + class={[@list_params.order == :updated_at && "bg-zinc-100! dark:bg-zinc-700!"]} + > + <.icon name="hero-clock" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("Updated")} + + <.button + patch={order_path(@list_params, :alphabetical)} + size="sm" + class={[@list_params.order == :alphabetical && "bg-zinc-100! dark:bg-zinc-700!"]} + > + <.icon name="hero-user-solid" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("A->Z")} + + +
+ +
+ + + <.pagination id={:bottom_pagination} pagination_params={@list_params} /> +
+ + <.structured_modal + :if={@live_action in [:new, :edit]} + id="record-set-modal" + on_close={JS.patch(back_path(@list_params))} + > + <.live_component + module={MusicLibraryWeb.RecordSetLive.Form} + id={@record_set.id || :new} + title={@page_title} + action={@live_action} + record_set={@record_set} + patch={back_path(@list_params)} + /> + + + <.structured_modal + :if={@live_action == :add_record} + id="record-picker-modal" + on_close={JS.patch(back_path(@list_params))} + > + <.live_component + module={MusicLibraryWeb.RecordSetLive.RecordPicker} + id={"record-picker-#{@record_set.id}"} + title={@page_title} + record_set={@record_set} + patch={back_path(@list_params)} + /> + +
+ """ + end + @impl true def mount(_params, _session, socket) do {:ok, assign(socket, :current_section, :record_sets)} diff --git a/lib/music_library_web/live/record_set_live/index.html.heex b/lib/music_library_web/live/record_set_live/index.html.heex deleted file mode 100644 index aa3c6e91..00000000 --- a/lib/music_library_web/live/record_set_live/index.html.heex +++ /dev/null @@ -1,273 +0,0 @@ - -
-
- <.search_form query={@list_params.query} /> - <.button - variant="solid" - size="sm" - patch={~p"/record-sets/new"} - > - <.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("New Set")} - -
-
- -
- <.button_group> - <.button - patch={order_path(@list_params, :updated_at)} - size="sm" - class={[@list_params.order == :updated_at && "bg-zinc-100! dark:bg-zinc-700!"]} - > - <.icon name="hero-clock" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("Updated")} - - <.button - patch={order_path(@list_params, :alphabetical)} - size="sm" - class={[@list_params.order == :alphabetical && "bg-zinc-100! dark:bg-zinc-700!"]} - > - <.icon name="hero-user-solid" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("A->Z")} - - -
- -
- - - <.pagination id={:bottom_pagination} pagination_params={@list_params} /> -
- - <.structured_modal - :if={@live_action in [:new, :edit]} - id="record-set-modal" - on_close={JS.patch(back_path(@list_params))} - > - <.live_component - module={MusicLibraryWeb.RecordSetLive.Form} - id={@record_set.id || :new} - title={@page_title} - action={@live_action} - record_set={@record_set} - patch={back_path(@list_params)} - /> - - - <.structured_modal - :if={@live_action == :add_record} - id="record-picker-modal" - on_close={JS.patch(back_path(@list_params))} - > - <.live_component - module={MusicLibraryWeb.RecordSetLive.RecordPicker} - id={"record-picker-#{@record_set.id}"} - title={@page_title} - record_set={@record_set} - patch={back_path(@list_params)} - /> - -
diff --git a/lib/music_library_web/live/record_set_live/show.ex b/lib/music_library_web/live/record_set_live/show.ex index 0693718d..7890da6f 100644 --- a/lib/music_library_web/live/record_set_live/show.ex +++ b/lib/music_library_web/live/record_set_live/show.ex @@ -8,6 +8,221 @@ defmodule MusicLibraryWeb.RecordSetLive.Show do alias MusicLibraryWeb.Markdown alias Phoenix.LiveView.JS + @impl true + def render(assigns) do + ~H""" + +
+
+
+

+ {@record_set.name} +

+ + {gettext("%{collected}/%{total} records", RecordSet.count_by_status(@record_set))} + +
+
+ <.dropdown id="set-actions" placement="bottom-end"> + <:toggle> + <.button variant="soft"> + {gettext("Actions")} + <.icon + name="hero-ellipsis-vertical" + class="h-5 w-5 text-zinc-500 dark:text-zinc-400 cursor-pointer" + aria-hidden="true" + data-slot="icon" + /> + + + <.dropdown_link + id="set-actions-edit" + patch={~p"/record-sets/#{@record_set}/show/edit"} + > + {gettext("Edit")} + + <.separator /> + <.dropdown_button + phx-click="delete_set" + 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!" + ]} + > + {gettext("Delete")} + + +
+
+ +
+ {render_description(@record_set.description)} +
+ +
+
+
+ <.link + :if={item.record.purchased_at} + navigate={~p"/collection/#{item.record}"} + > + + + <.link + :if={!item.record.purchased_at} + navigate={~p"/wishlist/#{item.record}"} + > + + +
+ <.icon + name="hero-bars-2" + class="size-3.5 text-zinc-800 dark:text-zinc-200" + aria-hidden="true" + /> +
+
+ <.dropdown + id={"item-actions-#{item.record.id}"} + placement="bottom-end" + > + <:toggle> + {gettext("Actions")} + <.icon + name="hero-ellipsis-vertical" + class="size-5 text-zinc-800 dark:text-zinc-200 cursor-pointer" + aria-hidden="true" + data-slot="icon" + /> + + <.dropdown_button + :if={item.position > 0} + phx-click="move_up" + phx-value-record-id={item.record.id} + > + {gettext("Move left")} + + <.dropdown_button + :if={item.position < length(@record_set.items) - 1} + phx-click="move_down" + phx-value-record-id={item.record.id} + > + {gettext("Move right")} + + <.separator /> + <.dropdown_button + phx-click="remove_record" + phx-value-record-id={item.record.id} + data-confirm={gettext("Remove this record from the set?")} + class={[ + "text-red-900! hover:bg-red-50! dark:text-red-500! dark:hover:bg-red-900/30! dark:hover:text-red-600!" + ]} + > + {gettext("Remove")} + + +
+

+ <.artist_links joinphrase_class="text-sm" artists={item.record.artists} /> +

+

+ {item.record.title} +

+

+ {format_label(item.record.format)} · {type_label(item.record.type)} +

+

+ <.icon + name="hero-calendar-days" + class="-mt-1 h-4 w-4" + aria-hidden="true" + data-slot="icon" + /> + {Records.Record.format_release_date(item.record.release_date)} +

+
+ <.link + patch={~p"/record-sets/#{@record_set}/show/add-record"} + class={[ + "aspect-square", + "border-2 border-dashed border-zinc-300 dark:border-zinc-600", + "rounded-lg flex items-center justify-center", + "hover:border-zinc-400 dark:hover:border-zinc-500", + "hover:bg-zinc-50 dark:hover:bg-zinc-800", + "transition-colors cursor-pointer" + ]} + > + <.icon + name="hero-plus" + class="h-8 w-8 text-zinc-400 dark:text-zinc-500" + aria-hidden="true" + data-slot="icon" + /> + +
+
+
+ + <.structured_modal + :if={@live_action == :edit} + id="record-set-modal" + on_close={JS.patch(~p"/record-sets/#{@record_set}")} + > + <.live_component + module={MusicLibraryWeb.RecordSetLive.Form} + id={@record_set.id} + title={@page_title} + action={@live_action} + record_set={@record_set} + patch={~p"/record-sets/#{@record_set}"} + /> + + + <.structured_modal + :if={@live_action == :add_record} + id="record-picker-modal" + on_close={JS.patch(~p"/record-sets/#{@record_set}")} + > + <.live_component + module={MusicLibraryWeb.RecordSetLive.RecordPicker} + id={"record-picker-#{@record_set.id}"} + title={@page_title} + record_set={@record_set} + patch={~p"/record-sets/#{@record_set}"} + /> + +
+ """ + end + @impl true def mount(_params, _session, socket) do {:ok, assign(socket, :current_section, :record_sets)} diff --git a/lib/music_library_web/live/record_set_live/show.html.heex b/lib/music_library_web/live/record_set_live/show.html.heex deleted file mode 100644 index 1b6bc6d3..00000000 --- a/lib/music_library_web/live/record_set_live/show.html.heex +++ /dev/null @@ -1,209 +0,0 @@ - -
-
-
-

- {@record_set.name} -

- - {gettext("%{collected}/%{total} records", RecordSet.count_by_status(@record_set))} - -
-
- <.dropdown id="set-actions" placement="bottom-end"> - <:toggle> - <.button variant="soft"> - {gettext("Actions")} - <.icon - name="hero-ellipsis-vertical" - class="h-5 w-5 text-zinc-500 dark:text-zinc-400 cursor-pointer" - aria-hidden="true" - data-slot="icon" - /> - - - <.dropdown_link - id="set-actions-edit" - patch={~p"/record-sets/#{@record_set}/show/edit"} - > - {gettext("Edit")} - - <.separator /> - <.dropdown_button - phx-click="delete_set" - 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!" - ]} - > - {gettext("Delete")} - - -
-
- -
- {render_description(@record_set.description)} -
- -
-
-
- <.link - :if={item.record.purchased_at} - navigate={~p"/collection/#{item.record}"} - > - - - <.link - :if={!item.record.purchased_at} - navigate={~p"/wishlist/#{item.record}"} - > - - -
- <.icon - name="hero-bars-2" - class="size-3.5 text-zinc-800 dark:text-zinc-200" - aria-hidden="true" - /> -
-
- <.dropdown - id={"item-actions-#{item.record.id}"} - placement="bottom-end" - > - <:toggle> - {gettext("Actions")} - <.icon - name="hero-ellipsis-vertical" - class="size-5 text-zinc-800 dark:text-zinc-200 cursor-pointer" - aria-hidden="true" - data-slot="icon" - /> - - <.dropdown_button - :if={item.position > 0} - phx-click="move_up" - phx-value-record-id={item.record.id} - > - {gettext("Move left")} - - <.dropdown_button - :if={item.position < length(@record_set.items) - 1} - phx-click="move_down" - phx-value-record-id={item.record.id} - > - {gettext("Move right")} - - <.separator /> - <.dropdown_button - phx-click="remove_record" - phx-value-record-id={item.record.id} - data-confirm={gettext("Remove this record from the set?")} - class={[ - "text-red-900! hover:bg-red-50! dark:text-red-500! dark:hover:bg-red-900/30! dark:hover:text-red-600!" - ]} - > - {gettext("Remove")} - - -
-

- <.artist_links joinphrase_class="text-sm" artists={item.record.artists} /> -

-

- {item.record.title} -

-

- {format_label(item.record.format)} · {type_label(item.record.type)} -

-

- <.icon - name="hero-calendar-days" - class="-mt-1 h-4 w-4" - aria-hidden="true" - data-slot="icon" - /> - {Records.Record.format_release_date(item.record.release_date)} -

-
- <.link - patch={~p"/record-sets/#{@record_set}/show/add-record"} - class={[ - "aspect-square", - "border-2 border-dashed border-zinc-300 dark:border-zinc-600", - "rounded-lg flex items-center justify-center", - "hover:border-zinc-400 dark:hover:border-zinc-500", - "hover:bg-zinc-50 dark:hover:bg-zinc-800", - "transition-colors cursor-pointer" - ]} - > - <.icon - name="hero-plus" - class="h-8 w-8 text-zinc-400 dark:text-zinc-500" - aria-hidden="true" - data-slot="icon" - /> - -
-
-
- - <.structured_modal - :if={@live_action == :edit} - id="record-set-modal" - on_close={JS.patch(~p"/record-sets/#{@record_set}")} - > - <.live_component - module={MusicLibraryWeb.RecordSetLive.Form} - id={@record_set.id} - title={@page_title} - action={@live_action} - record_set={@record_set} - patch={~p"/record-sets/#{@record_set}"} - /> - - - <.structured_modal - :if={@live_action == :add_record} - id="record-picker-modal" - on_close={JS.patch(~p"/record-sets/#{@record_set}")} - > - <.live_component - module={MusicLibraryWeb.RecordSetLive.RecordPicker} - id={"record-picker-#{@record_set.id}"} - title={@page_title} - record_set={@record_set} - patch={~p"/record-sets/#{@record_set}"} - /> - -
diff --git a/lib/music_library_web/live/scrobble_live/index.ex b/lib/music_library_web/live/scrobble_live/index.ex index 9986d3de..e6e200e4 100644 --- a/lib/music_library_web/live/scrobble_live/index.ex +++ b/lib/music_library_web/live/scrobble_live/index.ex @@ -7,6 +7,141 @@ defmodule MusicLibraryWeb.ScrobbleLive.Index do alias MusicLibrary.Records alias MusicLibrary.ScrobbleActivity + @impl true + def render(assigns) do + ~H""" + +
+
+
+ <.search_form query={@search_query} /> + <.button :if={!@can_scrobble?} size="sm" href={LastFm.auth_url()}> + {gettext("Connect your Last.fm account")} + +
+
+ <%= if @search_results != [] && @selected_release_group == nil do %> +
+

{gettext("Release Groups")}

+
    +
  • +
    + {release_group.title} ~p"/images/cover-not-found.png" <> "';"} + /> +
    +

    + {release_group.artists} +

    +

    + {release_group.title} +

    +

    + {Records.Record.format_release_date(release_group.release_date)} + · + <.badge variant="soft" size="xs">{type_label(release_group.type)} +

    +
    +
    +
  • +
+
+ <% end %> + + <%= if @selected_release_group && @releases != [] do %> +
+
+ <.button + variant="ghost" + size="sm" + phx-click="clear_selection" + > + <.icon name="hero-arrow-left" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("Back")} + +

+ {gettext("Releases for \"%{title}\"", title: @selected_release_group.title)} +

+
+ +
    +
  • + <.link + navigate={~p"/scrobble/#{release.id}"} + class="flex items-center gap-x-4 py-5 px-4 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors" + > + {release.title} ~p"/images/cover-not-found.png" <> "';"} + /> +
    +

    + {release.title} +

    +
    + {release.date} + + {country_label(release.country)} + + <.badge :if={release.catalog_number} variant="soft" size="xs"> + {release.catalog_number} + + + {ngettext("1 disc", "%{count} discs", Release.media_count(release))} + +
    +
    + +
  • +
+
+ <% end %> + + <%= if @loading && @search_query != "" do %> +
+ <.loading class="size-8 mx-auto text-zinc-400" /> +

+ <%= if @selected_release_group do %> + {gettext("Loading releases...")} + <% else %> + {gettext("Searching...")} + <% end %> +

+
+ <% end %> + + <%= if @search_query != "" && @search_results == [] && not @loading do %> +
+ <.icon + name="hero-magnifying-glass" + class="h-12 w-12 mx-auto text-zinc-300 dark:text-zinc-600" + /> +

+ {gettext("No release groups found for \"%{query}\"", query: @search_query)} +

+

+ {gettext("Try a different search term or check the spelling")} +

+
+ <% end %> +
+
+ """ + end + @impl true def mount(_params, _session, socket) do {:ok, diff --git a/lib/music_library_web/live/scrobble_live/index.html.heex b/lib/music_library_web/live/scrobble_live/index.html.heex deleted file mode 100644 index ce476da1..00000000 --- a/lib/music_library_web/live/scrobble_live/index.html.heex +++ /dev/null @@ -1,129 +0,0 @@ - -
-
-
- <.search_form query={@search_query} /> - <.button :if={!@can_scrobble?} size="sm" href={LastFm.auth_url()}> - {gettext("Connect your Last.fm account")} - -
-
- <%= if @search_results != [] && @selected_release_group == nil do %> -
-

{gettext("Release Groups")}

-
    -
  • -
    - {release_group.title} ~p"/images/cover-not-found.png" <> "';"} - /> -
    -

    - {release_group.artists} -

    -

    - {release_group.title} -

    -

    - {Records.Record.format_release_date(release_group.release_date)} - · - <.badge variant="soft" size="xs">{type_label(release_group.type)} -

    -
    -
    -
  • -
-
- <% end %> - - <%= if @selected_release_group && @releases != [] do %> -
-
- <.button - variant="ghost" - size="sm" - phx-click="clear_selection" - > - <.icon name="hero-arrow-left" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("Back")} - -

- {gettext("Releases for \"%{title}\"", title: @selected_release_group.title)} -

-
- -
    -
  • - <.link - navigate={~p"/scrobble/#{release.id}"} - class="flex items-center gap-x-4 py-5 px-4 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors" - > - {release.title} ~p"/images/cover-not-found.png" <> "';"} - /> -
    -

    - {release.title} -

    -
    - {release.date} - - {country_label(release.country)} - - <.badge :if={release.catalog_number} variant="soft" size="xs"> - {release.catalog_number} - - - {ngettext("1 disc", "%{count} discs", Release.media_count(release))} - -
    -
    - -
  • -
-
- <% end %> - - <%= if @loading && @search_query != "" do %> -
- <.loading class="size-8 mx-auto text-zinc-400" /> -

- <%= if @selected_release_group do %> - {gettext("Loading releases...")} - <% else %> - {gettext("Searching...")} - <% end %> -

-
- <% end %> - - <%= if @search_query != "" && @search_results == [] && not @loading do %> -
- <.icon - name="hero-magnifying-glass" - class="h-12 w-12 mx-auto text-zinc-300 dark:text-zinc-600" - /> -

- {gettext("No release groups found for \"%{query}\"", query: @search_query)} -

-

- {gettext("Try a different search term or check the spelling")} -

-
- <% end %> -
-
diff --git a/lib/music_library_web/live/scrobble_live/show.ex b/lib/music_library_web/live/scrobble_live/show.ex index ed3a3b1e..0b87be83 100644 --- a/lib/music_library_web/live/scrobble_live/show.ex +++ b/lib/music_library_web/live/scrobble_live/show.ex @@ -6,6 +6,105 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do alias MusicLibrary.ScrobbleActivity + @impl true + def render(assigns) do + ~H""" + +
+ <.alert + :if={not @can_scrobble} + color="warning" + title={gettext("Last.fm not connected")} + hide_close + > + {gettext( + "You need to connect your Last.fm account to scrobble. Please set up your Last.fm session key in the settings." + )} + + +
+ <.button variant="ghost" size="sm" navigate={~p"/scrobble"}> + <.icon name="hero-arrow-left" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("Back to search")} + +
+ +
+
+ {"Cover ~p"/images/cover-not-found.png" <> "';"} + /> +
+ +
+
+

+ {@release.artists |> Enum.map(& &1.name) |> Enum.join(", ")} +

+
+

+ {@release.title} +

+ +
+
+ <.dl_row :if={@release.date} label={gettext("Release Date")}> + {@release.date} + + <.dl_row :if={@release.country} label={gettext("Country")}> + {country_label(@release.country)} {@release.country} + + <.dl_row :if={@release.barcode} label={gettext("Barcode")}> + {@release.barcode} + + <.dl_row :if={@release.catalog_number} label={gettext("Catalog Number")}> + {@release.catalog_number} + + <.dl_row :if={@release.media != []} label={gettext("Media")}> + {ngettext("1 disc", "%{count} discs", MusicBrainz.Release.media_count(@release))} + +
+
+ +
+
+

{gettext("Tracks")}

+ + <.button + :if={@can_scrobble} + phx-click={ + if MapSet.size(@selected_tracks) > 0, + do: "scrobble_selected_tracks", + else: "scrobble_release" + } + size="sm" + > + {scrobble_button_label(@selected_tracks)} + +
+ + <.medium + :for={medium <- @release.media} + can_scrobble?={@can_scrobble} + already_scrobbled={false} + medium={medium} + release_artists={@release.artists} + selected_tracks={@selected_tracks} + media_count={MusicBrainz.Release.media_count(@release)} + myself={nil} + /> +
+
+
+
+
+ """ + end + @impl true def mount(_params, _session, socket) do {:ok, diff --git a/lib/music_library_web/live/scrobble_live/show.html.heex b/lib/music_library_web/live/scrobble_live/show.html.heex deleted file mode 100644 index e32d3eb3..00000000 --- a/lib/music_library_web/live/scrobble_live/show.html.heex +++ /dev/null @@ -1,93 +0,0 @@ - -
- <.alert - :if={not @can_scrobble} - color="warning" - title={gettext("Last.fm not connected")} - hide_close - > - {gettext( - "You need to connect your Last.fm account to scrobble. Please set up your Last.fm session key in the settings." - )} - - -
- <.button variant="ghost" size="sm" navigate={~p"/scrobble"}> - <.icon name="hero-arrow-left" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("Back to search")} - -
- -
-
- {"Cover ~p"/images/cover-not-found.png" <> "';"} - /> -
- -
-
-

- {@release.artists |> Enum.map(& &1.name) |> Enum.join(", ")} -

-
-

- {@release.title} -

- -
-
- <.dl_row :if={@release.date} label={gettext("Release Date")}> - {@release.date} - - <.dl_row :if={@release.country} label={gettext("Country")}> - {country_label(@release.country)} {@release.country} - - <.dl_row :if={@release.barcode} label={gettext("Barcode")}> - {@release.barcode} - - <.dl_row :if={@release.catalog_number} label={gettext("Catalog Number")}> - {@release.catalog_number} - - <.dl_row :if={@release.media != []} label={gettext("Media")}> - {ngettext("1 disc", "%{count} discs", MusicBrainz.Release.media_count(@release))} - -
-
- -
-
-

{gettext("Tracks")}

- - <.button - :if={@can_scrobble} - phx-click={ - if MapSet.size(@selected_tracks) > 0, - do: "scrobble_selected_tracks", - else: "scrobble_release" - } - size="sm" - > - {scrobble_button_label(@selected_tracks)} - -
- - <.medium - :for={medium <- @release.media} - can_scrobble?={@can_scrobble} - already_scrobbled={false} - medium={medium} - release_artists={@release.artists} - selected_tracks={@selected_tracks} - media_count={MusicBrainz.Release.media_count(@release)} - myself={nil} - /> -
-
-
-
-
diff --git a/lib/music_library_web/live/scrobble_rules_live/index.ex b/lib/music_library_web/live/scrobble_rules_live/index.ex index f95488ba..348a69e9 100644 --- a/lib/music_library_web/live/scrobble_rules_live/index.ex +++ b/lib/music_library_web/live/scrobble_rules_live/index.ex @@ -14,6 +14,143 @@ defmodule MusicLibraryWeb.ScrobbleRulesLive.Index do order: :inserted_at } + @impl true + def render(assigns) do + ~H""" + +
+
+
+

+ {gettext("Scrobble Rules")} +

+
+
+ <.button_group> + <.button + variant="solid" + size="sm" + patch={~p"/scrobble-rules/new"} + > + <.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("Add")} + + <.button + variant="solid" + size="sm" + phx-click="apply_all_rules" + > + <.icon name="hero-play" class="icon" /> + {gettext("Apply")} + + +
+
+
+ +
+ + + <.pagination id={:bottom_pagination} pagination_params={@list_params} /> +
+ + <.structured_modal + :if={@live_action in [:new, :edit]} + id="scrobble_rule-modal" + on_close={JS.patch(back_path(@list_params))} + > + <.live_component + module={MusicLibraryWeb.ScrobbleRulesLive.Form} + id={@scrobble_rule.id || :new} + title={@page_title} + action={@live_action} + scrobble_rule={@scrobble_rule} + patch={back_path(@list_params)} + /> + +
+ """ + end + @impl true def mount(_params, _session, socket) do {:ok, assign(socket, :current_section, :scrobble_rules)} diff --git a/lib/music_library_web/live/scrobble_rules_live/index.html.heex b/lib/music_library_web/live/scrobble_rules_live/index.html.heex deleted file mode 100644 index 8eaeb64e..00000000 --- a/lib/music_library_web/live/scrobble_rules_live/index.html.heex +++ /dev/null @@ -1,131 +0,0 @@ - -
-
-
-

- {gettext("Scrobble Rules")} -

-
-
- <.button_group> - <.button - variant="solid" - size="sm" - patch={~p"/scrobble-rules/new"} - > - <.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("Add")} - - <.button - variant="solid" - size="sm" - phx-click="apply_all_rules" - > - <.icon name="hero-play" class="icon" /> - {gettext("Apply")} - - -
-
-
- -
- - - <.pagination id={:bottom_pagination} pagination_params={@list_params} /> -
- - <.structured_modal - :if={@live_action in [:new, :edit]} - id="scrobble_rule-modal" - on_close={JS.patch(back_path(@list_params))} - > - <.live_component - module={MusicLibraryWeb.ScrobbleRulesLive.Form} - id={@scrobble_rule.id || :new} - title={@page_title} - action={@live_action} - scrobble_rule={@scrobble_rule} - patch={back_path(@list_params)} - /> - -
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 7c297966..5b5555a8 100644 --- a/lib/music_library_web/live/scrobbled_tracks_live/index.ex +++ b/lib/music_library_web/live/scrobbled_tracks_live/index.ex @@ -16,6 +16,197 @@ defmodule MusicLibraryWeb.ScrobbledTracksLive.Index do order: :scrobbled_at } + @impl true + def render(assigns) do + ~H""" + +
+
+
+ <.search_form query={@track_list_params.query} /> +
+
+ +
+ <.button_group> + <.button + patch={order_path(@track_list_params, :scrobbled_at)} + size="xs" + class={[ + @track_list_params.order == :scrobbled_at && "bg-zinc-100! dark:bg-zinc-700!" + ]} + > + <.icon + name="hero-clock-solid" + class="icon" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Scrobbled")} + + <.button + patch={order_path(@track_list_params, :title)} + size="xs" + class={[ + @track_list_params.order == :title && "bg-zinc-100! dark:bg-zinc-700!" + ]} + > + <.icon name="hero-musical-note-solid" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("Title")} + + <.button + patch={order_path(@track_list_params, :artist)} + size="xs" + class={[ + @track_list_params.order == :artist && "bg-zinc-100! dark:bg-zinc-700!" + ]} + > + <.icon name="hero-user-solid" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("Artist")} + + <.button + patch={order_path(@track_list_params, :album)} + size="xs" + class={[ + @track_list_params.order == :album && "bg-zinc-100! dark:bg-zinc-700!" + ]} + > + <.icon name="hero-musical-note-solid" class="icon" aria-hidden="true" data-slot="icon" /> + {gettext("Album")} + + + <.refresh_lastfm_feed_button /> +
+
+ +
+ + + <.pagination id={:bottom_pagination} pagination_params={@track_list_params} /> +
+ + <.structured_modal + :if={@live_action == :edit} + id="track-modal" + on_close={JS.patch(back_path(@track_list_params))} + > + <.live_component + module={MusicLibraryWeb.ScrobbledTracksLive.Form} + id={@track.scrobbled_at_uts} + action={@live_action} + track={@track} + patch={back_path(@track_list_params)} + /> + +
+ """ + end + @impl true def mount(_params, _session, socket) do socket = 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 deleted file mode 100644 index a2ad6fa8..00000000 --- a/lib/music_library_web/live/scrobbled_tracks_live/index.html.heex +++ /dev/null @@ -1,185 +0,0 @@ - -
-
-
- <.search_form query={@track_list_params.query} /> -
-
- -
- <.button_group> - <.button - patch={order_path(@track_list_params, :scrobbled_at)} - size="xs" - class={[ - @track_list_params.order == :scrobbled_at && "bg-zinc-100! dark:bg-zinc-700!" - ]} - > - <.icon - name="hero-clock-solid" - class="icon" - aria-hidden="true" - data-slot="icon" - /> - {gettext("Scrobbled")} - - <.button - patch={order_path(@track_list_params, :title)} - size="xs" - class={[ - @track_list_params.order == :title && "bg-zinc-100! dark:bg-zinc-700!" - ]} - > - <.icon name="hero-musical-note-solid" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("Title")} - - <.button - patch={order_path(@track_list_params, :artist)} - size="xs" - class={[ - @track_list_params.order == :artist && "bg-zinc-100! dark:bg-zinc-700!" - ]} - > - <.icon name="hero-user-solid" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("Artist")} - - <.button - patch={order_path(@track_list_params, :album)} - size="xs" - class={[ - @track_list_params.order == :album && "bg-zinc-100! dark:bg-zinc-700!" - ]} - > - <.icon name="hero-musical-note-solid" class="icon" aria-hidden="true" data-slot="icon" /> - {gettext("Album")} - - - <.refresh_lastfm_feed_button /> -
-
- -
- - - <.pagination id={:bottom_pagination} pagination_params={@track_list_params} /> -
- - <.structured_modal - :if={@live_action == :edit} - id="track-modal" - on_close={JS.patch(back_path(@track_list_params))} - > - <.live_component - module={MusicLibraryWeb.ScrobbledTracksLive.Form} - id={@track.scrobbled_at_uts} - action={@live_action} - track={@track} - patch={back_path(@track_list_params)} - /> - -
diff --git a/lib/music_library_web/live/stats_live/index.ex b/lib/music_library_web/live/stats_live/index.ex index c5245f20..c533bbcb 100644 --- a/lib/music_library_web/live/stats_live/index.ex +++ b/lib/music_library_web/live/stats_live/index.ex @@ -11,6 +11,337 @@ defmodule MusicLibraryWeb.StatsLive.Index do alias MusicLibrary.{Collection, Records, ScrobbleActivity, Wishlist} alias MusicLibraryWeb.StatsLive.{TopAlbums, TopArtists} + @impl true + def render(assigns) do + ~H""" + +
+

+ {gettext("Records")} +

+
+ <.album_preview + record={@latest_record} + title={gettext("Latest purchase")} + class="col-span-3 sm:col-span-2" + /> + <.counter + title={gettext("Collection")} + count={@collection_count} + path={~p"/collection"} + /> + <.counter title={gettext("Wishlist")} count={@wishlist_count} path={~p"/wishlist"} /> + <.counter + title={gettext("Scrobbles")} + count={to_compact(@scrobble_count)} + tooltip={@scrobble_count} + /> +
+
+ +
+
+

+ {gettext("Formats")} +

+ <.counters_by_category + categories_with_counts={@collection_count_by_format} + category_format_fn={&format_label/1} + category_path_fn={fn format -> ~p"/collection?query=format:#{format}" end} + /> +
+ +
+

+ {gettext("Types")} +

+ <.counters_by_category + categories_with_counts={@collection_count_by_type} + category_format_fn={&type_label/1} + category_path_fn={fn type -> ~p"/collection?query=type:#{type}" end} + /> +
+
+ +
+
+ + +
+
+ +
+
+

+ {gettext("Scrobble activity")} +

+ <.refresh_lastfm_feed_button /> +
+ <.tabs id="scrobble-activity" class="mt-4"> + <.tabs_list active_tab={@scrobble_activity_mode} variant="segmented" class="w-48"> + <:tab + name="albums" + phx-click={JS.push("set_scrobble_activity_mode", value: %{mode: "albums"})} + > + {gettext("Albums")} + + <:tab + name="tracks" + phx-click={JS.push("set_scrobble_activity_mode", value: %{mode: "tracks"})} + > + {gettext("Tracks")} + + + + <.tabs_panel name="albums" active={@scrobble_activity_mode == "albums"}> + + + <.tabs_panel name="tracks" active={@scrobble_activity_mode == "tracks"}> + + + +
+ +
+
+
+

+ {gettext("On This day")} +

+ <.form + :let={f} + for={to_form(%{"current_date" => @current_date})} + phx-change="set_current_date" + > + <.date_picker size="xs" field={f[:current_date]}> + <:outer_suffix> + <.button + size="xs" + type="button" + phx-click={ + JS.push("set_current_date", value: %{"current_date" => Date.utc_today()}) + } + > + Today + + + + +
+
+ <.records_on_this_day + current_date={@current_date} + records={@records_on_this_day} + record_show_path={fn record -> ~p"/collection/#{record}" end} + /> +
+
+
+

+ {gettext("Top %{n} Collection Artists", %{n: length(@records_by_artist)})} +

+
+ <.vertical_bar_chart + data={@records_by_artist} + width={600} + height={26 * length(@records_by_artist)} + color_class="fill-red-500" + label_fn={fn datum -> datum.name end} + value_fn={fn datum -> datum.count end} + datum_click={ + fn datum -> + JS.navigate(~p"/artists/#{datum.id}") + end + } + class="w-full" + /> +
+
+ +
+

+ {gettext("Top %{n} Collection Genres", %{n: length(@records_by_genre)})} +

+
+ <.vertical_bar_chart + data={@records_by_genre} + width={600} + height={26 * length(@records_by_genre)} + color_class="fill-zinc-500" + label_fn={fn {genre, _count} -> genre end} + value_fn={fn {_genre, count} -> count end} + datum_click={ + fn {genre, _count} -> + JS.navigate(~p"/collection?#{%{query: ~s(genre:"#{genre}")}}") + end + } + class="w-full" + /> +
+
+
+
+ """ + end + + @impl true def mount(_params, _session, socket) do current_date = DateTime.now!(socket.assigns.timezone) |> DateTime.to_date() latest_record = Collection.get_latest_record() @@ -44,6 +375,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do )} end + @impl true def handle_event("refresh_lastfm_feed", _, socket) do LastFm.refresh_scrobbled_tracks() {:noreply, socket} @@ -96,6 +428,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do end end + @impl true def handle_info(%{track_count: 0}, socket) do {:noreply, socket} end diff --git a/lib/music_library_web/live/stats_live/index.html.heex b/lib/music_library_web/live/stats_live/index.html.heex deleted file mode 100644 index 3ebc85d8..00000000 --- a/lib/music_library_web/live/stats_live/index.html.heex +++ /dev/null @@ -1,324 +0,0 @@ - -
-

- {gettext("Records")} -

-
- <.album_preview - record={@latest_record} - title={gettext("Latest purchase")} - class="col-span-3 sm:col-span-2" - /> - <.counter - title={gettext("Collection")} - count={@collection_count} - path={~p"/collection"} - /> - <.counter title={gettext("Wishlist")} count={@wishlist_count} path={~p"/wishlist"} /> - <.counter - title={gettext("Scrobbles")} - count={to_compact(@scrobble_count)} - tooltip={@scrobble_count} - /> -
-
- -
-
-

- {gettext("Formats")} -

- <.counters_by_category - categories_with_counts={@collection_count_by_format} - category_format_fn={&format_label/1} - category_path_fn={fn format -> ~p"/collection?query=format:#{format}" end} - /> -
- -
-

- {gettext("Types")} -

- <.counters_by_category - categories_with_counts={@collection_count_by_type} - category_format_fn={&type_label/1} - category_path_fn={fn type -> ~p"/collection?query=type:#{type}" end} - /> -
-
- -
-
- - -
-
- -
-
-

- {gettext("Scrobble activity")} -

- <.refresh_lastfm_feed_button /> -
- <.tabs id="scrobble-activity" class="mt-4"> - <.tabs_list active_tab={@scrobble_activity_mode} variant="segmented" class="w-48"> - <:tab - name="albums" - phx-click={JS.push("set_scrobble_activity_mode", value: %{mode: "albums"})} - > - {gettext("Albums")} - - <:tab - name="tracks" - phx-click={JS.push("set_scrobble_activity_mode", value: %{mode: "tracks"})} - > - {gettext("Tracks")} - - - - <.tabs_panel name="albums" active={@scrobble_activity_mode == "albums"}> - - - <.tabs_panel name="tracks" active={@scrobble_activity_mode == "tracks"}> - - - -
- -
-
-
-

- {gettext("On This day")} -

- <.form - :let={f} - for={to_form(%{"current_date" => @current_date})} - phx-change="set_current_date" - > - <.date_picker size="xs" field={f[:current_date]}> - <:outer_suffix> - <.button - size="xs" - type="button" - phx-click={ - JS.push("set_current_date", value: %{"current_date" => Date.utc_today()}) - } - > - Today - - - - -
-
- <.records_on_this_day - current_date={@current_date} - records={@records_on_this_day} - record_show_path={fn record -> ~p"/collection/#{record}" end} - /> -
-
-
-

- {gettext("Top %{n} Collection Artists", %{n: length(@records_by_artist)})} -

-
- <.vertical_bar_chart - data={@records_by_artist} - width={600} - height={26 * length(@records_by_artist)} - color_class="fill-red-500" - label_fn={fn datum -> datum.name end} - value_fn={fn datum -> datum.count end} - datum_click={ - fn datum -> - JS.navigate(~p"/artists/#{datum.id}") - end - } - class="w-full" - /> -
-
- -
-

- {gettext("Top %{n} Collection Genres", %{n: length(@records_by_genre)})} -

-
- <.vertical_bar_chart - data={@records_by_genre} - width={600} - height={26 * length(@records_by_genre)} - color_class="fill-zinc-500" - label_fn={fn {genre, _count} -> genre end} - value_fn={fn {_genre, count} -> count end} - datum_click={ - fn {genre, _count} -> - JS.navigate(~p"/collection?#{%{query: ~s(genre:"#{genre}")}}") - end - } - class="w-full" - /> -
-
-
-
diff --git a/lib/music_library_web/live/universal_search_live/index.ex b/lib/music_library_web/live/universal_search_live/index.ex index 900f942a..e25bef60 100644 --- a/lib/music_library_web/live/universal_search_live/index.ex +++ b/lib/music_library_web/live/universal_search_live/index.ex @@ -6,6 +6,137 @@ defmodule MusicLibraryWeb.UniversalSearchLive.Index do alias MusicLibrary.Search alias Phoenix.LiveView.ColocatedHook + @impl true + def render(assigns) do + ~H""" +
+ <.structured_modal + :if={@show_modal} + id="universal-search-root" + open={@show_modal} + on_close={JS.push("close_modal", value: %{}, target: "#universal-search")} + > +
+
+ <.icon + name="hero-magnifying-glass" + class="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-zinc-400" + /> + + <.input + name="query" + id="universal-search-input" + placeholder="Search records and artists..." + value={@search_query} + phx-debounce="300" + autofocus + /> +
+
+ + <.empty_state :if={@search_query == ""} /> + + <.no_results :if={@search_query != "" and @total_results == 0} query={@search_query} /> + +
0} class="max-h-148 md:max-h-164 overflow-y-auto mt-4"> + <.search_result_group + :if={length(@search_results.artists) > 0} + title="Artists" + count={length(@search_results.artists)} + total_count={@search_counts.artists_count} + > + <.search_result_artist + :for={%{artist: artist, image_data_hash: image_data_hash} <- @search_results.artists} + artist={artist} + image_data_hash={image_data_hash} + phx-click="navigate_to_artist" + phx-value-id={artist.musicbrainz_id} + phx-target={@myself} + /> + +
+ <.search_result_group + :if={length(@search_results.collection) > 0} + title="Collection" + count={length(@search_results.collection)} + total_count={@search_counts.collection_count} + > + <.search_result_record + :for={record <- @search_results.collection} + record={record} + type={:collection} + phx-click="navigate_to_record" + phx-value-id={record.id} + phx-value-type="collection" + phx-target={@myself} + /> + <:actions> + <.view_all_button + :if={@search_counts.collection_count > length(@search_results.collection)} + count={@search_counts.collection_count} + target="collection" + phx-click="navigate_to_collection" + phx-value-query={@search_query} + phx-target={@myself} + /> + + + <.search_result_group + :if={length(@search_results.wishlist) > 0} + title="Wishlist" + count={length(@search_results.wishlist)} + total_count={@search_counts.wishlist_count} + > + <.search_result_record + :for={record <- @search_results.wishlist} + record={record} + type={:wishlist} + phx-click="navigate_to_record" + phx-value-id={record.id} + phx-value-type="wishlist" + phx-target={@myself} + /> + <:actions> + <.view_all_button + :if={@search_counts.wishlist_count > length(@search_results.wishlist)} + count={@search_counts.wishlist_count} + target="wishlist" + phx-click="navigate_to_wishlist" + phx-value-query={@search_query} + phx-target={@myself} + /> + + + <.search_result_group + :if={length(@search_results.record_sets) > 0} + title="Record Sets" + count={length(@search_results.record_sets)} + total_count={@search_counts.record_sets_count} + > + <.search_result_record_set + :for={record_set <- @search_results.record_sets} + record_set={record_set} + phx-click="navigate_to_record_set" + phx-value-id={record_set.id} + phx-target={@myself} + /> + <:actions> + <.view_all_button + :if={@search_counts.record_sets_count > length(@search_results.record_sets)} + count={@search_counts.record_sets_count} + target="record set" + phx-click="navigate_to_record_sets" + phx-value-query={@search_query} + phx-target={@myself} + /> + + + <.results_footer total_results={@total_results} /> + +
+ """ + end + def universal_search_trigger(assigns) do ~H"""