feat(mixtape): DiscographyBuilderView for Artist Collections
Cross-source artist discography builder. Fetches the artist's Qobuz releases (via v2_get_artist_page) and LocalLibrary albums by artist name (via v2_library_get_albums filtered client-side), normalizes titles to group variants of the same album, ranks each group by quality_score (bit_depth × 10M + sample_rate_hz + format_bonus), and renders a primary row + indented alternate rows per group. Compilations detected heuristically (best of / greatest hits / anthology / ...) and unchecked by default. Default selection: primary of each non-compilation group. User can check alternates to include them too. Order by release date / title / manual (preserves input order). Submit creates an artist_collection with source_type='artist_discography' + source_ref=artistId and enqueues items in display order. ArtistDetailView gains a "Build Artist Collection" action button (LibraryBig icon, secondary style, rendered only when callback prop is present). Entry point from CollectionsView left as-is (artist picker deferred). Added 'discography-builder' to ViewType union. Toast keys toast.collectionCreated / toast.collectionCreateFailed added to all five locale files (en, es, de, fr, pt). cc
This commit is contained in:
9 files changed
+880
-4
No files matched your search
+28
-2
@@ -189,6 +189,7 @@
|
||||
import MixtapesView from '$lib/components/views/MixtapesView.svelte';
|
||||
import CollectionsView from '$lib/components/views/CollectionsView.svelte';
|
||||
import MixtapeCollectionDetailView from '$lib/components/views/MixtapeCollectionDetailView.svelte';
|
||||
import DiscographyBuilderView from '$lib/components/views/DiscographyBuilderView.svelte';
|
||||
import {
|
||||
collectionsStore,
|
||||
createCollection,
|
||||
@@ -572,6 +573,7 @@
|
||||
|
||||
// Mixtape / Collection routing state
|
||||
let mixtapeDetailId = $state<string | null>(null);
|
||||
let discographyArtistId = $state<string | null>(null);
|
||||
let showCreateModal = $state(false);
|
||||
let createModalKind = $state<CollectionKind>('mixtape');
|
||||
let createModalName = $state('');
|
||||
@@ -5454,6 +5456,10 @@
|
||||
onLabelClick={handleLabelClick}
|
||||
onMusicianClick={handleMusicianClick}
|
||||
onLocationClick={handleLocationClick}
|
||||
onBuildArtistCollection={(artistId) => {
|
||||
discographyArtistId = artistId;
|
||||
navTo('discography-builder', artistId);
|
||||
}}
|
||||
activeTrackId={currentTrack?.id ?? null}
|
||||
isPlaybackActive={isPlaying}
|
||||
/>
|
||||
@@ -5941,8 +5947,8 @@
|
||||
onOpen={(id) => openMixtapeDetail(id)}
|
||||
onCreate={() => openCreateModal('collection')}
|
||||
onBuildArtistCollection={() => {
|
||||
// TODO: Phase 8 — open DiscographyBuilderView
|
||||
console.log('[+page] Build Artist Collection clicked — Phase 8 feature');
|
||||
// Artist picker not yet available from CollectionsView — entry point lives in ArtistDetailView
|
||||
console.log('[+page] Build Artist Collection: use ArtistDetailView entry point');
|
||||
}}
|
||||
/>
|
||||
{:else if activeView === 'mixtape-detail'}
|
||||
@@ -5964,6 +5970,26 @@
|
||||
<p>No collection selected.</p>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if activeView === 'discography-builder'}
|
||||
{#if discographyArtistId}
|
||||
<DiscographyBuilderView
|
||||
artistId={discographyArtistId}
|
||||
onBack={() => {
|
||||
const prevId = discographyArtistId;
|
||||
discographyArtistId = null;
|
||||
navigateTo('artist', prevId ?? undefined);
|
||||
}}
|
||||
onCreated={(col) => {
|
||||
discographyArtistId = null;
|
||||
mixtapeDetailId = col.id;
|
||||
navTo('mixtape-detail', col.id);
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="detail-placeholder">
|
||||
<p>No artist selected.</p>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<!-- Catch-all fallback: view has no matching data, show loading/error -->
|
||||
<div class="view-error">
|
||||
|
||||
Reference in new issue
Block a user