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

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

<.camera_button camera={@camera} />
<.button disabled={length(@releases) == 0} phx-disable-with={gettext("Importing...")} phx-click={JS.push("import_releases", target: "#barcode-scanner")} > {gettext("Import releases")}
""" end attr :camera, :atom, required: true, values: [:pending, :allowed, :denied] def camera_button(assigns) do ~H""" """ 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.artists}

{@release.title}

{release_format_label(@release)} · {Records.Record.format_release(@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 Logger.debug(fn -> "Camera access allowed" end) {:noreply, assign(socket, camera: :allowed)} end def handle_event("camera_denied", _params, socket) do Logger.debug(fn -> "Camera access denied" end) {:noreply, assign(socket, camera: :denied)} end def handle_event("barcode_scanned", %{"number" => number}, socket) do Logger.debug(fn -> "Scanned barcode #{number}" end) socket = case MusicBrainz.search_release_by_barcode(number) do {:ok, [best_match_release | _other_releases]} -> Logger.debug(fn -> "Found release #{best_match_release.id}" end) assign_release_with_status(best_match_release, socket) {:ok, []} -> Logger.debug(fn -> "No release found for barcode #{number}" end) put_flash( socket, :error, gettext("No release found for barcode %{number}", number: number) ) {:error, _reason} -> Logger.error(fn -> "Failed to search release for barcode #{number}" 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() # TODO: error handling when a release fails to import :ok = Enum.each(socket.assigns.releases, fn {status, record_id, release} -> if status == :new do Records.import_from_musicbrainz_release(release.id, format: MusicBrainz.ReleaseSearchResult.format(release), purchased_at: current_time ) end if status == :wishlisted do record = Records.get_record!(record_id) Records.update_record(record, %{"purchased_at" => current_time}) end end) qs = %{order: :purchase} {:noreply, socket |> assign(:releases, []) |> put_flash(:info, gettext("Records imported successfully")) |> push_patch(to: ~p"/collection?#{qs}")} end defp assign_release_with_status(release, socket) do format = MusicBrainz.ReleaseSearchResult.format(release) release_with_status = case Records.get_release_status(release.id, format) do nil -> {:new, nil, release} %{record_id: record_id, purchased_at: nil} -> {:wishlisted, record_id, release} %{record_id: record_id} -> {:collected, record_id, release} end assign(socket, :releases, [release_with_status | socket.assigns.releases]) end defp release_format_label(release) do release |> MusicBrainz.ReleaseSearchResult.format() |> RecordComponents.format_label() end end