fix: bugs with sorting and dragging songs

This commit is contained in:
Phan An 2022-11-17 17:22:29 +01:00
parent 5479ac29fd
commit 2bba83e78e
No known key found for this signature in database
GPG key ID: A81E4477F0BB6FDC
2 changed files with 47 additions and 32 deletions

View file

@ -17,8 +17,10 @@
@click="sort('track')"
>
#
<icon v-if="sortField === 'track' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'track' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
<template v-if="config.sortable">
<icon v-if="sortField === 'track' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'track' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
</template>
</span>
<span
class="title-artist"
@ -28,8 +30,10 @@
@click="sort('title')"
>
Title
<icon v-if="sortField === 'title' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'title' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
<template v-if="config.sortable">
<icon v-if="sortField === 'title' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'title' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
</template>
</span>
<span
class="album"
@ -39,8 +43,10 @@
@click="sort('album_name')"
>
Album
<icon v-if="sortField === 'album_name' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'album_name' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
<template v-if="config.sortable">
<icon v-if="sortField === 'album_name' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'album_name' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
</template>
</span>
<span
class="time"
@ -50,11 +56,13 @@
@click="sort('length')"
>
Time
<icon v-if="sortField === 'length' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'length' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
<template v-if="config.sortable">
<icon v-if="sortField === 'length' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'length' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
</template>
</span>
<span class="extra">
<SongListSorter :field="sortField" :order="sortOrder" @sort="sort"/>
<SongListSorter v-if="config.sortable" :field="sortField" :order="sortOrder" @sort="sort"/>
</span>
</div>
@ -75,6 +83,7 @@
@dragenter.prevent="onDragEnter"
@dragover.prevent
@drop.prevent="onDrop(item, $event)"
@dragend.prevent="onDragEnd"
@contextmenu.prevent="openContextMenu(item, $event)"
/>
</VirtualScroller>
@ -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<SongListSortField>, Clos
const [sortOrder, setSortOrder] = requireInjection<[Ref<SortOrder>, Closure]>(SongListSortOrderKey)
const [config] = requireInjection<[Partial<SongListConfig>]>(SongListConfigKey, [{}])
const router = requireInjection(RouterKey)
const screen = router.$currentRoute.value.screen
const wrapper = ref<HTMLElement>()
const lastSelectedRow = ref<SongRow>()
const sortFields = ref<SongListSortField[]>([])
const songRows = ref<SongRow[]>([])
@ -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 {

View file

@ -20,8 +20,13 @@ import SongListControls from '@/components/song/SongListControls.vue'
import ThumbnailStack from '@/components/ui/ThumbnailStack.vue'
export const useSongList = (songs: Ref<Song[]>, config: Partial<SongListConfig> = {}) => {
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<InstanceType<typeof SongList>>()
@ -34,9 +39,6 @@ export const useSongList = (songs: Ref<Song[]>, config: Partial<SongListConfig>
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<Song[]>, config: Partial<SongListConfig>
const sortField = ref<SongListSortField | null>(((): 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<Song[]>, config: Partial<SongListConfig>
provideReadonly(SongsKey, songs, false)
provideReadonly(SelectedSongsKey, selectedSongs, false)
provideReadonly(SongListConfigKey, reactive(config))
provideReadonly(SongListConfigKey, config)
provideReadonly(SongListSortFieldKey, sortField)
provideReadonly(SongListSortOrderKey, sortOrder)