drag drop pt. 2

This commit is contained in:
2026-02-06 05:30:50 -08:00
parent 03be6760cc
commit c18f4e80b9
2 changed files with 83 additions and 14 deletions

View File

@@ -279,10 +279,12 @@
// We reorder by track.id (annSongId). The player module adjusts traversal state. // We reorder by track.id (annSongId). The player module adjusts traversal state.
let dragId = $state<number | null>(null); let dragId = $state<number | null>(null);
let dragOverId = $state<number | null>(null); let dragOverId = $state<number | null>(null);
let dragOverEdge = $state<"top" | "bottom" | null>(null);
function onDragStart(trackId: number, e: DragEvent) { function onDragStart(trackId: number, e: DragEvent) {
dragId = trackId; dragId = trackId;
dragOverId = null; dragOverId = null;
dragOverEdge = null;
// Best-effort: set payload (useful for some browsers) // Best-effort: set payload (useful for some browsers)
try { 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) { function onDragOver(trackId: number, e: DragEvent) {
// Required to allow dropping // Required to allow dropping
e.preventDefault(); e.preventDefault();
dragOverId = trackId; dragOverId = trackId;
dragOverEdge = edgeFromPointer(e);
try { try {
e.dataTransfer!.dropEffect = "move"; e.dataTransfer!.dropEffect = "move";
@@ -319,22 +333,59 @@
if (sourceId == null) { if (sourceId == null) {
dragId = null; dragId = null;
dragOverId = null; dragOverId = null;
dragOverEdge = null;
return; return;
} }
if (sourceId === targetTrackId) { // Reorder by queue indices (insert between rows based on pointer edge)
dragId = null;
dragOverId = null;
return;
}
// Reorder by queue indices
const fromIndex = snap.queue.findIndex((t) => t.id === sourceId); 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; dragId = null;
dragOverId = 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; return;
} }
@@ -342,17 +393,31 @@
dragId = null; dragId = null;
dragOverId = null; dragOverId = null;
dragOverEdge = null;
} }
function onDragEnd() { function onDragEnd() {
dragId = null; dragId = null;
dragOverId = null; dragOverId = null;
dragOverEdge = null;
} }
function draggableHint(trackId: number) { function draggableHint(trackId: number) {
if (dragId == null) return ""; if (dragId == null) return "";
if (dragId === trackId) return "opacity-70"; 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 ""; return "";
} }
@@ -783,7 +848,9 @@
<li <li
class={[ class={[
"flex items-center gap-2 border-b px-2 py-2 last:border-b-0", "flex items-center gap-2 border-b px-2 py-2 last:border-b-0",
// visual affordances
draggableHint(item.track.id), draggableHint(item.track.id),
dropIndicatorClass(item.track.id),
] ]
.filter(Boolean) .filter(Boolean)
.join(" ")} .join(" ")}
@@ -1070,7 +1137,9 @@
<li <li
class={[ class={[
"flex items-center gap-2 border-b px-2 py-2 last:border-b-0", "flex items-center gap-2 border-b px-2 py-2 last:border-b-0",
// visual affordances
draggableHint(item.track.id), draggableHint(item.track.id),
dropIndicatorClass(item.track.id),
] ]
.filter(Boolean) .filter(Boolean)
.join(" ")} .join(" ")}

View File

@@ -67,20 +67,20 @@
} }
</script> </script>
<div class="rounded border px-3 py-2"> <div class="rounded border flex-row px-3 py-2">
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1"> <div class="flex flex-wrap w-fit items-baseline gap-x-2 gap-y-1">
<span class="rounded bg-muted px-2 py-0.5 text-sm text-muted-foreground" <span class="rounded bg-muted px-2 py-0.5 text-sm text-muted-foreground"
>{displayTypeNumber}</span >{displayTypeNumber}</span
> >
{animeName} {animeName}
</div> </div>
<div class="mt-1 text-foreground/80"> <div class="mt-1 w-fit text-foreground/80">
{songName} {songName}
<span class="text-sm text-muted-foreground">{artistDisplay}</span> <span class="text-sm text-muted-foreground">{artistDisplay}</span>
</div> </div>
<div class="mt-2 flex flex-wrap items-center gap-2"> <div class="mt-2 w-fitflex flex-wrap items-center gap-2">
<button <button
type="button" type="button"
class="btn-icon" class="btn-icon"