import { arrayify, getPlayableProp, logger, pluralize } from '@/utils' import { albumStore, artistStore, playlistFolderStore, playlistStore, songStore } from '@/stores' type Draggable = MaybeArray | Album | Artist | Playlist | PlaylistFolder const draggableTypes = ['playables', 'album', 'artist', 'playlist', 'playlist-folder'] type DraggableType = typeof draggableTypes[number] const createGhostDragImage = (event: DragEvent, text: string): void => { if (!event.dataTransfer) { return } let dragGhost = document.querySelector('#dragGhost') if (!dragGhost) { // Create the element to be the ghost drag image. dragGhost = document.createElement('div') dragGhost.id = 'dragGhost' document.body.appendChild(dragGhost) } dragGhost.textContent = text event.dataTransfer.setDragImage(dragGhost, 0, 0) } const getDragType = (event: DragEvent) => { return draggableTypes.find(type => event.dataTransfer?.types.includes(`application/x-koel.${type}`)) } export const useDraggable = (type: DraggableType) => { const startDragging = (event: DragEvent, dragged: Draggable) => { if (!event.dataTransfer) { return } event.dataTransfer.effectAllowed = 'copyMove' let text: string let data: any switch (type) { case 'playables': dragged = arrayify(dragged) text = dragged.length === 1 ? `${dragged[0].title} by ${getPlayableProp(dragged[0], 'artist_name', 'podcast_author')}` : pluralize(dragged, 'item') data = dragged.map(song => song.id) break case 'album': dragged = dragged text = `All songs in ${dragged.name}` data = dragged.id break case 'artist': dragged = dragged text = `All songs by ${dragged.name}` data = dragged.id break case 'playlist': dragged = dragged text = dragged.name data = dragged.id break case 'playlist-folder': dragged = dragged text = dragged.name data = dragged.id break default: return } event.dataTransfer.setData(`application/x-koel.${type}`, JSON.stringify(data)) createGhostDragImage(event, text) } return { startDragging, } } export const useDroppable = (acceptedTypes: DraggableType[]) => { const acceptsDrop = (event: DragEvent) => { const type = getDragType(event) return Boolean(type && acceptedTypes.includes(type)) } const getDroppedData = (event: DragEvent) => { const type = getDragType(event) if (!type) { return null } try { return JSON.parse(event.dataTransfer!.getData(`application/x-koel.${type}`)!) } catch (error: unknown) { logger.warn('Failed to parse dropped data', error) return null } } const resolveDroppedValue = async (event: DragEvent): Promise => { try { switch (getDragType(event)) { case 'playlist': const id = String(JSON.parse(event.dataTransfer!.getData('application/x-koel.playlist'))) return playlistStore.byId(id) as T | undefined default: } } catch (error: unknown) { logger.error(error, event) } } const resolveDroppedItems = async (event: DragEvent) => { try { const type = getDragType(event) if (!type) { return [] } const data = getDroppedData(event) switch (type) { case 'playables': return songStore.byIds(data) case 'album': const album = await albumStore.resolve(data) return album ? await songStore.fetchForAlbum(album) : [] case 'artist': const artist = await artistStore.resolve(data) return artist ? await songStore.fetchForArtist(artist) : [] case 'playlist': const playlist = playlistStore.byId(data) return playlist ? await songStore.fetchForPlaylist(playlist) : [] case 'playlist-folder': const folder = playlistFolderStore.byId(data) return folder ? await songStore.fetchForPlaylistFolder(folder) : [] default: throw new Error(`Unknown drag type: ${type}`) } } catch (error: unknown) { logger.error(error, event) return [] } } return { acceptsDrop, getDroppedData, resolveDroppedValue, resolveDroppedItems, } }