Replace vue-dnd-code with vue-draggable-next

This commit is contained in:
2026-01-05 22:22:42 +05:00
parent 627c9fda99
commit ff2b41c2e7
7 changed files with 109 additions and 149 deletions

View File

@ -1,42 +0,0 @@
<script setup lang="ts">
import { useDraggable } from '@vue-dnd-kit/core';
import { computed } from 'vue';
const { index, source } = defineProps<{
index: number;
source: any[];
}>();
const { elementRef, handleDragStart, isOvered, isDragging } = useDraggable({
data: computed(() => ({
index,
source,
})),
});
</script>
<template>
<div ref="elementRef" @pointerdown="handleDragStart" :class="{
'is-over': isOvered,
'is-dragging': isDragging,
}" class="draggable">
<slot />
</div>
</template>
<style>
.draggable {
cursor: move;
user-select: none;
touch-action: none;
}
.is-dragging {
opacity: 0.2;
}
.is-over {
padding-top: 0.5rem;
border-top: 2px solid var(--border);
}
</style>

View File

@ -1,10 +1,10 @@
<template>
<Frame margin="none" class="px-2 py-3 flex h-fit gap-2 hover:bg-muted" :class="selected && 'bg-muted'">
<Frame margin="none" class="px-2 py-3 flex h-fit gap-2 hover:bg-muted">
<div class="text-[2rem] h-full">
<Draggable />
</div>
<Separator orientation="vertical" />
<div class="w-fit flex-1 flex flex-col justify-between">
<div class="w-fit flex-1 flex flex-col justify-between cursor-pointer" data-no-drag @click="onPlayClick">
<div class="w-full">
<div class="flex items-start justify-between w-full">
<h4 class="scroll-m-20 text-xl font-semibold tracking-tight truncate max-w-[32ch]">
@ -26,8 +26,8 @@
</Badge>
</div>
</div>
<Separator orientation="vertical" />
<div class="max-w-20 max-h-20 w-20 h-20" v-if="imageUrl">
<Separator orientation="vertical" class="cursor-pointer" data-no-drag @click="onPlayClick" />
<div class="max-w-20 max-h-20 w-20 h-20 cursor-pointer" data-no-drag v-if="imageUrl" @click="onPlayClick">
<NuxtImg class="object-cover w-full h-full rounded-md" :src="imageUrl" :alt="title" />
</div>
</Frame>
@ -42,14 +42,22 @@ import Frame from '@/components/ui/frame/Frame.vue'
interface Props {
title: string
author: string
selected?: boolean
badges?: string[]
imageUrl?: string
date?: string
}
const emit = defineEmits<{
onPlay: []
}>();
withDefaults(defineProps<Props>(), {
authorLabel: 'Author',
badges: () => []
})
function onPlayClick(e: MouseEvent) {
emit("onPlay")
e.stopPropagation();
}
</script>

View File

@ -1,16 +1,15 @@
<script setup lang="ts">
import { Search } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { InputWithIcon } from '@/components/ui/input'
import { Outline } from '@/components/ui/outline'
import { SidebarTrigger } from '@/components/ui/sidebar'
import MusicCard from '~/components/internal/musiccard/MusicCard.vue'
import { DnDOperations, useDroppable } from '@vue-dnd-kit/core'
import Draggable from '~/components/action/Draggable.vue'
import { useCurrentPlaylistStore } from '~/stores/use-current-playlist-store'
import { bulkReorder, getGetPlaylistTracksQueryKey } from '~/composeables/api/track-controller/track-controller'
import { useGetPlaylistTracks } from '~/composeables/api/track-controller/track-controller'
import { useMutation, useQueryClient } from '@tanstack/vue-query'
import { Search } from 'lucide-vue-next'
import { VueDraggableNext } from 'vue-draggable-next'
import MusicCard from '~/components/internal/musiccard/MusicCard.vue'
import type { TrackResponse } from '~/composeables/api/models'
import { bulkReorder, getGetPlaylistTracksQueryKey, useGetPlaylistTracks } from '~/composeables/api/track-controller/track-controller'
import { useCurrentPlaylistStore } from '~/stores/use-current-playlist-store'
const searchValue = ref('')
@ -56,23 +55,33 @@ const { mutate: reorderTracks } = useMutation({
},
});
interface MappedTrack {
id: number;
title: string;
author: string;
authorLabel: string;
badges: string[];
imageUrl: string | undefined;
date: string;
}
const trackCount = computed(() => playlistTracks.value?.data.length || 0)
const mappedTracks = ref<MappedTrack[]>([])
const mappedTracks = computed(() => {
watchEffect(() => {
if (!playlistTracks.value) return []
const tracks = playlistTracks.value.data
return tracks.map((track: any, index: number) => ({
id: track.trackId || track.id,
title: track.track?.title || track.title || 'Unknown Track',
author: track.track?.artist?.name || track.artist || 'Unknown Artist',
authorLabel: "Artist",
badges: track.track?.format ? [track.track.format] : ['mp3'],
imageUrl: track.track?.coverUrl || track.coverUrl || getDefaultImage(index),
date: formatDate(track.addedDate || track.createdAt),
selected: false
}))
const result = tracks
.map((track: TrackResponse, index: number) => ({
id: track.trackId!,
title: track.title || 'Unknown Track',
author: track.artist || 'Unknown Artist',
authorLabel: "Artist",
badges: ['mp3'], // TODO: badges
imageUrl: getDefaultImage(index), // TODO: imageUrl
date: formatDate(''), // TODO: createdAt
}));
mappedTracks.value = result;
})
const getDefaultImage = (index: number) => {
@ -108,34 +117,16 @@ const filteredTracks = computed(() => {
track.author.toLowerCase().includes(searchValue.value.toLowerCase()) ||
track.badges.some(badge => badge.toLowerCase().includes(searchValue.value.toLowerCase()))
)
})
const selectTrack = (trackId: number) => {
mappedTracks.value.forEach(track => {
track.selected = track.id === trackId
})
}
const { elementRef: tracksRef } = useDroppable({
data: {
source: mappedTracks.value,
},
events: {
onDrop: (store, payload) => {
DnDOperations.applyTransfer(store);
if (currentPlaylistStore.id === -1) return;
if (mappedTracks.value.length !== trackCount.value) return;
if (mappedTracks.value.some(t => !t)) return;
const trackIds = mappedTracks.value?.map(t => t.id);
reorderTracks({
playlistId: currentPlaylistStore.id,
trackIds,
});
},
},
});
function onTrackOrderChange() {
const trackIds = mappedTracks.value?.map(t => t.id);
reorderTracks({
playlistId: currentPlaylistStore.id,
trackIds,
});
}
watch(() => currentPlaylistStore.id, (newId) => {
if (newId !== -1) {
refetch()
@ -178,14 +169,14 @@ watch(() => currentPlaylistStore.id, (newId) => {
</div>
<div v-else ref="tracksRef" class="space-y-2">
<TransitionGroup name="list" ref="tracksRef">
<Draggable v-for="(track, index) in filteredTracks" :key="track.id" :index="index"
:source="mappedTracks">
<VueDraggableNext v-model="mappedTracks" group="tracks" @change="onTrackOrderChange"
item-key="id">
<div v-for="track in mappedTracks" :key="track.id">
<MusicCard :key="track.id" :title="track.title" :author="track.author"
:authorLabel="track.authorLabel" :badges="track.badges" :imageUrl="track.imageUrl"
:date="track.date" :selected="track.selected" @click="selectTrack(track.id)" />
</Draggable>
</TransitionGroup>
:date="track.date" />
</div>
</VueDraggableNext>
</div>
</div>
</div>

View File

@ -1,5 +0,0 @@
import VueDnDKitPlugin from '@vue-dnd-kit/core';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VueDnDKitPlugin);
});