drag drop pt. 2
This commit is contained in:
@@ -279,10 +279,12 @@
|
||||
// We reorder by track.id (annSongId). The player module adjusts traversal state.
|
||||
let dragId = $state<number | null>(null);
|
||||
let dragOverId = $state<number | null>(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 @@
|
||||
<li
|
||||
class={[
|
||||
"flex items-center gap-2 border-b px-2 py-2 last:border-b-0",
|
||||
// visual affordances
|
||||
draggableHint(item.track.id),
|
||||
dropIndicatorClass(item.track.id),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
@@ -1070,7 +1137,9 @@
|
||||
<li
|
||||
class={[
|
||||
"flex items-center gap-2 border-b px-2 py-2 last:border-b-0",
|
||||
// visual affordances
|
||||
draggableHint(item.track.id),
|
||||
dropIndicatorClass(item.track.id),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
|
||||
@@ -67,20 +67,20 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="rounded border px-3 py-2">
|
||||
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
||||
<div class="rounded border flex-row px-3 py-2">
|
||||
<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"
|
||||
>{displayTypeNumber}</span
|
||||
>
|
||||
{animeName}
|
||||
</div>
|
||||
|
||||
<div class="mt-1 text-foreground/80">
|
||||
<div class="mt-1 w-fit text-foreground/80">
|
||||
{songName}
|
||||
<span class="text-sm text-muted-foreground">— {artistDisplay}</span>
|
||||
</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
|
||||
type="button"
|
||||
class="btn-icon"
|
||||
|
||||
Reference in New Issue
Block a user