2022-09-03 08:32:09 +00:00
|
|
|
import { arrayify, logger, pluralize } from '@/utils'
|
2022-12-07 20:35:31 +00:00
|
|
|
import { albumStore, artistStore, playlistFolderStore, playlistStore, songStore } from '@/stores'
|
2022-09-03 08:32:09 +00:00
|
|
|
|
2022-12-07 20:35:31 +00:00
|
|
|
type Draggable = Song | Song[] | Album | Artist | Playlist | PlaylistFolder
|
|
|
|
const draggableTypes = <const>['songs', 'album', 'artist', 'playlist', 'playlist-folder']
|
2022-10-10 07:00:02 +00:00
|
|
|
type DraggableType = typeof draggableTypes[number]
|
2022-09-03 08:32:09 +00:00
|
|
|
|
|
|
|
const createGhostDragImage = (event: DragEvent, text: string): void => {
|
|
|
|
if (!event.dataTransfer) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let dragGhost = document.querySelector<HTMLElement>('#dragGhost')
|
|
|
|
|
|
|
|
if (!dragGhost) {
|
|
|
|
// Create the element to be the ghost drag image.
|
|
|
|
dragGhost = document.createElement('div')
|
|
|
|
dragGhost.id = 'dragGhost'
|
|
|
|
document.body.appendChild(dragGhost)
|
|
|
|
}
|
|
|
|
|
|
|
|
dragGhost.innerText = text
|
|
|
|
event.dataTransfer.setDragImage(dragGhost, 0, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
const getDragType = (event: DragEvent) => {
|
2022-10-10 07:00:02 +00:00
|
|
|
return draggableTypes.find(type => event.dataTransfer?.types.includes(`application/x-koel.${type}`))
|
2022-09-03 08:32:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export const useDraggable = (type: DraggableType) => {
|
|
|
|
const startDragging = (event: DragEvent, dragged: Draggable) => {
|
|
|
|
if (!event.dataTransfer) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-12-29 21:05:58 +00:00
|
|
|
event.dataTransfer.effectAllowed = 'copyMove'
|
|
|
|
|
2022-12-07 20:35:31 +00:00
|
|
|
let text: string
|
2022-09-03 08:32:09 +00:00
|
|
|
let data: any
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case 'songs':
|
|
|
|
dragged = arrayify(<Song>dragged)
|
|
|
|
text = dragged.length === 1 ? `${dragged[0].title} by ${dragged[0].artist_name}` : pluralize(dragged, 'song')
|
|
|
|
|
|
|
|
data = dragged.map(song => song.id)
|
|
|
|
break
|
|
|
|
|
|
|
|
case 'album':
|
|
|
|
dragged = <Album>dragged
|
|
|
|
text = `All songs in ${dragged.name}`
|
|
|
|
data = dragged.id
|
|
|
|
break
|
|
|
|
|
|
|
|
case 'artist':
|
|
|
|
dragged = <Artist>dragged
|
|
|
|
text = `All songs by ${dragged.name}`
|
|
|
|
data = dragged.id
|
|
|
|
break
|
|
|
|
|
|
|
|
case 'playlist':
|
|
|
|
dragged = <Playlist>dragged
|
|
|
|
text = dragged.name
|
|
|
|
data = dragged.id
|
|
|
|
break
|
|
|
|
|
2022-12-07 20:35:31 +00:00
|
|
|
case 'playlist-folder':
|
|
|
|
dragged = <PlaylistFolder>dragged
|
|
|
|
text = dragged.name
|
|
|
|
data = dragged.id
|
|
|
|
break
|
|
|
|
|
2022-09-03 08:32:09 +00:00
|
|
|
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)
|
2022-12-07 13:31:38 +00:00
|
|
|
return Boolean(type && acceptedTypes.includes(type))
|
2022-09-03 08:32:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const getDroppedData = (event: DragEvent) => {
|
|
|
|
const type = getDragType(event)
|
|
|
|
|
|
|
|
if (!type) return null
|
|
|
|
|
|
|
|
try {
|
|
|
|
return JSON.parse(event.dataTransfer?.getData(`application/x-koel.${type}`)!)
|
|
|
|
} catch (e) {
|
|
|
|
logger.warn('Failed to parse dropped data', e)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const resolveDroppedValue = async <T = Playlist> (event: DragEvent): Promise<T | undefined> => {
|
|
|
|
try {
|
|
|
|
switch (getDragType(event)) {
|
|
|
|
case 'playlist':
|
|
|
|
return playlistStore
|
2024-01-18 11:13:05 +00:00
|
|
|
.byId(event.dataTransfer!.getData('application/x-koel.playlist')) as T | undefined
|
2022-09-03 08:32:09 +00:00
|
|
|
default:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
logger.error(error, event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const resolveDroppedSongs = async (event: DragEvent) => {
|
|
|
|
try {
|
|
|
|
const type = getDragType(event)
|
|
|
|
if (!type) return <Song[]>[]
|
|
|
|
|
|
|
|
const data = getDroppedData(event)
|
|
|
|
switch (type) {
|
|
|
|
case 'songs':
|
|
|
|
return songStore.byIds(<string[]>data)
|
|
|
|
case 'album':
|
|
|
|
const album = await albumStore.resolve(<number>data)
|
|
|
|
return album ? await songStore.fetchForAlbum(album) : <Song[]>[]
|
|
|
|
case 'artist':
|
|
|
|
const artist = await artistStore.resolve(<number>data)
|
|
|
|
return artist ? await songStore.fetchForArtist(artist) : <Song[]>[]
|
|
|
|
case 'playlist':
|
2024-01-18 11:13:05 +00:00
|
|
|
const playlist = playlistStore.byId(<string>data)
|
2022-09-03 08:32:09 +00:00
|
|
|
return playlist ? await songStore.fetchForPlaylist(playlist) : <Song[]>[]
|
2022-12-07 20:35:31 +00:00
|
|
|
case 'playlist-folder':
|
|
|
|
const folder = playlistFolderStore.byId(<string>data)
|
|
|
|
return folder ? await songStore.fetchForPlaylistFolder(folder) : <Song[]>[]
|
|
|
|
default:
|
|
|
|
throw new Error(`Unknown drag type: ${type}`)
|
2022-09-03 08:32:09 +00:00
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
logger.error(error, event)
|
|
|
|
return <Song[]>[]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
acceptsDrop,
|
2022-10-10 07:00:02 +00:00
|
|
|
getDroppedData,
|
2022-09-03 08:32:09 +00:00
|
|
|
resolveDroppedValue,
|
|
|
|
resolveDroppedSongs
|
|
|
|
}
|
|
|
|
}
|