mirror of
https://github.com/koel/koel
synced 2024-11-28 15:00:42 +00:00
128 lines
3.4 KiB
Vue
128 lines
3.4 KiB
Vue
<template>
|
|
<li
|
|
class="playlist-folder"
|
|
:class="{ droppable }"
|
|
tabindex="0"
|
|
@dragleave="onDragLeave"
|
|
@dragover="onDragOver"
|
|
@drop="onDrop"
|
|
>
|
|
<a @click.prevent="toggle" @contextmenu.prevent="onContextMenu">
|
|
<icon :icon="opened ? faFolderOpen : faFolder" fixed-width />
|
|
{{ folder.name }}
|
|
</a>
|
|
|
|
<ul v-if="playlistsInFolder.length" v-show="opened">
|
|
<PlaylistSidebarItem v-for="playlist in playlistsInFolder" :key="playlist.id" :list="playlist" class="sub-item" />
|
|
</ul>
|
|
|
|
<div
|
|
v-if="opened"
|
|
:class="droppableOnHatch && 'droppable'"
|
|
class="hatch"
|
|
@dragover="onDragOverHatch"
|
|
@dragleave.prevent="onDragLeaveHatch"
|
|
@drop.prevent="onDropOnHatch"
|
|
/>
|
|
</li>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { faFolder, faFolderOpen } from '@fortawesome/free-solid-svg-icons'
|
|
import { computed, defineAsyncComponent, ref, toRefs } from 'vue'
|
|
import { playlistFolderStore, playlistStore } from '@/stores'
|
|
import { eventBus } from '@/utils'
|
|
import { useDroppable } from '@/composables'
|
|
|
|
const PlaylistSidebarItem = defineAsyncComponent(() => import('@/components/playlist/PlaylistSidebarItem.vue'))
|
|
|
|
const props = defineProps<{ folder: PlaylistFolder }>()
|
|
const { folder } = toRefs(props)
|
|
|
|
const opened = ref(false)
|
|
const droppable = ref(false)
|
|
const droppableOnHatch = ref(false)
|
|
|
|
const playlistsInFolder = computed(() => playlistStore.byFolder(folder.value))
|
|
|
|
const { acceptsDrop, resolveDroppedValue } = useDroppable(['playlist'])
|
|
|
|
const toggle = () => (opened.value = !opened.value)
|
|
|
|
const onDragOver = (event: DragEvent) => {
|
|
if (!acceptsDrop(event)) return false
|
|
|
|
event.preventDefault()
|
|
event.dataTransfer!.dropEffect = 'move'
|
|
droppable.value = true
|
|
opened.value = true
|
|
}
|
|
|
|
const onDragLeave = () => (droppable.value = false)
|
|
|
|
const onDrop = async (event: DragEvent) => {
|
|
droppable.value = false
|
|
|
|
if (!acceptsDrop(event)) return false
|
|
|
|
event.preventDefault()
|
|
|
|
const playlist = await resolveDroppedValue<Playlist>(event)
|
|
if (!playlist || playlist.folder_id === folder.value.id) return
|
|
|
|
await playlistFolderStore.addPlaylistToFolder(folder.value, playlist)
|
|
}
|
|
|
|
const onDragLeaveHatch = () => (droppableOnHatch.value = false)
|
|
|
|
const onDragOverHatch = (event: DragEvent) => {
|
|
if (!acceptsDrop(event)) return false
|
|
|
|
event.preventDefault()
|
|
event.dataTransfer!.dropEffect = 'move'
|
|
droppableOnHatch.value = true
|
|
}
|
|
|
|
const onDropOnHatch = async (event: DragEvent) => {
|
|
droppableOnHatch.value = false
|
|
droppable.value = false
|
|
|
|
const playlist = (await resolveDroppedValue<Playlist>(event))!
|
|
|
|
// if the playlist isn't in the folder, don't do anything. The folder will handle the drop.
|
|
if (playlist.folder_id !== folder.value.id) return
|
|
|
|
// otherwise, the user is trying to remove the playlist from the folder.
|
|
event.stopPropagation()
|
|
await playlistFolderStore.removePlaylistFromFolder(folder.value, playlist)
|
|
}
|
|
|
|
const onContextMenu = event => eventBus.emit('PLAYLIST_FOLDER_CONTEXT_MENU_REQUESTED', event, folder.value)
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
li.playlist-folder {
|
|
position: relative;
|
|
|
|
a {
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
&.droppable {
|
|
box-shadow: inset 0 0 0 1px var(--color-accent);
|
|
border-radius: 4px;
|
|
cursor: copy;
|
|
}
|
|
|
|
.hatch {
|
|
position: absolute;
|
|
bottom: 0;
|
|
width: 100%;
|
|
height: .5rem;
|
|
|
|
&.droppable {
|
|
border-bottom: 3px solid var(--color-highlight);
|
|
}
|
|
}
|
|
}
|
|
</style>
|