feat(phase37): implement clickable context tooltip navigation
StackIcon Component:
- Now accepts onClick prop
- Changed from div to button element
- Added arrow (→) that appears on hover
- Tooltip text changed to "Click to go to: {source}"
- Visual feedback: hover background + active scale
Navigation Logic (+page.svelte):
- Added handleContextNavigation function
- Switch based on context_type:
- album → handleAlbumClick
- playlist → navigate + set selectedPlaylistId
- artist_top → handleArtistClick
- favorites → navigate to favorites
- home_list → navigate to home
- search → navigate to search
- Import getCurrentContext from playbackContextStore
UI Integration:
- ExpandedPlayer: Added onContextClick prop, passed to StackIcon
- FocusMode: Added onContextClick prop, passed to StackIcon
- Both receive handleContextNavigation callback
Phase 3.7 COMPLETE ✅
cp
This commit is contained in:
1 parent
b070884ee8
commit
584650fee1
4 files changed
+112
-7
No files matched your search
@@ -50,6 +50,7 @@
|
||||
onOpenFocusMode?: () => void;
|
||||
onCast?: () => void;
|
||||
isCastConnected?: boolean;
|
||||
onContextClick?: () => void;
|
||||
// Lyrics props
|
||||
lyricsLines?: LyricsLine[];
|
||||
lyricsActiveIndex?: number;
|
||||
@@ -89,6 +90,7 @@
|
||||
onOpenFocusMode,
|
||||
onCast,
|
||||
isCastConnected = false,
|
||||
onContextClick,
|
||||
lyricsLines = [],
|
||||
lyricsActiveIndex = -1,
|
||||
lyricsActiveProgress = 0,
|
||||
@@ -247,7 +249,7 @@
|
||||
<div class="track-info">
|
||||
<h1 class="title">{trackTitle}</h1>
|
||||
<div class="artist-album-row">
|
||||
<StackIcon size={16} class="stack-icon" />
|
||||
<StackIcon size={16} class="stack-icon" onClick={onContextClick} />
|
||||
<h2 class="artist">{artist}</h2>
|
||||
<span class="separator">·</span>
|
||||
<h3 class="album">{album}</h3>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
onSeek: (time: number) => void;
|
||||
onContextClick?: () => void;
|
||||
// Lyrics props
|
||||
lyricsLines?: LyricsLine[];
|
||||
lyricsActiveIndex?: number;
|
||||
@@ -44,6 +45,7 @@
|
||||
currentTime,
|
||||
duration,
|
||||
onSeek,
|
||||
onContextClick,
|
||||
lyricsLines = [],
|
||||
lyricsActiveIndex = -1,
|
||||
lyricsActiveProgress = 0,
|
||||
@@ -187,7 +189,7 @@
|
||||
<div class="track-meta">
|
||||
<div class="track-title">{trackTitle}</div>
|
||||
<div class="track-artist-row">
|
||||
<StackIcon size={12} class="stack-icon" />
|
||||
<StackIcon size={12} class="stack-icon" onClick={onContextClick} />
|
||||
<div class="track-artist">{artist}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,14 +6,23 @@
|
||||
interface Props {
|
||||
size?: number;
|
||||
class?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
let { size = 16, class: className = '' }: Props = $props();
|
||||
let { size = 16, class: className = '', onClick }: Props = $props();
|
||||
|
||||
let context = $state(getCurrentContext());
|
||||
let displayInfo = $state(context ? getContextDisplayInfo() : null);
|
||||
let showIcon = $state(getCachedPreferences().show_context_icon);
|
||||
|
||||
function handleClick(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
if (onClick) {
|
||||
console.log('[StackIcon] Clicked - navigating to source');
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to context changes
|
||||
$effect(() => {
|
||||
const unsubscribe = subscribe(() => {
|
||||
@@ -43,9 +52,15 @@
|
||||
</script>
|
||||
|
||||
{#if context && displayInfo && showIcon}
|
||||
<div class="stack-icon-wrapper {className}" title="Playing from: {displayInfo}">
|
||||
<button
|
||||
class="stack-icon-wrapper {className}"
|
||||
title="Click to go to: {displayInfo}"
|
||||
onclick={handleClick}
|
||||
type="button"
|
||||
>
|
||||
<Layers size={size} strokeWidth={2} />
|
||||
</div>
|
||||
<span class="arrow">→</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
@@ -53,13 +68,36 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 4px 6px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.stack-icon-wrapper:hover {
|
||||
opacity: 1;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.stack-icon-wrapper:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.arrow {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
margin-left: -2px;
|
||||
}
|
||||
|
||||
.stack-icon-wrapper:hover .arrow {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
+64
-1
@@ -29,7 +29,8 @@
|
||||
import {
|
||||
initPlaybackContextStore,
|
||||
setPlaybackContext,
|
||||
clearPlaybackContext
|
||||
clearPlaybackContext,
|
||||
getCurrentContext
|
||||
} from '$lib/stores/playbackContextStore';
|
||||
import {
|
||||
initPlaybackPreferences,
|
||||
@@ -371,6 +372,66 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the source of current playback context
|
||||
*/
|
||||
async function handleContextNavigation() {
|
||||
const context = getCurrentContext();
|
||||
if (!context) {
|
||||
console.log('[ContextNav] No context available');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[ContextNav] Navigating to:', context);
|
||||
|
||||
try {
|
||||
switch (context.context_type) {
|
||||
case 'album':
|
||||
// Navigate to album page
|
||||
await handleAlbumClick(context.context_id);
|
||||
break;
|
||||
|
||||
case 'playlist':
|
||||
// Navigate to playlist page
|
||||
const playlistId = parseInt(context.context_id);
|
||||
if (!isNaN(playlistId)) {
|
||||
selectedPlaylistId = playlistId;
|
||||
navigateTo('playlist');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'artist_top':
|
||||
// Navigate to artist page
|
||||
const artistId = parseInt(context.context_id);
|
||||
if (!isNaN(artistId)) {
|
||||
await handleArtistClick(artistId);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'favorites':
|
||||
// Navigate to favorites page
|
||||
navigateTo('favorites');
|
||||
break;
|
||||
|
||||
case 'home_list':
|
||||
// Navigate to home page
|
||||
navigateTo('home');
|
||||
break;
|
||||
|
||||
case 'search':
|
||||
// Navigate to search (could restore query if needed)
|
||||
navigateTo('search');
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('[ContextNav] Unknown context type:', context.context_type);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[ContextNav] Navigation failed:', err);
|
||||
showToast('Failed to navigate to source', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
interface ArtistAlbumsResponse {
|
||||
items: QobuzAlbum[];
|
||||
total: number;
|
||||
@@ -2201,6 +2262,7 @@
|
||||
handleAlbumClick(currentTrack.albumId);
|
||||
}
|
||||
}}
|
||||
onContextClick={handleContextNavigation}
|
||||
/>
|
||||
{:else}
|
||||
<NowPlayingBar
|
||||
@@ -2293,6 +2355,7 @@
|
||||
lyricsSynced={lyricsIsSynced}
|
||||
lyricsLoading={lyricsStatus === 'loading'}
|
||||
lyricsError={lyricsStatus === 'error' ? lyricsError : (lyricsStatus === 'not_found' ? 'No lyrics found' : null)}
|
||||
onContextClick={handleContextNavigation}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
||||
Reference in new issue
Block a user