73 lines
3.0 KiB
TypeScript
73 lines
3.0 KiB
TypeScript
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>
|
|
);
|
|
}
|