fix(ui): one transport for every control, and a queue panel that fits
The queue panel drove the playback store directly, so its buttons played locally while another device held the audio. Both control sets now go through one transport that forwards a press when the audio is elsewhere. The panel's artwork is capped against viewport height too: at full width on a phone the square alone pushed Up Next off the screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { Artwork } from './Artwork';
|
||||
import { ArtistLinks } from './ArtistLinks';
|
||||
import { TrackRow, formatDuration } from './TrackRow';
|
||||
import { albumService } from '../services/albumService';
|
||||
import { useTransport } from '../hooks/useTransport';
|
||||
|
||||
export function NowPlayingPanel({ onClose }: { onClose: () => void }) {
|
||||
const panelRef = useRef<HTMLElement>(null);
|
||||
@@ -34,7 +35,8 @@ export function NowPlayingPanel({ onClose }: { onClose: () => void }) {
|
||||
previous?.focus();
|
||||
};
|
||||
}, []);
|
||||
const { currentTrack, queue, isPlaying, position, duration, play, pause, next, prev, setPosition } = usePlaybackStore();
|
||||
const { currentTrack, queue, isPlaying, position, duration } = usePlaybackStore();
|
||||
const transport = useTransport();
|
||||
|
||||
const currentIdx = currentTrack ? queue.findIndex((t) => t.id === currentTrack.id) : -1;
|
||||
const upNext = currentIdx >= 0 ? queue.slice(currentIdx + 1) : queue;
|
||||
@@ -56,17 +58,21 @@ export function NowPlayingPanel({ onClose }: { onClose: () => void }) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* The artwork below is capped against viewport height, not just width.
|
||||
At full width on a phone the square alone is taller than the space
|
||||
between the top bar and the transport, which pushed Up Next — the
|
||||
reason the panel opens — entirely off the screen. */}
|
||||
<div className="p-3 space-y-3 sm:p-4 sm:space-y-4">
|
||||
{currentTrack?.album_id ? (
|
||||
<Link to="/albums/$albumId" params={{ albumId: currentTrack.album_id }}
|
||||
className="group mx-auto block aspect-square w-full max-w-sm rounded-xl overflow-hidden relative shadow-lg shadow-black/40" title="Go to album">
|
||||
className="group mx-auto block aspect-square w-full max-w-[min(100%,34vh)] rounded-xl overflow-hidden relative shadow-lg shadow-black/40" title="Go to album">
|
||||
<Artwork seed={`${currentTrack.title} ${currentTrack.artist}`} src={artwork} className="w-full h-full transition-transform group-hover:scale-105" rounded="xl" />
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/0 group-hover:bg-black/40 transition-colors">
|
||||
<Disc3 size={28} className="text-on-accent opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<div className="mx-auto aspect-square w-full max-w-sm rounded-xl overflow-hidden shadow-lg shadow-black/40">
|
||||
<div className="mx-auto aspect-square w-full max-w-[min(100%,34vh)] rounded-xl overflow-hidden shadow-lg shadow-black/40">
|
||||
<Artwork seed={currentTrack ? `${currentTrack.title} ${currentTrack.artist}` : 'empty'} src={artwork} className="w-full h-full" rounded="xl" />
|
||||
</div>
|
||||
)}
|
||||
@@ -90,7 +96,7 @@ export function NowPlayingPanel({ onClose }: { onClose: () => void }) {
|
||||
<input
|
||||
type="range" min={0} max={Math.max(duration, 0.1)} step={0.1}
|
||||
value={Math.min(position, duration || 0)}
|
||||
onChange={(e) => setPosition(Number(e.target.value))}
|
||||
onChange={(e) => transport.seek(Number(e.target.value))}
|
||||
disabled={!currentTrack || duration <= 0}
|
||||
className="w-full cursor-pointer"
|
||||
/>
|
||||
@@ -101,15 +107,16 @@ export function NowPlayingPanel({ onClose }: { onClose: () => void }) {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-4 sm:gap-6">
|
||||
<button onClick={prev} aria-label="Previous" className="rounded-md p-2 text-muted hover:text-text hover:bg-surface0"><SkipBack size={20} /></button>
|
||||
<button onClick={transport.prev} aria-label="Previous" className="rounded-md p-2 text-muted hover:text-text hover:bg-surface0"><SkipBack size={20} /></button>
|
||||
<button
|
||||
onClick={() => isPlaying ? pause() : play()}
|
||||
onClick={transport.toggle}
|
||||
aria-label={isPlaying ? 'Pause' : 'Play'}
|
||||
disabled={!currentTrack}
|
||||
className="transport-btn"
|
||||
>
|
||||
{isPlaying ? <Pause size={18} fill="currentColor" /> : <Play size={18} fill="currentColor" />}
|
||||
</button>
|
||||
<button onClick={next} aria-label="Next" className="rounded-md p-2 text-muted hover:text-text hover:bg-surface0"><SkipForward size={20} /></button>
|
||||
<button onClick={transport.next} aria-label="Next" className="rounded-md p-2 text-muted hover:text-text hover:bg-surface0"><SkipForward size={20} /></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user