fix(label): jump-nav below header, track play resilience, description fallback
1. Move jump-nav below header (was above), match ArtistDetailView margins 2. Wrap handleTrackPlay context/queue in try-catch so onTrackPlay always fires 3. Add description fallback: fetch from v2_get_label if label/page omits it 4. Add detailed console logging for track play debugging
This commit is contained in:
1 parent
4c2a63239e
commit
4f223ba31a
1 file changed
+90
-44
@@ -114,6 +114,7 @@
|
|||||||
|
|
||||||
// Description expand
|
// Description expand
|
||||||
let descriptionExpanded = $state(false);
|
let descriptionExpanded = $state(false);
|
||||||
|
let labelDescription = $state<string | null>(null);
|
||||||
|
|
||||||
// Favorites reactivity
|
// Favorites reactivity
|
||||||
let trackFavoritesVersion = $state(0);
|
let trackFavoritesVersion = $state(0);
|
||||||
@@ -256,6 +257,11 @@
|
|||||||
|
|
||||||
console.log('[LabelView] Raw label page data:', JSON.stringify(result).slice(0, 500));
|
console.log('[LabelView] Raw label page data:', JSON.stringify(result).slice(0, 500));
|
||||||
|
|
||||||
|
// Set description from page data
|
||||||
|
if (result.description) {
|
||||||
|
labelDescription = result.description;
|
||||||
|
}
|
||||||
|
|
||||||
// Parse top tracks
|
// Parse top tracks
|
||||||
if (result.top_tracks && result.top_tracks.length > 0) {
|
if (result.top_tracks && result.top_tracks.length > 0) {
|
||||||
topTracks = parseTopTracks(result.top_tracks as Record<string, unknown>[]);
|
topTracks = parseTopTracks(result.top_tracks as Record<string, unknown>[]);
|
||||||
@@ -314,6 +320,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadLabelDescription() {
|
||||||
|
// Fallback: fetch description from v2_get_label if label/page didn't include it
|
||||||
|
try {
|
||||||
|
const detail = await invoke<{ description?: string }>('v2_get_label', {
|
||||||
|
labelId,
|
||||||
|
limit: 1,
|
||||||
|
offset: 0
|
||||||
|
});
|
||||||
|
if (detail?.description) {
|
||||||
|
labelDescription = detail.description;
|
||||||
|
console.log('[LabelView] Description loaded from v2_get_label fallback');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[LabelView] Failed to load label description fallback:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadMoreLabels() {
|
async function loadMoreLabels() {
|
||||||
try {
|
try {
|
||||||
const result = await invoke<{ has_more?: boolean; items?: LabelExploreItem[] }>(
|
const result = await invoke<{ has_more?: boolean; items?: LabelExploreItem[] }>(
|
||||||
@@ -346,30 +369,37 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleTrackPlay(track: Track, trackIndex?: number) {
|
async function handleTrackPlay(track: Track, trackIndex?: number) {
|
||||||
if (topTracks.length > 0) {
|
console.log('[LabelView] handleTrackPlay called:', { trackId: track.id, title: track.title, trackIndex });
|
||||||
const trackIds = topTracks.map((trk) => trk.id);
|
|
||||||
const index = trackIndex !== undefined ? trackIndex : trackIds.indexOf(track.id);
|
// Set context and queue — wrapped in try-catch so playback still starts even if context fails
|
||||||
|
try {
|
||||||
|
if (topTracks.length > 0) {
|
||||||
|
const trackIds = topTracks.map((trk) => trk.id);
|
||||||
|
const index = trackIndex !== undefined ? trackIndex : trackIds.indexOf(track.id);
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
await setPlaybackContext(
|
||||||
|
'label_top',
|
||||||
|
labelId.toString(),
|
||||||
|
pageData?.name || labelName,
|
||||||
|
'qobuz',
|
||||||
|
trackIds,
|
||||||
|
index
|
||||||
|
);
|
||||||
|
console.log(`[LabelView] Context set: "${pageData?.name}" top tracks, ${trackIds.length} tracks, index ${index}`);
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
await setPlaybackContext(
|
|
||||||
'label_top',
|
|
||||||
labelId.toString(),
|
|
||||||
pageData?.name || labelName,
|
|
||||||
'qobuz',
|
|
||||||
trackIds,
|
|
||||||
index
|
|
||||||
);
|
|
||||||
console.log(`[LabelView] Context created: "${pageData?.name}" top tracks, ${trackIds.length} tracks, starting at ${index}`);
|
|
||||||
try {
|
|
||||||
const queueTracks = buildTopTracksQueue(topTracks);
|
const queueTracks = buildTopTracksQueue(topTracks);
|
||||||
await invoke('v2_set_queue', { tracks: queueTracks, startIndex: index });
|
await invoke('v2_set_queue', { tracks: queueTracks, startIndex: index });
|
||||||
} catch (err) {
|
console.log('[LabelView] Queue set successfully');
|
||||||
console.error('Failed to set queue:', err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[LabelView] Failed to set context/queue (continuing to play):', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always fire play — even if context/queue setup failed
|
||||||
if (onTrackPlay) {
|
if (onTrackPlay) {
|
||||||
|
console.log('[LabelView] Calling onTrackPlay with track:', track.id);
|
||||||
onTrackPlay({
|
onTrackPlay({
|
||||||
id: track.id,
|
id: track.id,
|
||||||
title: track.title,
|
title: track.title,
|
||||||
@@ -385,12 +415,22 @@
|
|||||||
artistId: track.performer?.id,
|
artistId: track.performer?.id,
|
||||||
isrc: track.isrc,
|
isrc: track.isrc,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('[LabelView] onTrackPlay is not defined!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handlePlayAllTracks() {
|
async function handlePlayAllTracks() {
|
||||||
if (topTracks.length === 0 || !onTrackPlay) return;
|
if (topTracks.length === 0) return;
|
||||||
await handleTrackPlay(topTracks[0], 0);
|
if (!onTrackPlay) {
|
||||||
|
console.warn('[LabelView] handlePlayAllTracks: onTrackPlay is not defined!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await handleTrackPlay(topTracks[0], 0);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[LabelView] handlePlayAllTracks failed:', err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePlayAllTracksNext() {
|
function handlePlayAllTracksNext() {
|
||||||
@@ -493,7 +533,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
loadLabelPage();
|
loadLabelPage().then(() => {
|
||||||
|
// If page data didn't include description, fetch it separately
|
||||||
|
if (!labelDescription) loadLabelDescription();
|
||||||
|
});
|
||||||
loadMoreLabels();
|
loadMoreLabels();
|
||||||
unsubFavorites = subscribeFavorites(() => {
|
unsubFavorites = subscribeFavorites(() => {
|
||||||
trackFavoritesVersion++;
|
trackFavoritesVersion++;
|
||||||
@@ -518,7 +561,10 @@
|
|||||||
playlists = [];
|
playlists = [];
|
||||||
artists = [];
|
artists = [];
|
||||||
moreLabels = [];
|
moreLabels = [];
|
||||||
loadLabelPage();
|
labelDescription = null;
|
||||||
|
loadLabelPage().then(() => {
|
||||||
|
if (!labelDescription) loadLabelDescription();
|
||||||
|
});
|
||||||
loadMoreLabels();
|
loadMoreLabels();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -594,26 +640,6 @@
|
|||||||
<span>{$t('actions.back')}</span>
|
<span>{$t('actions.back')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Jump Nav -->
|
|
||||||
{#if showJumpNav}
|
|
||||||
<div class="jump-nav">
|
|
||||||
<div class="jump-nav-left">
|
|
||||||
<div class="jump-label">Jump to</div>
|
|
||||||
<div class="jump-links">
|
|
||||||
{#each jumpSections as section}
|
|
||||||
<button
|
|
||||||
class="jump-link"
|
|
||||||
class:active={activeJumpSection === section.id}
|
|
||||||
onclick={() => scrollToSection(section.el, section.id)}
|
|
||||||
>
|
|
||||||
{$t(section.labelKey)}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="label-header section-anchor" bind:this={headerSection}>
|
<header class="label-header section-anchor" bind:this={headerSection}>
|
||||||
<div class="label-image-wrapper">
|
<div class="label-image-wrapper">
|
||||||
@@ -634,9 +660,9 @@
|
|||||||
<div class="label-header-info">
|
<div class="label-header-info">
|
||||||
<div class="label-subtitle">{$t('label.title')}</div>
|
<div class="label-subtitle">{$t('label.title')}</div>
|
||||||
<h1 class="label-name">{pageData.name}</h1>
|
<h1 class="label-name">{pageData.name}</h1>
|
||||||
{#if pageData.description}
|
{#if labelDescription}
|
||||||
<div class="label-description" class:expanded={descriptionExpanded}>
|
<div class="label-description" class:expanded={descriptionExpanded}>
|
||||||
{@html pageData.description}
|
{@html labelDescription}
|
||||||
</div>
|
</div>
|
||||||
<button class="read-more-btn" onclick={() => descriptionExpanded = !descriptionExpanded}>
|
<button class="read-more-btn" onclick={() => descriptionExpanded = !descriptionExpanded}>
|
||||||
{descriptionExpanded ? $t('label.readLess') : $t('label.readMore')}
|
{descriptionExpanded ? $t('label.readLess') : $t('label.readMore')}
|
||||||
@@ -645,6 +671,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<!-- Jump Nav (below header, sticky on scroll) -->
|
||||||
|
{#if showJumpNav}
|
||||||
|
<div class="jump-nav">
|
||||||
|
<div class="jump-nav-left">
|
||||||
|
<div class="jump-label">Jump to</div>
|
||||||
|
<div class="jump-links">
|
||||||
|
{#each jumpSections as section}
|
||||||
|
<button
|
||||||
|
class="jump-link"
|
||||||
|
class:active={activeJumpSection === section.id}
|
||||||
|
onclick={() => scrollToSection(section.el, section.id)}
|
||||||
|
>
|
||||||
|
{$t(section.labelKey)}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- Popular Tracks (mirrors ArtistDetailView exactly) -->
|
<!-- Popular Tracks (mirrors ArtistDetailView exactly) -->
|
||||||
{#if topTracks.length > 0}
|
{#if topTracks.length > 0}
|
||||||
<div class="top-tracks-section section-anchor" bind:this={popularTracksSection}>
|
<div class="top-tracks-section section-anchor" bind:this={popularTracksSection}>
|
||||||
@@ -1027,8 +1073,8 @@
|
|||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
border-bottom: 1px solid var(--alpha-6);
|
border-bottom: 1px solid var(--alpha-6);
|
||||||
box-shadow: 0 4px 8px -4px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 4px 8px -4px rgba(0, 0, 0, 0.5);
|
||||||
margin: 0 -8px 24px -18px;
|
margin: 0 -8px 24px -24px;
|
||||||
width: calc(100% + 26px);
|
width: calc(100% + 32px);
|
||||||
}
|
}
|
||||||
.jump-nav-left { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
|
.jump-nav-left { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
|
||||||
.jump-label { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; }
|
.jump-label { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; }
|
||||||
|
|||||||
Reference in new issue
Block a user