From 2bba83e78e8e73be7f7afa1db27fbc7b73ee54f3 Mon Sep 17 00:00:00 2001 From: Phan An Date: Thu, 17 Nov 2022 17:22:29 +0100 Subject: [PATCH] fix: bugs with sorting and dragging songs --- .../assets/js/components/song/SongList.vue | 63 +++++++++++-------- .../assets/js/composables/useSongList.ts | 16 ++--- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/resources/assets/js/components/song/SongList.vue b/resources/assets/js/components/song/SongList.vue index 18a855db..bdddb297 100644 --- a/resources/assets/js/components/song/SongList.vue +++ b/resources/assets/js/components/song/SongList.vue @@ -17,8 +17,10 @@ @click="sort('track')" > # - - + Title - - + Album - - + Time - - + - + @@ -75,6 +83,7 @@ @dragenter.prevent="onDragEnter" @dragover.prevent @drop.prevent="onDrop(item, $event)" + @dragend.prevent="onDragEnd" @contextmenu.prevent="openContextMenu(item, $event)" /> @@ -88,14 +97,7 @@ import { faCaretDown, faCaretUp } from '@fortawesome/free-solid-svg-icons' import { nextTick, onMounted, Ref, ref, watch } from 'vue' import { eventBus, requireInjection } from '@/utils' import { useDraggable, useDroppable } from '@/composables' -import { - RouterKey, - SelectedSongsKey, - SongListConfigKey, - SongListSortFieldKey, - SongListSortOrderKey, - SongsKey -} from '@/symbols' +import { SelectedSongsKey, SongListConfigKey, SongListSortFieldKey, SongListSortOrderKey, SongsKey } from '@/symbols' import VirtualScroller from '@/components/ui/VirtualScroller.vue' import SongListItem from '@/components/song/SongListItem.vue' @@ -119,9 +121,7 @@ const [sortField, setSortField] = requireInjection<[Ref, Clos const [sortOrder, setSortOrder] = requireInjection<[Ref, Closure]>(SongListSortOrderKey) const [config] = requireInjection<[Partial]>(SongListConfigKey, [{}]) -const router = requireInjection(RouterKey) - -const screen = router.$currentRoute.value.screen +const wrapper = ref() const lastSelectedRow = ref() const sortFields = ref([]) const songRows = ref([]) @@ -241,6 +241,10 @@ const onDragStart = (row: SongRow, event: DragEvent) => { row.selected = true } + // Add "dragging" class to the wrapper so that we can disable pointer events on child elements. + // This prevents dragleave events from firing when the user drags the mouse over the child elements. + wrapper.value.classList.add('dragging') + startDragging(event, selectedSongs.value) } @@ -248,7 +252,7 @@ const onDragEnter = (event: DragEvent) => { if (!config.reorderable) return if (acceptsDrop(event)) { - (event.target as Element).parentElement?.classList.add('droppable') + (event.target as HTMLElement).closest('.song-item')?.classList.add('droppable') event.dataTransfer!.dropEffect = 'move' } @@ -257,18 +261,23 @@ const onDragEnter = (event: DragEvent) => { const onDrop = (item: SongRow, event: DragEvent) => { if (!config.reorderable || !getDroppedData(event) || !selectedSongs.value.length) { + wrapper.value.classList.remove('dragging') return onDragLeave(event) } + wrapper.value.classList.remove('dragging') + emit('reorder', item.song) return onDragLeave(event) } const onDragLeave = (event: DragEvent) => { - (event.target as Element).parentElement?.classList.remove('droppable') + (event.target as HTMLElement).closest('.song-item')?.classList.remove('droppable') return false } +const onDragEnd = () => wrapper.value.classList.remove('dragging') + const openContextMenu = async (row: SongRow, event: MouseEvent) => { if (!row.selected) { clearSelection() @@ -305,9 +314,13 @@ onMounted(() => render()) z-index: 2; // fix stack-context related issue when e.g., footer would cover the sort context menu } - div.droppable { - border-bottom-width: 3px; - border-bottom-color: var(--color-green); + &.dragging .song-item * { + pointer-events: none; + } + + .droppable { + position: relative; + box-shadow: 0 3px 0 var(--color-green); } .song-list-header > span, .song-item > span { diff --git a/resources/assets/js/composables/useSongList.ts b/resources/assets/js/composables/useSongList.ts index e6eb07f8..2caef0fa 100644 --- a/resources/assets/js/composables/useSongList.ts +++ b/resources/assets/js/composables/useSongList.ts @@ -20,8 +20,13 @@ import SongListControls from '@/components/song/SongListControls.vue' import ThumbnailStack from '@/components/ui/ThumbnailStack.vue' export const useSongList = (songs: Ref, config: Partial = {}) => { + config = reactive(config) const router = requireInjection(RouterKey) - const screen = router.$currentRoute.value.screen + + router.onRouteChanged(route => { + config.reorderable = route.screen === 'Queue' + config.sortable = !['Queue', 'RecentlyPlayed', 'Search.Songs'].includes(route.screen) + }) const songList = ref>() @@ -34,9 +39,6 @@ export const useSongList = (songs: Ref, config: Partial headerLayout.value = direction === 'down' ? 'collapsed' : 'expanded' } - config.reorderable = screen !== 'Queue' - config.sortable = !['Queue', 'RecentlyPlayed', 'Search.Songs'].includes(screen) - const duration = computed(() => songStore.getFormattedLength(songs.value)) const thumbnails = computed(() => { @@ -76,8 +78,8 @@ export const useSongList = (songs: Ref, config: Partial const sortField = ref(((): SongListSortField | null => { if (!config.sortable) return null - if (screen === 'Album' || screen === 'Artist') return 'track' - if (screen === 'Search.Songs') return null + if (router.$currentRoute.value.screen === 'Album' || router.$currentRoute.value.screen === 'Artist') return 'track' + if (router.$currentRoute.value.screen === 'Search.Songs') return null return 'title' })()) @@ -107,7 +109,7 @@ export const useSongList = (songs: Ref, config: Partial provideReadonly(SongsKey, songs, false) provideReadonly(SelectedSongsKey, selectedSongs, false) - provideReadonly(SongListConfigKey, reactive(config)) + provideReadonly(SongListConfigKey, config) provideReadonly(SongListSortFieldKey, sortField) provideReadonly(SongListSortOrderKey, sortOrder)