Uniform Scrobble Anything UX/UI with rest of the application

This commit is contained in:
Claudio Ortolina
2026-02-10 10:48:38 +00:00
parent 6c10d9ed18
commit b4c5c902bc
6 changed files with 250 additions and 202 deletions
@@ -1,7 +1,7 @@
defmodule MusicLibraryWeb.ScrobbleLive.Index do defmodule MusicLibraryWeb.ScrobbleLive.Index do
use MusicLibraryWeb, :live_view use MusicLibraryWeb, :live_view
import MusicLibraryWeb.RecordComponents, only: [type_label: 1] import MusicLibraryWeb.RecordComponents, only: [type_label: 1, country_label: 1]
alias MusicBrainz.{Release, ReleaseGroupSearchResult} alias MusicBrainz.{Release, ReleaseGroupSearchResult}
alias MusicLibrary.Records alias MusicLibrary.Records
@@ -29,16 +29,16 @@
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"} onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
/> />
<div class="min-w-0 flex-auto"> <div class="min-w-0 flex-auto">
<h1 class="truncate text-sm leading-6 text-zinc-700 dark:text-zinc-400"> <p class="truncate text-sm leading-6 text-zinc-700 dark:text-zinc-400">
{release_group.artists} {release_group.artists}
</h1> </p>
<h2 class="truncate mt-1 flex font-semibold text-sm sm:text-base leading-5 text-zinc-700 dark:text-zinc-300 text-wrap"> <p class="truncate mt-1 flex font-semibold text-sm sm:text-base leading-5 text-zinc-700 dark:text-zinc-300 text-wrap">
{release_group.title} {release_group.title}
</h2> </p>
<p class="mt-1 text-xs leading-5 text-zinc-500 dark:text-zinc-400"> <p class="mt-1 flex items-center gap-1 text-xs leading-5 text-zinc-500 dark:text-zinc-400">
{Records.Record.format_release_date(release_group.release_date)} · {type_label( {Records.Record.format_release_date(release_group.release_date)}
release_group.type <span>&middot;</span>
)} <.badge variant="soft" size="xs">{type_label(release_group.type)}</.badge>
</p> </p>
</div> </div>
</div> </div>
@@ -55,69 +55,57 @@
size="sm" size="sm"
phx-click="clear_selection" phx-click="clear_selection"
> >
<.icon name="hero-arrow-left" class="h-4 w-4" /> <.icon name="hero-arrow-left" class="icon" aria-hidden="true" data-slot="icon" />
{gettext("Back")}
</.button> </.button>
<h3 class="text-lg font-semibold"> <h3 class="text-lg font-semibold">
{gettext("Releases for \"%{title}\"", title: @selected_release_group.title)} {gettext("Releases for \"%{title}\"", title: @selected_release_group.title)}
</h3> </h3>
</div> </div>
<div class="grid gap-3"> <ul class="divide-y divide-zinc-100 dark:divide-slate-300/30">
<%= for release <- @releases do %> <li :for={release <- @releases}>
<div class="bg-white dark:bg-zinc-800 shadow rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"> <.link
<.link navigate={~p"/scrobble/#{release.id}"}
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"
class="block p-4" >
> <img
<div class="flex justify-start"> class="w-20 flex-none rounded-lg"
<div> alt={release.title}
<img src={Release.thumb_url(release)}
class="w-20 flex-none rounded-lg mr-4" onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
alt={release.title} />
src={Release.thumb_url(release)} <div class="min-w-0 flex-auto">
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"} <p class="font-medium text-zinc-900 dark:text-zinc-100">
/> {release.title}
</div> </p>
<div> <div class="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-zinc-500 dark:text-zinc-400">
<h4 class="font-medium text-gray-900 dark:text-gray-100"> <span :if={release.date}>{release.date}</span>
{release.title} <span :if={release.country}>
</h4> {country_label(release.country)}
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1 space-y-1"> </span>
<%= if release.date do %> <.badge :if={release.catalog_number} variant="soft" size="xs">
<p>Released: {release.date}</p> {release.catalog_number}
<% end %> </.badge>
<%= if release.country do %> <span :if={release.media != []}>
<p>Country: {release.country}</p> {ngettext("1 disc", "%{count} discs", Release.media_count(release))}
<% end %> </span>
<%= if release.barcode do %>
<p>Barcode: {release.barcode}</p>
<% end %>
<%= if release.catalog_number do %>
<p>Catalog: {release.catalog_number}</p>
<% end %>
<%= if release.media != [] do %>
<p>
{ngettext("1 disc", "%{count} discs", Release.media_count(release))}
</p>
<% end %>
</div>
</div>
</div> </div>
</.link> </div>
</div> </.link>
<% end %> </li>
</div> </ul>
</div> </div>
<% end %> <% end %>
<%= if @loading && @search_query != "" do %> <%= if @loading && @search_query != "" do %>
<div class="text-center py-8"> <div class="text-center py-8">
<.icon name="hero-arrow-path" class="h-8 w-8 animate-spin mx-auto text-gray-400" /> <.loading class="size-8 mx-auto text-zinc-400" />
<p class="text-gray-600 dark:text-gray-400 mt-2"> <p class="text-zinc-600 dark:text-zinc-400 mt-2">
<%= if @selected_release_group do %> <%= if @selected_release_group do %>
Loading releases... {gettext("Loading releases...")}
<% else %> <% else %>
Searching... {gettext("Searching...")}
<% end %> <% end %>
</p> </p>
</div> </div>
@@ -127,13 +115,13 @@
<div class="text-center py-8"> <div class="text-center py-8">
<.icon <.icon
name="hero-magnifying-glass" name="hero-magnifying-glass"
class="h-12 w-12 mx-auto text-gray-300 dark:text-gray-600" class="h-12 w-12 mx-auto text-zinc-300 dark:text-zinc-600"
/> />
<p class="text-gray-600 dark:text-gray-400 mt-3"> <p class="text-zinc-600 dark:text-zinc-400 mt-3">
No release groups found for "{@search_query}" {gettext("No release groups found for \"%{query}\"", query: @search_query)}
</p> </p>
<p class="text-sm text-gray-500 dark:text-gray-500 mt-1"> <p class="text-sm text-zinc-500 dark:text-zinc-500 mt-1">
Try a different search term or check the spelling {gettext("Try a different search term or check the spelling")}
</p> </p>
</div> </div>
<% end %> <% end %>
@@ -2,6 +2,7 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do
use MusicLibraryWeb, :live_view use MusicLibraryWeb, :live_view
import(MusicLibraryWeb.Components.Release, only: [medium: 1, scrobble_button_label: 1]) import(MusicLibraryWeb.Components.Release, only: [medium: 1, scrobble_button_label: 1])
import MusicLibraryWeb.RecordComponents, only: [country_label: 1]
alias MusicLibrary.ScrobbleActivity alias MusicLibrary.ScrobbleActivity
@@ -1,108 +1,93 @@
<Layouts.app flash={@flash} current_section={:scrobble} socket={@socket}> <Layouts.app flash={@flash} current_section={:scrobble} socket={@socket}>
<div> <div>
<div class="space-y-6"> <.alert
<div class="flex items-center gap-4"> :if={not @can_scrobble}
<div class="min-w-0 flex-1"></div> color="warning"
</div> 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."
)}
</.alert>
<div <div class="mt-2 mb-4">
:if={not @can_scrobble} <.button variant="ghost" size="sm" navigate={~p"/scrobble"}>
class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4" <.icon name="hero-arrow-left" class="icon" aria-hidden="true" data-slot="icon" />
> {gettext("Back to search")}
<div class="flex"> </.button>
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-yellow-400" /> </div>
<div class="ml-3">
<p class="text-sm text-yellow-800 dark:text-yellow-200">
{gettext(
"You need to connect your Last.fm account to scrobble. Please set up your Last.fm session key in the settings."
)}
</p>
</div>
</div>
</div>
<div class="bg-white dark:bg-zinc-800 shadow rounded-lg"> <div class="md:flex mt-4 px-4 md:gap-x-4">
<div class="p-6"> <div class="drop-shadow-sm md:max-w-152 lg:min-w-152">
<h1 class="text-base lg:text-2xl text-zinc-900 dark:text-zinc-200 font-semibold"> <img
{@release.title} src={MusicBrainz.Release.thumb_url(@release)}
</h1> alt={"Cover art for #{@release.title}"}
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1"> class="w-full rounded-lg drop-shadow-sm"
<span :if={@release.artists != []}> loading="lazy"
by {@release.artists |> Enum.map(& &1.name) |> Enum.join(", ")} onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
</span>
<span :if={@release.date}>
{@release.date}
</span>
</p>
<div class="flex gap-6 mt-4">
<div class="flex-shrink-0">
<img
src={MusicBrainz.Release.thumb_url(@release)}
alt={"Cover art for #{@release.title}"}
class="w-32 h-32 object-cover rounded-lg shadow-sm"
loading="lazy"
/>
</div>
<div class="flex-1">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
<div :if={@release.date}>
<span class="font-medium text-gray-600 dark:text-gray-400">
{gettext("Release Date:")}
</span>
<br />{@release.date}
</div>
<div :if={@release.country}>
<span class="font-medium text-gray-600 dark:text-gray-400">
{gettext("Country:")}
</span>
<br />{@release.country}
</div>
<div :if={@release.barcode}>
<span class="font-medium text-gray-600 dark:text-gray-400">
{gettext("Barcode:")}
</span>
<br />{@release.barcode}
</div>
<div :if={@release.catalog_number}>
<span class="font-medium text-gray-600 dark:text-gray-400">
{gettext("Catalog Number:")}
</span>
<br />{@release.catalog_number}
</div>
</div>
</div>
</div>
</div>
</div>
<div :if={@release.media != []} class="mt-6 space-y-4">
<div class="flex justify-between items-center">
<h3 class="text-lg font-semibold mb-4">{gettext("Tracks")}</h3>
<.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)}
</.button>
</div>
<.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}
/> />
</div> </div>
<div class="grow">
<div class="mt-4 md:mt-0 flex justify-between items-center">
<h1 :if={@release.artists != []} class="text-base font-medium leading-6 text-zinc-700">
{@release.artists |> Enum.map(& &1.name) |> Enum.join(", ")}
</h1>
</div>
<h2 class="mt-1 flex font-semibold text-lg md:text-2xl text-zinc-700 dark:text-zinc-300 text-wrap">
{@release.title}
</h2>
<div class="mt-4 md:mt-8">
<dl class="divide-y divide-zinc-100 dark:divide-slate-300/30">
<.dl_row :if={@release.date} label={gettext("Release Date")}>
{@release.date}
</.dl_row>
<.dl_row :if={@release.country} label={gettext("Country")}>
{country_label(@release.country)} {@release.country}
</.dl_row>
<.dl_row :if={@release.barcode} label={gettext("Barcode")}>
<code>{@release.barcode}</code>
</.dl_row>
<.dl_row :if={@release.catalog_number} label={gettext("Catalog Number")}>
<code>{@release.catalog_number}</code>
</.dl_row>
<.dl_row :if={@release.media != []} label={gettext("Media")}>
{ngettext("1 disc", "%{count} discs", MusicBrainz.Release.media_count(@release))}
</.dl_row>
</dl>
</div>
<div :if={@release.media != []} class="mt-6 space-y-4">
<div class="flex justify-between items-center">
<h3 class="text-lg font-semibold">{gettext("Tracks")}</h3>
<.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)}
</.button>
</div>
<.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}
/>
</div>
</div>
</div> </div>
</div> </div>
</Layouts.app> </Layouts.app>
+57 -20
View File
@@ -682,6 +682,7 @@ msgid "Oban"
msgstr "" msgstr ""
#: lib/music_library_web/components/record_form.ex #: lib/music_library_web/components/record_form.ex
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Release Date" msgid "Release Date"
msgstr "" msgstr ""
@@ -1423,6 +1424,7 @@ msgid "Release Groups"
msgstr "" msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex #: lib/music_library_web/live/scrobble_live/index.html.heex
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "1 disc" msgid "1 disc"
msgid_plural "%{count} discs" msgid_plural "%{count} discs"
@@ -1434,26 +1436,6 @@ msgstr[1] ""
msgid "Releases for \"%{title}\"" msgid "Releases for \"%{title}\""
msgstr "" msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Barcode:"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Catalog Number:"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Country:"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Release Date:"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex #: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You need to connect your Last.fm account to scrobble. Please set up your Last.fm session key in the settings." msgid "You need to connect your Last.fm account to scrobble. Please set up your Last.fm session key in the settings."
@@ -1909,3 +1891,58 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Search for artist image online" msgid "Search for artist image online"
msgstr "" msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Back"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Back to search"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Barcode"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Catalog Number"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Country"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Last.fm not connected"
msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Loading releases..."
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Media"
msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "No release groups found for \"%{query}\""
msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Searching..."
msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Try a different search term or check the spelling"
msgstr ""
+57 -20
View File
@@ -682,6 +682,7 @@ msgid "Oban"
msgstr "" msgstr ""
#: lib/music_library_web/components/record_form.ex #: lib/music_library_web/components/record_form.ex
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Release Date" msgid "Release Date"
msgstr "" msgstr ""
@@ -1423,6 +1424,7 @@ msgid "Release Groups"
msgstr "" msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex #: lib/music_library_web/live/scrobble_live/index.html.heex
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "1 disc" msgid "1 disc"
msgid_plural "%{count} discs" msgid_plural "%{count} discs"
@@ -1434,26 +1436,6 @@ msgstr[1] ""
msgid "Releases for \"%{title}\"" msgid "Releases for \"%{title}\""
msgstr "" msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Barcode:"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Catalog Number:"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Country:"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "Release Date:"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex #: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You need to connect your Last.fm account to scrobble. Please set up your Last.fm session key in the settings." msgid "You need to connect your Last.fm account to scrobble. Please set up your Last.fm session key in the settings."
@@ -1909,3 +1891,58 @@ msgstr ""
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Search for artist image online" msgid "Search for artist image online"
msgstr "" msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "Back"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Back to search"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "Barcode"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "Catalog Number"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "Country"
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Last.fm not connected"
msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "Loading releases..."
msgstr ""
#: lib/music_library_web/live/scrobble_live/show.html.heex
#, elixir-autogen, elixir-format
msgid "Media"
msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "No release groups found for \"%{query}\""
msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "Searching..."
msgstr ""
#: lib/music_library_web/live/scrobble_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Try a different search term or check the spelling"
msgstr ""