drag and drop init
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Play, X } from "@lucide/svelte";
|
import { GripVertical, Play, X } from "@lucide/svelte";
|
||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
import { player } from "$lib/player/store.svelte";
|
import { player } from "$lib/player/store.svelte";
|
||||||
import type { Track } from "$lib/player/types";
|
import type { Track } from "$lib/player/types";
|
||||||
@@ -14,6 +14,30 @@
|
|||||||
// If it's in the queue, we can just set currentId.
|
// If it's in the queue, we can just set currentId.
|
||||||
player.playId(track.id);
|
player.playId(track.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dragOverIndex = $state<number | null>(null);
|
||||||
|
|
||||||
|
function onDragStart(e: DragEvent, index: number) {
|
||||||
|
if (!e.dataTransfer) return;
|
||||||
|
e.dataTransfer.effectAllowed = "move";
|
||||||
|
e.dataTransfer.setData("text/plain", index.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragOver(e: DragEvent, index: number) {
|
||||||
|
e.preventDefault(); // allow drop
|
||||||
|
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
||||||
|
dragOverIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDrop(e: DragEvent, toIndex: number) {
|
||||||
|
e.preventDefault();
|
||||||
|
dragOverIndex = null;
|
||||||
|
const fromIndexStr = e.dataTransfer?.getData("text/plain");
|
||||||
|
if (fromIndexStr) {
|
||||||
|
const fromIndex = parseInt(fromIndexStr, 10);
|
||||||
|
player.move(fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -40,27 +64,36 @@
|
|||||||
Queue is empty
|
Queue is empty
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
{#each player.displayQueue as track (track.id)}
|
{#each player.displayQueue as track, i (track.id)}
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
draggable="true"
|
||||||
|
ondragstart={(e) => onDragStart(e, i)}
|
||||||
|
ondragover={(e) => onDragOver(e, i)}
|
||||||
|
ondrop={(e) => onDrop(e, i)}
|
||||||
onclick={() => onJump(track)}
|
onclick={() => onJump(track)}
|
||||||
onkeydown={(e) => e.key === "Enter" && onJump(track)}
|
onkeydown={(e) => e.key === "Enter" && onJump(track)}
|
||||||
class="group flex items-center gap-2 px-3 py-2 rounded-md hover:bg-muted/50 transition-colors cursor-pointer text-sm"
|
class="group flex items-center gap-2 px-3 py-2 rounded-md hover:bg-muted/50 transition-colors cursor-pointer text-sm"
|
||||||
class:active={player.currentId === track.id}
|
class:active={player.currentId === track.id}
|
||||||
|
class:border-t-2={dragOverIndex === i}
|
||||||
|
class:border-primary={dragOverIndex === i}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="w-8 shrink-0 text-center text-xs text-muted-foreground/60 font-mono"
|
class="w-8 shrink-0 flex items-center justify-center cursor-grab active:cursor-grabbing text-muted-foreground/50 hover:text-foreground"
|
||||||
|
aria-label="Drag to reorder"
|
||||||
|
>
|
||||||
|
<GripVertical class="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="w-6 shrink-0 text-center text-xs text-muted-foreground/60 font-mono"
|
||||||
>
|
>
|
||||||
{#if player.currentId === track.id}
|
{#if player.currentId === track.id}
|
||||||
<div
|
<div
|
||||||
class="w-2 h-2 bg-primary rounded-full mx-auto animate-pulse"
|
class="w-2 h-2 bg-primary rounded-full mx-auto animate-pulse"
|
||||||
></div>
|
></div>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="group-hover:hidden">#</span>
|
<span class="text-xs">{i + 1}</span>
|
||||||
<Play
|
|
||||||
class="hidden group-hover:block mx-auto h-3 w-3 text-muted-foreground"
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,28 @@ class PlayerStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
move(fromIdx: number, toIdx: number) {
|
||||||
|
if (fromIdx === toIdx) return;
|
||||||
|
|
||||||
|
if (this.isShuffled) {
|
||||||
|
const indices = [...this.shuffledIndices];
|
||||||
|
if (fromIdx < 0 || fromIdx >= indices.length) return;
|
||||||
|
if (toIdx < 0 || toIdx >= indices.length) return;
|
||||||
|
|
||||||
|
const [item] = indices.splice(fromIdx, 1);
|
||||||
|
indices.splice(toIdx, 0, item);
|
||||||
|
this.shuffledIndices = indices;
|
||||||
|
} else {
|
||||||
|
const q = [...this.queue];
|
||||||
|
if (fromIdx < 0 || fromIdx >= q.length) return;
|
||||||
|
if (toIdx < 0 || toIdx >= q.length) return;
|
||||||
|
|
||||||
|
const [item] = q.splice(fromIdx, 1);
|
||||||
|
q.splice(toIdx, 0, item);
|
||||||
|
this.queue = q;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Playback Controls
|
// Playback Controls
|
||||||
|
|
||||||
next() {
|
next() {
|
||||||
|
|||||||
Reference in New Issue
Block a user