From 0eaaf667486c9a8a22925bc14b36858a419dbf67 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 10 May 2026 15:37:11 -0400 Subject: [PATCH] refactor(web): playlistTrackToRef helper to dedupe TrackRef reconstruction (#375) PlaylistTrackRow.svelte and PlaylistCard.toTrackRefs both rebuilt TrackRef from PlaylistTrack with the same field-by-field literal. Server adding a new TrackRef field would have needed both sites updated; now one helper owns the mapping. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/lib/components/PlaylistCard.svelte | 24 +++++-------------- .../lib/components/PlaylistTrackRow.svelte | 22 ++++------------- web/src/lib/playlists/playlistTrackToRef.ts | 22 +++++++++++++++++ 3 files changed, 33 insertions(+), 35 deletions(-) create mode 100644 web/src/lib/playlists/playlistTrackToRef.ts diff --git a/web/src/lib/components/PlaylistCard.svelte b/web/src/lib/components/PlaylistCard.svelte index ba67479c..45f58e22 100644 --- a/web/src/lib/components/PlaylistCard.svelte +++ b/web/src/lib/components/PlaylistCard.svelte @@ -4,6 +4,7 @@ import type { Playlist, PlaylistTrack, TrackRef } from '$lib/api/types'; import { user } from '$lib/auth/store.svelte'; import { getPlaylist, refreshDiscover, refreshForYou } from '$lib/api/playlists'; + import { playlistTrackToRef } from '$lib/playlists/playlistTrackToRef'; import { errCode } from '$lib/api/errors'; import { qk } from '$lib/api/queries'; import { playQueue } from '$lib/player/store.svelte'; @@ -25,25 +26,12 @@ menuOpen = !menuOpen; } - // Map PlaylistTrack rows to TrackRef shape and drop tracks whose upstream - // file has been removed (track_id is null). Mirrors PlaylistTrackRow's - // liveTrack derivation. + // Map PlaylistTrack rows to TrackRef shape and drop tracks whose + // upstream file has been removed (track_id null). function toTrackRefs(rows: PlaylistTrack[]): TrackRef[] { - const out: TrackRef[] = []; - for (const r of rows) { - if (!r.track_id) continue; - out.push({ - id: r.track_id, - title: r.title, - album_id: r.album_id ?? '', - album_title: r.album_title, - artist_id: r.artist_id ?? '', - artist_name: r.artist_name, - duration_sec: r.duration_sec, - stream_url: r.stream_url ?? '' - }); - } - return out; + return rows + .map((r) => playlistTrackToRef(r)) + .filter((t): t is TrackRef => t !== null); } async function onPlayClick(e: MouseEvent) { diff --git a/web/src/lib/components/PlaylistTrackRow.svelte b/web/src/lib/components/PlaylistTrackRow.svelte index 275aff1b..595a85fa 100644 --- a/web/src/lib/components/PlaylistTrackRow.svelte +++ b/web/src/lib/components/PlaylistTrackRow.svelte @@ -3,7 +3,8 @@ import { draggable, type DragEventData } from '@neodrag/svelte'; import LikeButton from './LikeButton.svelte'; import TrackMenu from './TrackMenu.svelte'; - import type { PlaylistTrack, TrackRef } from '$lib/api/types'; + import type { PlaylistTrack } from '$lib/api/types'; + import { playlistTrackToRef } from '$lib/playlists/playlistTrackToRef'; import { offsetToDelta } from './queue-row-math'; let { @@ -22,22 +23,9 @@ const isUnavailable = $derived(row.track_id === null); - // Construct a minimal TrackRef for the kebab menu when the upstream - // track still exists. When unavailable, the menu is hidden. - const liveTrack = $derived( - row.track_id - ? ({ - id: row.track_id, - title: row.title, - album_id: row.album_id ?? '', - album_title: row.album_title, - artist_id: row.artist_id ?? '', - artist_name: row.artist_name, - duration_sec: row.duration_sec, - stream_url: row.stream_url ?? '' - } as TrackRef) - : null - ); + // Reconstruct a minimal TrackRef for the kebab menu when the + // upstream track still exists. When unavailable, the menu is hidden. + const liveTrack = $derived(playlistTrackToRef(row)); let measuredRowHeight = 56; // sensible default; replaced on dragStart diff --git a/web/src/lib/playlists/playlistTrackToRef.ts b/web/src/lib/playlists/playlistTrackToRef.ts new file mode 100644 index 00000000..929c1d4e --- /dev/null +++ b/web/src/lib/playlists/playlistTrackToRef.ts @@ -0,0 +1,22 @@ +// Converts a PlaylistTrack row into the minimal TrackRef shape used +// by play queues + track menus. Centralizes the mapping so server +// additions (new TrackRef fields) only update one site. +// +// Returns null when the upstream track has been removed (track_id +// is null on the playlist row); callers should filter nulls. + +import type { PlaylistTrack, TrackRef } from '$lib/api/types'; + +export function playlistTrackToRef(row: PlaylistTrack): TrackRef | null { + if (!row.track_id) return null; + return { + id: row.track_id, + title: row.title, + album_id: row.album_id ?? '', + album_title: row.album_title, + artist_id: row.artist_id ?? '', + artist_name: row.artist_name, + duration_sec: row.duration_sec, + stream_url: row.stream_url ?? '' + }; +}