import { useEffect, useRef, useState } from 'react' export type QueueItem = { slug: string title: string artist: string album_slug: string | null duration: number | null } type QueueListProps = { apiRoot: string queue: QueueItem[] order: number[] playingOrigIdx: number scrollSignal: number onPlay: (origIdx: number) => void onRemove: (origIdx: number) => void onMove: (fromPos: number, toPos: number) => void } function pad(n: number) { return String(n).padStart(2, '0') } function fmt(secs: number) { if (!secs || Number.isNaN(secs)) return '0:00' const s = Math.floor(secs) const m = Math.floor(s / 60) const h = Math.floor(m / 60) if (h > 0) return `${h}:${pad(m % 60)}:${pad(s % 60)}` return `${m}:${pad(s % 60)}` } function Cover({ src }: { src: string }) { const [errored, setErrored] = useState(false) useEffect(() => { setErrored(false) }, [src]) if (errored) return <>🎵 return setErrored(true)} /> } export function QueueList({ apiRoot, queue, order, playingOrigIdx, scrollSignal, onPlay, onRemove, onMove, }: QueueListProps) { const playingRef = useRef(null) const [draggingPos, setDraggingPos] = useState(null) const [dragOverPos, setDragOverPos] = useState(null) useEffect(() => { if (playingRef.current) { playingRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) } }, [playingOrigIdx, scrollSignal]) if (!queue.length) { return (
🎵
Select an album to start
) } return ( <> {order.map((origIdx, pos) => { const t = queue[origIdx] if (!t) return null const isPlaying = origIdx === playingOrigIdx const coverSrc = t.album_slug ? `${apiRoot}/tracks/${t.slug}/cover` : '' const dur = t.duration ? fmt(t.duration) : '' const isDragging = draggingPos === pos const isDragOver = dragOverPos === pos return (
onPlay(origIdx)} onDragStart={(e) => { setDraggingPos(pos) e.dataTransfer?.setData('text/plain', String(pos)) }} onDragEnd={() => { setDraggingPos(null) setDragOverPos(null) }} onDragOver={(e) => { e.preventDefault() }} onDragEnter={() => { setDragOverPos(pos) }} onDragLeave={() => { setDragOverPos((cur) => (cur === pos ? null : cur)) }} onDrop={(e) => { e.preventDefault() setDragOverPos(null) const from = parseInt(e.dataTransfer?.getData('text/plain') ?? '', 10) if (!Number.isNaN(from)) onMove(from, pos) setDraggingPos(null) }} > {isPlaying ? '' : pos + 1}
{coverSrc ? : <>🎵}
{t.title}
{t.artist || ''}
{dur}
) })} ) }