Show record status when scanning

For now it only imports new records - it should also purchase wishlisted
ones.
This commit is contained in:
Claudio Ortolina
2025-02-19 10:21:44 +00:00
parent 5ef05a94f5
commit ce75b42d06
3 changed files with 118 additions and 67 deletions
+12
View File
@@ -98,6 +98,18 @@ defmodule MusicLibrary.Records do
def get_record!(id), do: Repo.get!(Record, id) def get_record!(id), do: Repo.get!(Record, id)
def get_release_status(release_id, format) do
q =
from r in fragment("records, json_each(records.release_ids)"),
where: fragment("records.format = ?", ^format) and r.value == ^release_id,
select: %{
record_id: fragment("records.id"),
purchased_at: fragment("records.purchased_at")
}
Repo.one(q)
end
def get_artist_records(musicbrainz_id) do def get_artist_records(musicbrainz_id) do
q = q =
from r in Record, from r in Record,
@@ -1,8 +1,8 @@
defmodule MusicLibraryWeb.RecordLive.BarcodeScannerComponent do defmodule MusicLibraryWeb.RecordLive.BarcodeScannerComponent do
use MusicLibraryWeb, :live_component use MusicLibraryWeb, :live_component
alias MusicLibraryWeb.RecordComponents
alias MusicLibrary.Records alias MusicLibrary.Records
alias MusicLibraryWeb.RecordComponents
require Logger require Logger
@@ -24,6 +24,44 @@ defmodule MusicLibraryWeb.RecordLive.BarcodeScannerComponent do
</h1> </h1>
</header> </header>
<div class="mt-4"> <div class="mt-4">
<.camera_button camera={@camera} />
<video :if={!(@camera == :denied)} class="w-full hidden h-96" id="camera-preview" playsinline />
</div>
<ul class="divide-y divide-zinc-100 dark:divide-slate-300/30 mt-5">
<li
:for={{status, release} <- @releases}
id={release.id}
class="flex justify-between gap-x-6 py-5 hover:bg-zinc-50 dark:hover:bg-zinc-700"
phx-mounted={
JS.transition(
{"first:ease-in duration-300", "first:opacity-0 first:p-0 first:h-0",
"first:opacity-100"},
time: 300
)
}
>
<.release status={status} release={release} />
</li>
</ul>
<div class="mt-4 flex justify-center">
<.button
disabled={length(@releases) == 0}
phx-disable-with={gettext("Importing...")}
phx-click={JS.push("import_releases", target: "#barcode-scanner")}
>
{gettext("Import releases")}
</.button>
</div>
</div>
"""
end
attr :camera, :atom, required: true, values: [:pending, :allowed, :denied]
def camera_button(assigns) do
~H"""
<button <button
:if={!(@camera == :allowed)} :if={!(@camera == :allowed)}
id="camera-button" id="camera-button"
@@ -50,41 +88,14 @@ defmodule MusicLibraryWeb.RecordLive.BarcodeScannerComponent do
</svg> </svg>
<span class="mt-2 block text-sm font-semibold text-zinc-900">{gettext("Open camera")}</span> <span class="mt-2 block text-sm font-semibold text-zinc-900">{gettext("Open camera")}</span>
</button> </button>
<video :if={!(@camera == :denied)} class="w-full hidden h-96" id="camera-preview" playsinline />
</div>
<ul class="divide-y divide-zinc-100 dark:divide-slate-300/30 mt-5">
<.result :for={release <- @releases} id={release.id} release={release} />
</ul>
<div class="mt-4 flex justify-center">
<.button
disabled={length(@releases) == 0}
phx-disable-with={gettext("Importing...")}
phx-click={JS.push("import_releases", target: "#barcode-scanner")}
>
{gettext("Import releases")}
</.button>
</div>
</div>
""" """
end end
attr :id, :string, required: true
attr :release, MusicBrainz.ReleaseSearchResult, required: true attr :release, MusicBrainz.ReleaseSearchResult, required: true
attr :status, :atom, required: true, values: [:collected, :wishlisted, :new]
defp result(assigns) do defp release(assigns) do
~H""" ~H"""
<li
id={@id}
class="flex justify-between gap-x-6 py-5 hover:bg-zinc-50 dark:hover:bg-zinc-700"
phx-mounted={
JS.transition(
{"first:ease-in duration-300", "first:opacity-0 first:p-0 first:h-0", "first:opacity-100"},
time: 300
)
}
>
<div class="flex items-center justify-between w-full px-4"> <div class="flex items-center justify-between w-full px-4">
<div class="min-w-0 flex-auto"> <div class="min-w-0 flex-auto">
<h1 class="text-sm leading-6 text-zinc-700 dark:text-zinc-400"> <h1 class="text-sm leading-6 text-zinc-700 dark:text-zinc-400">
@@ -99,8 +110,10 @@ defmodule MusicLibraryWeb.RecordLive.BarcodeScannerComponent do
)} )}
</p> </p>
</div> </div>
<.badge :if={@status == :new} color={:gray} text={gettext("New")} />
<.badge :if={@status == :wishlisted} color={:yellow} text={gettext("Wishlisted")} />
<.badge :if={@status == :collected} color={:green} text={gettext("Collected")} />
</div> </div>
</li>
""" """
end end
@@ -118,11 +131,10 @@ defmodule MusicLibraryWeb.RecordLive.BarcodeScannerComponent do
def handle_event("barcode_scanned", %{"number" => number}, socket) do def handle_event("barcode_scanned", %{"number" => number}, socket) do
Logger.debug(fn -> "Scanned barcode #{number}" end) Logger.debug(fn -> "Scanned barcode #{number}" end)
# TODO: inform when record is already in collection
socket = socket =
case MusicBrainz.search_release_by_barcode(number) do case MusicBrainz.search_release_by_barcode(number) do
{:ok, [best_match_release | _other_releases]} -> {:ok, [best_match_release | _other_releases]} ->
assign(socket, :releases, [best_match_release | socket.assigns.releases]) assign_release_with_status(best_match_release, socket)
{:ok, []} -> {:ok, []} ->
put_flash( put_flash(
@@ -146,11 +158,13 @@ defmodule MusicLibraryWeb.RecordLive.BarcodeScannerComponent do
current_time = DateTime.utc_now() current_time = DateTime.utc_now()
# TODO: error handling when a release fails to import # TODO: error handling when a release fails to import
:ok = :ok =
Enum.each(socket.assigns.releases, fn release -> Enum.each(socket.assigns.releases, fn {status, release} ->
if status == :new do
Records.import_from_musicbrainz_release(release.id, Records.import_from_musicbrainz_release(release.id,
format: MusicBrainz.ReleaseSearchResult.format(release), format: MusicBrainz.ReleaseSearchResult.format(release),
purchased_at: current_time purchased_at: current_time
) )
end
end) end)
qs = %{order: :purchase} qs = %{order: :purchase}
@@ -162,6 +176,24 @@ defmodule MusicLibraryWeb.RecordLive.BarcodeScannerComponent do
|> push_patch(to: ~p"/collection?#{qs}")} |> push_patch(to: ~p"/collection?#{qs}")}
end 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, release}
%{record_id: _, purchased_at: nil} ->
{:wishlisted, release}
_collected ->
{:collected, release}
end
assign(socket, :releases, [release_with_status | socket.assigns.releases])
end
defp release_format_label(release) do defp release_format_label(release) do
release release
|> MusicBrainz.ReleaseSearchResult.format() |> MusicBrainz.ReleaseSearchResult.format()
+13 -6
View File
@@ -343,11 +343,13 @@ msgstr ""
msgid "Choose which format to import" msgid "Choose which format to import"
msgstr "" msgstr ""
#: lib/music_library_web/live/record_live/barcode_scanner_component.ex:115
#: lib/music_library_web/live/stats_live/index.html.heex:141 #: lib/music_library_web/live/stats_live/index.html.heex:141
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Collected" msgid "Collected"
msgstr "" msgstr ""
#: lib/music_library_web/live/record_live/barcode_scanner_component.ex:114
#: lib/music_library_web/live/stats_live/index.html.heex:147 #: lib/music_library_web/live/stats_live/index.html.heex:147
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Wishlisted" msgid "Wishlisted"
@@ -635,32 +637,37 @@ msgstr ""
msgid "Scan one or more barcodes" msgid "Scan one or more barcodes"
msgstr "" msgstr ""
#: lib/music_library_web/live/record_live/barcode_scanner_component.ex:51 #: lib/music_library_web/live/record_live/barcode_scanner_component.ex:89
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Open camera" msgid "Open camera"
msgstr "" msgstr ""
#: lib/music_library_web/live/record_live/barcode_scanner_component.ex:66 #: lib/music_library_web/live/record_live/barcode_scanner_component.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Import releases" msgid "Import releases"
msgstr "" msgstr ""
#: lib/music_library_web/live/record_live/barcode_scanner_component.ex:138 #: lib/music_library_web/live/record_live/barcode_scanner_component.ex:150
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Failed to search release for barcode %{number}" msgid "Failed to search release for barcode %{number}"
msgstr "" msgstr ""
#: lib/music_library_web/live/record_live/barcode_scanner_component.ex:131 #: lib/music_library_web/live/record_live/barcode_scanner_component.ex:143
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "No release found for barcode %{number}" msgid "No release found for barcode %{number}"
msgstr "" msgstr ""
#: lib/music_library_web/live/record_live/barcode_scanner_component.ex:63 #: lib/music_library_web/live/record_live/barcode_scanner_component.ex:51
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Importing..." msgid "Importing..."
msgstr "" msgstr ""
#: lib/music_library_web/live/record_live/barcode_scanner_component.ex:161 #: lib/music_library_web/live/record_live/barcode_scanner_component.ex:175
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Records imported successfully" msgid "Records imported successfully"
msgstr "" msgstr ""
#: lib/music_library_web/live/record_live/barcode_scanner_component.ex:113
#, elixir-autogen, elixir-format
msgid "New"
msgstr ""