defmodule MusicLibraryWeb.BarcodeScannerComponent do use MusicLibraryWeb, :live_component alias MusicBrainz.ReleaseGroupSearchResult alias MusicLibrary.BarcodeScan alias MusicLibrary.Records alias MusicLibraryWeb.RecordComponents require Logger @impl true def mount(socket) do {:ok, socket |> assign(:camera, :pending) |> assign(:scan_results, [])} end @impl true def render(assigns) do ~H"""

{gettext("Scan one or more barcodes")}

<.camera_button camera={@camera} />
<.button disabled={length(@scan_results) == 0} phx-disable-with={gettext("Adding...")} phx-click={JS.push("import_releases", target: "#barcode-scanner")} > {gettext("Add releases")}
""" end attr :camera, :atom, required: true, values: [:pending, :allowed, :denied] def camera_button(assigns) do ~H""" """ end attr :class, :string, required: true def barcode_icon(assigns) do ~H""" """ end attr :scan_result, BarcodeScan.Result, required: true defp scan_result(assigns) do ~H""" <.barcode_not_found :if={@scan_result.status == :not_found} number={@scan_result.number} /> <.release :if={@scan_result.status != :not_found} release={@scan_result.release} record_id={@scan_result.record_id} status={@scan_result.status} /> """ end attr :number, :string, required: true defp barcode_not_found(assigns) do ~H"""

{gettext("Barcode not found")}

{@number}

""" end attr :release, MusicBrainz.ReleaseSearchResult, required: true attr :record_id, :string attr :status, :atom, required: true, values: [:collected, :wishlisted, :new] defp release(assigns) do ~H"""
{@release.release_group.title} ~p"/images/cover-not-found.png" <> "';"} />

{@release.artists}

{@release.title}

{release_format_label(@release)} · {Records.Record.format_release_date(@release.date)} · {RecordComponents.type_label( @release.release_group.type )}

<.badge :if={@status == :new} color={:gray} text={gettext("New")} /> <.link :if={@status == :wishlisted} navigate={~p"/wishlist/#{@record_id}"}> <.badge color={:yellow} text={gettext("Wishlisted")} /> <.link :if={@status == :collected} navigate={~p"/collection/#{@record_id}"}> <.badge color={:green} text={gettext("Collected")} />
""" end @impl true def handle_event("camera_allowed", _params, socket) do {:noreply, assign(socket, camera: :allowed)} end def handle_event("camera_denied", _params, socket) do {:noreply, assign(socket, camera: :denied)} end def handle_event("barcode_scanned", %{"number" => number}, socket) do socket = case BarcodeScan.scan(number) do {:ok, scan_result} -> assign(socket, :scan_results, [scan_result | socket.assigns.scan_results]) {:error, reason} -> Logger.error(fn -> "Failed to search release for barcode #{number}: #{inspect(reason)}" end) put_flash( socket, :error, gettext("Failed to search release for barcode %{number}", number: number) ) end {:noreply, socket} end def handle_event("import_releases", _params, socket) do current_time = DateTime.utc_now() socket = case BarcodeScan.import_results(socket.assigns.scan_results, current_time) do [] -> put_flash(socket, :info, gettext("Records imported successfully")) errors -> errors_summary = Enum.map_join(errors, "\n", fn {number, reason} -> "#{number}: #{inspect(reason)}" end) put_flash( socket, :error, gettext("Some records could not be imported: %{summary}", summary: errors_summary) ) end qs = %{order: :purchase} {:noreply, socket |> assign(:scan_results, []) |> push_patch(to: ~p"/collection?#{qs}")} end defp release_format_label(release) do release |> MusicBrainz.ReleaseSearchResult.format() |> RecordComponents.format_label() end end