initial state: muzick music player + recommendation engine
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { X } from 'lucide-react';
|
||||
import { usePlaybackStore } from '../store/usePlaybackStore';
|
||||
import { trackService } from '../services/trackService';
|
||||
import { albumService } from '../services/albumService';
|
||||
import { Artwork } from './Artwork';
|
||||
import { ArtistLinks } from './ArtistLinks';
|
||||
import { SyncedLyrics } from './SyncedLyrics';
|
||||
|
||||
/**
|
||||
* Dedicated, roomy lyrics view — overlays the content area (not the playback
|
||||
* bar, so transport stays usable). Separated from the queue sidebar so the
|
||||
* karaoke lyrics get the space they deserve.
|
||||
*/
|
||||
export function LyricsOverlay({ onClose }: { onClose: () => void }) {
|
||||
const currentTrack = usePlaybackStore((s) => s.currentTrack);
|
||||
|
||||
const albumQ = useQuery({
|
||||
queryKey: ['album', currentTrack?.album_id],
|
||||
queryFn: () => albumService.getAlbum(currentTrack!.album_id),
|
||||
enabled: !!currentTrack?.album_id,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
const lyricsQ = useQuery({
|
||||
queryKey: ['lyrics', currentTrack?.id],
|
||||
queryFn: () => trackService.getLyrics(currentTrack!.id),
|
||||
enabled: !!currentTrack,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 z-40 glass flex flex-col animate-rise">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-4 px-6 py-4 border-b border-border/70">
|
||||
{currentTrack && (
|
||||
<div className="w-12 h-12 flex-none rounded-lg overflow-hidden shadow-md shadow-black/40">
|
||||
<Artwork seed={`${currentTrack.title} ${currentTrack.artist}`} src={albumQ.data?.artwork_id} className="w-full h-full" rounded="lg" />
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-lg font-bold text-text truncate">{currentTrack?.title ?? 'Lyrics'}</div>
|
||||
{currentTrack && (
|
||||
<ArtistLinks artists={currentTrack.artists} fallback={currentTrack.artist} className="block text-sm text-muted truncate" />
|
||||
)}
|
||||
</div>
|
||||
<button onClick={onClose} aria-label="Close lyrics" className="rounded-md p-2 text-muted hover:text-text hover:bg-surface1 transition-colors">
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Lyrics */}
|
||||
<div
|
||||
className="flex-1 overflow-y-auto mx-auto w-full max-w-2xl"
|
||||
style={{
|
||||
WebkitMaskImage: 'linear-gradient(to bottom, transparent 0, #000 10%, #000 90%, transparent 100%)',
|
||||
maskImage: 'linear-gradient(to bottom, transparent 0, #000 10%, #000 90%, transparent 100%)',
|
||||
}}
|
||||
>
|
||||
{!currentTrack ? (
|
||||
<p className="px-6 py-10 text-center text-sm text-muted italic">Nothing playing.</p>
|
||||
) : lyricsQ.isLoading ? (
|
||||
<p className="px-6 py-10 text-center text-sm text-muted">Loading…</p>
|
||||
) : lyricsQ.isError || !lyricsQ.data ? (
|
||||
<p className="px-6 py-10 text-center text-sm text-muted italic">No lyrics available.</p>
|
||||
) : (
|
||||
<SyncedLyrics synced={lyricsQ.data.synced_lyrics} plain={lyricsQ.data.lyrics_text} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user