refactor(web): converge PlaylistTrackRow drag-reorder onto @neodrag/svelte (B4)
This commit is contained in:
@@ -1,25 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { GripVertical, X } from 'lucide-svelte';
|
||||
import { draggable, type DragEventData } from '@neodrag/svelte';
|
||||
import LikeButton from './LikeButton.svelte';
|
||||
import TrackMenu from './TrackMenu.svelte';
|
||||
import type { PlaylistTrack, TrackRef } from '$lib/api/types';
|
||||
import { offsetToDelta } from './queue-row-math';
|
||||
|
||||
let {
|
||||
row,
|
||||
isOwner,
|
||||
onRemove,
|
||||
onPlay,
|
||||
onDragStart,
|
||||
onDragOver,
|
||||
onDrop
|
||||
onMove
|
||||
}: {
|
||||
row: PlaylistTrack;
|
||||
isOwner: boolean;
|
||||
onRemove: (position: number) => void;
|
||||
onPlay: (position: number) => void;
|
||||
onDragStart?: (position: number) => void;
|
||||
onDragOver?: (e: DragEvent, position: number) => void;
|
||||
onDrop?: (e: DragEvent, position: number) => void;
|
||||
onMove?: (fromPos: number, toPos: number) => void;
|
||||
} = $props();
|
||||
|
||||
const isUnavailable = $derived(row.track_id === null);
|
||||
@@ -41,21 +39,42 @@
|
||||
: null
|
||||
);
|
||||
|
||||
let measuredRowHeight = 56; // sensible default; replaced on dragStart
|
||||
|
||||
function handleDragStart(data: DragEventData) {
|
||||
const rect = data.rootNode.getBoundingClientRect();
|
||||
if (rect.height > 0) measuredRowHeight = rect.height;
|
||||
}
|
||||
|
||||
function handleDragEnd(data: DragEventData) {
|
||||
const delta = offsetToDelta(data.offsetY, measuredRowHeight);
|
||||
if (delta === 0) return;
|
||||
onMove?.(row.position, row.position + delta);
|
||||
}
|
||||
|
||||
function format(sec: number): string {
|
||||
const m = Math.floor(sec / 60);
|
||||
const s = sec % 60;
|
||||
return `${m}:${s.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// Drag is gated on owner+available. When neither, the action runs but
|
||||
// the dragEnd handler skips by returning before onMove fires; visually
|
||||
// the user never sees the row move.
|
||||
const canDrag = $derived(isOwner && !isUnavailable);
|
||||
</script>
|
||||
|
||||
<div
|
||||
role="listitem"
|
||||
class="flex items-center gap-3 px-3 py-2 text-sm transition-colors hover:bg-surface-hover
|
||||
{isUnavailable ? 'text-text-muted' : 'text-text-primary'}"
|
||||
draggable={isOwner && !isUnavailable}
|
||||
ondragstart={() => onDragStart?.(row.position)}
|
||||
ondragover={(e) => { e.preventDefault(); onDragOver?.(e, row.position); }}
|
||||
ondrop={(e) => { e.preventDefault(); onDrop?.(e, row.position); }}
|
||||
use:draggable={{
|
||||
axis: 'y',
|
||||
bounds: 'parent',
|
||||
disabled: !canDrag,
|
||||
onDragStart: handleDragStart,
|
||||
onDragEnd: handleDragEnd
|
||||
}}
|
||||
>
|
||||
{#if isOwner}
|
||||
<span
|
||||
|
||||
@@ -31,20 +31,16 @@
|
||||
const playlistQuery = $derived($playlistStore);
|
||||
const isOwner = $derived(user.value?.id === playlistQuery?.data?.user_id);
|
||||
|
||||
let dragFromPos = $state<number | null>(null);
|
||||
|
||||
async function onDrop(toPos: number) {
|
||||
if (dragFromPos === null || !playlistQuery?.data) {
|
||||
return;
|
||||
}
|
||||
if (dragFromPos === toPos) {
|
||||
dragFromPos = null;
|
||||
return;
|
||||
}
|
||||
const cur = playlistQuery.data.tracks.map((t) => t.position);
|
||||
const moved = cur.splice(dragFromPos, 1)[0];
|
||||
cur.splice(toPos, 0, moved);
|
||||
dragFromPos = null;
|
||||
async function onMove(fromPos: number, toPos: number) {
|
||||
if (!playlistQuery?.data) return;
|
||||
const tracks = playlistQuery.data.tracks;
|
||||
// Clamp toPos so a drag past the list edges resolves cleanly to the
|
||||
// first/last slot rather than producing an out-of-range index.
|
||||
const clamped = Math.max(0, Math.min(tracks.length - 1, toPos));
|
||||
if (fromPos === clamped) return;
|
||||
const cur = tracks.map((t) => t.position);
|
||||
const moved = cur.splice(fromPos, 1)[0];
|
||||
cur.splice(clamped, 0, moved);
|
||||
try {
|
||||
await reorderPlaylist(id, cur);
|
||||
await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
|
||||
@@ -268,8 +264,7 @@
|
||||
isOwner={isOwner ?? false}
|
||||
onRemove={onRemove}
|
||||
onPlay={onPlay}
|
||||
onDragStart={(p) => (dragFromPos = p)}
|
||||
onDrop={(_, p) => onDrop(p)}
|
||||
onMove={onMove}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user