koel/resources/assets/js/composables/useSongList.ts

148 lines
4.6 KiB
TypeScript
Raw Normal View History

import { differenceBy, orderBy, sampleSize, take, throttle } from 'lodash'
import isMobile from 'ismobilejs'
import { computed, provide, reactive, Ref, ref } from 'vue'
2022-04-24 08:50:45 +00:00
import { playbackService } from '@/services'
2022-04-25 16:38:33 +00:00
import { queueStore, songStore } from '@/stores'
2022-11-18 18:44:20 +00:00
import { eventBus, provideReadonly } from '@/utils'
import { useRouter } from '@/composables'
2022-04-15 14:24:30 +00:00
import {
SelectedSongsKey,
SongListConfigKey,
SongListSortFieldKey,
SongListSortOrderKey,
SongListFilterKeywordsKey,
SongsKey
} from '@/symbols'
2022-04-15 14:24:30 +00:00
import ControlsToggle from '@/components/ui/ScreenControlsToggle.vue'
import SongList from '@/components/song/SongList.vue'
import SongListControls from '@/components/song/SongListControls.vue'
2022-07-16 09:52:39 +00:00
import ThumbnailStack from '@/components/ui/ThumbnailStack.vue'
export const useSongList = (
songs: Ref<Song[]>,
config: Partial<SongListConfig> = { sortable: true, reorderable: true }
) => {
const filterKeywords = ref('')
config = reactive(config)
2022-11-18 18:44:20 +00:00
const { isCurrentScreen, go, onRouteChanged } = useRouter()
2022-11-18 18:44:20 +00:00
onRouteChanged(route => {
config.reorderable = route.screen === 'Queue'
config.sortable = !['Queue', 'RecentlyPlayed', 'Search.Songs'].includes(route.screen)
})
2022-04-23 21:24:02 +00:00
const songList = ref<InstanceType<typeof SongList>>()
2022-04-15 14:24:30 +00:00
2022-04-23 21:24:02 +00:00
const isPhone = isMobile.phone
2022-04-15 14:24:30 +00:00
const selectedSongs = ref<Song[]>([])
const showingControls = ref(false)
const headerLayout = ref<ScreenHeaderLayout>('expanded')
2022-07-16 09:52:39 +00:00
const onScrollBreakpoint = (direction: 'up' | 'down') => {
headerLayout.value = direction === 'down' ? 'collapsed' : 'expanded'
}
2022-04-15 14:24:30 +00:00
2022-04-23 21:24:02 +00:00
const duration = computed(() => songStore.getFormattedLength(songs.value))
2022-04-15 14:24:30 +00:00
2022-07-16 09:52:39 +00:00
const thumbnails = computed(() => {
const songsWithCover = songs.value.filter(song => song.album_cover)
const sampleCovers = sampleSize(songsWithCover, 20).map(song => song.album_cover)
return take(Array.from(new Set(sampleCovers)), 4)
})
2022-12-02 16:17:37 +00:00
const getSongsToPlay = (): Song[] => songList.value!.getAllSongsWithSort()
2022-10-21 20:06:43 +00:00
const playAll = (shuffle: boolean) => {
playbackService.queueAndPlay(getSongsToPlay(), shuffle)
2022-11-18 18:44:20 +00:00
go('queue')
2022-10-21 20:06:43 +00:00
}
2022-04-24 08:50:45 +00:00
const playSelected = (shuffle: boolean) => playbackService.queueAndPlay(selectedSongs.value, shuffle)
2022-04-15 14:24:30 +00:00
const applyFilter = throttle((keywords: string) => (filterKeywords.value = keywords), 200)
const onPressEnter = async (event: KeyboardEvent) => {
if (selectedSongs.value.length === 1) {
queueStore.queueIfNotQueued(selectedSongs.value[0])
2022-04-24 08:50:45 +00:00
await playbackService.play(selectedSongs.value[0])
return
}
// • Only Enter: Queue songs to bottom
// • Shift+Enter: Queues song to top
// • Cmd/Ctrl+Enter: Queues song to bottom and play the first selected song
// • Cmd/Ctrl+Shift+Enter: Queue songs to top and play the first queued song
event.shiftKey ? queueStore.queueToTop(selectedSongs.value) : queueStore.queue(selectedSongs.value)
if (event.ctrlKey || event.metaKey) {
2022-04-24 08:50:45 +00:00
await playbackService.play(selectedSongs.value[0])
}
2022-11-18 18:44:20 +00:00
go('queue')
}
2022-07-05 15:09:20 +00:00
const sortField = ref<SongListSortField | null>(((): SongListSortField | null => {
if (!config.sortable) return null
2022-11-18 18:44:20 +00:00
if (isCurrentScreen('Artist', 'Album')) return 'track'
if (isCurrentScreen('Search.Songs', 'Queue', 'RecentlyPlayed')) return null
return 'title'
2022-07-05 15:09:20 +00:00
})())
2022-06-10 10:47:46 +00:00
2022-07-05 15:09:20 +00:00
const sortOrder = ref<SortOrder>('asc')
2022-06-10 10:47:46 +00:00
2022-07-05 15:09:20 +00:00
const sort = (by: SongListSortField | null = sortField.value, order: SortOrder = sortOrder.value) => {
if (!config.sortable) return
2022-07-05 15:09:20 +00:00
if (!by) return
sortField.value = by
sortOrder.value = order
let sortFields: SongListSortField[] = [by]
if (by === 'track') {
sortFields = ['disc', 'track', 'title']
2022-07-05 15:09:20 +00:00
} else if (by === 'album_name') {
sortFields.push('artist_name', 'disc', 'track', 'title')
2022-07-05 15:09:20 +00:00
} else if (by === 'artist_name') {
sortFields.push('album_name', 'disc', 'track', 'title')
2022-06-10 10:47:46 +00:00
}
2022-07-05 15:09:20 +00:00
songs.value = orderBy(songs.value, sortFields, order)
2022-06-10 10:47:46 +00:00
}
eventBus.on('SONGS_DELETED', deletedSongs => (songs.value = differenceBy(songs.value, deletedSongs, 'id')))
2022-07-20 08:00:02 +00:00
provideReadonly(SongsKey, songs, false)
provideReadonly(SelectedSongsKey, selectedSongs, false)
provideReadonly(SongListConfigKey, config)
2022-07-20 08:00:02 +00:00
provideReadonly(SongListSortFieldKey, sortField)
provideReadonly(SongListSortOrderKey, sortOrder)
2022-06-10 10:47:46 +00:00
provide(SongListFilterKeywordsKey, filterKeywords)
2022-04-15 14:24:30 +00:00
return {
SongList,
SongListControls,
2022-06-10 10:47:46 +00:00
ControlsToggle,
2022-07-16 09:52:39 +00:00
ThumbnailStack,
2022-04-21 16:06:45 +00:00
songs,
2022-07-16 09:52:39 +00:00
headerLayout,
2022-07-05 15:09:20 +00:00
sortField,
sortOrder,
2022-04-23 21:24:02 +00:00
duration,
2022-07-16 09:52:39 +00:00
thumbnails,
2022-04-15 14:24:30 +00:00
songList,
selectedSongs,
showingControls,
isPhone,
onPressEnter,
2022-04-15 14:24:30 +00:00
playAll,
playSelected,
applyFilter,
2022-07-16 09:52:39 +00:00
onScrollBreakpoint,
2022-06-10 10:47:46 +00:00
sort
2022-04-15 14:24:30 +00:00
}
}