mirror of
https://github.com/koel/koel
synced 2024-11-28 06:50:27 +00:00
feat: synchronize sorting
This commit is contained in:
parent
bd5cd1e621
commit
64c6eebdcd
5 changed files with 58 additions and 35 deletions
|
@ -86,7 +86,7 @@ let initialized = false
|
||||||
const fetchSongs = async () => {
|
const fetchSongs = async () => {
|
||||||
await favoriteStore.fetch()
|
await favoriteStore.fetch()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
sort('title', 'asc')
|
sort()
|
||||||
}
|
}
|
||||||
|
|
||||||
eventBus.on('LOAD_MAIN_CONTENT', async (view: MainViewName) => {
|
eventBus.on('LOAD_MAIN_CONTENT', async (view: MainViewName) => {
|
||||||
|
|
|
@ -47,8 +47,7 @@
|
||||||
<template v-else>
|
<template v-else>
|
||||||
The playlist is currently empty.
|
The playlist is currently empty.
|
||||||
<span class="d-block secondary">
|
<span class="d-block secondary">
|
||||||
Drag songs into its name in the sidebar
|
Drag songs into its name in the sidebar or use the "Add To…" button to fill it up.
|
||||||
or use the "Add To…" button to fill it up.
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</ScreenEmptyState>
|
</ScreenEmptyState>
|
||||||
|
@ -57,7 +56,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { difference } from 'lodash'
|
import { difference } from 'lodash'
|
||||||
import { defineAsyncComponent, nextTick, ref, toRef } from 'vue'
|
import { defineAsyncComponent, ref, toRef } from 'vue'
|
||||||
import { alerts, eventBus, pluralize } from '@/utils'
|
import { alerts, eventBus, pluralize } from '@/utils'
|
||||||
import { commonStore, playlistStore, songStore } from '@/stores'
|
import { commonStore, playlistStore, songStore } from '@/stores'
|
||||||
import { downloadService } from '@/services'
|
import { downloadService } from '@/services'
|
||||||
|
@ -107,8 +106,7 @@ const fetchSongs = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
playlistSongs.value = await songStore.fetchForPlaylist(playlist.value!)
|
playlistSongs.value = await songStore.fetchForPlaylist(playlist.value!)
|
||||||
loading.value = false
|
loading.value = false
|
||||||
await nextTick()
|
sort()
|
||||||
sort('title', 'asc')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
eventBus.on({
|
eventBus.on({
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
@click="sort('track')"
|
@click="sort('track')"
|
||||||
>
|
>
|
||||||
#
|
#
|
||||||
<i v-show="currentSortField === 'track' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
<i v-show="sortField === 'track' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
||||||
<i v-show="currentSortField === 'track' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
<i v-show="sortField === 'track' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="config.columns.includes('title')"
|
v-if="config.columns.includes('title')"
|
||||||
|
@ -27,8 +27,8 @@
|
||||||
@click="sort('title')"
|
@click="sort('title')"
|
||||||
>
|
>
|
||||||
Title
|
Title
|
||||||
<i v-show="currentSortField === 'title' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
<i v-show="sortField === 'title' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
||||||
<i v-show="currentSortField === 'title' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
<i v-show="sortField === 'title' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="config.columns.includes('artist')"
|
v-if="config.columns.includes('artist')"
|
||||||
|
@ -37,8 +37,8 @@
|
||||||
@click="sort('artist_name')"
|
@click="sort('artist_name')"
|
||||||
>
|
>
|
||||||
Artist
|
Artist
|
||||||
<i v-show="currentSortField === 'artist_name' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
<i v-show="sortField === 'artist_name' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
||||||
<i v-show="currentSortField === 'artist_name' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
<i v-show="sortField === 'artist_name' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="config.columns.includes('album')"
|
v-if="config.columns.includes('album')"
|
||||||
|
@ -47,8 +47,8 @@
|
||||||
@click="sort('album_name')"
|
@click="sort('album_name')"
|
||||||
>
|
>
|
||||||
Album
|
Album
|
||||||
<i v-show="currentSortField === 'album_name' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
<i v-show="sortField === 'album_name' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
||||||
<i v-show="currentSortField === 'album_name' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
<i v-show="sortField === 'album_name' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="config.columns.includes('length')"
|
v-if="config.columns.includes('length')"
|
||||||
|
@ -56,8 +56,8 @@
|
||||||
data-testid="header-length"
|
data-testid="header-length"
|
||||||
@click="sort('length')"
|
@click="sort('length')"
|
||||||
>
|
>
|
||||||
<i v-show="currentSortField === 'length' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
<i v-show="sortField === 'length' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
||||||
<i v-show="currentSortField === 'length' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
<i v-show="sortField === 'length' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
||||||
<i class="duration-header fa fa-clock-o"></i>
|
<i class="duration-header fa fa-clock-o"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="favorite"></span>
|
<span class="favorite"></span>
|
||||||
|
@ -87,7 +87,14 @@ import isMobile from 'ismobilejs'
|
||||||
import { findIndex } from 'lodash'
|
import { findIndex } from 'lodash'
|
||||||
import { computed, defineAsyncComponent, inject, onMounted, ref, watch } from 'vue'
|
import { computed, defineAsyncComponent, inject, onMounted, ref, watch } from 'vue'
|
||||||
import { $, eventBus, startDragging } from '@/utils'
|
import { $, eventBus, startDragging } from '@/utils'
|
||||||
import { SelectedSongsKey, SongListConfigKey, SongListTypeKey, SongsKey } from '@/symbols'
|
import {
|
||||||
|
SelectedSongsKey,
|
||||||
|
SongListConfigKey,
|
||||||
|
SongListSortFieldKey,
|
||||||
|
SongListSortOrderKey,
|
||||||
|
SongListTypeKey,
|
||||||
|
SongsKey
|
||||||
|
} from '@/symbols'
|
||||||
|
|
||||||
const VirtualScroller = defineAsyncComponent(() => import('@/components/ui/VirtualScroller.vue'))
|
const VirtualScroller = defineAsyncComponent(() => import('@/components/ui/VirtualScroller.vue'))
|
||||||
const SongListItem = defineAsyncComponent(() => import('@/components/song/SongListItem.vue'))
|
const SongListItem = defineAsyncComponent(() => import('@/components/song/SongListItem.vue'))
|
||||||
|
@ -97,10 +104,11 @@ const emit = defineEmits(['press:enter', 'press:delete', 'reorder', 'sort', 'scr
|
||||||
const items = inject(SongsKey, ref([]))
|
const items = inject(SongsKey, ref([]))
|
||||||
const type = inject(SongListTypeKey, 'all-songs')
|
const type = inject(SongListTypeKey, 'all-songs')
|
||||||
const selectedSongs = inject(SelectedSongsKey, ref([]))
|
const selectedSongs = inject(SelectedSongsKey, ref([]))
|
||||||
|
const sortField = inject(SongListSortFieldKey, ref('title'))
|
||||||
|
const sortOrder = inject(SongListSortOrderKey, ref('asc'))
|
||||||
|
|
||||||
const lastSelectedRow = ref<SongRow>()
|
const lastSelectedRow = ref<SongRow>()
|
||||||
const sortFields = ref<SongListSortField[]>([])
|
const sortFields = ref<SongListSortField[]>([])
|
||||||
const sortOrder = ref<SortOrder>('asc')
|
|
||||||
const songRows = ref<SongRow[]>([])
|
const songRows = ref<SongRow[]>([])
|
||||||
|
|
||||||
const allowReordering = type === 'queue'
|
const allowReordering = type === 'queue'
|
||||||
|
@ -116,12 +124,6 @@ const config = computed((): SongListConfig => {
|
||||||
}, inject(SongListConfigKey, {}))
|
}, inject(SongListConfigKey, {}))
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentSortField = ref<SongListSortField | null>((() => {
|
|
||||||
if (type === 'album' || type === 'artist') return 'track'
|
|
||||||
if (type === 'search-results') return null
|
|
||||||
return config.value.sortable ? 'title' : null
|
|
||||||
})())
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Since song objects themselves are shared by all song lists, we can't use them directly to
|
* Since song objects themselves are shared by all song lists, we can't use them directly to
|
||||||
* determine their selection status (selected/unselected). Therefore, for each song list, we
|
* determine their selection status (selected/unselected). Therefore, for each song list, we
|
||||||
|
@ -144,7 +146,7 @@ const sort = (field: SongListSortField) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSortField.value = field
|
sortField.value = field
|
||||||
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
|
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
|
||||||
|
|
||||||
emit('sort', field, sortOrder.value)
|
emit('sort', field, sortOrder.value)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { computed, getCurrentInstance, provide, reactive, Ref, ref } from 'vue'
|
import { computed, provide, reactive, Ref, ref } from 'vue'
|
||||||
import isMobile from 'ismobilejs'
|
import isMobile from 'ismobilejs'
|
||||||
import { orderBy } from 'lodash'
|
import { orderBy } from 'lodash'
|
||||||
|
|
||||||
|
@ -9,14 +9,20 @@ import router from '@/router'
|
||||||
import ControlsToggle from '@/components/ui/ScreenControlsToggle.vue'
|
import ControlsToggle from '@/components/ui/ScreenControlsToggle.vue'
|
||||||
import SongList from '@/components/song/SongList.vue'
|
import SongList from '@/components/song/SongList.vue'
|
||||||
import SongListControls from '@/components/song/SongListControls.vue'
|
import SongListControls from '@/components/song/SongListControls.vue'
|
||||||
import { SelectedSongsKey, SongListConfigKey, SongListTypeKey, SongsKey } from '@/symbols'
|
import {
|
||||||
|
SelectedSongsKey,
|
||||||
|
SongListConfigKey,
|
||||||
|
SongListSortFieldKey,
|
||||||
|
SongListSortOrderKey,
|
||||||
|
SongListTypeKey,
|
||||||
|
SongsKey
|
||||||
|
} from '@/symbols'
|
||||||
|
|
||||||
export const useSongList = (
|
export const useSongList = (
|
||||||
songs: Ref<Song[]>,
|
songs: Ref<Song[]>,
|
||||||
type: SongListType,
|
type: SongListType,
|
||||||
config: Partial<SongListConfig> = {}
|
config: Partial<SongListConfig> = {}
|
||||||
) => {
|
) => {
|
||||||
const vm = getCurrentInstance()
|
|
||||||
const songList = ref<InstanceType<typeof SongList>>()
|
const songList = ref<InstanceType<typeof SongList>>()
|
||||||
|
|
||||||
const isPhone = isMobile.phone
|
const isPhone = isMobile.phone
|
||||||
|
@ -50,32 +56,47 @@ export const useSongList = (
|
||||||
router.go('/queue')
|
router.go('/queue')
|
||||||
}
|
}
|
||||||
|
|
||||||
const sort = (sortField: SongListSortField | null, sortOrder: SortOrder) => {
|
const sortField = ref<SongListSortField | null>(((): SongListSortField | null => {
|
||||||
if (!sortField) return
|
if (type === 'album' || type === 'artist') return 'track'
|
||||||
|
if (type === 'search-results') return null
|
||||||
|
return config.sortable ? 'title' : null
|
||||||
|
})())
|
||||||
|
|
||||||
let sortFields: SongListSortField[] = [sortField]
|
const sortOrder = ref<SortOrder>('asc')
|
||||||
|
|
||||||
if (sortField === 'track') {
|
const sort = (by: SongListSortField | null = sortField.value, order: SortOrder = sortOrder.value) => {
|
||||||
|
if (!by) return
|
||||||
|
|
||||||
|
sortField.value = by
|
||||||
|
sortOrder.value = order
|
||||||
|
|
||||||
|
let sortFields: SongListSortField[] = [by]
|
||||||
|
|
||||||
|
if (by === 'track') {
|
||||||
sortFields.push('disc', 'title')
|
sortFields.push('disc', 'title')
|
||||||
} else if (sortField === 'album_name') {
|
} else if (by === 'album_name') {
|
||||||
sortFields.push('artist_name', 'track', 'disc', 'title')
|
sortFields.push('artist_name', 'track', 'disc', 'title')
|
||||||
} else if (sortField === 'artist_name') {
|
} else if (by === 'artist_name') {
|
||||||
sortFields.push('album_name', 'track', 'disc', 'title')
|
sortFields.push('album_name', 'track', 'disc', 'title')
|
||||||
}
|
}
|
||||||
|
|
||||||
songs.value = orderBy(songs.value, sortFields, sortOrder)
|
songs.value = orderBy(songs.value, sortFields, order)
|
||||||
}
|
}
|
||||||
|
|
||||||
provide(SongListTypeKey, type)
|
provide(SongListTypeKey, type)
|
||||||
provide(SongsKey, songs)
|
provide(SongsKey, songs)
|
||||||
provide(SelectedSongsKey, selectedSongs)
|
provide(SelectedSongsKey, selectedSongs)
|
||||||
provide(SongListConfigKey, reactive(config))
|
provide(SongListConfigKey, reactive(config))
|
||||||
|
provide(SongListSortFieldKey, sortField)
|
||||||
|
provide(SongListSortOrderKey, sortOrder)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
SongList,
|
SongList,
|
||||||
SongListControls,
|
SongListControls,
|
||||||
ControlsToggle,
|
ControlsToggle,
|
||||||
songs,
|
songs,
|
||||||
|
sortField,
|
||||||
|
sortOrder,
|
||||||
duration,
|
duration,
|
||||||
songList,
|
songList,
|
||||||
selectedSongs,
|
selectedSongs,
|
||||||
|
|
|
@ -4,3 +4,5 @@ export const SongListTypeKey: InjectionKey<SongListType> = Symbol('SongListType'
|
||||||
export const SongsKey: InjectionKey<Ref<Song[]>> = Symbol('Songs')
|
export const SongsKey: InjectionKey<Ref<Song[]>> = Symbol('Songs')
|
||||||
export const SelectedSongsKey: InjectionKey<Ref<Song[]>> = Symbol('SelectedSongs')
|
export const SelectedSongsKey: InjectionKey<Ref<Song[]>> = Symbol('SelectedSongs')
|
||||||
export const SongListConfigKey: InjectionKey<Partial<SongListConfig>> = Symbol('SongListConfig')
|
export const SongListConfigKey: InjectionKey<Partial<SongListConfig>> = Symbol('SongListConfig')
|
||||||
|
export const SongListSortFieldKey: InjectionKey<Ref<SongListSortField>> = Symbol('SongListSortField')
|
||||||
|
export const SongListSortOrderKey: InjectionKey<Ref<SortOrder>> = Symbol('SongListSortOrder')
|
||||||
|
|
Loading…
Reference in a new issue