From 03be6760cc44ae0e654a66fd6fdcf9e2364144c3 Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Fri, 6 Feb 2026 05:23:07 -0800 Subject: [PATCH] drag drop --- src/lib/components/GlobalPlayer.svelte | 106 ++++++++++++++++++++++++- src/lib/player/player.svelte.ts | 91 ++++++++++++++++----- 2 files changed, 174 insertions(+), 23 deletions(-) diff --git a/src/lib/components/GlobalPlayer.svelte b/src/lib/components/GlobalPlayer.svelte index 2dd3c76..82037b0 100644 --- a/src/lib/components/GlobalPlayer.svelte +++ b/src/lib/components/GlobalPlayer.svelte @@ -25,6 +25,7 @@ nowPlayingLabel, prev, removeTrack, + reorderTrackById, schedulePersistNow, setUiOpen, setVolume, @@ -274,6 +275,87 @@ let clearQueueDialogOpen = $state(false); + // --- Drag & drop reorder (works for both linear and shuffle) --- + // We reorder by track.id (annSongId). The player module adjusts traversal state. + let dragId = $state(null); + let dragOverId = $state(null); + + function onDragStart(trackId: number, e: DragEvent) { + dragId = trackId; + dragOverId = null; + + // Best-effort: set payload (useful for some browsers) + try { + e.dataTransfer?.setData("text/plain", String(trackId)); + e.dataTransfer!.effectAllowed = "move"; + } catch { + // ignore + } + } + + function onDragOver(trackId: number, e: DragEvent) { + // Required to allow dropping + e.preventDefault(); + dragOverId = trackId; + + try { + e.dataTransfer!.dropEffect = "move"; + } catch { + // ignore + } + } + + function onDrop(targetTrackId: number, e: DragEvent) { + e.preventDefault(); + + // Prefer our internal dragId; fall back to transfer data + let sourceId = dragId; + if (sourceId == null) { + const raw = e.dataTransfer?.getData("text/plain"); + const parsed = raw ? Number(raw) : NaN; + if (Number.isFinite(parsed)) sourceId = parsed; + } + + if (sourceId == null) { + dragId = null; + dragOverId = null; + return; + } + + if (sourceId === targetTrackId) { + dragId = null; + dragOverId = null; + return; + } + + // Reorder by queue indices + const fromIndex = snap.queue.findIndex((t) => t.id === sourceId); + const toIndex = snap.queue.findIndex((t) => t.id === targetTrackId); + + if (fromIndex === -1 || toIndex === -1) { + dragId = null; + dragOverId = null; + return; + } + + reorderTrackById(sourceId, toIndex); + + dragId = null; + dragOverId = null; + } + + function onDragEnd() { + dragId = null; + dragOverId = null; + } + + function draggableHint(trackId: number) { + if (dragId == null) return ""; + if (dragId === trackId) return "opacity-70"; + if (dragOverId === trackId) return "ring-2 ring-primary/40"; + return ""; + } + function formatTime(seconds: number) { if (!Number.isFinite(seconds) || seconds < 0) return "0:00"; const s = Math.floor(seconds); @@ -699,7 +781,17 @@
    {#each queueDisplay as item (item.track.id)}
  • onDragStart(item.track.id, e)} + ondragover={(e) => onDragOver(item.track.id, e)} + ondrop={(e) => onDrop(item.track.id, e)} + ondragend={onDragEnd} >