feat: do not manually emit selected songs

This commit is contained in:
Phan An 2022-07-04 16:18:41 +02:00
parent 9ae60b90ea
commit d59c0c8bfe
No known key found for this signature in database
GPG key ID: A81E4477F0BB6FDC
9 changed files with 23 additions and 35 deletions

View file

@ -2,18 +2,12 @@ import lodash from 'lodash'
import factory from '@/__tests__/factory' import factory from '@/__tests__/factory'
import { expect, it } from 'vitest' import { expect, it } from 'vitest'
import { fireEvent } from '@testing-library/vue' import { fireEvent } from '@testing-library/vue'
import { eventBus, noop } from '@/utils'
import UnitTestCase from '@/__tests__/UnitTestCase' import UnitTestCase from '@/__tests__/UnitTestCase'
import SongList from './SongList.vue' import SongList from './SongList.vue'
let songs: Song[] let songs: Song[]
new class extends UnitTestCase { new class extends UnitTestCase {
protected beforeEach () {
// suppress the warning
super.beforeEach(() => eventBus.on('SET_SELECTED_SONGS', noop))
}
private renderComponent (type: SongListType = 'all-songs') { private renderComponent (type: SongListType = 'all-songs') {
songs = factory<Song>('song', 3) songs = factory<Song>('song', 3)

View file

@ -85,9 +85,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import isMobile from 'ismobilejs' import isMobile from 'ismobilejs'
import { findIndex } from 'lodash' import { findIndex } from 'lodash'
import { computed, defineAsyncComponent, getCurrentInstance, 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 { SongListConfigKey, SongListTypeKey, SongsKey } from '@/symbols' import { SelectedSongsKey, SongListConfigKey, 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'))
@ -96,6 +96,7 @@ 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 lastSelectedRow = ref<SongRow>() const lastSelectedRow = ref<SongRow>()
const sortFields = ref<SongListSortField[]>([]) const sortFields = ref<SongListSortField[]>([])
@ -103,7 +104,10 @@ const sortOrder = ref<SortOrder>('asc')
const songRows = ref<SongRow[]>([]) const songRows = ref<SongRow[]>([])
const allowReordering = type === 'queue' const allowReordering = type === 'queue'
const selectedSongs = computed(() => songRows.value.filter(row => row.selected).map(row => row.song))
watch(songRows, () => {
selectedSongs.value = songRows.value.filter(row => row.selected).map(row => row.song)
}, { deep: true })
const config = computed((): SongListConfig => { const config = computed((): SongListConfig => {
return Object.assign({ return Object.assign({
@ -113,7 +117,7 @@ const config = computed((): SongListConfig => {
}) })
const currentSortField = ref<SongListSortField | null>((() => { const currentSortField = ref<SongListSortField | null>((() => {
if (['album', 'artist'].includes(type)) return 'track' if (type === 'album' || type === 'artist') return 'track'
if (type === 'search-results') return null if (type === 'search-results') return null
return config.value.sortable ? 'title' : null return config.value.sortable ? 'title' : null
})()) })())
@ -153,9 +157,6 @@ const render = () => {
watch(items, () => render(), { deep: true }) watch(items, () => render(), { deep: true })
const vm = getCurrentInstance()
watch(selectedSongs, () => eventBus.emit('SET_SELECTED_SONGS', selectedSongs.value, vm?.parent))
const handleDelete = () => { const handleDelete = () => {
emit('press:delete') emit('press:delete')
clearSelection() clearSelection()

View file

@ -1,9 +1,8 @@
import { ComponentInternalInstance, computed, getCurrentInstance, provide, reactive, Ref, ref } from 'vue' import { computed, getCurrentInstance, provide, reactive, Ref, ref } from 'vue'
import isMobile from 'ismobilejs' import isMobile from 'ismobilejs'
import { orderBy } from 'lodash' import { orderBy } from 'lodash'
import { playbackService } from '@/services' import { playbackService } from '@/services'
import { eventBus } from '@/utils'
import { queueStore, songStore } from '@/stores' import { queueStore, songStore } from '@/stores'
import router from '@/router' import router from '@/router'
@ -67,12 +66,6 @@ export const useSongList = (
songs.value = orderBy(songs.value, sortFields, sortOrder) songs.value = orderBy(songs.value, sortFields, sortOrder)
} }
eventBus.on({
SET_SELECTED_SONGS (songs: Song[], target: ComponentInternalInstance) {
target === vm && (selectedSongs.value = songs)
}
})
provide(SongListTypeKey, type) provide(SongListTypeKey, type)
provide(SongsKey, songs) provide(SongsKey, songs)
provide(SelectedSongsKey, selectedSongs) provide(SelectedSongsKey, selectedSongs)

View file

@ -9,7 +9,6 @@ export type EventName =
| 'PLAY_YOUTUBE_VIDEO' | 'PLAY_YOUTUBE_VIDEO'
| 'INIT_EQUALIZER' | 'INIT_EQUALIZER'
| 'TOGGLE_VISUALIZER' | 'TOGGLE_VISUALIZER'
| 'SET_SELECTED_SONGS'
| 'SEARCH_KEYWORDS_CHANGED' | 'SEARCH_KEYWORDS_CHANGED'
| 'SONG_CONTEXT_MENU_REQUESTED' | 'SONG_CONTEXT_MENU_REQUESTED'
| 'ALBUM_CONTEXT_MENU_REQUESTED' | 'ALBUM_CONTEXT_MENU_REQUESTED'

View file

@ -41,10 +41,10 @@ export const Cache = {
this.storage.delete(this.normalizeKey(key)) this.storage.delete(this.normalizeKey(key))
}, },
resolve<T> (key: any, resolver: Closure) { async resolve<T> (key: any, fetcher: Closure) {
key = this.normalizeKey(key) key = this.normalizeKey(key)
this.hit(key) || this.set(key, resolver()) this.hit(key) || this.set(key, await fetcher())
return this.storage.get(key)!.value return this.storage.get(key)!.value
} }
} }

View file

@ -61,7 +61,7 @@ export const albumStore = {
let album = this.byId(id) let album = this.byId(id)
if (!album) { if (!album) {
album = Cache.resolve<Album>(['album', id], async () => await httpService.get<Album>(`albums/${id}`)) album = await Cache.resolve<Album>(['album', id], async () => await httpService.get<Album>(`albums/${id}`))
this.syncWithVault(album) this.syncWithVault(album)
} }

View file

@ -23,10 +23,9 @@ export const artistStore = {
ids.forEach(id => this.vault.delete(id)) ids.forEach(id => this.vault.delete(id))
}, },
isVarious: (artist: Artist | number) => { isVarious: (artist: Artist | number) => (typeof artist === 'number')
if (typeof artist === 'number') return artist === VARIOUS_ARTISTS_ID ? artist === VARIOUS_ARTISTS_ID
return artist.id === VARIOUS_ARTISTS_ID : artist.id === VARIOUS_ARTISTS_ID,
},
isUnknown: (artist: Artist | number) => (typeof artist === 'number') isUnknown: (artist: Artist | number) => (typeof artist === 'number')
? artist === UNKNOWN_ARTIST_ID ? artist === UNKNOWN_ARTIST_ID
@ -56,7 +55,7 @@ export const artistStore = {
let artist = this.byId(id) let artist = this.byId(id)
if (!artist) { if (!artist) {
artist = Cache.resolve<Artist>(['artist', id], async () => await httpService.get<Artist>(`artists/${id}`)) artist = await Cache.resolve<Artist>(['artist', id], async () => await httpService.get<Artist>(`artists/${id}`))
this.syncWithVault(artist) this.syncWithVault(artist)
} }

View file

@ -180,21 +180,21 @@ export const songStore = {
}, },
async fetchForAlbum (album: Album) { async fetchForAlbum (album: Album) {
return Cache.resolve<Song[]>( return await Cache.resolve<Song[]>(
[`album.songs`, album.id], [`album.songs`, album.id],
async () => this.syncWithVault(await httpService.get<Song[]>(`albums/${album.id}/songs`)) async () => this.syncWithVault(await httpService.get<Song[]>(`albums/${album.id}/songs`))
) )
}, },
async fetchForArtist (artist: Artist) { async fetchForArtist (artist: Artist) {
return Cache.resolve<Song[]>( return await Cache.resolve<Song[]>(
['artist.songs', artist.id], ['artist.songs', artist.id],
async () => this.syncWithVault(await httpService.get<Song[]>(`artists/${artist.id}/songs`)) async () => this.syncWithVault(await httpService.get<Song[]>(`artists/${artist.id}/songs`))
) )
}, },
async fetchForPlaylist (playlist: Playlist) { async fetchForPlaylist (playlist: Playlist) {
return Cache.resolve<Song[]>( return await Cache.resolve<Song[]>(
[`playlist.songs`, playlist.id], [`playlist.songs`, playlist.id],
async () => this.syncWithVault(await httpService.get<Song[]>(`playlists/${playlist.id}/songs`)) async () => this.syncWithVault(await httpService.get<Song[]>(`playlists/${playlist.id}/songs`))
) )

View file

@ -44,5 +44,7 @@ export const slugToTitle = (slug: string, separator = '-') => {
return title.replace(/\s+/g, ' ').trim() return title.replace(/\s+/g, ' ').trim()
} }
export const pluralize = (count: number, singular: string) => export const pluralize = (count: number | undefined, singular: string) => {
count === 1 ? `${count} ${singular}` : `${count.toLocaleString()} ${singular}s` count = count ?? 0
return count === 1 ? `${count} ${singular}` : `${count.toLocaleString()} ${singular}s`
}