music (web): click the dock track info to open its album in /music

The bottom player dock's cover + title/artist is now a button — clicking it sets
the music:cwd channel to the playing album and navigates to /music, landing on
that album's tracklist (current track highlighted). Uses the global panel
channel + react-router navigate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 12:55:53 +00:00
co-authored by Claude Opus 4.8
parent 5b17121de8
commit 3f2efbf852
@@ -1,9 +1,11 @@
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router';
import { useClient } from 'hooks/useClient';
import { usePanelChannel } from 'hooks/usePanelChannel';
import { Play, Pause, SkipBack, SkipForward, X, Volume2, VolumeX } from 'lucide-react';
import { useSeekBar, SeekBar } from '../apps/FileViewer/renderers/SeekBar';
import { MusicHeart } from '../apps/Music/MusicHeart';
import { sortTracks, trackHomePath, type AlbumMeta, type NowPlaying } from '../apps/Music/shared';
import { MUSIC_ROOT, MUSIC_CWD_CHANNEL, sortTracks, trackHomePath, type AlbumMeta, type NowPlaying } from '../apps/Music/shared';
import { useMusicPlayer, type PlayerTrack } from './useMusicPlayer';
// Mounted once in the persistent DashboardLayout (outside <Routes>), so it owns the single <audio>
@@ -15,6 +17,8 @@ const fmt = (s: number): string =>
export const MusicPlayerHost = () => {
const { token, get, put } = useClient();
const navigate = useNavigate();
const [, setCwd] = usePanelChannel<string | null>(MUSIC_CWD_CHANNEL, null);
const { current, index, queue, playing, toggle, next, prev, setPlaying, close, loadQueue } = useMusicPlayer();
const audioRef = useRef<HTMLAudioElement>(null);
const [position, setPosition] = useState(0);
@@ -128,6 +132,13 @@ export const MusicPlayerHost = () => {
const subtitle = current?.artist ?? current?.albumRel.split('/').slice(-2, -1)[0] ?? '';
const pct = duration ? (position / duration) * 100 : 0;
// Clicking the track info jumps the /music library to the playing album (and navigates there).
const openCurrentAlbum = () => {
if (!current) return;
setCwd(`${MUSIC_ROOT}/${current.albumRel}`);
navigate('/music');
};
return (
<>
<audio
@@ -148,21 +159,28 @@ export const MusicPlayerHost = () => {
/>
{current && (
<div className="fixed inset-x-0 bottom-0 z-50 flex items-center gap-3 border-t border-border bg-card/95 px-4 py-2 backdrop-blur">
{/* cover + info */}
<div className="flex h-11 w-11 shrink-0 items-center justify-center overflow-hidden rounded bg-muted">
<img
src={coverUrl(current.albumRel)}
alt=""
className="h-full w-full object-cover"
onError={(e) => {
(e.currentTarget as HTMLImageElement).style.visibility = 'hidden';
}}
/>
</div>
<div className="hidden w-44 shrink-0 sm:block">
<p className="truncate text-sm font-medium text-foreground">{current.title ?? current.file}</p>
<p className="truncate text-xs text-muted-foreground">{subtitle}</p>
</div>
{/* cover + info — click to open this album in /music */}
<button
type="button"
onClick={openCurrentAlbum}
title="Show in library"
className="flex min-w-0 items-center gap-3 rounded text-left transition-opacity hover:opacity-80"
>
<div className="flex h-11 w-11 shrink-0 items-center justify-center overflow-hidden rounded bg-muted">
<img
src={coverUrl(current.albumRel)}
alt=""
className="h-full w-full object-cover"
onError={(e) => {
(e.currentTarget as HTMLImageElement).style.visibility = 'hidden';
}}
/>
</div>
<div className="hidden w-44 shrink-0 sm:block">
<p className="truncate text-sm font-medium text-foreground">{current.title ?? current.file}</p>
<p className="truncate text-xs text-muted-foreground">{subtitle}</p>
</div>
</button>
{/* transport */}
<div className="flex shrink-0 items-center gap-1">