Default collection and wishlist to a grid layout

This commit is contained in:
Claudio Ortolina
2025-10-31 17:55:38 +00:00
parent 583a25bf9d
commit 12272608bc
8 changed files with 282 additions and 47 deletions
@@ -299,6 +299,104 @@ defmodule MusicLibraryWeb.RecordComponents do
""" """
end end
attr :records, :list, required: true
attr :id, :string, required: true
attr :record_show_path, :any, required: true
attr :record_edit_path, :any, required: true
def dense_record_grid(assigns) do
~H"""
<div class="mt-4">
<ul
id={@id}
phx-update="stream"
role="list"
class="mt-4 grid grid-cols-3 gap-x-4 gap-y-8 sm:grid-cols-4 md:grid-cols-6 xl:grid-cols-8"
>
<li :for={{id, record} <- @records} id={id} class="relative">
<div class="group overflow-hidden rounded-lg bg-zinc-100 focus-within:ring-2 focus-within:ring-zinc-500 focus-within:ring-offset-2 focus-within:ring-offset-zinc-100">
<div class="relative">
<.record_cover
record={record}
class="pointer-events-none aspect-square object-cover group-hover:opacity-75"
width={560}
/>
<span
:if={Records.Record.included_release_groups_count(record) > 0}
class={[
"absolute right-0 bottom-0 rounded-br-lg rounded-tl-lg px-2",
"text-sm font-medium",
"bg-zinc-50 dark:bg-zinc-500/10",
"text-zinc-700 dark:text-zinc-400",
"border border-zinc-600/20 dark:border-zinc-500/20"
]}
>
{Records.Record.included_release_groups_count(record)}
</span>
</div>
<button
type="button"
class="absolute inset-0 focus:outline-hidden"
phx-click={JS.navigate(@record_show_path.(record))}
>
<span class="sr-only">{gettext("View details")}</span>
</button>
</div>
<div class="flex justify-between items-center">
<p class="pointer-events-none mt-2 block truncate text-sm font-medium text-zinc-900 dark:text-zinc-300">
{record.title}
</p>
<.dropdown id={"actions-#{record.id}"} placement="bottom-end">
<:toggle>
<.button variant="ghost" class="mt-2">
<span class="sr-only">{gettext("Actions")}</span>
<.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"
/>
</.button>
</:toggle>
<.focus_wrap id={"actions-#{record.id}-focus-wrap"}>
<.dropdown_link id={"actions-#{record.id}-edit"} patch={@record_edit_path.(record)}>
{gettext("Edit")}
</.dropdown_link>
<.dropdown_link
:if={!record.purchased_at}
id={"actions-#{record.id}-purchase"}
phx-click={
JS.dispatch("music_library:confetti")
|> JS.push("add-to-collection", value: %{id: record.id})
}
>
{gettext("Purchased")}
</.dropdown_link>
<.dropdown_separator />
<.dropdown_link
id={"actions-#{record.id}-delete"}
phx-click={JS.push("delete", value: %{id: record.id}) |> hide("##{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!"
>
{gettext("Delete")}
</.dropdown_link>
</.focus_wrap>
</.dropdown>
</div>
<p class="pointer-events-none block text-sm font-medium text-zinc-500">
{format_label(record.format)} · {type_label(record.type)}
</p>
<p class="pointer-events-none block text-sm font-medium text-zinc-500">
{Records.Record.format_release_date(record.release_date)}
</p>
</li>
</ul>
</div>
"""
end
attr :release, :map, required: true attr :release, :map, required: true
attr :class, :string, required: false, default: nil attr :class, :string, required: false, default: nil
@@ -12,13 +12,16 @@ defmodule MusicLibraryWeb.CollectionLive.Index do
@default_records_list_params %{ @default_records_list_params %{
query: "", query: "",
page: 1, page: 1,
page_size: 50, page_size: 48,
order: :purchase order: :purchase
} }
@impl true @impl true
def mount(_params, _session, socket) do def mount(_params, _session, socket) do
{:ok, assign(socket, :current_section, :collection)} {:ok,
socket
|> assign(:current_section, :collection)
|> assign(:display, :grid)}
end end
@impl true @impl true
@@ -124,6 +127,18 @@ defmodule MusicLibraryWeb.CollectionLive.Index do
end end
end end
def handle_event("set_display", %{"mode" => mode}, socket) do
mode = parse_mode(mode)
{:noreply,
socket
|> assign(:display, mode)
|> load_and_assign_records(socket.assigns.record_list_params)}
end
defp parse_mode("grid"), do: :grid
defp parse_mode("list"), do: :list
defp load_and_assign_records(socket, record_list_params) do defp load_and_assign_records(socket, record_list_params) do
offset = page_to_offset(record_list_params.page, record_list_params.page_size) offset = page_to_offset(record_list_params.page, record_list_params.page_size)
@@ -52,10 +52,49 @@
{gettext("A->Z")} {gettext("A->Z")}
</.button> </.button>
</.button_group> </.button_group>
<.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>
<.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")}
</.button>
</.button_group>
</div> </div>
</div> </div>
<.dense_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}
/>
<.record_list <.record_list
:if={@display == :list}
records={@streams.records} records={@streams.records}
record_show_path={fn record -> ~p"/collection/#{record}" end} record_show_path={fn record -> ~p"/collection/#{record}" end}
record_edit_path={fn record -> ~p"/collection/#{record}/edit" end} record_edit_path={fn record -> ~p"/collection/#{record}/edit" end}
@@ -11,7 +11,7 @@ defmodule MusicLibraryWeb.WishlistLive.Index do
@default_records_list_params %{ @default_records_list_params %{
query: "", query: "",
page: 1, page: 1,
page_size: 50, page_size: 48,
order: :alphabetical order: :alphabetical
} }
@@ -23,6 +23,7 @@ defmodule MusicLibraryWeb.WishlistLive.Index do
socket socket
|> assign(current_section: :wishlist) |> assign(current_section: :wishlist)
|> assign(:import_query, "") |> assign(:import_query, "")
|> assign(:display, :grid)
|> assign(:current_date, current_date)} |> assign(:current_date, current_date)}
end end
@@ -140,6 +141,18 @@ defmodule MusicLibraryWeb.WishlistLive.Index do
end end
end end
def handle_event("set_display", %{"mode" => mode}, socket) do
mode = parse_mode(mode)
{:noreply,
socket
|> assign(:display, mode)
|> load_and_assign_records(socket.assigns.record_list_params)}
end
defp parse_mode("grid"), do: :grid
defp parse_mode("list"), do: :list
defp load_and_assign_records(socket, record_list_params) do defp load_and_assign_records(socket, record_list_params) do
offset = page_to_offset(record_list_params.page, record_list_params.page_size) offset = page_to_offset(record_list_params.page, record_list_params.page_size)
@@ -39,10 +39,49 @@
{gettext("A->Z")} {gettext("A->Z")}
</.button> </.button>
</.button_group> </.button_group>
<.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>
<.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")}
</.button>
</.button_group>
</div> </div>
</div> </div>
<.dense_record_grid
:if={@display == :grid}
id="wishlist"
records={@streams.records}
record_show_path={fn record -> ~p"/wishlist/#{record}" end}
record_edit_path={fn record -> ~p"/wishlist/#{record}/edit" end}
/>
<.record_list <.record_list
:if={@display == :list}
current_date={@current_date} current_date={@current_date}
records={@streams.records} records={@streams.records}
record_show_path={fn record -> ~p"/wishlist/#{record}" end} record_show_path={fn record -> ~p"/wishlist/#{record}" end}
+32 -20
View File
@@ -1500,11 +1500,33 @@ msgstr ""
msgid "Today" msgid "Today"
msgstr "" msgstr ""
#: lib/music_library_web/live/collection_live/index.html.heex
#: lib/music_library_web/live/wishlist_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Grid"
msgstr ""
#: lib/music_library_web/live/collection_live/index.html.heex
#: lib/music_library_web/live/wishlist_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "List"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex #: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Artists" msgid "Artists"
msgstr "" msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Database"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format
msgid "Database vacuumed successfully."
msgstr ""
#: lib/music_library_web/components/layouts/app.html.heex #: lib/music_library_web/components/layouts/app.html.heex
#: lib/music_library_web/live/maintenance_live/index.ex #: lib/music_library_web/live/maintenance_live/index.ex
#: lib/music_library_web/live/maintenance_live/index.html.heex #: lib/music_library_web/live/maintenance_live/index.html.heex
@@ -1512,6 +1534,11 @@ msgstr ""
msgid "Maintenance" msgid "Maintenance"
msgstr "" msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format
msgid "Operation started in the background."
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex #: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Refresh Discogs data" msgid "Refresh Discogs data"
@@ -1527,6 +1554,11 @@ msgstr ""
msgid "Regenerate record embeddings" msgid "Regenerate record embeddings"
msgstr "" msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Run lower-level operations on the database"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex #: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Run operations on the entire artist database. Monitor execution via the Oban dashboard." msgid "Run operations on the entire artist database. Monitor execution via the Oban dashboard."
@@ -1537,26 +1569,6 @@ msgstr ""
msgid "Run operations on the entire record database. Monitor execution via the Oban dashboard." msgid "Run operations on the entire record database. Monitor execution via the Oban dashboard."
msgstr "" msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Database"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format
msgid "Database vacuumed successfully."
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format
msgid "Operation started in the background."
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Run lower-level operations on the database"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex #: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Running vacuum..." msgid "Running vacuum..."
+32 -20
View File
@@ -1500,11 +1500,33 @@ msgstr ""
msgid "Today" msgid "Today"
msgstr "" msgstr ""
#: lib/music_library_web/live/collection_live/index.html.heex
#: lib/music_library_web/live/wishlist_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Grid"
msgstr ""
#: lib/music_library_web/live/collection_live/index.html.heex
#: lib/music_library_web/live/wishlist_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "List"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex #: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Artists" msgid "Artists"
msgstr "" msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Database"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format
msgid "Database vacuumed successfully."
msgstr ""
#: lib/music_library_web/components/layouts/app.html.heex #: lib/music_library_web/components/layouts/app.html.heex
#: lib/music_library_web/live/maintenance_live/index.ex #: lib/music_library_web/live/maintenance_live/index.ex
#: lib/music_library_web/live/maintenance_live/index.html.heex #: lib/music_library_web/live/maintenance_live/index.html.heex
@@ -1512,6 +1534,11 @@ msgstr ""
msgid "Maintenance" msgid "Maintenance"
msgstr "" msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format
msgid "Operation started in the background."
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex #: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Refresh Discogs data" msgid "Refresh Discogs data"
@@ -1527,6 +1554,11 @@ msgstr ""
msgid "Regenerate record embeddings" msgid "Regenerate record embeddings"
msgstr "" msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Run lower-level operations on the database"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex #: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Run operations on the entire artist database. Monitor execution via the Oban dashboard." msgid "Run operations on the entire artist database. Monitor execution via the Oban dashboard."
@@ -1537,26 +1569,6 @@ msgstr ""
msgid "Run operations on the entire record database. Monitor execution via the Oban dashboard." msgid "Run operations on the entire record database. Monitor execution via the Oban dashboard."
msgstr "" msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Database"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format
msgid "Database vacuumed successfully."
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Operation started in the background."
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Run lower-level operations on the database"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.html.heex #: lib/music_library_web/live/maintenance_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Running vacuum..." msgid "Running vacuum..."
@@ -49,7 +49,10 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
{expected_present, expected_absent} = {expected_present, expected_absent} =
Enum.split(records, page_size) Enum.split(records, page_size)
session = visit(conn, ~p"/collection?order=alphabetical&page_size=#{page_size}") session =
conn
|> visit(~p"/collection?order=alphabetical&page_size=#{page_size}")
|> click_button("List")
for record <- expected_present do for record <- expected_present do
cover_url = cover_url(record, 160) cover_url = cover_url(record, 160)
@@ -139,7 +142,9 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
qs = [query: record.title] qs = [query: record.title]
session = session =
visit(conn, ~p"/collection?#{qs}") conn
|> visit(~p"/collection?#{qs}")
|> click_button("List")
cover_url = cover_url(record, 160) cover_url = cover_url(record, 160)
@@ -180,7 +185,9 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
] ]
session = session =
visit(conn, ~p"/collection?#{qs}") conn
|> visit(~p"/collection?#{qs}")
|> click_button("List")
for record <- present do for record <- present do
cover_url = cover_url(record, 160) cover_url = cover_url(record, 160)
@@ -234,7 +241,7 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
|> assert_has("p", text: "Record updated successfully") |> assert_has("p", text: "Record updated successfully")
updated_record = MusicLibrary.Records.get_record!(record.id) updated_record = MusicLibrary.Records.get_record!(record.id)
updated_cover_url = cover_url(updated_record, 160) updated_cover_url = cover_url(updated_record, 560)
assert updated_record.cover_hash !== record.cover_hash assert updated_record.cover_hash !== record.cover_hash
assert_has(session, "img[src='#{updated_cover_url}']") assert_has(session, "img[src='#{updated_cover_url}']")