From 2edbda54d57a1353866c4fdcf51cefb8df0e8998 Mon Sep 17 00:00:00 2001 From: Phan An Date: Mon, 10 Oct 2022 09:00:02 +0200 Subject: [PATCH] fix: broken song reordering (#1524) --- .../assets/js/components/song/SongList.vue | 64 ++++++++----------- .../assets/js/composables/useDragAndDrop.ts | 10 ++- 2 files changed, 29 insertions(+), 45 deletions(-) diff --git a/resources/assets/js/components/song/SongList.vue b/resources/assets/js/components/song/SongList.vue index 8ad05c0d..b295e536 100644 --- a/resources/assets/js/components/song/SongList.vue +++ b/resources/assets/js/components/song/SongList.vue @@ -87,11 +87,11 @@ :item="item" draggable="true" @click="rowClicked(item, $event)" - @dragleave="removeDroppableState" - @dragstart="rowDragStart(item, $event)" - @dragenter.prevent="allowDrop" + @dragleave="onDragLeave" + @dragstart="onDragStart(item, $event)" + @dragenter.prevent="onDragEnter" @dragover.prevent - @drop.prevent="handleDrop(item, $event)" + @drop.prevent="onDrop(item, $event)" @contextmenu.prevent="openContextMenu(item, $event)" /> @@ -103,9 +103,9 @@ import { findIndex } from 'lodash' import isMobile from 'ismobilejs' import { faAngleDown, faAngleUp } from '@fortawesome/free-solid-svg-icons' import { faClock } from '@fortawesome/free-regular-svg-icons' -import { computed, nextTick, onMounted, ref, watch } from 'vue' +import { computed, onMounted, Ref, ref, watch } from 'vue' import { eventBus, requireInjection } from '@/utils' -import { useDraggable } from '@/composables' +import { useDraggable, useDroppable } from '@/composables' import { ScreenNameKey, SelectedSongsKey, @@ -119,15 +119,16 @@ import VirtualScroller from '@/components/ui/VirtualScroller.vue' import SongListItem from '@/components/song/SongListItem.vue' const { startDragging } = useDraggable('songs') +const { getDroppedData, acceptsDrop } = useDroppable(['songs']) const emit = defineEmits(['press:enter', 'press:delete', 'reorder', 'sort', 'scroll-breakpoint', 'scrolled-to-end']) -const [items] = requireInjection(SongsKey) +const [items] = requireInjection<[Ref]>(SongsKey) const [screen] = requireInjection<[ScreenName]>(ScreenNameKey) -const [selectedSongs, setSelectedSongs] = requireInjection(SelectedSongsKey) -const [sortField, setSortField] = requireInjection(SongListSortFieldKey) -const [sortOrder, setSortOrder] = requireInjection(SongListSortOrderKey) -const [injectedConfig] = requireInjection(SongListConfigKey, {}) +const [selectedSongs, setSelectedSongs] = requireInjection<[Ref, Closure]>(SelectedSongsKey) +const [sortField, setSortField] = requireInjection<[Ref, Closure]>(SongListSortFieldKey) +const [sortOrder, setSortOrder] = requireInjection<[Ref, Closure]>(SongListSortOrderKey) +const [injectedConfig] = requireInjection<[Partial]>(SongListConfigKey, [{}]) const lastSelectedRow = ref() const sortFields = ref([]) @@ -250,12 +251,7 @@ const selectRowsBetween = (first: SongRow, second: SongRow) => { } } -/** - * Enable dragging songs by capturing the dragstart event on a table row. - * Even though the event is triggered on one row only, we'll collect other - * selected rows, if any, as well. - */ -const rowDragStart = (row: SongRow, event: DragEvent) => { +const onDragStart = (row: SongRow, event: DragEvent) => { // If the user is dragging an unselected row, clear the current selection. if (!row.selected) { clearSelection() @@ -265,52 +261,42 @@ const rowDragStart = (row: SongRow, event: DragEvent) => { startDragging(event, selectedSongs.value) } -/** - * Add a "droppable" class and set the drop effect when other songs are dragged over a row. - */ -const allowDrop = (event: DragEvent) => { - if (!allowReordering) return; +const onDragEnter = (event: DragEvent) => { + if (!allowReordering) return - (event.target as Element).parentElement?.classList.add('droppable') - event.dataTransfer!.dropEffect = 'move' + if (acceptsDrop(event)) { + (event.target as Element).parentElement?.classList.add('droppable') + event.dataTransfer!.dropEffect = 'move' + } return false } -const handleDrop = (item: SongRow, event: DragEvent) => { - if ( - !allowReordering || - !event.dataTransfer!.getData('application/x-koel.text+plain') || - !selectedSongs.value.length - ) { - return removeDroppableState(event) +const onDrop = (item: SongRow, event: DragEvent) => { + if (!allowReordering || !getDroppedData(event) || !selectedSongs.value.length) { + return onDragLeave(event) } emit('reorder', item.song) - return removeDroppableState(event) + return onDragLeave(event) } -const removeDroppableState = (event: DragEvent) => { +const onDragLeave = (event: DragEvent) => { (event.target as Element).parentElement?.classList.remove('droppable') return false } const openContextMenu = async (row: SongRow, event: MouseEvent) => { - // If the user is right-clicking an unselected row, - // clear the current selection and select it instead. if (!row.selected) { clearSelection() toggleRow(row) } - await nextTick() - eventBus.emit('SONG_CONTEXT_MENU_REQUESTED', event, selectedSongs.value) } defineExpose({ - getAllSongsWithSort, - sort + getAllSongsWithSort }) onMounted(() => render()) diff --git a/resources/assets/js/composables/useDragAndDrop.ts b/resources/assets/js/composables/useDragAndDrop.ts index b03e24e7..68169c67 100644 --- a/resources/assets/js/composables/useDragAndDrop.ts +++ b/resources/assets/js/composables/useDragAndDrop.ts @@ -2,7 +2,8 @@ import { arrayify, logger, pluralize } from '@/utils' import { albumStore, artistStore, playlistStore, songStore } from '@/stores' type Draggable = Song | Song[] | Album | Artist | Playlist -type DraggableType = 'songs' | 'album' | 'artist' | 'playlist' +const draggableTypes = ['songs', 'album', 'artist', 'playlist'] +type DraggableType = typeof draggableTypes[number] const createGhostDragImage = (event: DragEvent, text: string): void => { if (!event.dataTransfer) { @@ -23,11 +24,7 @@ const createGhostDragImage = (event: DragEvent, text: string): void => { } const getDragType = (event: DragEvent) => { - const types: DraggableType[] = ['songs', 'album', 'artist', 'playlist'] - - for (let i = 0, count = types.length; i < count; ++i) { - if (event.dataTransfer?.types.includes(`application/x-koel.${types[i]}`)) return types[i] - } + return draggableTypes.find(type => event.dataTransfer?.types.includes(`application/x-koel.${type}`)) } export const useDraggable = (type: DraggableType) => { @@ -139,6 +136,7 @@ export const useDroppable = (acceptedTypes: DraggableType[]) => { return { acceptsDrop, + getDroppedData, resolveDroppedValue, resolveDroppedSongs }