From 364b29ceee8cd19ea2cf293c9f041665d8e9abd5 Mon Sep 17 00:00:00 2001 From: Frank Stellmacher <171614930+Psychotoxical@users.noreply.github.com> Date: Sun, 3 May 2026 14:03:07 +0200 Subject: [PATCH] fix(queue): restore PR #420 QueuePanel.tsx parts lost in #419 squash (#440) The drag-row-outside-queue-to-remove feature shipped in #420 (by cucadmuh) had its QueuePanel.tsx wiring wiped when #419 was squash- merged from a branch that pre-dated #420. The DragDropContext and MiniPlayer parts of #420 survived; only QueuePanel.tsx was overwritten. Restored to the state on commit 274ac5b3: - Import `registerQueueDragHitTest` from DragDropContext - Subscribe to `removeTrack` from playerStore - Register the queue rect as a hit-test region on mount - Document-level psy-drop listener that removes the dragged row when the drop coordinates fall outside the queue rect Co-authored-by: cucadmuh <49571317+cucadmuh@users.noreply.github.com> --- src/components/QueuePanel.tsx | 44 ++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/components/QueuePanel.tsx b/src/components/QueuePanel.tsx index 92786174..67b2bbe5 100644 --- a/src/components/QueuePanel.tsx +++ b/src/components/QueuePanel.tsx @@ -23,7 +23,7 @@ import { copyTextToClipboard } from '../utils/serverMagicString'; import { showToast } from '../utils/toast'; import { useThemeStore } from '../store/themeStore'; import { useLyricsStore } from '../store/lyricsStore'; -import { useDragDrop } from '../contexts/DragDropContext'; +import { useDragDrop, registerQueueDragHitTest } from '../contexts/DragDropContext'; import LyricsPane from './LyricsPane'; import NowPlayingInfo from './NowPlayingInfo'; import { TFunction } from 'i18next'; @@ -350,6 +350,7 @@ function QueuePanelHostOrSolo() { const clearQueue = usePlayerStore(s => s.clearQueue); const reorderQueue = usePlayerStore(s => s.reorderQueue); + const removeTrack = usePlayerStore(s => s.removeTrack); const shuffleQueue = usePlayerStore(s => s.shuffleQueue); const enqueue = usePlayerStore(s => s.enqueue); const enqueueAt = usePlayerStore(s => s.enqueueAt); @@ -492,6 +493,16 @@ function QueuePanelHostOrSolo() { const asideRef = useRef(null); + useEffect(() => { + const hitTest = (cx: number, cy: number) => { + const el = asideRef.current; + if (!el) return false; + const r = el.getBoundingClientRect(); + return cx >= r.left && cx <= r.right && cy >= r.top && cy <= r.bottom; + }; + return registerQueueDragHitTest(hitTest); + }, []); + const { isDragging: isPsyDragging, startDrag, payload: psyPayload } = useDragDrop(); /** Only these drag types may be dropped into the queue. */ const QUEUE_DROP_TYPES = new Set(['song', 'album', 'queue_reorder']); @@ -559,6 +570,37 @@ function QueuePanelHostOrSolo() { return () => aside.removeEventListener('psy-drop', onPsyDrop); }, [enqueueAt]); + // Drag a queue row outside the panel → remove (drop never reaches `aside`). + useEffect(() => { + const onDocPsyDrop = (e: Event) => { + if (!isQueueVisible) return; + const d = (e as CustomEvent<{ data?: string; clientX?: number; clientY?: number }>).detail; + if (!d?.data) return; + const cx = d.clientX; + const cy = d.clientY; + if (typeof cx !== 'number' || typeof cy !== 'number') return; + let parsed: { type?: string; index?: number } | null = null; + try { + parsed = JSON.parse(d.data); + } catch { + return; + } + if (parsed?.type !== 'queue_reorder' || typeof parsed.index !== 'number') return; + const aside = asideRef.current; + if (!aside) return; + const r = aside.getBoundingClientRect(); + const inside = + cx >= r.left && cx <= r.right && cy >= r.top && cy <= r.bottom; + if (inside) return; + psyDragFromIdxRef.current = null; + externalDropTargetRef.current = null; + setExternalDropTarget(null); + removeTrack(parsed.index); + }; + document.addEventListener('psy-drop', onDocPsyDrop); + return () => document.removeEventListener('psy-drop', onDocPsyDrop); + }, [isQueueVisible, removeTrack]); + useEffect(function queueAutoScroll() { if (suppressNextAutoScrollRef.current) { suppressNextAutoScrollRef.current = false;