Promote Collection to proper namespace
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
defmodule MusicLibraryWeb.CollectionLive.Index do
|
||||
use MusicLibraryWeb, :live_view
|
||||
import MusicLibraryWeb.Pagination
|
||||
|
||||
alias MusicLibrary.Records
|
||||
alias MusicLibrary.Collection
|
||||
|
||||
@default_records_list_params %{
|
||||
query: "",
|
||||
page: 1,
|
||||
page_size: 100
|
||||
}
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
socket =
|
||||
if static_changed?(socket) do
|
||||
put_flash(socket, :warning, gettext("The application has been updated, please reload."))
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
{:ok, assign(socket, :nav_section, :records)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(params, _url, socket) do
|
||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||
end
|
||||
|
||||
defp apply_action(socket, :import, params) do
|
||||
socket =
|
||||
if get_in(socket.assigns, [:streams, :records]) == nil do
|
||||
socket
|
||||
|> apply_action(:index, params)
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
socket
|
||||
|> assign(:page_title, gettext("Import from MusicBrainz"))
|
||||
|> assign(:record, nil)
|
||||
end
|
||||
|
||||
defp apply_action(socket, :edit, params = %{"id" => id}) do
|
||||
socket =
|
||||
if get_in(socket.assigns, [:streams, :records]) == nil do
|
||||
socket
|
||||
|> apply_action(:index, params)
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
socket
|
||||
|> assign(:page_title, gettext("Edit"))
|
||||
|> assign(:record, Records.get_record!(id))
|
||||
end
|
||||
|
||||
defp apply_action(socket, :index, params) do
|
||||
query = params["query"] || ""
|
||||
total_records = Collection.search_records_count(query)
|
||||
|
||||
record_list_params =
|
||||
@default_records_list_params
|
||||
|> merge_query(query)
|
||||
|> merge_pagination(params, total_records)
|
||||
|
||||
offset = page_to_offset(record_list_params.page, record_list_params.page_size)
|
||||
|
||||
records =
|
||||
Collection.search_records(query, limit: record_list_params.page_size, offset: offset)
|
||||
|
||||
socket
|
||||
|> assign(:page_title, gettext("Collection"))
|
||||
|> assign(:record, nil)
|
||||
|> assign(:record_list_params, record_list_params)
|
||||
|> stream(:records, records, reset: true)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({MusicLibraryWeb.RecordLive.FormComponent, {:saved, record}}, socket) do
|
||||
{:noreply, stream_insert(socket, :records, record)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("delete", %{"id" => id}, socket) do
|
||||
record = Records.get_record!(id)
|
||||
{:ok, _} = Records.delete_record(record)
|
||||
|
||||
{:noreply, stream_delete(socket, :records, record)}
|
||||
end
|
||||
|
||||
def handle_event("search", %{"query" => query}, socket) do
|
||||
qs =
|
||||
@default_records_list_params
|
||||
|> Map.put(:query, query)
|
||||
|> Map.take([:query, :page, :page_size])
|
||||
|
||||
{:noreply, push_patch(socket, to: ~p"/records?#{qs}")}
|
||||
end
|
||||
|
||||
def handle_event("import", %{"id" => musicbrainz_id, "format" => format}, socket) do
|
||||
current_time = DateTime.utc_now()
|
||||
|
||||
case Records.import_from_musicbrainz_release_group(musicbrainz_id,
|
||||
format: format,
|
||||
purchased_at: current_time
|
||||
) do
|
||||
{:ok, record} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("Record imported successfully"))
|
||||
|> push_navigate(to: ~p"/records/#{record.id}")}
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign(socket, form: to_form(changeset))}
|
||||
|
||||
{:error, reason} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, gettext("Error importing record") <> "," <> inspect(reason))
|
||||
|> push_patch(to: ~p"/records")}
|
||||
end
|
||||
end
|
||||
|
||||
defp merge_query(record_list_params, query) do
|
||||
Map.put(record_list_params, :query, query)
|
||||
end
|
||||
|
||||
defp merge_pagination(record_list_params, params, total_records) do
|
||||
record_list_params
|
||||
|> Map.put(:page, parse_int_or_default(params["page"], record_list_params.page))
|
||||
|> Map.put(
|
||||
:page_size,
|
||||
parse_int_or_default(params["page_size"], record_list_params.page_size)
|
||||
)
|
||||
|> Map.put(:total_entries, total_records)
|
||||
end
|
||||
|
||||
defp parse_int_or_default(nil, default), do: default
|
||||
|
||||
defp parse_int_or_default(value, _default) when is_binary(value) do
|
||||
String.to_integer(value)
|
||||
end
|
||||
|
||||
defp back_path(record_list_params) do
|
||||
qs =
|
||||
record_list_params
|
||||
|> Map.take([:query, :page, :page_size])
|
||||
|> Enum.filter(fn {_, v} -> v not in ["", nil] end)
|
||||
|
||||
~p"/records?#{qs}"
|
||||
end
|
||||
|
||||
defp musicbrainz_url(record) do
|
||||
"https://musicbrainz.org/release-group/#{record.musicbrainz_id}"
|
||||
end
|
||||
|
||||
defp toggle_actions_menu(record_id) do
|
||||
JS.toggle(to: "#actions-#{record_id}")
|
||||
|> JS.toggle_class("pointer-events-none", to: "#records > li")
|
||||
end
|
||||
|
||||
def close_actions_menu(record_id) do
|
||||
JS.hide(to: "#actions-#{record_id}")
|
||||
|> JS.remove_class("pointer-events-none", to: "#records > li")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,230 @@
|
||||
<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"/records/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"/records/#{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"
|
||||
patch={~p"/records?query=mbid:#{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>
|
||||
<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"/records/#{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"/records/#{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} />
|
||||
@@ -0,0 +1,101 @@
|
||||
defmodule MusicLibraryWeb.CollectionLive.Show do
|
||||
use MusicLibraryWeb, :live_view
|
||||
|
||||
alias MusicLibrary.Records
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
back_url =
|
||||
if connected?(socket) do
|
||||
socket
|
||||
|> get_connect_params()
|
||||
|> Map.get("_live_referer", ~p"/records")
|
||||
else
|
||||
~p"/records"
|
||||
end
|
||||
|
||||
socket =
|
||||
if static_changed?(socket) do
|
||||
put_flash(socket, :warning, gettext("The application has been updated, please reload."))
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
{:ok, assign(socket, :back_url, back_url)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(%{"id" => id}, _, socket) do
|
||||
record = Records.get_record!(id)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:nav_section, :records)
|
||||
|> assign(:page_title, page_title(socket.assigns.live_action))
|
||||
|> assign(:record, record)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("delete", %{"id" => id}, socket) do
|
||||
record = Records.get_record!(id)
|
||||
{:ok, _} = Records.delete_record(record)
|
||||
|
||||
{:noreply, push_navigate(socket, to: ~p"/records")}
|
||||
end
|
||||
|
||||
def handle_event("refresh_musicbrainz_data", %{"id" => id}, socket) do
|
||||
record = Records.get_record!(id)
|
||||
|
||||
case Records.refresh_musicbrainz_data(record) do
|
||||
{:ok, updated_record} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("MusicBrainz data refreshed successfully"))
|
||||
|> assign(:record, updated_record)}
|
||||
|
||||
{:error, reason} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
gettext("Error refreshing MusicBrainz data") <> "," <> inspect(reason)
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("refresh_cover", %{"id" => id}, socket) do
|
||||
record = Records.get_record!(id)
|
||||
|
||||
case Records.refresh_cover(record) do
|
||||
{:ok, updated_record} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("Cover refreshed successfully"))
|
||||
|> assign(:record, updated_record)}
|
||||
|
||||
{:error, reason} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
gettext("Error refreshing MusicBrainz data") <> "," <> inspect(reason)
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({MusicLibraryWeb.RecordLive.FormComponent, {:saved, record}}, socket) do
|
||||
{:noreply, assign(socket, :record, record)}
|
||||
end
|
||||
|
||||
defp page_title(:show), do: gettext("Show")
|
||||
defp page_title(:edit), do: gettext("Edit")
|
||||
|
||||
defp musicbrainz_url(record) do
|
||||
"https://musicbrainz.org/release-group/#{record.musicbrainz_id}"
|
||||
end
|
||||
|
||||
defp human_datetime(dt) do
|
||||
"#{dt.day}/#{dt.month}/#{dt.year}"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,173 @@
|
||||
<div class="md:columns-2 mt-4 px-4">
|
||||
<div class="drop-shadow max-w-3xl">
|
||||
<img
|
||||
class="w-full rounded-lg drop-shadow"
|
||||
src={~p"/covers/#{@record.id}?vsn=#{@record.cover_hash || ""}"}
|
||||
alt={@record.title}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 md:mt-0">
|
||||
<h1 class="text-base font-medium 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"
|
||||
patch={~p"/records?query=mbid:#{artist.musicbrainz_id}"}
|
||||
>
|
||||
<%= artist.name %>
|
||||
</.link>
|
||||
</h1>
|
||||
<h2 class="mt-1 flex font-semibold text-base sm:text-lg leading-5 text-zinc-700 dark:text-zinc-300 text-wrap">
|
||||
<%= @record.title %>
|
||||
</h2>
|
||||
<p class="mt-2 text-sm leading-5 text-zinc-500 dark:text-zinc-400">
|
||||
<%= Records.Record.format_release(@record.release) %> · <%= Records.Record.format_long_label(
|
||||
@record.format
|
||||
) %> · <%= Records.Record.type_long_label(@record.type) %>
|
||||
</p>
|
||||
|
||||
<nav class="mt-5 isolate inline-flex rounded-md shadow-sm">
|
||||
<.link patch={~p"/records/#{@record}/show/edit"} phx-click={JS.push_focus()}>
|
||||
<.button type="button" class="relative inline-flex items-center rounded-r-none">
|
||||
<%= gettext("Edit") %>
|
||||
</.button>
|
||||
</.link>
|
||||
<.link phx-click={JS.push("refresh_cover", value: %{id: @record.id})}>
|
||||
<.button type="button" class="relative -ml-px inline-flex items-center rounded-none">
|
||||
<span class="sr-only"><%= gettext("Refresh") %></span>
|
||||
<.icon
|
||||
name="hero-arrow-path"
|
||||
class="h-4 w-4 mr-1 phx-click-loading:animate-spin"
|
||||
aria-hidden="true"
|
||||
data-slot="icon"
|
||||
/>
|
||||
<%= gettext("Cover") %>
|
||||
</.button>
|
||||
</.link>
|
||||
<.link phx-click={JS.push("refresh_musicbrainz_data", value: %{id: @record.id})}>
|
||||
<.button type="button" class="relative -ml-px inline-flex items-center rounded-none">
|
||||
<span class="sr-only"><%= gettext("Refresh") %></span>
|
||||
<.icon
|
||||
name="hero-arrow-path"
|
||||
class="h-4 w-4 mr-1 phx-click-loading:animate-spin"
|
||||
aria-hidden="true"
|
||||
data-slot="icon"
|
||||
/>
|
||||
<%= gettext("MB Data") %>
|
||||
</.button>
|
||||
</.link>
|
||||
<.link
|
||||
phx-click={JS.push("delete", value: %{id: @record.id})}
|
||||
data-confirm={gettext("Are you sure?")}
|
||||
>
|
||||
<.button
|
||||
type="button"
|
||||
class="relative -ml-px inline-flex items-center rounded-l-none !text-red-600 hover:!text-red-500 dark:!text-red-700 hover:dark:!text-red-500"
|
||||
>
|
||||
<%= gettext("Delete") %>
|
||||
</.button>
|
||||
</.link>
|
||||
</nav>
|
||||
</div>
|
||||
<div>
|
||||
<dl class="mt-4 divide-y divide-zinc-100 dark:divide-slate-300/30">
|
||||
<div class="py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium leading-6 text-zinc-900 dark:text-zinc-400">
|
||||
<%= gettext("Genres") %>
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-6 text-zinc-700 dark:text-zinc-300 sm:col-span-2 sm:mt-0">
|
||||
<%= Enum.join(@record.genres, ", ") %>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium leading-6 text-zinc-900 dark:text-zinc-400">
|
||||
<%= gettext("MusicBrainz ID") %>
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm flex leading-6 text-zinc-700 dark:text-zinc-300 sm:col-span-2 sm:mt-0">
|
||||
<a href={musicbrainz_url(@record)}>
|
||||
<code id={"mb-#{@record.musicbrainz_id}"}><%= @record.musicbrainz_id %></code>
|
||||
</a>
|
||||
<button phx-click={
|
||||
JS.dispatch("music_library:clipcopy", to: "#mb-" <> @record.musicbrainz_id)
|
||||
|> JS.transition("animate-shake")
|
||||
}>
|
||||
<span class="sr-only"><%= gettext("Copy MusicBrainz ID to clipboard") %></span>
|
||||
<.icon
|
||||
name="hero-clipboard-document"
|
||||
class="-mt-1 h-5 w-5"
|
||||
aria-hidden="true"
|
||||
data-slot="icon"
|
||||
/>
|
||||
</button>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium leading-6 text-zinc-900 dark:text-zinc-400">
|
||||
<%= gettext("Purchased on") %>
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-6 text-zinc-700 dark:text-zinc-300 sm:col-span-2 sm:mt-0">
|
||||
<%= human_datetime(@record.purchased_at) %>
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
:if={Records.Record.child_release_groups_count(@record) > 0}
|
||||
class="py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"
|
||||
>
|
||||
<dt class="text-sm font-medium leading-6 text-zinc-900 dark:text-zinc-400">
|
||||
<%= gettext("Includes") %>
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-6 text-zinc-700 dark:text-zinc-300 sm:col-span-2 sm:mt-0">
|
||||
<ul>
|
||||
<li :for={child_release_group <- Records.Record.child_release_groups(@record)}>
|
||||
<%= child_release_group.artists %> - <%= child_release_group.title %>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium leading-6 text-zinc-900 dark:text-zinc-400">
|
||||
<%= gettext("Inserted at") %>
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-6 text-zinc-700 dark:text-zinc-300 sm:col-span-2 sm:mt-0">
|
||||
<%= human_datetime(@record.inserted_at) %>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium leading-6 text-zinc-900 dark:text-zinc-400">
|
||||
<%= gettext("Updated at") %>
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-6 text-zinc-700 dark:text-zinc-300 sm:col-span-2 sm:mt-0">
|
||||
<%= human_datetime(@record.updated_at) %>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details class="mt-4 px-4 text-zinc-700 hover:text-zinc-500 dark:text-zinc-400 dark:hover:text-zinc-300">
|
||||
<summary class="text-xs sm:text-sm"><%= gettext("MusicBrainz data") %></summary>
|
||||
<pre><code class="text-xs sm:text-sm"><%= Jason.encode!(@record.musicbrainz_data, pretty: true) %></code></pre>
|
||||
</details>
|
||||
|
||||
<div class="mt-8">
|
||||
<.back navigate={@back_url}>
|
||||
<%= gettext("Back to records") %>
|
||||
</.back>
|
||||
</div>
|
||||
|
||||
<.modal
|
||||
:if={@live_action == :edit}
|
||||
id="record-modal"
|
||||
show
|
||||
on_cancel={JS.patch(~p"/records/#{@record}")}
|
||||
>
|
||||
<.live_component
|
||||
module={MusicLibraryWeb.RecordLive.FormComponent}
|
||||
id={@record.id}
|
||||
title={@page_title}
|
||||
action={@live_action}
|
||||
show_purchased_at={true}
|
||||
record={@record}
|
||||
patch={~p"/records/#{@record}"}
|
||||
/>
|
||||
</.modal>
|
||||
Reference in New Issue
Block a user