Files
music_library/lib/music_library_web/live/collection_live/index.html.heex
T
Claudio Ortolina 24319b1bff Annotate templates with TODOs for markup replacement
To open source the project down the line, it makes sense to have either
everything custom or using OSS components in order to keep licensing as
simple as possible.
2024-12-04 14:43:07 +00:00

232 lines
7.9 KiB
Plaintext

<div>
<header class="gap-6 mb-2">
<div class="flex items-center justify-between gap-6 mb-2 mt-2">
<form class="w-2/3" for={@record_list_params.query} phx-submit="search" phx-change="search">
<.input
type="search"
id={:query}
name={:query}
value={@record_list_params.query}
placeholder={gettext("Search")}
phx-debounce="500"
autocorrect="off"
autocapitalize="none"
/>
</form>
<.link patch={~p"/collection/import"}>
<.button>{gettext("Import")}</.button>
</.link>
</div>
</header>
<p class="text-right text-sm max-sm:text-xs mt-8 text-zinc-900 dark:text-zinc-400">
{gettext(
"Showing <b>%{visible}</b> of <b>%{total}</b> records",
%{visible: Enum.count(@streams.records), total: @record_list_params.total_entries}
)
|> raw()}
</p>
</div>
<ul
class="divide-y divide-zinc-100 dark:divide-slate-300/30 mt-5"
role="list"
id="records"
phx-update="stream"
>
<li
:for={{id, record} <- @streams.records}
phx-click={JS.navigate(~p"/collection/#{record}")}
class="flex justify-between gap-x-6 py-5 hover:bg-zinc-50 dark:hover:bg-zinc-800 px-2 -mx-2 md:px-4 md:-mx-4 cursor-pointer"
id={id}
>
<div class="flex min-w-0 gap-x-4 items-center">
<img
class="w-20 flex-none rounded-lg"
alt={record.title}
src={~p"/covers/#{record.id}?vsn=#{record.cover_hash || ""}"}
/>
<div class="min-w-0 flex-auto">
<h1 class="text-sm leading-6 text-zinc-700">
<.link
:for={artist <- record.artists}
class="text-zinc-700 hover:text-zinc-500 dark:text-zinc-400 dark:hover:text-zinc-300"
navigate={~p"/artists/#{artist.musicbrainz_id}"}
>
{artist.name}
</.link>
</h1>
<h2 class="mt-1 flex font-semibold text-sm sm:text-base leading-5 text-zinc-700 dark:text-zinc-300 text-wrap">
{record.title}
</h2>
<p class="mt-1 text-xs leading-5 text-zinc-500 dark:text-zinc-400">
{Records.Record.format_release(record.release)}
<span class="sm:hidden">
· {Records.Record.format_long_label(record.format)} · {Records.Record.type_long_label(
record.type
)}
<span :if={Records.Record.child_release_groups_count(record) > 0}>
·
<span class="sr-only">
{gettext("Number of included records")}
</span>
<span class={[
"inline-flex items-center rounded-full",
"px-2 py-1 text-xs font-medium",
"ring-1 ring-inset",
"bg-gray-50 dark:bg-gray-400/10",
"text-gray-600 dark:text-gray-500",
"ring-gray-500/10 dark:ring-gray-400/20"
]}>
{Records.Record.child_release_groups_count(record)}
</span>
</span>
</span>
</p>
</div>
</div>
<div class="flex shrink-0 items-center gap-x-6">
<div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-xs leading-6 text-zinc-900 dark:text-zinc-300">
{Records.Record.format_long_label(record.format)} · {Records.Record.type_long_label(
record.type
)}
<span :if={Records.Record.child_release_groups_count(record) > 0}>
·
<span class="sr-only">
{gettext("Number of included records")}
</span>
<span class={[
"inline-flex items-center rounded-full",
"px-2 py-1 text-xs font-medium",
"ring-1 ring-inset",
"bg-gray-50 dark:bg-gray-400/10",
"text-gray-600 dark:text-gray-500",
"ring-gray-500/10 dark:ring-gray-400/20"
]}>
{Records.Record.child_release_groups_count(record)}
</span>
</span>
</p>
</div>
<%!-- TODO: replace with OSS version --%>
<div class="relative flex-none">
<button
type="button"
class="text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-300"
aria-expanded="false"
aria-haspopup="true"
phx-click={toggle_actions_menu(record.id)}
phx-click-away={close_actions_menu(record.id)}
>
<span class="sr-only">{gettext("Open options")}</span>
<.icon
name="hero-ellipsis-vertical"
class="-mt-1 h-5 w-5"
aria-hidden="true"
data-slot="icon"
/>
</button>
<!--
Dropdown menu, show/hide based on menu state.
Entering: "transition ease-out duration-100"
From: "transform opacity-0 scale-95"
To: "transform opacity-100 scale-100"
Leaving: "transition ease-in duration-75"
From: "transform opacity-100 scale-100"
To: "transform opacity-0 scale-95"
-->
<.focus_wrap
id={"actions-#{record.id}"}
class={[
"hidden pointer-events-auto absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white dark:bg-zinc-800 py-2 shadow-lg ring-1 ring-zinc-900/5 focus:outline-none"
]}
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu-0-button"
tabindex="-1"
>
<.link
class="block px-3 py-1 text-sm leading-6 text-zinc-900 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:text-zinc-300 dark:hover:bg-zinc-700"
role="menuitem"
tabindex="-1"
id={"actions-#{record.id}-show"}
navigate={~p"/collection/#{record}"}
>
{gettext("Show")}
</.link>
<a
href={musicbrainz_url(record)}
target=".blank"
class="block px-3 py-1 text-sm leading-6 text-zinc-900 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:text-zinc-300 dark:hover:bg-zinc-700"
role="menuitem"
tabindex="-1"
id={"actions-#{record.id}-musicbrainz"}
>
{gettext("View on MusicBrainz")}
</a>
<.link
class="block px-3 py-1 text-sm leading-6 text-zinc-900 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:text-zinc-300 dark:hover:bg-zinc-700"
role="menuitem"
tabindex="-1"
id={"actions-#{record.id}-edit"}
patch={~p"/collection/#{record}/edit"}
>
{gettext("Edit")}
</.link>
<.link
class="block px-3 py-1 text-sm leading-6 text-red-900 hover:bg-red-50 dark:text-red-700 dark:hover:bg-red-900/30 dark:hover:text-red-600"
role="menuitem"
tabindex="-1"
id={"actions-#{record.id}-delete"}
phx-click={JS.push("delete", value: %{id: record.id}) |> hide("##{id}")}
data-confirm={gettext("Are you sure?")}
>
{gettext("Delete")}
</.link>
</.focus_wrap>
</div>
</div>
</li>
</ul>
<.modal
:if={@live_action == :edit}
id="record-modal"
show
on_cancel={JS.patch(back_path(@record_list_params))}
>
<.live_component
module={MusicLibraryWeb.RecordLive.FormComponent}
id={@record.id}
title={@page_title}
action={@live_action}
show_purchased_at={true}
record={@record}
patch={back_path(@record_list_params)}
/>
</.modal>
<.modal
:if={@live_action == :import}
id="record-modal"
show
on_cancel={JS.patch(back_path(@record_list_params))}
>
<.live_component
module={MusicLibraryWeb.RecordLive.ImportComponent}
id={:search}
title={@page_title}
action={@live_action}
record={@record}
patch={back_path(@record_list_params)}
initial_query=""
icon_name="hero-plus"
/>
</.modal>
<.pagination id={:bottom_pagination} pagination_params={@record_list_params} />