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>
This commit is contained in:
Frank Stellmacher
2026-05-03 14:03:07 +02:00
committed by GitHub
parent dcec30166a
commit 364b29ceee
+43 -1
View File
@@ -23,7 +23,7 @@ import { copyTextToClipboard } from '../utils/serverMagicString';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/toast';
import { useThemeStore } from '../store/themeStore'; import { useThemeStore } from '../store/themeStore';
import { useLyricsStore } from '../store/lyricsStore'; import { useLyricsStore } from '../store/lyricsStore';
import { useDragDrop } from '../contexts/DragDropContext'; import { useDragDrop, registerQueueDragHitTest } from '../contexts/DragDropContext';
import LyricsPane from './LyricsPane'; import LyricsPane from './LyricsPane';
import NowPlayingInfo from './NowPlayingInfo'; import NowPlayingInfo from './NowPlayingInfo';
import { TFunction } from 'i18next'; import { TFunction } from 'i18next';
@@ -350,6 +350,7 @@ function QueuePanelHostOrSolo() {
const clearQueue = usePlayerStore(s => s.clearQueue); const clearQueue = usePlayerStore(s => s.clearQueue);
const reorderQueue = usePlayerStore(s => s.reorderQueue); const reorderQueue = usePlayerStore(s => s.reorderQueue);
const removeTrack = usePlayerStore(s => s.removeTrack);
const shuffleQueue = usePlayerStore(s => s.shuffleQueue); const shuffleQueue = usePlayerStore(s => s.shuffleQueue);
const enqueue = usePlayerStore(s => s.enqueue); const enqueue = usePlayerStore(s => s.enqueue);
const enqueueAt = usePlayerStore(s => s.enqueueAt); const enqueueAt = usePlayerStore(s => s.enqueueAt);
@@ -492,6 +493,16 @@ function QueuePanelHostOrSolo() {
const asideRef = useRef<HTMLElement>(null); const asideRef = useRef<HTMLElement>(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(); const { isDragging: isPsyDragging, startDrag, payload: psyPayload } = useDragDrop();
/** Only these drag types may be dropped into the queue. */ /** Only these drag types may be dropped into the queue. */
const QUEUE_DROP_TYPES = new Set(['song', 'album', 'queue_reorder']); const QUEUE_DROP_TYPES = new Set(['song', 'album', 'queue_reorder']);
@@ -559,6 +570,37 @@ function QueuePanelHostOrSolo() {
return () => aside.removeEventListener('psy-drop', onPsyDrop); return () => aside.removeEventListener('psy-drop', onPsyDrop);
}, [enqueueAt]); }, [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() { useEffect(function queueAutoScroll() {
if (suppressNextAutoScrollRef.current) { if (suppressNextAutoScrollRef.current) {
suppressNextAutoScrollRef.current = false; suppressNextAutoScrollRef.current = false;