From c18f4e80b9c2ee55a775758762445b9c5906ec49 Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Fri, 6 Feb 2026 05:30:50 -0800 Subject: [PATCH] drag drop pt. 2 --- src/lib/components/GlobalPlayer.svelte | 89 +++++++++++++++++++++++--- src/lib/components/SongEntry.svelte | 8 +-- 2 files changed, 83 insertions(+), 14 deletions(-) diff --git a/src/lib/components/GlobalPlayer.svelte b/src/lib/components/GlobalPlayer.svelte index 82037b0..7b3b1b1 100644 --- a/src/lib/components/GlobalPlayer.svelte +++ b/src/lib/components/GlobalPlayer.svelte @@ -279,10 +279,12 @@ // We reorder by track.id (annSongId). The player module adjusts traversal state. let dragId = $state(null); let dragOverId = $state(null); + let dragOverEdge = $state<"top" | "bottom" | null>(null); function onDragStart(trackId: number, e: DragEvent) { dragId = trackId; dragOverId = null; + dragOverEdge = null; // Best-effort: set payload (useful for some browsers) try { @@ -293,10 +295,22 @@ } } + function edgeFromPointer(e: DragEvent) { + const el = e.currentTarget as HTMLElement | null; + if (!el) return "bottom" as const; + + const rect = el.getBoundingClientRect(); + const y = e.clientY; + const mid = rect.top + rect.height / 2; + return y < mid ? ("top" as const) : ("bottom" as const); + } + function onDragOver(trackId: number, e: DragEvent) { // Required to allow dropping e.preventDefault(); + dragOverId = trackId; + dragOverEdge = edgeFromPointer(e); try { e.dataTransfer!.dropEffect = "move"; @@ -319,22 +333,59 @@ if (sourceId == null) { dragId = null; dragOverId = null; + dragOverEdge = null; return; } - if (sourceId === targetTrackId) { - dragId = null; - dragOverId = null; - return; - } - - // Reorder by queue indices + // Reorder by queue indices (insert between rows based on pointer edge) const fromIndex = snap.queue.findIndex((t) => t.id === sourceId); - const toIndex = snap.queue.findIndex((t) => t.id === targetTrackId); + const targetIndex = snap.queue.findIndex((t) => t.id === targetTrackId); - if (fromIndex === -1 || toIndex === -1) { + if (fromIndex === -1 || targetIndex === -1) { dragId = null; dragOverId = null; + dragOverEdge = null; + return; + } + + const edge = dragOverEdge ?? edgeFromPointer(e); + + // Compute insertion index in the *post-removal* list. + // + // We interpret: + // - edge === "top" -> insert before target row + // - edge === "bottom" -> insert after target row + // + // Because `reorderTrackById` uses "toIndex in current queue", we must + // translate the desired insertion slot into that API carefully. + // + // Approach: + // - Convert "insert after" into a "before the next item" index. + // - Adjust for the fact that removing `fromIndex` shifts indices. + let insertIndex = edge === "top" ? targetIndex : targetIndex + 1; + + // Clamp to [0..len] + insertIndex = Math.max(0, Math.min(snap.queue.length, insertIndex)); + + // If moving downward and inserting after/before beyond itself, removal shifts indices. + // `reorderTrackById` expects a final *index of the moved item*, so we convert the + // insertion slot (0..len) into a destination index (0..len-1). + let toIndex: number; + if (insertIndex <= fromIndex) { + // You're inserting somewhere before (or at) the source position: + // final index is exactly the insert slot. + toIndex = insertIndex; + } else { + // You're inserting after the source position; after removal, everything shifts left by 1. + // The moved item ends up at insertIndex - 1. + toIndex = insertIndex - 1; + } + + // If dropping "between" where it already is, no-op + if (toIndex === fromIndex) { + dragId = null; + dragOverId = null; + dragOverEdge = null; return; } @@ -342,17 +393,31 @@ dragId = null; dragOverId = null; + dragOverEdge = null; } function onDragEnd() { dragId = null; dragOverId = null; + dragOverEdge = null; } function draggableHint(trackId: number) { if (dragId == null) return ""; if (dragId === trackId) return "opacity-70"; - if (dragOverId === trackId) return "ring-2 ring-primary/40"; + if (dragOverId === trackId) { + if (dragOverEdge === "top") return "ring-2 ring-primary/40 ring-offset-0"; + if (dragOverEdge === "bottom") + return "ring-2 ring-primary/40 ring-offset-0"; + return "ring-2 ring-primary/40 ring-offset-0"; + } + return ""; + } + + function dropIndicatorClass(trackId: number) { + if (dragOverId !== trackId) return ""; + if (dragOverEdge === "top") return "border-t-2 border-t-primary/50"; + if (dragOverEdge === "bottom") return "border-b-2 border-b-primary/50"; return ""; } @@ -783,7 +848,9 @@
  • -
    -
    +
    +
    {displayTypeNumber} {animeName}
    -
    +
    {songName} — {artistDisplay}
    -
    +