import React from 'react'; import { Play } from 'lucide-react'; import type { TFunction } from 'i18next'; import OverlayScrollArea from '../OverlayScrollArea'; import { usePlayerStore } from '../../store/playerStore'; import { useLuckyMixStore } from '../../store/luckyMixStore'; import type { Track, PlayerState } from '../../store/playerStoreTypes'; import { formatTime } from '../../utils/queuePanelHelpers'; type StartDrag = ( payload: { data: string; label: string }, x: number, y: number, ) => void; interface Props { queue: Track[]; queueIndex: number; contextMenu: PlayerState['contextMenu']; playTrack: PlayerState['playTrack']; activeTab: string; queueListRef: React.RefObject; suppressNextAutoScrollRef: React.MutableRefObject; isQueueDrag: boolean; psyDragFromIdxRef: React.MutableRefObject; externalDropTarget: { idx: number; before: boolean } | null; startDrag: StartDrag; orbitAttributionLabel: (trackId: string) => string | null; luckyRolling: boolean; t: TFunction; } export function QueueList({ queue, queueIndex, contextMenu, playTrack, activeTab, queueListRef, suppressNextAutoScrollRef, isQueueDrag, psyDragFromIdxRef, externalDropTarget, startDrag, orbitAttributionLabel, luckyRolling, t, }: Props) { return ( {queue.length === 0 ? (
{t('queue.emptyQueue')}
) : ( <> {queue.map((track, idx) => { const isPlaying = idx === queueIndex; const isFirstAutoAdded = track.autoAdded && (idx === 0 || !queue[idx - 1].autoAdded); const isFirstRadioAdded = track.radioAdded && (idx === 0 || !queue[idx - 1].radioAdded); let dragStyle: React.CSSProperties = {}; if (isQueueDrag && psyDragFromIdxRef.current === idx) { dragStyle = { opacity: 0.4, background: 'var(--bg-hover)' }; } else if (isQueueDrag && externalDropTarget?.idx === idx) { if (externalDropTarget.before) { dragStyle = { borderTop: '2px solid var(--accent)', paddingTop: '6px', marginTop: '-2px' }; } else { dragStyle = { borderBottom: '2px solid var(--accent)', paddingBottom: '6px', marginBottom: '-2px' }; } } return ( {isFirstRadioAdded && (
{t('queue.radioAdded')}
)} {isFirstAutoAdded && (
{t('queue.autoAdded')}
)}
{ suppressNextAutoScrollRef.current = true; // Pass the row index so a click on a duplicate track lands on // *this* slot, not the first occurrence (issue #500). playTrack(track, queue, undefined, undefined, idx); }} onContextMenu={(e) => { e.preventDefault(); usePlayerStore.getState().openContextMenu(e.clientX, e.clientY, track, 'queue-item', idx); }} onMouseDown={(e) => { if (e.button !== 0) return; e.preventDefault(); const startX = e.clientX; const startY = e.clientY; const onMove = (me: MouseEvent) => { if (Math.abs(me.clientX - startX) > 5 || Math.abs(me.clientY - startY) > 5) { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); psyDragFromIdxRef.current = idx; startDrag({ data: JSON.stringify({ type: 'queue_reorder', index: idx }), label: track.title }, me.clientX, me.clientY); } }; const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); }; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); }} style={dragStyle} >
{isPlaying && } {track.title}
{track.artist}
{(() => { const label = orbitAttributionLabel(track.id); return label ?
{label}
: null; })()}
{formatTime(track.duration)}
{luckyRolling && isPlaying && ( )}
); })} )}
); }