Add function to download a record's tracklist for printing
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
defmodule MusicLibrary.Records.TracklistPdf do
|
||||
alias MusicBrainz.Release
|
||||
alias MusicLibraryWeb.Duration
|
||||
alias Typst.Format
|
||||
|
||||
@spec generate(MusicLibrary.Records.Record.t(), Release.t()) ::
|
||||
{:ok, binary()} | {:error, term()}
|
||||
def generate(record, release) do
|
||||
markup = build_markup(record, release)
|
||||
Typst.render_to_pdf(markup)
|
||||
end
|
||||
|
||||
defp build_markup(record, release) do
|
||||
"""
|
||||
#set page(width: 120mm, height: 120mm, margin: 5mm)
|
||||
#set text(font: "Liberation Sans", size: 8pt)
|
||||
|
||||
#align(center)[
|
||||
#text(size: 10pt, weight: "bold")[#{Format.escape(artist_names(record))}]
|
||||
#linebreak()
|
||||
#text(size: 9pt, style: "italic")[#{Format.escape(record.title)}]
|
||||
]
|
||||
|
||||
#v(3mm)
|
||||
|
||||
#{build_media(release)}
|
||||
"""
|
||||
end
|
||||
|
||||
defp build_media(release) do
|
||||
media_count = Release.media_count(release)
|
||||
|
||||
release.media
|
||||
|> Enum.map_join("\n", fn medium ->
|
||||
build_medium(medium, media_count)
|
||||
end)
|
||||
end
|
||||
|
||||
defp build_medium(medium, media_count) do
|
||||
header =
|
||||
if media_count > 1 do
|
||||
label = medium_label(medium)
|
||||
|
||||
"""
|
||||
#v(2mm)
|
||||
#text(size: 8pt, weight: "bold")[#{Format.escape(label)}]
|
||||
#v(1mm)
|
||||
"""
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
tracks =
|
||||
medium.tracks
|
||||
|> Enum.map_join("\n", &build_track/1)
|
||||
|
||||
header <> tracks
|
||||
end
|
||||
|
||||
defp build_track(track) do
|
||||
number = Format.escape(track.number || to_string(track.position))
|
||||
title = Format.escape(track.title)
|
||||
|
||||
duration =
|
||||
if track.length do
|
||||
Format.escape(Duration.format_duration(track.length))
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
"""
|
||||
#grid(
|
||||
columns: (12pt, 1fr, auto),
|
||||
gutter: 4pt,
|
||||
align(right)[#{number}],
|
||||
[#{title}],
|
||||
align(right)[#{duration}]
|
||||
)\
|
||||
"""
|
||||
end
|
||||
|
||||
defp artist_names(record) do
|
||||
Enum.map_join(record.artists, fn artist ->
|
||||
artist.name <> (artist.joinphrase || "")
|
||||
end)
|
||||
end
|
||||
|
||||
defp medium_label(medium) do
|
||||
title =
|
||||
if medium.title && medium.title != "" do
|
||||
medium.title
|
||||
else
|
||||
"Disc #{medium.number}"
|
||||
end
|
||||
|
||||
if medium.format do
|
||||
"#{title} (#{medium.format})"
|
||||
else
|
||||
title
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,6 +4,7 @@ defmodule MusicLibraryWeb.Components.Release do
|
||||
require Logger
|
||||
|
||||
alias MusicBrainz.Release
|
||||
alias MusicLibrary.Records.TracklistPdf
|
||||
alias MusicLibrary.ScrobbleActivity
|
||||
alias MusicLibraryWeb.Duration
|
||||
alias MusicLibraryWeb.ErrorMessages
|
||||
@@ -69,23 +70,34 @@ defmodule MusicLibraryWeb.Components.Release do
|
||||
/>
|
||||
{gettext("Tracks")}
|
||||
</label>
|
||||
<.button
|
||||
:if={@can_scrobble? && @release_with_tracks.ok?}
|
||||
size="sm"
|
||||
disabled={@already_scrobbled}
|
||||
phx-click={
|
||||
if MapSet.size(@selected_tracks) > 0,
|
||||
do: "scrobble_selected_tracks",
|
||||
else: "scrobble_release"
|
||||
}
|
||||
phx-target={@myself}
|
||||
phx-disable-with={gettext("Scrobbling...")}
|
||||
>
|
||||
{scrobble_button_label(@selected_tracks)}
|
||||
</.button>
|
||||
<.button :if={!@can_scrobble?} size="sm" href={LastFm.auth_url()}>
|
||||
{gettext("Connect your Last.fm account")}
|
||||
</.button>
|
||||
<div class="flex items-center gap-2">
|
||||
<.button
|
||||
:if={@release_with_tracks.ok?}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
phx-click="print_tracklist"
|
||||
phx-target={@myself}
|
||||
>
|
||||
<.icon name="hero-printer" class="h-4 w-4" />
|
||||
</.button>
|
||||
<.button
|
||||
:if={@can_scrobble? && @release_with_tracks.ok?}
|
||||
size="sm"
|
||||
disabled={@already_scrobbled}
|
||||
phx-click={
|
||||
if MapSet.size(@selected_tracks) > 0,
|
||||
do: "scrobble_selected_tracks",
|
||||
else: "scrobble_release"
|
||||
}
|
||||
phx-target={@myself}
|
||||
phx-disable-with={gettext("Scrobbling...")}
|
||||
>
|
||||
{scrobble_button_label(@selected_tracks)}
|
||||
</.button>
|
||||
<.button :if={!@can_scrobble?} size="sm" href={LastFm.auth_url()}>
|
||||
{gettext("Connect your Last.fm account")}
|
||||
</.button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :if={@release_with_tracks} class="space-y-4 mt-4">
|
||||
@@ -387,6 +399,38 @@ defmodule MusicLibraryWeb.Components.Release do
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("print_tracklist", _params, socket) when release_loaded?(socket.assigns) do
|
||||
release = socket.assigns.release_with_tracks.result
|
||||
record = socket.assigns.record
|
||||
|
||||
case TracklistPdf.generate(record, release) do
|
||||
{:ok, pdf_binary} ->
|
||||
filename = "#{record.title} - Tracklist.pdf"
|
||||
|
||||
{:noreply,
|
||||
push_event(socket, "music_library:download", %{
|
||||
data: Base.encode64(pdf_binary),
|
||||
filename: filename,
|
||||
content_type: "application/pdf"
|
||||
})}
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Error generating tracklist PDF: #{inspect(reason)}")
|
||||
|
||||
put_toast!(
|
||||
:error,
|
||||
gettext("Error generating tracklist PDF") <>
|
||||
": " <> ErrorMessages.friendly_message(reason)
|
||||
)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("print_tracklist", _params, socket) do
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("load_release_tracks", _params, socket) do
|
||||
selected_release_id = socket.assigns.record.selected_release_id
|
||||
|
||||
|
||||
Reference in New Issue
Block a user