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.
|
// 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(" ")}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user