Files
music_library/lib/music_library_web/components/import_component.ex
T
2025-03-01 17:36:21 +00:00

170 lines
5.5 KiB
Elixir

defmodule MusicLibraryWeb.ImportComponent do
use MusicLibraryWeb, :live_component
import MusicLibraryWeb.RecordComponents,
only: [toggle_actions_menu: 1, close_actions_menu: 1, format_label: 1, type_label: 1]
alias MusicBrainz.ReleaseGroupSearchResult
alias MusicLibrary.Records
@impl true
def render(assigns) do
~H"""
<div>
<.simple_form
for={@form}
id={:import_form}
phx-target={@myself}
phx-change="search"
phx-submit="search"
class="px-4"
>
<.input
id={:mb_query}
name={:mb_query}
field={@form[:mb_query]}
type="text"
label={gettext("Search for a record on MusicBrainz")}
prompt={gettext("Search for records")}
phx-debounce="500"
autocorrect="off"
autocapitalize="none"
autofocus
/>
</.simple_form>
<ul
id="release-groups"
phx-update="stream"
role="list"
class="divide-y divide-zinc-100 dark:divide-slate-300/30 mt-5"
>
<li
id="release-groups-empty"
class="only:flex hidden items-center justify-center h-32 text-md text-zinc-500"
>
{gettext("No results")}
</li>
<.result
:for={{id, release_group} <- @streams.release_groups}
id={id}
release_group={release_group}
icon_name={@icon_name}
/>
</ul>
</div>
"""
end
attr :id, :string, required: true
attr :icon_name, :string, required: true
attr :release_group, MusicBrainz.ReleaseGroupSearchResult, required: true
defp result(assigns) do
~H"""
<li id={@id} class="flex justify-between gap-x-6 py-5 hover:bg-zinc-50 dark:hover:bg-zinc-700">
<div class="shrink-0 flex items-center justify-between w-full px-4">
<img
class="w-20 flex-none rounded-lg mr-4"
alt={@release_group.title}
src={ReleaseGroupSearchResult.thumb_url(@release_group)}
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
/>
<div class="min-w-0 flex-auto">
<h1 class="text-sm leading-6 text-zinc-700 dark:text-zinc-400">
{@release_group.artists}
</h1>
<h2 class="mt-1 flex font-semibold text-sm sm:text-base leading-5 text-zinc-700 dark:text-zinc-300 text-wrap">
{@release_group.title}
</h2>
<p class="mt-1 text-xs leading-5 text-zinc-500 dark:text-zinc-400">
{Records.Record.format_release(@release_group.release)} · {type_label(@release_group.type)}
</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(@release_group.id)}
phx-click-away={close_actions_menu(@release_group.id)}
>
<span class="sr-only">{gettext("Choose which format to import")}</span>
<.icon name={@icon_name} 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-#{@release_group.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-hidden"
]}
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu-0-button"
>
<.link
:for={format <- Records.Record.formats()}
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="0"
id={"actions-#{@release_group.id}-#{format}-import"}
phx-click={
JS.push("import", value: %{id: @release_group.id, format: format}, page_loading: true)
}
>
{format_label(format)}
</.link>
</.focus_wrap>
</div>
</div>
</li>
"""
end
attr :type, :string, required: true
defp type_badge(assigns) do
~H"""
<span class="inline-flex items-center rounded-md bg-zinc-50 px-2 py-1 text-xs font-medium text-zinc-600 ring-1 ring-inset ring-zinc-500/10">
{@type}
</span>
"""
end
@impl true
def mount(socket) do
{:ok,
socket
|> stream_configure(:release_groups,
dom_id: fn rg -> "musicbrainz_#{rg.id}" end
)
|> stream(:release_groups, [])
|> assign(:form, to_form(%{"mb_query" => ""}))}
end
@impl true
def handle_event("search", %{"mb_query" => mb_query}, socket) do
{:ok, release_groups} = search(mb_query)
{:noreply,
socket
|> stream(:release_groups, release_groups, reset: true)
|> assign(:form, to_form(%{"mb_query" => mb_query}))}
end
defp search(""), do: {:ok, []}
defp search(mb_query) do
MusicBrainz.search_release_group(mb_query, limit: 10)
end
end