147 lines
4.9 KiB
Vue
147 lines
4.9 KiB
Vue
<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'
|
|
|
|
const searchValue = ref('')
|
|
|
|
// Mock data
|
|
const tracks = ref([
|
|
{
|
|
id: 1,
|
|
title: "Best of Chobits OST - Beyond",
|
|
author: "ビヨンド",
|
|
authorLabel: "Author",
|
|
badges: ["mp3", "jpop", "anime"],
|
|
imageUrl: "https://github.com/yavuzceliker/sample-images/blob/main/docs/image-1.jpg?raw=true",
|
|
date: "about 17 years ago",
|
|
selected: true
|
|
},
|
|
{
|
|
id: 2,
|
|
title: "Summer Vibessssssssssssssssssssssssssssssssssssfawefjioawefjeaiofjeaoifjeaofejiaofejaiojfoaiss",
|
|
author: "Various Artists",
|
|
authorLabel: "Artists",
|
|
badges: ["mp3", "summer", "mix"],
|
|
imageUrl: "https://github.com/yavuzceliker/sample-images/blob/main/docs/image-2.jpg?raw=true",
|
|
date: "about 1 hour ago",
|
|
selected: false
|
|
},
|
|
{
|
|
id: 3,
|
|
title: "Unknown Track",
|
|
author: "Unknown Artist",
|
|
authorLabel: "Author",
|
|
badges: ["wav"],
|
|
imageUrl: "https://github.com/yavuzceliker/sample-images/blob/main/docs/image-3.jpg?raw=true",
|
|
selected: false
|
|
},
|
|
{
|
|
id: 4,
|
|
title: "Single Track",
|
|
author: "Solo Artist",
|
|
authorLabel: "Author",
|
|
badges: [],
|
|
imageUrl: "https://github.com/yavuzceliker/sample-images/blob/main/docs/image-5.jpg?raw=true",
|
|
date: "recently added",
|
|
selected: false
|
|
}
|
|
])
|
|
|
|
const filteredTracks = computed(() => {
|
|
if (!searchValue.value) return tracks.value
|
|
|
|
return tracks.value.filter(track =>
|
|
track.title.toLowerCase().includes(searchValue.value.toLowerCase()) ||
|
|
track.author.toLowerCase().includes(searchValue.value.toLowerCase()) ||
|
|
track.badges.some(badge => badge.toLowerCase().includes(searchValue.value.toLowerCase()))
|
|
)
|
|
})
|
|
|
|
const selectTrack = (trackId: number) => {
|
|
tracks.value.forEach(track => {
|
|
track.selected = track.id === trackId
|
|
})
|
|
}
|
|
|
|
const { elementRef: tracksRef } = useDroppable({
|
|
data: {
|
|
source: tracks.value,
|
|
},
|
|
events: {
|
|
onDrop: DnDOperations.applyTransfer,
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex-1">
|
|
<NuxtLayout name="default">
|
|
<template #header>
|
|
<Outline side="bottom" padding="dense" class="w-full flex">
|
|
<div class="flex gap-8 w-full items-center">
|
|
<SidebarTrigger :size="5" />
|
|
<h2 class="scroll-m-20 text-3xl font-semibold tracking-tight transition-colors first:mt-0">
|
|
Tracks
|
|
</h2>
|
|
</div>
|
|
<Button>
|
|
Export
|
|
</Button>
|
|
</Outline>
|
|
</template>
|
|
<div class="flex flex-col w-full p-4 gap-4 h-full">
|
|
<InputWithIcon v-model="searchValue" :icon="Search" placeholder="Search..." type="search" icon-size="5"
|
|
id="user-search" class="w-full" />
|
|
|
|
<div ref="tracksRef" class="space-y-2">
|
|
<TransitionGroup name="list" ref="tracksRef">
|
|
<Draggable v-for="(track, index) in filteredTracks" :key="track.id" :index="index"
|
|
:source="tracks">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
<template #sidebar>
|
|
<Outline padding="none" class="h-full" side="left">
|
|
<Outline padding="dense" side="bottom">
|
|
<p class="leading-7 not-first:mt-6 font-semibold">
|
|
Metadata editor
|
|
</p>
|
|
</Outline>
|
|
</Outline>
|
|
</template>
|
|
</NuxtLayout>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
.list-move {
|
|
transition: 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
|
|
}
|
|
|
|
.list-enter-active,
|
|
.list-leave-active {
|
|
transition: 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
|
|
}
|
|
|
|
.list-enter-from,
|
|
.list-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(-20px);
|
|
}
|
|
|
|
.list-leave-active {
|
|
position: absolute;
|
|
pointer-events: none;
|
|
}
|
|
</style>
|