feat(track): show Qobuz subtitle/edition (version) in core renderers (#360)
Qobuz returns a separate `version` field for tracks belonging to remix albums, reissues, and deluxe editions (e.g. "Player's Ball Mix", "Nine Inch Noize Version", "Remastered 2024"). The mobile app shows it parenthesized after the title so remix albums are distinguishable from originals; qbz only showed the bare title, making remix albums impossible to navigate (issue #360). Backend ------- - Add `version: Option<String>` to qbz_models::Track (the V2 model consumed by qbz-qobuz/qbz-library) and to api::models::Track (the legacy struct still used by some V2 command paths). - Add `version: Option<String>` to qbz_models::QueueTrack so the field travels through the local queue, persistence, QConnect remote queue, and frontend snapshots without being lost in conversion. - Propagate `version` in every Track→QueueTrack converter: qconnect::model_track_to_core_queue_track, link_resolver::track_to_queue_track_from_api, mixtape::enqueue::track_to_queue_track_from_api and the inline builders. Plex/local builders set version=None (no edition concept outside Qobuz). - V2QueueTrack (the wire shape exposed to the frontend) gains the field with serde default so older clients keep deserializing. Frontend -------- - New `formatTrackTitle(track)` helper in src/lib/utils/trackTitle.ts: returns `title` when version is empty, else `title (version)`. - Add `version?: string | null` to QobuzTrack, Track (UI), PlaylistTrack, DisplayTrack, BackendQueueTrack, QueueTrack (UI), and PlayingTrack so the field flows from the wire to the renderer. - Propagate `version` through the four buildQueueTrackFrom* builders in trackActions.ts and through the QobuzAlbum→AlbumDetail adapter. - Use formatTrackTitle in the high-traffic renderers: - NowPlayingBar (currentTrack derived in routes/+page.svelte) - QueuePanel: now-playing label, upcoming-track rows, history rows - VirtualizedTrackList: default getTrackTitle accessor - AlbumDetailView: TrackRow title binding - PlaylistDetailView: TrackRow title binding - SearchView: popular-track card label and the two track-list rows (Tracks tab + cross-search "All" view) Tests ----- - 196 lib tests pass (no behavior change for the no-version path). - npm run check: 0 errors. Follow-up: more views still pass `{track.title}` directly (HomeView, ArtistDetailView, FavoritesView, ForYouTab, MixtapeCollectionDetailView, LocalLibraryView, the various Suggest/Top/Label views, the immersive panels, the miniplayer surface, and the Track*Modal stack). They all render UI Track-shaped objects that now carry version, so each is a mechanical "import formatTrackTitle + replace {track.title} with {formatTrackTitle(track)}". Doing them in a follow-up commit to keep this one reviewable. cc
This commit is contained in:
20 files changed
+118
-12
No files matched your search
+11
-3
@@ -184,6 +184,7 @@
|
||||
import { resolveAwardIdByName } from '$lib/stores/awardCatalogStore';
|
||||
import { getDefaultFavoritesTab } from '$lib/utils/favorites';
|
||||
import { platform } from '$lib/utils/platform';
|
||||
import { formatTrackTitle } from '$lib/utils/trackTitle';
|
||||
import type { FavoritesPreferences, ResolvedMusician } from '$lib/types';
|
||||
|
||||
// Mixtapes / Collections views and store
|
||||
@@ -1224,6 +1225,7 @@
|
||||
id: String(track.id),
|
||||
artwork: track.artwork_url || '',
|
||||
title: track.title,
|
||||
version: track.version ?? null,
|
||||
artist: track.artist,
|
||||
duration: formatDuration(track.duration_secs),
|
||||
trackId: track.id
|
||||
@@ -5478,6 +5480,7 @@
|
||||
id: String(trk.id),
|
||||
artwork: trk.artwork_url || '',
|
||||
title: trk.title,
|
||||
version: trk.version ?? null,
|
||||
artist: trk.artist,
|
||||
duration: formatDuration(trk.duration_secs),
|
||||
trackId: trk.id
|
||||
@@ -5521,11 +5524,16 @@
|
||||
});
|
||||
});
|
||||
|
||||
// Derived values for NowPlayingBar
|
||||
// Derived values for NowPlayingBar. `version` is propagated raw so
|
||||
// each render site can call formatTrackTitle() to compose the
|
||||
// displayed string. Keeping the field separate (instead of pre-
|
||||
// formatting here) lets components style title vs version
|
||||
// differently if desired (#360).
|
||||
const currentQueueTrack = $derived<QueueTrack | null>(currentTrack ? {
|
||||
id: String(currentTrack.id),
|
||||
artwork: currentTrack.artwork,
|
||||
title: currentTrack.title,
|
||||
version: currentTrack.version ?? null,
|
||||
artist: currentTrack.artist,
|
||||
duration: formatDuration(currentTrack.duration),
|
||||
trackId: currentTrack.id // For favorite checking in QueuePanel
|
||||
@@ -6484,7 +6492,7 @@
|
||||
{#if currentTrack}
|
||||
<NowPlayingBar
|
||||
artwork={resolvedArtwork}
|
||||
trackTitle={currentTrack.title}
|
||||
trackTitle={formatTrackTitle(currentTrack)}
|
||||
artist={currentTrack.artist}
|
||||
album={currentTrack.album}
|
||||
quality={currentTrack.quality}
|
||||
@@ -6586,7 +6594,7 @@
|
||||
if (isFocusModeOpen) closeFocusMode();
|
||||
}}
|
||||
artwork={resolvedArtwork}
|
||||
trackTitle={currentTrack.title}
|
||||
trackTitle={formatTrackTitle(currentTrack)}
|
||||
artist={currentTrack.artist}
|
||||
album={currentTrack.album}
|
||||
trackId={currentTrack.id}
|
||||
|
||||
Reference in new issue
Block a user