mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
feat: allow refreshing playlists (#1579)
This commit is contained in:
parent
3b15622693
commit
1e12e55de3
11 changed files with 186 additions and 138 deletions
|
@ -23,14 +23,14 @@ new class extends UnitTestCase {
|
|||
screen: 'Playlist'
|
||||
}, { id: playlist.id.toString() })
|
||||
|
||||
await waitFor(() => expect(fetchMock).toHaveBeenCalledWith(playlist))
|
||||
await waitFor(() => expect(fetchMock).toHaveBeenCalledWith(playlist, false))
|
||||
|
||||
return { rendered, fetchMock }
|
||||
}
|
||||
|
||||
protected test () {
|
||||
it('renders the playlist', async () => {
|
||||
const { getByTestId, queryByTestId } = (await this.renderComponent(factory<Song>('song', 10))).rendered
|
||||
const { rendered: { getByTestId, queryByTestId } } = (await this.renderComponent(factory<Song>('song', 10)))
|
||||
|
||||
await waitFor(() => {
|
||||
getByTestId('song-list')
|
||||
|
@ -39,7 +39,7 @@ new class extends UnitTestCase {
|
|||
})
|
||||
|
||||
it('displays the empty state if playlist is empty', async () => {
|
||||
const { getByTestId, queryByTestId } = (await this.renderComponent([])).rendered
|
||||
const { rendered: { getByTestId, queryByTestId } } = (await this.renderComponent([]))
|
||||
|
||||
await waitFor(() => {
|
||||
getByTestId('screen-empty-state')
|
||||
|
@ -49,7 +49,7 @@ new class extends UnitTestCase {
|
|||
|
||||
it('downloads the playlist', async () => {
|
||||
const downloadMock = this.mock(downloadService, 'fromPlaylist')
|
||||
const { getByText } = (await this.renderComponent(factory<Song>('song', 10))).rendered
|
||||
const { rendered: { getByText } } = (await this.renderComponent(factory<Song>('song', 10)))
|
||||
|
||||
await this.tick()
|
||||
await fireEvent.click(getByText('Download All'))
|
||||
|
@ -58,14 +58,20 @@ new class extends UnitTestCase {
|
|||
})
|
||||
|
||||
it('deletes the playlist', async () => {
|
||||
const { getByTitle } = (await this.renderComponent([])).rendered
|
||||
|
||||
// mock *after* rendering to not tamper with "ACTIVATE_SCREEN" emission
|
||||
const emitMock = this.mock(eventBus, 'emit')
|
||||
const { rendered: { getByTitle } } = (await this.renderComponent([]))
|
||||
|
||||
await fireEvent.click(getByTitle('Delete this playlist'))
|
||||
|
||||
await waitFor(() => expect(emitMock).toHaveBeenCalledWith('PLAYLIST_DELETE', playlist))
|
||||
})
|
||||
|
||||
it('refreshes the playlist', async () => {
|
||||
const { rendered: { getByTitle }, fetchMock } = (await this.renderComponent([]))
|
||||
|
||||
await fireEvent.click(getByTitle('Refresh'))
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(playlist, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<section v-if="playlist" id="playlistWrapper">
|
||||
<ScreenHeader :layout="songs.length === 0 ? 'collapsed' : headerLayout">
|
||||
<ScreenHeader :layout="songs.length === 0 ? 'collapsed' : headerLayout" :disabled="loading">
|
||||
{{ playlist.name }}
|
||||
<ControlsToggle v-if="songs.length" v-model="showingControls"/>
|
||||
|
||||
|
@ -29,11 +29,12 @@
|
|||
@deletePlaylist="destroy"
|
||||
@playAll="playAll"
|
||||
@playSelected="playSelected"
|
||||
@refresh="fetchSongs(true)"
|
||||
/>
|
||||
</template>
|
||||
</ScreenHeader>
|
||||
|
||||
<SongListSkeleton v-if="loading"/>
|
||||
<SongListSkeleton v-show="loading"/>
|
||||
<SongList
|
||||
v-if="!loading && songs.length"
|
||||
ref="songList"
|
||||
|
@ -83,7 +84,10 @@ const playlistId = ref<number>()
|
|||
const playlist = ref<Playlist>()
|
||||
const loading = ref(false)
|
||||
|
||||
const controlsConfig: Partial<SongListControlsConfig> = { deletePlaylist: true }
|
||||
const controlsConfig: Partial<SongListControlsConfig> = {
|
||||
deletePlaylist: true,
|
||||
refresh: true
|
||||
}
|
||||
|
||||
const {
|
||||
SongList,
|
||||
|
@ -115,9 +119,9 @@ const editPlaylist = () => eventBus.emit('MODAL_SHOW_EDIT_PLAYLIST_FORM', playli
|
|||
|
||||
const removeSelected = async () => await removeSongsFromPlaylist(playlist.value!, selectedSongs.value)
|
||||
|
||||
const fetchSongs = async () => {
|
||||
const fetchSongs = async (refresh = false) => {
|
||||
loading.value = true
|
||||
songs.value = await songStore.fetchForPlaylist(playlist.value!)
|
||||
songs.value = await songStore.fetchForPlaylist(playlist.value!, refresh)
|
||||
loading.value = false
|
||||
sort()
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<template v-slot:controls>
|
||||
<BtnGroup uppercased v-if="hasUploadFailures">
|
||||
<Btn data-testid="upload-retry-all-btn" green @click="retryAll">
|
||||
<icon :icon="faRotateBack"/>
|
||||
<icon :icon="faRotateRight"/>
|
||||
Retry All
|
||||
</Btn>
|
||||
<Btn data-testid="upload-remove-all-btn" orange @click="removeFailedEntries">
|
||||
|
@ -58,7 +58,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faRotateBack, faTrashCan, faUpload, faWarning } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faRotateRight, faTrashCan, faUpload, faWarning } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, defineAsyncComponent, ref, toRef } from 'vue'
|
||||
|
||||
import { isDirectoryReadingSupported as canDropFolders } from '@/utils'
|
||||
|
|
|
@ -1,38 +1,6 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`renders 1`] = `
|
||||
<section id="songsWrapper">
|
||||
<header class="screen-header expanded" data-v-5691beb5="">
|
||||
<aside class="thumbnail-wrapper" data-v-5691beb5="">
|
||||
<div class="thumbnail-stack single" style="background-image: url(undefined/resources/assets/img/covers/default.svg);" data-v-55bfc268="" data-v-5691beb5-s=""><span data-testid="thumbnail" data-v-55bfc268=""></span></div>
|
||||
</aside>
|
||||
<main data-v-5691beb5="">
|
||||
<div class="heading-wrapper" data-v-5691beb5="">
|
||||
<h1 class="name" data-v-5691beb5=""> All Songs
|
||||
<!--v-if-->
|
||||
</h1><span class="meta text-secondary" data-v-5691beb5=""><span data-v-5691beb5-s="">420 songs</span><span data-v-5691beb5-s="">34:17:36</span></span>
|
||||
</div>
|
||||
<div class="song-list-controls" data-testid="song-list-controls" data-v-d396e0d2="" data-v-5691beb5-s=""><span class="btn-group" uppercased="" data-v-e884c19a="" data-v-d396e0d2=""><button type="button" class="btn-shuffle-all" data-testid="btn-shuffle-all" orange="" title="Shuffle all songs" data-v-e368fe26="" data-v-d396e0d2=""><br data-testid="icon" icon="[object Object]" fixed-width="" data-v-d396e0d2=""> All </button><!--v-if--><!--v-if--><!--v-if--><!--v-if--></span>
|
||||
<div class="add-to" data-testid="add-to-menu" tabindex="0" data-v-42061e3e="" data-v-d396e0d2="" style="display: none;">
|
||||
<section class="existing-playlists" data-v-42061e3e="">
|
||||
<p data-v-42061e3e="">Add 0 songs to</p>
|
||||
<ul data-v-42061e3e="">
|
||||
<li data-testid="queue" tabindex="0" data-v-42061e3e="">Queue</li>
|
||||
<li class="favorites" data-testid="add-to-favorites" tabindex="0" data-v-42061e3e=""> Favorites </li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="new-playlist" data-testid="new-playlist" data-v-42061e3e="">
|
||||
<p data-v-42061e3e="">or create a new playlist</p>
|
||||
<form class="form-save form-simple form-new-playlist" data-v-42061e3e=""><input data-testid="new-playlist-name" placeholder="Playlist name" required="" type="text" data-v-42061e3e=""><button type="submit" title="Save" data-v-e368fe26="" data-v-42061e3e="">⏎</button></form>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</header><br data-testid="song-list">
|
||||
</section>
|
||||
`;
|
||||
|
||||
exports[`renders 2`] = `
|
||||
<section id="songsWrapper">
|
||||
<header class="screen-header expanded" data-v-5691beb5="">
|
||||
<aside class="thumbnail-wrapper" data-v-5691beb5="">
|
||||
|
@ -44,7 +12,8 @@ exports[`renders 2`] = `
|
|||
<!--v-if-->
|
||||
</h1><span class="meta text-secondary" data-v-5691beb5=""><span data-v-5691beb5-s="">420 songs</span><span data-v-5691beb5-s="">34 hr 17 min</span></span>
|
||||
</div>
|
||||
<div class="song-list-controls" data-testid="song-list-controls" data-v-d396e0d2="" data-v-5691beb5-s=""><span class="btn-group" uppercased="" data-v-e884c19a="" data-v-d396e0d2=""><button type="button" class="btn-shuffle-all" data-testid="btn-shuffle-all" orange="" title="Shuffle all songs" data-v-e368fe26="" data-v-d396e0d2=""><br data-testid="icon" icon="[object Object]" fixed-width="" data-v-d396e0d2=""> All </button><!--v-if--><!--v-if--><!--v-if--><!--v-if--></span>
|
||||
<div class="song-list-controls" data-testid="song-list-controls" data-v-d396e0d2="" data-v-5691beb5-s="">
|
||||
<div class="wrapper" data-v-d396e0d2=""><span class="btn-group" uppercased="" data-v-e884c19a="" data-v-d396e0d2=""><button type="button" class="btn-shuffle-all" data-testid="btn-shuffle-all" orange="" title="Shuffle all songs" data-v-e368fe26="" data-v-d396e0d2=""><br data-testid="icon" icon="[object Object]" fixed-width="" data-v-d396e0d2=""> All </button><!--v-if--><!--v-if--><!--v-if--></span><span class="btn-group" data-v-e884c19a="" data-v-d396e0d2=""></span></div>
|
||||
<div class="add-to" data-testid="add-to-menu" tabindex="0" data-v-42061e3e="" data-v-d396e0d2="" style="display: none;">
|
||||
<section class="existing-playlists" data-v-42061e3e="">
|
||||
<p data-v-42061e3e="">Add 0 songs to</p>
|
||||
|
|
|
@ -1,83 +1,90 @@
|
|||
<template>
|
||||
<div class="song-list-controls" data-testid="song-list-controls" ref="el">
|
||||
<BtnGroup uppercased>
|
||||
<template v-if="mergedConfig.play">
|
||||
<template v-if="altPressed">
|
||||
<Btn
|
||||
v-if="selectedSongs.length < 2 && songs.length"
|
||||
class="btn-play-all"
|
||||
orange
|
||||
title="Play all songs"
|
||||
@click.prevent="playAll"
|
||||
>
|
||||
<icon :icon="faPlay" fixed-width/>
|
||||
All
|
||||
</Btn>
|
||||
<div ref="el" class="song-list-controls" data-testid="song-list-controls">
|
||||
<div class="wrapper">
|
||||
<BtnGroup uppercased>
|
||||
<template v-if="mergedConfig.play">
|
||||
<template v-if="altPressed">
|
||||
<Btn
|
||||
v-if="selectedSongs.length < 2 && songs.length"
|
||||
class="btn-play-all"
|
||||
orange
|
||||
title="Play all songs"
|
||||
@click.prevent="playAll"
|
||||
>
|
||||
<icon :icon="faPlay" fixed-width/>
|
||||
All
|
||||
</Btn>
|
||||
|
||||
<Btn
|
||||
v-if="selectedSongs.length > 1"
|
||||
class="btn-play-selected"
|
||||
orange
|
||||
title="Play selected songs"
|
||||
@click.prevent="playSelected"
|
||||
>
|
||||
<icon :icon="faPlay" fixed-width/>
|
||||
Selected
|
||||
</Btn>
|
||||
<Btn
|
||||
v-if="selectedSongs.length > 1"
|
||||
class="btn-play-selected"
|
||||
orange
|
||||
title="Play selected songs"
|
||||
@click.prevent="playSelected"
|
||||
>
|
||||
<icon :icon="faPlay" fixed-width/>
|
||||
Selected
|
||||
</Btn>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<Btn
|
||||
v-if="selectedSongs.length < 2 && songs.length"
|
||||
class="btn-shuffle-all"
|
||||
data-testid="btn-shuffle-all"
|
||||
orange
|
||||
title="Shuffle all songs"
|
||||
@click.prevent="shuffle"
|
||||
>
|
||||
<icon :icon="faRandom" fixed-width/>
|
||||
All
|
||||
</Btn>
|
||||
|
||||
<Btn
|
||||
v-if="selectedSongs.length > 1"
|
||||
class="btn-shuffle-selected"
|
||||
data-testid="btn-shuffle-selected"
|
||||
orange
|
||||
title="Shuffle selected songs"
|
||||
@click.prevent="shuffleSelected"
|
||||
>
|
||||
<icon :icon="faRandom" fixed-width/>
|
||||
Selected
|
||||
</Btn>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<Btn
|
||||
v-if="selectedSongs.length < 2 && songs.length"
|
||||
class="btn-shuffle-all"
|
||||
data-testid="btn-shuffle-all"
|
||||
orange
|
||||
title="Shuffle all songs"
|
||||
@click.prevent="shuffle"
|
||||
>
|
||||
<icon :icon="faRandom" fixed-width/>
|
||||
All
|
||||
</Btn>
|
||||
<Btn
|
||||
v-if="selectedSongs.length"
|
||||
:title="`${showingAddToMenu ? 'Cancel' : 'Add selected songs to…'}`"
|
||||
class="btn-add-to"
|
||||
data-testid="add-to-btn"
|
||||
green
|
||||
@click.prevent.stop="toggleAddToMenu"
|
||||
>
|
||||
{{ showingAddToMenu ? 'Cancel' : 'Add To…' }}
|
||||
</Btn>
|
||||
|
||||
<Btn
|
||||
v-if="selectedSongs.length > 1"
|
||||
class="btn-shuffle-selected"
|
||||
data-testid="btn-shuffle-selected"
|
||||
orange
|
||||
title="Shuffle selected songs"
|
||||
@click.prevent="shuffleSelected"
|
||||
>
|
||||
<icon :icon="faRandom" fixed-width/>
|
||||
Selected
|
||||
</Btn>
|
||||
</template>
|
||||
</template>
|
||||
<Btn v-if="showClearQueueButton" red title="Clear current queue" @click.prevent="clearQueue">Clear</Btn>
|
||||
</BtnGroup>
|
||||
|
||||
<Btn
|
||||
v-if="selectedSongs.length"
|
||||
:title="`${showingAddToMenu ? 'Cancel' : 'Add selected songs to…'}`"
|
||||
class="btn-add-to"
|
||||
data-testid="add-to-btn"
|
||||
green
|
||||
@click.prevent.stop="toggleAddToMenu"
|
||||
>
|
||||
{{ showingAddToMenu ? 'Cancel' : 'Add To…' }}
|
||||
</Btn>
|
||||
<BtnGroup>
|
||||
<Btn v-if="config.refresh" v-koel-tooltip green title="Refresh" @click.prevent="refresh">
|
||||
<icon :icon="faRotateRight" fixed-width/>
|
||||
</Btn>
|
||||
|
||||
<Btn v-if="showClearQueueButton" red title="Clear current queue" @click.prevent="clearQueue">Clear</Btn>
|
||||
|
||||
<Btn
|
||||
v-if="showDeletePlaylistButton"
|
||||
v-koel-tooltip
|
||||
class="del btn-delete-playlist"
|
||||
red
|
||||
title="Delete this playlist"
|
||||
@click.prevent="deletePlaylist"
|
||||
>
|
||||
<icon :icon="faTrashCan"/>
|
||||
</Btn>
|
||||
|
||||
</BtnGroup>
|
||||
<Btn
|
||||
v-if="showDeletePlaylistButton"
|
||||
v-koel-tooltip
|
||||
class="del btn-delete-playlist"
|
||||
red
|
||||
title="Delete this playlist"
|
||||
@click.prevent="deletePlaylist"
|
||||
>
|
||||
<icon :icon="faTrashCan"/>
|
||||
</Btn>
|
||||
</BtnGroup>
|
||||
</div>
|
||||
|
||||
<AddToMenu
|
||||
v-koel-clickaway="closeAddToMenu"
|
||||
|
@ -90,7 +97,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faPlay, faRandom, faTrashCan } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faPlay, faRandom, faRotateRight, faTrashCan } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, toRefs } from 'vue'
|
||||
import { SelectedSongsKey, SongsKey } from '@/symbols'
|
||||
import { requireInjection } from '@/utils'
|
||||
|
@ -118,14 +125,15 @@ const mergedConfig = computed((): SongListControlsConfig => Object.assign({
|
|||
newPlaylist: true
|
||||
},
|
||||
clearQueue: false,
|
||||
deletePlaylist: false
|
||||
deletePlaylist: false,
|
||||
refresh: false
|
||||
}, config.value)
|
||||
)
|
||||
|
||||
const showClearQueueButton = computed(() => mergedConfig.value.clearQueue)
|
||||
const showDeletePlaylistButton = computed(() => mergedConfig.value.deletePlaylist)
|
||||
|
||||
const emit = defineEmits(['playAll', 'playSelected', 'clearQueue', 'deletePlaylist'])
|
||||
const emit = defineEmits(['playAll', 'playSelected', 'clearQueue', 'deletePlaylist', 'refresh'])
|
||||
|
||||
const shuffle = () => emit('playAll', true)
|
||||
const shuffleSelected = () => emit('playSelected', true)
|
||||
|
@ -133,6 +141,7 @@ const playAll = () => emit('playAll', false)
|
|||
const playSelected = () => emit('playSelected', false)
|
||||
const clearQueue = () => emit('clearQueue')
|
||||
const deletePlaylist = () => emit('deletePlaylist')
|
||||
const refresh = () => emit('refresh')
|
||||
const closeAddToMenu = () => (showingAddToMenu.value = false)
|
||||
const registerKeydown = (event: KeyboardEvent) => event.key === 'Alt' && (altPressed.value = true)
|
||||
const registerKeyup = (event: KeyboardEvent) => event.key === 'Alt' && (altPressed.value = false)
|
||||
|
@ -168,5 +177,10 @@ onUnmounted(() => {
|
|||
<style lang="scss" scoped>
|
||||
.song-list-controls {
|
||||
position: relative;
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
.btn-group {
|
||||
--radius: 5px;
|
||||
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<header class="screen-header" :class="layout">
|
||||
<header class="screen-header" :class="[ layout, disabled ? 'disabled' : '' ]">
|
||||
<aside class="thumbnail-wrapper">
|
||||
<slot name="thumbnail"></slot>
|
||||
</aside>
|
||||
|
@ -20,7 +20,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = withDefaults(defineProps<{ layout?: ScreenHeaderLayout }>(), { layout: 'expanded' })
|
||||
const props = withDefaults(defineProps<{
|
||||
layout?: ScreenHeaderLayout,
|
||||
disabled?: boolean,
|
||||
}>(), {
|
||||
layout: 'expanded',
|
||||
disabled: false
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -40,6 +46,15 @@ header.screen-header {
|
|||
line-height: normal;
|
||||
padding: 1.8rem;
|
||||
|
||||
&.disabled {
|
||||
opacity: .5;
|
||||
cursor: not-allowed;
|
||||
|
||||
*, *::before, *::after {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
.thumbnail-wrapper {
|
||||
margin-right: 1.5rem;
|
||||
|
|
|
@ -3,7 +3,7 @@ import isMobile from 'ismobilejs'
|
|||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import { expect, it } from 'vitest'
|
||||
import factory from '@/__tests__/factory'
|
||||
import { authService, http } from '@/services'
|
||||
import { authService, cache, http } from '@/services'
|
||||
import { albumStore, artistStore, commonStore, overviewStore, preferenceStore, songStore, SongUpdateResult } from '.'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
|
@ -226,10 +226,37 @@ new class extends UnitTestCase {
|
|||
const getMock = this.mock(http, 'get').mockResolvedValueOnce(songs)
|
||||
const syncMock = this.mock(songStore, 'syncWithVault', songs)
|
||||
|
||||
await songStore.fetchForPlaylist(playlist)
|
||||
const fetched = await songStore.fetchForPlaylist(playlist)
|
||||
|
||||
expect(getMock).toHaveBeenCalledWith('playlists/42/songs')
|
||||
expect(syncMock).toHaveBeenCalledWith(songs)
|
||||
expect(fetched).toEqual(songs)
|
||||
})
|
||||
|
||||
it('fetches for playlist with cache', async () => {
|
||||
const songs = factory<Song>('song', 3)
|
||||
const playlist = factory<Playlist>('playlist', { id: 42 })
|
||||
cache.set(['playlist.songs', playlist.id], songs)
|
||||
|
||||
const getMock = this.mock(http, 'get')
|
||||
|
||||
const fetched = await songStore.fetchForPlaylist(playlist)
|
||||
|
||||
expect(getMock).not.toHaveBeenCalled()
|
||||
expect(fetched).toEqual(songs)
|
||||
})
|
||||
|
||||
it('fetches for playlist discarding cache', async () => {
|
||||
const songs = factory<Song>('song', 3)
|
||||
const playlist = factory<Playlist>('playlist', { id: 42 })
|
||||
cache.set(['playlist.songs', playlist.id], songs)
|
||||
|
||||
const getMock = this.mock(http, 'get').mockResolvedValueOnce([])
|
||||
|
||||
await songStore.fetchForPlaylist(playlist, true)
|
||||
|
||||
expect(getMock).toHaveBeenCalled()
|
||||
expect(cache.get(['playlist.songs', playlist.id])).toEqual([])
|
||||
})
|
||||
|
||||
it('paginates', async () => {
|
||||
|
|
|
@ -140,24 +140,37 @@ export const songStore = {
|
|||
watch(() => song.play_count, () => overviewStore.refresh())
|
||||
},
|
||||
|
||||
async cacheable (key: any, fetcher: Promise<Song[]>) {
|
||||
const songs = await cache.remember<Song[]>(key, async () => this.syncWithVault(await fetcher))
|
||||
return songs.filter(song => !song.deleted)
|
||||
},
|
||||
ensureNotDeleted: (songs: Song | Song[]) => arrayify(songs).filter(song => !song.deleted),
|
||||
|
||||
async fetchForAlbum (album: Album | number) {
|
||||
const id = typeof album === 'number' ? album : album.id
|
||||
return await this.cacheable(['album.songs', id], http.get<Song[]>(`albums/${id}/songs`))
|
||||
|
||||
return this.ensureNotDeleted(await cache.remember<Song[]>(
|
||||
[`album.songs`, id],
|
||||
async () => this.syncWithVault(await http.get<Song[]>(`albums/${id}/songs`))
|
||||
))
|
||||
},
|
||||
|
||||
async fetchForArtist (artist: Artist | number) {
|
||||
const id = typeof artist === 'number' ? artist : artist.id
|
||||
return await this.cacheable(['artist.songs', id], http.get<Song[]>(`artists/${id}/songs`))
|
||||
|
||||
return this.ensureNotDeleted(await cache.remember<Song[]>(
|
||||
[`artist.songs`, id],
|
||||
async () => this.syncWithVault(await http.get<Song[]>(`artists/${id}/songs`))
|
||||
))
|
||||
},
|
||||
|
||||
async fetchForPlaylist (playlist: Playlist | number) {
|
||||
async fetchForPlaylist (playlist: Playlist | number, refresh = false) {
|
||||
const id = typeof playlist === 'number' ? playlist : playlist.id
|
||||
return await this.cacheable(['playlist.songs', id], http.get<Song[]>(`playlists/${id}/songs`))
|
||||
|
||||
if (refresh) {
|
||||
cache.remove(['playlist.songs', id])
|
||||
}
|
||||
|
||||
return this.ensureNotDeleted(await cache.remember<Song[]>(
|
||||
[`playlist.songs`, id],
|
||||
async () => this.syncWithVault(await http.get<Song[]>(`playlists/${id}/songs`))
|
||||
))
|
||||
},
|
||||
|
||||
async fetchForPlaylistFolder (folder: PlaylistFolder) {
|
||||
|
|
|
@ -3,8 +3,7 @@ import DialogBox from '@/components/ui/DialogBox.vue'
|
|||
import MessageToaster from '@/components/ui/MessageToaster.vue'
|
||||
import Router from '@/router'
|
||||
|
||||
export interface ReadonlyInjectionKey<T> extends InjectionKey<[Readonly<T> | DeepReadonly<T>, Closure]> {
|
||||
}
|
||||
export type ReadonlyInjectionKey<T> = InjectionKey<[Readonly<T> | DeepReadonly<T>, Closure]>
|
||||
|
||||
export const RouterKey: InjectionKey<Router> = Symbol('Router')
|
||||
export const ScreenNameKey: ReadonlyInjectionKey<ScreenName> = Symbol('ScreenName')
|
||||
|
|
1
resources/assets/js/types.d.ts
vendored
1
resources/assets/js/types.d.ts
vendored
|
@ -324,6 +324,7 @@ interface SongListControlsConfig {
|
|||
addTo: AddToMenuConfig
|
||||
clearQueue: boolean
|
||||
deletePlaylist: boolean
|
||||
refresh: boolean
|
||||
}
|
||||
|
||||
type ThemeableProperty = '--color-text-primary'
|
||||
|
|
Loading…
Reference in a new issue