Extract internal record set card component
This commit is contained in:
@@ -67,196 +67,12 @@ defmodule MusicLibraryWeb.RecordSetLive.Index do
|
|||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li
|
<.record_set_card
|
||||||
:for={{id, record_set} <- @streams.record_sets}
|
:for={{id, record_set} <- @streams.record_sets}
|
||||||
id={id}
|
id={id}
|
||||||
class="bg-white dark:bg-zinc-900 rounded-lg border border-zinc-200 dark:border-zinc-700 p-4"
|
record_set={record_set}
|
||||||
>
|
list_params={@list_params}
|
||||||
<div class="flex items-baseline justify-between mb-3">
|
/>
|
||||||
<div class="grow">
|
|
||||||
<header class="sm:flex items-baseline justify-start">
|
|
||||||
<h2 class="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
|
|
||||||
<.link navigate={~p"/record-sets/#{record_set}"} class="hover:underline">
|
|
||||||
{record_set.name}
|
|
||||||
</.link>
|
|
||||||
</h2>
|
|
||||||
<span class="sm:ml-2 text-xs text-zinc-500 dark:text-zinc-400">
|
|
||||||
{gettext("%{collected}/%{total} records", RecordSet.count_by_status(record_set))}
|
|
||||||
</span>
|
|
||||||
</header>
|
|
||||||
<article
|
|
||||||
:if={record_set.description}
|
|
||||||
class="text-sm my-4 prose dark:prose-invert prose-zinc prose-sm prose-h1:text-sm max-w-none"
|
|
||||||
>
|
|
||||||
{render_description(record_set.description)}
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<.dropdown id={"set-actions-#{record_set.id}"} placement="bottom-end">
|
|
||||||
<:toggle>
|
|
||||||
<.button variant="soft">
|
|
||||||
<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>
|
|
||||||
<.dropdown_link
|
|
||||||
id={"set-actions-#{record_set.id}-edit"}
|
|
||||||
patch={
|
|
||||||
~p"/record-sets/#{record_set}/edit?#{Map.take(@list_params, [:query, :page, :page_size, :order]) |> Enum.filter(fn {_, v} -> v not in ["", nil] end)}"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{gettext("Edit")}
|
|
||||||
</.dropdown_link>
|
|
||||||
<.separator />
|
|
||||||
<.dropdown_button
|
|
||||||
phx-click="delete_set"
|
|
||||||
phx-value-id={record_set.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_button>
|
|
||||||
</.dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="grid grid-cols-3 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-12 gap-3 pb-2"
|
|
||||||
id={"record-set-#{record_set.id}-items"}
|
|
||||||
phx-hook="SortableList"
|
|
||||||
data-set-id={record_set.id}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
:for={item <- record_set.items}
|
|
||||||
data-sortable-item
|
|
||||||
data-record-id={item.record.id}
|
|
||||||
class={[
|
|
||||||
"flex-none group relative",
|
|
||||||
is_nil(item.record.purchased_at) &&
|
|
||||||
"opacity-60 dark:opacity-40 hover:opacity-100 transition-opacity"
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<.link
|
|
||||||
:if={item.record.purchased_at}
|
|
||||||
navigate={~p"/collection/#{item.record}"}
|
|
||||||
>
|
|
||||||
<MusicLibraryWeb.RecordComponents.record_cover
|
|
||||||
record={item.record}
|
|
||||||
class="rounded-lg aspect-square object-cover"
|
|
||||||
width={256}
|
|
||||||
/>
|
|
||||||
</.link>
|
|
||||||
<.link
|
|
||||||
:if={!item.record.purchased_at}
|
|
||||||
navigate={~p"/wishlist/#{item.record}"}
|
|
||||||
>
|
|
||||||
<MusicLibraryWeb.RecordComponents.record_cover
|
|
||||||
record={item.record}
|
|
||||||
class="rounded-lg aspect-square object-cover"
|
|
||||||
width={256}
|
|
||||||
/>
|
|
||||||
</.link>
|
|
||||||
<div
|
|
||||||
data-sortable-handle
|
|
||||||
class="absolute top-1 left-1 flex items-center justify-center rounded-full bg-zinc-100/50 hover:bg-zinc-100/75 dark:bg-zinc-700/50 dark:hover:bg-zinc-700/75 size-8 sm:size-5 cursor-grab active:cursor-grabbing"
|
|
||||||
>
|
|
||||||
<.icon
|
|
||||||
name="hero-bars-2"
|
|
||||||
class="size-3.5 text-zinc-800 dark:text-zinc-200"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="absolute top-1 right-1 rounded-full bg-zinc-100/50 hover:bg-zinc-100/75 dark:bg-zinc-700/50 dark:hover:bg-zinc-700/75 size-5">
|
|
||||||
<.dropdown
|
|
||||||
id={"item-actions-#{record_set.id}-#{item.record.id}"}
|
|
||||||
placement="bottom-end"
|
|
||||||
>
|
|
||||||
<:toggle>
|
|
||||||
<span class="sr-only">{gettext("Actions")}</span>
|
|
||||||
<.icon
|
|
||||||
name="hero-ellipsis-vertical"
|
|
||||||
class="size-5 text-zinc-800 dark:text-zinc-200 cursor-pointer"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon"
|
|
||||||
/>
|
|
||||||
</:toggle>
|
|
||||||
<.dropdown_button
|
|
||||||
:if={item.position > 0}
|
|
||||||
phx-click="move_up"
|
|
||||||
phx-value-set-id={record_set.id}
|
|
||||||
phx-value-record-id={item.record.id}
|
|
||||||
>
|
|
||||||
{gettext("Move left")}
|
|
||||||
</.dropdown_button>
|
|
||||||
<.dropdown_button
|
|
||||||
:if={item.position < length(record_set.items) - 1}
|
|
||||||
phx-click="move_down"
|
|
||||||
phx-value-set-id={record_set.id}
|
|
||||||
phx-value-record-id={item.record.id}
|
|
||||||
>
|
|
||||||
{gettext("Move right")}
|
|
||||||
</.dropdown_button>
|
|
||||||
<.separator />
|
|
||||||
<.dropdown_button
|
|
||||||
phx-click="remove_record"
|
|
||||||
phx-value-set-id={record_set.id}
|
|
||||||
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")}
|
|
||||||
</.dropdown_button>
|
|
||||||
</.dropdown>
|
|
||||||
</div>
|
|
||||||
<h1 class="mt-1 text-sm sm:text-sm leading-6 text-zinc-700">
|
|
||||||
<.artist_links joinphrase_class="text-sm" artists={item.record.artists} />
|
|
||||||
</h1>
|
|
||||||
<h2 class="flex font-semibold text-sm sm:text-sm leading-5 text-zinc-700 dark:text-zinc-300 text-wrap">
|
|
||||||
{item.record.title}
|
|
||||||
</h2>
|
|
||||||
<p class="pointer-events-none block text-sm font-medium text-zinc-500">
|
|
||||||
{format_label(item.record.format)} · {type_label(item.record.type)}
|
|
||||||
</p>
|
|
||||||
<p class="pointer-events-none block text-sm font-medium text-zinc-500">
|
|
||||||
<.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)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<.link
|
|
||||||
patch={~p"/record-sets/#{record_set}/add-record"}
|
|
||||||
class={[
|
|
||||||
"flex-none 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"
|
|
||||||
/>
|
|
||||||
</.link>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<.pagination id={:bottom_pagination} pagination_params={@list_params} />
|
<.pagination id={:bottom_pagination} pagination_params={@list_params} />
|
||||||
@@ -451,6 +267,204 @@ defmodule MusicLibraryWeb.RecordSetLive.Index do
|
|||||||
~p"/record-sets?#{qs}"
|
~p"/record-sets?#{qs}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attr :id, :string, required: true
|
||||||
|
attr :record_set, RecordSet, required: true
|
||||||
|
attr :list_params, :map, required: true
|
||||||
|
|
||||||
|
defp record_set_card(assigns) do
|
||||||
|
~H"""
|
||||||
|
<li
|
||||||
|
id={@id}
|
||||||
|
class="bg-white dark:bg-zinc-900 rounded-lg border border-zinc-200 dark:border-zinc-700 p-4"
|
||||||
|
>
|
||||||
|
<div class="flex items-baseline justify-between mb-3">
|
||||||
|
<div class="grow">
|
||||||
|
<header class="sm:flex items-baseline justify-start">
|
||||||
|
<h2 class="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
|
||||||
|
<.link navigate={~p"/record-sets/#{@record_set}"} class="hover:underline">
|
||||||
|
{@record_set.name}
|
||||||
|
</.link>
|
||||||
|
</h2>
|
||||||
|
<span class="sm:ml-2 text-xs text-zinc-500 dark:text-zinc-400">
|
||||||
|
{gettext("%{collected}/%{total} records", RecordSet.count_by_status(@record_set))}
|
||||||
|
</span>
|
||||||
|
</header>
|
||||||
|
<article
|
||||||
|
:if={@record_set.description}
|
||||||
|
class="text-sm my-4 prose dark:prose-invert prose-zinc prose-sm prose-h1:text-sm max-w-none"
|
||||||
|
>
|
||||||
|
{render_description(@record_set.description)}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<.dropdown id={"set-actions-#{@record_set.id}"} placement="bottom-end">
|
||||||
|
<:toggle>
|
||||||
|
<.button variant="soft">
|
||||||
|
<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>
|
||||||
|
<.dropdown_link
|
||||||
|
id={"set-actions-#{@record_set.id}-edit"}
|
||||||
|
patch={
|
||||||
|
~p"/record-sets/#{@record_set}/edit?#{Map.take(@list_params, [:query, :page, :page_size, :order]) |> Enum.filter(fn {_, v} -> v not in ["", nil] end)}"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{gettext("Edit")}
|
||||||
|
</.dropdown_link>
|
||||||
|
<.separator />
|
||||||
|
<.dropdown_button
|
||||||
|
phx-click="delete_set"
|
||||||
|
phx-value-id={@record_set.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_button>
|
||||||
|
</.dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="grid grid-cols-3 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-12 gap-3 pb-2"
|
||||||
|
id={"record-set-#{@record_set.id}-items"}
|
||||||
|
phx-hook="SortableList"
|
||||||
|
data-set-id={@record_set.id}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:for={item <- @record_set.items}
|
||||||
|
data-sortable-item
|
||||||
|
data-record-id={item.record.id}
|
||||||
|
class={[
|
||||||
|
"flex-none group relative",
|
||||||
|
is_nil(item.record.purchased_at) &&
|
||||||
|
"opacity-60 dark:opacity-40 hover:opacity-100 transition-opacity"
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<.link
|
||||||
|
:if={item.record.purchased_at}
|
||||||
|
navigate={~p"/collection/#{item.record}"}
|
||||||
|
>
|
||||||
|
<MusicLibraryWeb.RecordComponents.record_cover
|
||||||
|
record={item.record}
|
||||||
|
class="rounded-lg aspect-square object-cover"
|
||||||
|
width={256}
|
||||||
|
/>
|
||||||
|
</.link>
|
||||||
|
<.link
|
||||||
|
:if={!item.record.purchased_at}
|
||||||
|
navigate={~p"/wishlist/#{item.record}"}
|
||||||
|
>
|
||||||
|
<MusicLibraryWeb.RecordComponents.record_cover
|
||||||
|
record={item.record}
|
||||||
|
class="rounded-lg aspect-square object-cover"
|
||||||
|
width={256}
|
||||||
|
/>
|
||||||
|
</.link>
|
||||||
|
<div
|
||||||
|
data-sortable-handle
|
||||||
|
class="absolute top-1 left-1 flex items-center justify-center rounded-full bg-zinc-100/50 hover:bg-zinc-100/75 dark:bg-zinc-700/50 dark:hover:bg-zinc-700/75 size-8 sm:size-5 cursor-grab active:cursor-grabbing"
|
||||||
|
>
|
||||||
|
<.icon
|
||||||
|
name="hero-bars-2"
|
||||||
|
class="size-3.5 text-zinc-800 dark:text-zinc-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="absolute top-1 right-1 rounded-full bg-zinc-100/50 hover:bg-zinc-100/75 dark:bg-zinc-700/50 dark:hover:bg-zinc-700/75 size-5">
|
||||||
|
<.dropdown
|
||||||
|
id={"item-actions-#{@record_set.id}-#{item.record.id}"}
|
||||||
|
placement="bottom-end"
|
||||||
|
>
|
||||||
|
<:toggle>
|
||||||
|
<span class="sr-only">{gettext("Actions")}</span>
|
||||||
|
<.icon
|
||||||
|
name="hero-ellipsis-vertical"
|
||||||
|
class="size-5 text-zinc-800 dark:text-zinc-200 cursor-pointer"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
</:toggle>
|
||||||
|
<.dropdown_button
|
||||||
|
:if={item.position > 0}
|
||||||
|
phx-click="move_up"
|
||||||
|
phx-value-set-id={@record_set.id}
|
||||||
|
phx-value-record-id={item.record.id}
|
||||||
|
>
|
||||||
|
{gettext("Move left")}
|
||||||
|
</.dropdown_button>
|
||||||
|
<.dropdown_button
|
||||||
|
:if={item.position < length(@record_set.items) - 1}
|
||||||
|
phx-click="move_down"
|
||||||
|
phx-value-set-id={@record_set.id}
|
||||||
|
phx-value-record-id={item.record.id}
|
||||||
|
>
|
||||||
|
{gettext("Move right")}
|
||||||
|
</.dropdown_button>
|
||||||
|
<.separator />
|
||||||
|
<.dropdown_button
|
||||||
|
phx-click="remove_record"
|
||||||
|
phx-value-set-id={@record_set.id}
|
||||||
|
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")}
|
||||||
|
</.dropdown_button>
|
||||||
|
</.dropdown>
|
||||||
|
</div>
|
||||||
|
<h1 class="mt-1 text-sm sm:text-sm leading-6 text-zinc-700">
|
||||||
|
<.artist_links joinphrase_class="text-sm" artists={item.record.artists} />
|
||||||
|
</h1>
|
||||||
|
<h2 class="flex font-semibold text-sm sm:text-sm leading-5 text-zinc-700 dark:text-zinc-300 text-wrap">
|
||||||
|
{item.record.title}
|
||||||
|
</h2>
|
||||||
|
<p class="pointer-events-none block text-sm font-medium text-zinc-500">
|
||||||
|
{format_label(item.record.format)} · {type_label(item.record.type)}
|
||||||
|
</p>
|
||||||
|
<p class="pointer-events-none block text-sm font-medium text-zinc-500">
|
||||||
|
<.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)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<.link
|
||||||
|
patch={~p"/record-sets/#{@record_set}/add-record"}
|
||||||
|
class={[
|
||||||
|
"flex-none 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"
|
||||||
|
/>
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
defp render_description(description) do
|
defp render_description(description) do
|
||||||
description
|
description
|
||||||
|> Markdown.to_html()
|
|> Markdown.to_html()
|
||||||
|
|||||||
Reference in New Issue
Block a user