mirror of
https://github.com/koel/koel
synced 2024-11-24 13:13:05 +00:00
migration: song list controls
This commit is contained in:
parent
6a06e5ef9b
commit
c3880df2bc
37 changed files with 201 additions and 215 deletions
|
@ -1,6 +1,6 @@
|
|||
import Component from '@/components/screens/album.vue'
|
||||
import SongList from '@/components/song/list.vue'
|
||||
import { download, albumInfo as albumInfoService, playback } from '@/services'
|
||||
import SongList from '@/components/song/SongList.vue'
|
||||
import { albumInfo as albumInfoService, download } from '@/services'
|
||||
import factory from '@/__tests__/factory'
|
||||
import { mock } from '@/__tests__/__helpers__'
|
||||
import { mount, shallow } from '@/__tests__/adapter'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Component from '@/components/screens/all-songs.vue'
|
||||
import SongList from '@/components/song/list.vue'
|
||||
import Component from '@/components/screens/AllSongsScreen.vue'
|
||||
import SongList from '@/components/song/SongList.vue'
|
||||
import factory from '@/__tests__/factory'
|
||||
import { songStore } from '@/stores'
|
||||
import { mount } from '@/__tests__/adapter'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Component from '@/components/screens/artist.vue'
|
||||
import SongList from '@/components/song/list.vue'
|
||||
import { download, artistInfo as artistInfoService, playback } from '@/services'
|
||||
import SongList from '@/components/song/SongList.vue'
|
||||
import { artistInfo as artistInfoService, download } from '@/services'
|
||||
import factory from '@/__tests__/factory'
|
||||
import { mock } from '@/__tests__/__helpers__'
|
||||
import { mount, shallow } from '@/__tests__/adapter'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Component from '@/components/screens/favorites.vue'
|
||||
import SongList from '@/components/song/list.vue'
|
||||
import SongListControls from '@/components/song/list-controls.vue'
|
||||
import SongList from '@/components/song/SongList.vue'
|
||||
import SongListControls from '@/components/songSongListControls.vue'
|
||||
import { download } from '@/services'
|
||||
import factory from '@/__tests__/factory'
|
||||
import { mock } from '@/__tests__/__helpers__'
|
||||
|
@ -45,7 +45,7 @@ describe('components/screens/favorites', () => {
|
|||
shallow(Component, {
|
||||
data: () => ({
|
||||
state: {
|
||||
songs: factory('song', 5),
|
||||
songs: factory('song', 5)
|
||||
},
|
||||
sharedState: { allowDownload: true },
|
||||
meta: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Component from '@/components/screens/playlist.vue'
|
||||
import SongList from '@/components/song/list.vue'
|
||||
import SongList from '@/components/song/SongList.vue'
|
||||
import factory from '@/__tests__/factory'
|
||||
import { eventBus } from '@/utils'
|
||||
import { playlistStore } from '@/stores'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Component from '@/components/screens/queue.vue'
|
||||
import SongList from '@/components/song/list.vue'
|
||||
import SongList from '@/components/song/SongList.vue'
|
||||
import factory from '@/__tests__/factory'
|
||||
import { queueStore, songStore } from '@/stores'
|
||||
import { playback } from '@/services'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Component from '@/components/screens/recently-played.vue'
|
||||
import SongList from '@/components/song/list.vue'
|
||||
import SongList from '@/components/song/SongList.vue'
|
||||
import factory from '@/__tests__/factory'
|
||||
import { recentlyPlayedStore } from '@/stores'
|
||||
import { eventBus } from '@/utils'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/components/song/list-controls.vue'
|
||||
import Component from '@/components/songSongListControls.vue'
|
||||
import factory from '@/__tests__/factory'
|
||||
import { take } from 'lodash'
|
||||
import { shallow, mount } from '@/__tests__/adapter'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import router from '@/router'
|
||||
import Component from '@/components/song/list.vue'
|
||||
import Component from '@/components/song/SongList.vue'
|
||||
import factory from '@/__tests__/factory'
|
||||
import { queueStore } from '@/stores'
|
||||
import { playback } from '@/services'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/components/ui/screen-controls-toggler.vue'
|
||||
import Component from '@/components/ui/ScreenControlsToggler.vue'
|
||||
import isMobile from 'ismobilejs'
|
||||
import { shallow } from '@/__tests__/adapter'
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ import HomeScreen from '@/components/screens/home.vue'
|
|||
import QueueScreen from '@/components/screens/queue.vue'
|
||||
import AlbumListScreen from '@/components/screens/album-list.vue'
|
||||
import ArtistListScreen from '@/components/screens/artist-list.vue'
|
||||
import AllSongsScreen from '@/components/screens/all-songs.vue'
|
||||
import AllSongsScreen from '@/components/screens/AllSongsScreen.vue'
|
||||
import PlaylistScreen from '@/components/screens/playlist.vue'
|
||||
import FavoritesScreen from '@/components/screens/favorites.vue'
|
||||
|
||||
|
|
|
@ -10,30 +10,33 @@
|
|||
|
||||
<template v-slot:controls>
|
||||
<SongListControls
|
||||
v-if="state.songs.length && (!isPhone || showingControls)"
|
||||
v-if="songs.length && (!isPhone || showingControls)"
|
||||
@playAll="playAll"
|
||||
@playSelected="playSelected"
|
||||
:songs="state.songs"
|
||||
:songs="songs"
|
||||
:config="songListControlConfig"
|
||||
:selectedSongs="selectedSongs"
|
||||
/>
|
||||
</template>
|
||||
</ScreenHeader>
|
||||
|
||||
<SongList :items="state.songs" type="all-songs" ref="songList"/>
|
||||
<SongList :items="songs" type="all-songs" ref="songList"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, toRef } from 'vue'
|
||||
import { pluralize } from '@/utils'
|
||||
import { songStore } from '@/stores'
|
||||
import { useSongList } from '@/composables'
|
||||
import { defineAsyncComponent, reactive } from 'vue'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
|
||||
const {
|
||||
SongList,
|
||||
SongListControls,
|
||||
ControlsToggler,
|
||||
songs,
|
||||
songList,
|
||||
meta,
|
||||
selectedSongs,
|
||||
|
@ -43,8 +46,5 @@ const {
|
|||
playAll,
|
||||
playSelected,
|
||||
toggleControls
|
||||
} = useSongList()
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const state = reactive(songStore.state)
|
||||
} = useSongList(toRef(songStore.state, 'songs'))
|
||||
</script>
|
|
@ -20,7 +20,7 @@ import { eventBus, limitBy } from '@/utils'
|
|||
import { albumStore, preferenceStore as preferences } from '@/stores'
|
||||
import { useInfiniteScroll } from '@/composables'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const AlbumCard = defineAsyncComponent(() => import('@/components/album/card.vue'))
|
||||
const ViewModeSwitch = defineAsyncComponent(() => import('@/components/ui/view-mode-switch.vue'))
|
||||
|
||||
|
|
|
@ -9,22 +9,22 @@
|
|||
</template>
|
||||
|
||||
<template v-slot:meta>
|
||||
<span v-if="album.songs.length">
|
||||
<span v-if="songs.length">
|
||||
by
|
||||
<a class="artist" v-if="isNormalArtist" :href="`#!/artist/${album.artist.id}`">{{ album.artist.name }}</a>
|
||||
<a v-if="isNormalArtist" :href="`#!/artist/${album.artist.id}`" class="artist">{{ album.artist.name }}</a>
|
||||
<span class="nope" v-else>{{ album.artist.name }}</span>
|
||||
•
|
||||
{{ pluralize(album.songs.length, 'song') }}
|
||||
{{ pluralize(songs.length, 'song') }}
|
||||
•
|
||||
{{ fmtLength }}
|
||||
|
||||
<template v-if="sharedState.useLastfm">
|
||||
•
|
||||
<a class="info" href @click.prevent="showInfo" title="View album's extra information">Info</a>
|
||||
<a class="info" href title="View album's extra information" @click.prevent="showInfo">Info</a>
|
||||
</template>
|
||||
<template v-if="sharedState.allowDownload">
|
||||
•
|
||||
<a class="download" href @click.prevent="download" title="Download all songs in album" role="button">
|
||||
<a class="download" href role="button" title="Download all songs in album" @click.prevent="download">
|
||||
Download All
|
||||
</a>
|
||||
</template>
|
||||
|
@ -33,19 +33,19 @@
|
|||
|
||||
<template v-slot:controls>
|
||||
<SongListControls
|
||||
v-if="album.songs.length && (!isPhone || showingControls)"
|
||||
@playAll="playAll"
|
||||
@playSelected="playSelected"
|
||||
:songs="album.songs"
|
||||
v-if="songs.length && (!isPhone || showingControls)"
|
||||
:config="songListControlConfig"
|
||||
:selectedSongs="selectedSongs"
|
||||
:songs="songs"
|
||||
@playAll="playAll"
|
||||
@playSelected="playSelected"
|
||||
/>
|
||||
</template>
|
||||
</ScreenHeader>
|
||||
|
||||
<SongList :items="album.songs" type="album" :config="listConfig" ref="songList"/>
|
||||
<SongList :items="songs" type="album" :config="listConfig" ref="songList"/>
|
||||
|
||||
<section class="info-wrapper" v-if="sharedState.useLastfm && showing">
|
||||
<section v-if="sharedState.useLastfm && showing" class="info-wrapper">
|
||||
<CloseModalBtn @click="showing = false"/>
|
||||
<div class="inner">
|
||||
<div class="loading" v-if="loading">
|
||||
|
@ -65,16 +65,20 @@ import { albumInfo as albumInfoService, download as downloadService } from '@/se
|
|||
import router from '@/router'
|
||||
import { useAlbumAttributes, useSongList } from '@/composables'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const AlbumInfo = defineAsyncComponent(() => import('@/components/album/AlbumInfo.vue'))
|
||||
const SoundBar = defineAsyncComponent(() => import('@/components/ui/sound-bar.vue'))
|
||||
const AlbumThumbnail = defineAsyncComponent(() => import('@/components/ui/AlbumArtistThumbnail.vue'))
|
||||
const CloseModalBtn = defineAsyncComponent(() => import('@/components/ui/close-modal-btn.vue'))
|
||||
|
||||
const props = defineProps<{ album: Album }>()
|
||||
const { album } = toRefs(props)
|
||||
|
||||
const {
|
||||
SongList,
|
||||
SongListControls,
|
||||
ControlsToggler,
|
||||
songs,
|
||||
songList,
|
||||
selectedSongs,
|
||||
showingControls,
|
||||
|
@ -83,10 +87,7 @@ const {
|
|||
playAll,
|
||||
playSelected,
|
||||
toggleControls
|
||||
} = useSongList()
|
||||
|
||||
const props = defineProps<{ album: Album }>()
|
||||
const { album } = toRefs(props)
|
||||
} = useSongList(ref(album.value.songs))
|
||||
|
||||
const { length, fmtLength } = useAlbumAttributes(album.value)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ const {
|
|||
makeScrollable
|
||||
} = useInfiniteScroll(9)
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const ArtistCard = defineAsyncComponent(() => import('@/components/artist/card.vue'))
|
||||
const ViewModeSwitch = defineAsyncComponent(() => import('@/components/ui/view-mode-switch.vue'))
|
||||
|
||||
|
|
|
@ -9,26 +9,26 @@
|
|||
</template>
|
||||
|
||||
<template v-slot:meta>
|
||||
<span v-if="artist.songs.length">
|
||||
<span v-if="songs.length">
|
||||
{{ pluralize(artist.albums.length, 'album') }}
|
||||
•
|
||||
{{ pluralize(artist.songs.length, 'song') }}
|
||||
{{ pluralize(songs.length, 'song') }}
|
||||
•
|
||||
{{ fmtLength }}
|
||||
|
||||
<template v-if="sharedState.useLastfm">
|
||||
•
|
||||
<a class="info" href @click.prevent="showInfo" title="View artist's extra information">Info</a>
|
||||
<a class="info" href title="View artist's extra information" @click.prevent="showInfo">Info</a>
|
||||
</template>
|
||||
|
||||
<template v-if="sharedState.allowDownload">
|
||||
•
|
||||
<a
|
||||
@click.prevent="download"
|
||||
class="download"
|
||||
href
|
||||
role="button"
|
||||
title="Download all songs by this artist"
|
||||
@click.prevent="download"
|
||||
>
|
||||
Download All
|
||||
</a>
|
||||
|
@ -38,17 +38,17 @@
|
|||
|
||||
<template v-slot:controls>
|
||||
<SongListControls
|
||||
v-if="artist.songs.length && (!isPhone || showingControls)"
|
||||
@playAll="playAll"
|
||||
@playSelected="playSelected"
|
||||
:songs="artist.songs"
|
||||
v-if="songs.length && (!isPhone || showingControls)"
|
||||
:config="songListControlConfig"
|
||||
:selectedSongs="selectedSongs"
|
||||
:songs="songs"
|
||||
@playAll="playAll"
|
||||
@playSelected="playSelected"
|
||||
/>
|
||||
</template>
|
||||
</ScreenHeader>
|
||||
|
||||
<SongList :items="artist.songs" type="artist" :config="listConfig" ref="songList"/>
|
||||
<SongList :items="songs" type="artist" :config="listConfig" ref="songList"/>
|
||||
|
||||
<section class="info-wrapper" v-if="sharedState.useLastfm && showing">
|
||||
<CloseModalBtn @click="showing = false"/>
|
||||
|
@ -70,12 +70,15 @@ import { artistInfo as artistInfoService, download as downloadService } from '@/
|
|||
import router from '@/router'
|
||||
import { useArtistAttributes, useSongList } from '@/composables'
|
||||
|
||||
const props = defineProps<{ artist: Artist }>()
|
||||
const { artist } = toRefs(props)
|
||||
|
||||
const {
|
||||
SongList,
|
||||
SongListControls,
|
||||
ControlsToggler,
|
||||
songList,
|
||||
state,
|
||||
songs,
|
||||
meta,
|
||||
selectedSongs,
|
||||
showingControls,
|
||||
|
@ -84,13 +87,11 @@ const {
|
|||
playAll,
|
||||
playSelected,
|
||||
toggleControls
|
||||
} = useSongList()
|
||||
} = useSongList(ref(artist.value.songs))
|
||||
|
||||
const props = defineProps<{ artist: Artist }>()
|
||||
const { artist } = toRefs(props)
|
||||
const { length, fmtLength, image } = useArtistAttributes(artist.value)
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const ArtistInfo = defineAsyncComponent(() => import('@/components/artist/info.vue'))
|
||||
const SoundBar = defineAsyncComponent(() => import('@/components/ui/sound-bar.vue'))
|
||||
const ArtistThumbnail = defineAsyncComponent(() => import('@/components/ui/AlbumArtistThumbnail.vue'))
|
||||
|
@ -112,7 +113,6 @@ watch(() => artist.value.albums.length, newAlbumCount => newAlbumCount || router
|
|||
|
||||
watch(artist, () => {
|
||||
showing.value = false
|
||||
// @ts-ignore
|
||||
songList.value?.sort()
|
||||
})
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
{{ pluralize(meta.songCount, 'song') }}
|
||||
•
|
||||
{{ meta.totalLength }}
|
||||
<template v-if="sharedState.allowDownload && state.songs.length">
|
||||
<template v-if="allowDownload && songs.length">
|
||||
•
|
||||
<a href @click.prevent="download" class="download" title="Download all songs in playlist" role="button">
|
||||
<a class="download" href role="button" title="Download all songs in playlist" @click.prevent="download">
|
||||
Download All
|
||||
</a>
|
||||
</template>
|
||||
|
@ -20,17 +20,17 @@
|
|||
|
||||
<template v-slot:controls>
|
||||
<SongListControls
|
||||
v-if="state.songs.length && (!isPhone || showingControls)"
|
||||
@playAll="playAll"
|
||||
@playSelected="playSelected"
|
||||
:songs="state.songs"
|
||||
v-if="songs.length && (!isPhone || showingControls)"
|
||||
:config="songListControlConfig"
|
||||
:selectedSongs="selectedSongs"
|
||||
:songs="songs"
|
||||
@playAll="playAll"
|
||||
@playSelected="playSelected"
|
||||
/>
|
||||
</template>
|
||||
</ScreenHeader>
|
||||
|
||||
<SongList v-if="state.songs.length" :items="state.songs" type="favorites" ref="songList"/>
|
||||
<SongList v-if="songs.length" ref="songList" :items="songs" type="favorites"/>
|
||||
|
||||
<ScreenPlaceholder v-else>
|
||||
<template v-slot:icon>
|
||||
|
@ -38,8 +38,8 @@
|
|||
</template>
|
||||
No favorites yet.
|
||||
<span class="secondary d-block">
|
||||
Click the
|
||||
<i class="fa fa-heart-o"></i>
|
||||
Click the
|
||||
<i class="fa fa-heart-o"></i>
|
||||
icon to mark a song as favorite.
|
||||
</span>
|
||||
</ScreenPlaceholder>
|
||||
|
@ -51,15 +51,16 @@ import { pluralize } from '@/utils'
|
|||
import { favoriteStore, sharedStore } from '@/stores'
|
||||
import { download as downloadService } from '@/services'
|
||||
import { useSongList } from '@/composables'
|
||||
import { defineAsyncComponent, reactive } from 'vue'
|
||||
import { defineAsyncComponent, toRef } from 'vue'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const ScreenPlaceholder = defineAsyncComponent(() => import('@/components/ui/screen-placeholder.vue'))
|
||||
|
||||
const {
|
||||
SongList,
|
||||
SongListControls,
|
||||
ControlsToggler,
|
||||
songs,
|
||||
songList,
|
||||
meta,
|
||||
selectedSongs,
|
||||
|
@ -69,10 +70,9 @@ const {
|
|||
playAll,
|
||||
playSelected,
|
||||
toggleControls
|
||||
} = useSongList()
|
||||
} = useSongList(toRef(favoriteStore.state, 'songs'))
|
||||
|
||||
const state = reactive(favoriteStore.state)
|
||||
const sharedState = reactive(sharedStore.state)
|
||||
const allowDownload = toRef(sharedStore.state, 'allowDownload')
|
||||
|
||||
const download = () => downloadService.fromFavorites()
|
||||
</script>
|
||||
|
|
|
@ -89,7 +89,7 @@ import router from '@/router'
|
|||
import { useInfiniteScroll } from '@/composables'
|
||||
import { computed, defineAsyncComponent, reactive, ref } from 'vue'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const AlbumCard = defineAsyncComponent(() => import('@/components/album/card.vue'))
|
||||
const ArtistCard = defineAsyncComponent(() => import('@/components/artist/card.vue'))
|
||||
const SongCard = defineAsyncComponent(() => import('@/components/song/card.vue'))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<section id="playlistWrapper">
|
||||
<section id="playlistWrapper" v-if="playlist">
|
||||
<ScreenHeader>
|
||||
{{ playlist.name }}
|
||||
<ControlsToggler v-if="playlist.populated" :showing-controls="showingControls" @toggleControls="toggleControls"/>
|
||||
|
@ -33,11 +33,11 @@
|
|||
|
||||
<template v-if="playlist.populated">
|
||||
<SongList
|
||||
v-if="playlist.songs.length"
|
||||
:items="playlist.songs"
|
||||
v-if="songs.length"
|
||||
ref="songList"
|
||||
:items="songs"
|
||||
:playlist="playlist"
|
||||
type="playlist"
|
||||
ref="songList"
|
||||
/>
|
||||
|
||||
<ScreenPlaceholder v-else>
|
||||
|
@ -62,23 +62,24 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { eventBus } from '@/utils'
|
||||
import { defineAsyncComponent, nextTick, ref } from 'vue'
|
||||
import { eventBus, pluralize } from '@/utils'
|
||||
import { playlistStore, sharedStore } from '@/stores'
|
||||
import { download as downloadService } from '@/services'
|
||||
import { useSongList } from '@/composables'
|
||||
import { defineAsyncComponent, nextTick, reactive, ref } from 'vue'
|
||||
import { pluralize } from '@/utils'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const ScreenPlaceholder = defineAsyncComponent(() => import('@/components/ui/screen-placeholder.vue'))
|
||||
|
||||
const playlist = ref<Playlist>()
|
||||
|
||||
const {
|
||||
SongList,
|
||||
SongListControls,
|
||||
ControlsToggler,
|
||||
songs,
|
||||
songList,
|
||||
meta,
|
||||
state,
|
||||
selectedSongs,
|
||||
showingControls,
|
||||
songListControlConfig,
|
||||
|
@ -86,15 +87,12 @@ const {
|
|||
playAll,
|
||||
playSelected,
|
||||
toggleControls
|
||||
} = useSongList({
|
||||
deletePlaylist: true
|
||||
})
|
||||
} = useSongList(ref(playlist.value?.songs || []), { deletePlaylist: true })
|
||||
|
||||
const playlist = ref<Playlist>(playlistStore.stub)
|
||||
const sharedState = reactive(sharedStore.state)
|
||||
const sharedState = sharedStore.state
|
||||
|
||||
const destroy = () => eventBus.emit('PLAYLIST_DELETE', playlist.value)
|
||||
const download = () => downloadService.fromPlaylist(playlist.value)
|
||||
const download = () => downloadService.fromPlaylist(playlist.value!)
|
||||
const editSmartPlaylist = () => eventBus.emit('MODAL_SHOW_EDIT_SMART_PLAYLIST_FORM', playlist.value)
|
||||
|
||||
/**
|
||||
|
@ -103,9 +101,8 @@ const editSmartPlaylist = () => eventBus.emit('MODAL_SHOW_EDIT_SMART_PLAYLIST_FO
|
|||
const populate = async (_playlist: Playlist) => {
|
||||
await playlistStore.fetchSongs(_playlist)
|
||||
playlist.value = _playlist
|
||||
state.songs = playlist.value.songs
|
||||
songs.value = playlist.value.songs
|
||||
await nextTick()
|
||||
// @ts-ignore
|
||||
songList.value?.sort()
|
||||
}
|
||||
|
||||
|
@ -116,7 +113,7 @@ eventBus.on('LOAD_MAIN_CONTENT', (view: MainViewName, _playlist: Playlist): void
|
|||
|
||||
if (_playlist.populated) {
|
||||
playlist.value = _playlist
|
||||
state.songs = playlist.value.songs
|
||||
songs.value = playlist.value.songs
|
||||
} else {
|
||||
populate(_playlist)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const ProfileForm = defineAsyncComponent(() => import('@/components/profile-preferences/profile-form.vue'))
|
||||
const LastfmIntegration = defineAsyncComponent(() => import('@/components/profile-preferences/lastfm-integration.vue'))
|
||||
const Preferences = defineAsyncComponent(() => import('@/components/profile-preferences/preferences.vue'))
|
||||
|
|
|
@ -46,19 +46,20 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, reactive, toRef } from 'vue'
|
||||
import { pluralize } from '@/utils'
|
||||
import { queueStore, songStore } from '@/stores'
|
||||
import { playback } from '@/services'
|
||||
import { useSongList } from '@/composables'
|
||||
import { computed, defineAsyncComponent, reactive, toRef } from 'vue'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const ScreenPlaceholder = defineAsyncComponent(() => import('@/components/ui/screen-placeholder.vue'))
|
||||
|
||||
const {
|
||||
SongList,
|
||||
SongListControls,
|
||||
ControlsToggler,
|
||||
songs,
|
||||
songList,
|
||||
meta,
|
||||
selectedSongs,
|
||||
|
@ -67,19 +68,13 @@ const {
|
|||
isPhone,
|
||||
playSelected,
|
||||
toggleControls
|
||||
} = useSongList({
|
||||
clearQueue: true
|
||||
})
|
||||
} = useSongList(toRef(queueStore.state, 'songs'), { clearQueue: true })
|
||||
|
||||
const songs = toRef(queueStore.state, 'songs')
|
||||
const songState = reactive(songStore.state)
|
||||
|
||||
const shouldShowShufflingAllLink = computed(() => songState.songs.length > 0)
|
||||
|
||||
const playAll = () => {
|
||||
playback.queueAndPlay(songs.value.length ? songList.value.getAllSongsWithSort() : songStore.all)
|
||||
}
|
||||
|
||||
const playAll = () => playback.queueAndPlay(songs.value.length ? songList.value.getAllSongsWithSort() : songStore.all)
|
||||
const shuffleAll = async () => await playback.queueAndPlay(songStore.all, true)
|
||||
const clearQueue = () => queueStore.clear()
|
||||
</script>
|
||||
|
|
|
@ -10,26 +10,24 @@
|
|||
|
||||
<template v-slot:controls>
|
||||
<SongListControls
|
||||
v-if="state.songs.length && (!isPhone || showingControls)"
|
||||
@playAll="playAll"
|
||||
@playSelected="playSelected"
|
||||
:songs="state.songs"
|
||||
v-if="songs.length && (!isPhone || showingControls)"
|
||||
:config="songListControlConfig"
|
||||
:selectedSongs="selectedSongs"
|
||||
:songs="songs"
|
||||
@playAll="playAll"
|
||||
@playSelected="playSelected"
|
||||
/>
|
||||
</template>
|
||||
</ScreenHeader>
|
||||
|
||||
<SongList v-if="state.songs.length" :items="state.songs" type="recently-played" :sortable="false"/>
|
||||
<SongList v-if="songs.length" :items="songs" :sortable="false" type="recently-played"/>
|
||||
|
||||
<ScreenPlaceholder v-else>
|
||||
<template v-slot:icon>
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</template>
|
||||
No songs recently played.
|
||||
<span class="secondary d-block">
|
||||
Start playing to populate this playlist.
|
||||
</span>
|
||||
<span class="secondary d-block">Start playing to populate this playlist.</span>
|
||||
</ScreenPlaceholder>
|
||||
</section>
|
||||
</template>
|
||||
|
@ -38,16 +36,17 @@
|
|||
import { eventBus, pluralize } from '@/utils'
|
||||
import { recentlyPlayedStore } from '@/stores'
|
||||
import { useSongList } from '@/composables'
|
||||
import { defineAsyncComponent, reactive } from 'vue'
|
||||
import { defineAsyncComponent, reactive, toRef } from 'vue'
|
||||
import { playback } from '@/services'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const ScreenPlaceholder = defineAsyncComponent(() => import('@/components/ui/screen-placeholder.vue'))
|
||||
|
||||
const {
|
||||
SongList,
|
||||
SongListControls,
|
||||
ControlsToggler,
|
||||
songs,
|
||||
songList,
|
||||
meta,
|
||||
selectedSongs,
|
||||
|
@ -56,11 +55,9 @@ const {
|
|||
isPhone,
|
||||
playSelected,
|
||||
toggleControls
|
||||
} = useSongList()
|
||||
} = useSongList(toRef(recentlyPlayedStore.state, 'songs'))
|
||||
|
||||
const state = reactive(recentlyPlayedStore.state)
|
||||
|
||||
const playAll = () => playback.queueAndPlay(state.songs)
|
||||
const playAll = () => playback.queueAndPlay(songs.value)
|
||||
|
||||
eventBus.on({
|
||||
'LOAD_MAIN_CONTENT': (view: MainViewName) => view === 'RecentlyPlayed' && recentlyPlayedStore.fetchAll()
|
||||
|
|
|
@ -65,7 +65,7 @@ import { eventBus } from '@/utils'
|
|||
import { searchStore } from '@/stores'
|
||||
import router from '@/router'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const ScreenPlaceholder = defineAsyncComponent(() => import('@/components/ui/screen-placeholder.vue'))
|
||||
const SongCard = defineAsyncComponent(() => import('@/components/song/card.vue'))
|
||||
const ArtistCard = defineAsyncComponent(() => import('@/components/artist/card.vue'))
|
||||
|
|
|
@ -10,34 +10,37 @@
|
|||
|
||||
<template v-slot:controls>
|
||||
<SongListControls
|
||||
v-if="state.songs.length && (!isPhone || showingControls)"
|
||||
v-if="songs.length && (!isPhone || showingControls)"
|
||||
:config="songListControlConfig"
|
||||
:selectedSongs="selectedSongs"
|
||||
:songs="state.songs"
|
||||
:songs="songs"
|
||||
@playAll="playAll"
|
||||
@playSelected="playSelected"
|
||||
/>
|
||||
</template>
|
||||
</ScreenHeader>
|
||||
|
||||
<SongList ref="songList" :items="state.songs" type="search-results"/>
|
||||
<SongList ref="songList" :items="songs" type="search-results"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, toRef, toRefs } from 'vue'
|
||||
import { searchStore } from '@/stores'
|
||||
import { computed, defineAsyncComponent, reactive, toRefs } from 'vue'
|
||||
import { useSongList } from '@/composables'
|
||||
import { pluralize } from '@/utils'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
|
||||
const props = defineProps<{ q: string }>()
|
||||
const { q } = toRefs(props)
|
||||
|
||||
const {
|
||||
SongList,
|
||||
SongListControls,
|
||||
ControlsToggler,
|
||||
songs,
|
||||
songList,
|
||||
state: songListState,
|
||||
meta,
|
||||
selectedSongs,
|
||||
showingControls,
|
||||
|
@ -46,12 +49,7 @@ const {
|
|||
playAll,
|
||||
playSelected,
|
||||
toggleControls
|
||||
} = useSongList()
|
||||
|
||||
const props = defineProps<{ q: string }>()
|
||||
const { q } = toRefs(props)
|
||||
|
||||
const state = reactive(searchStore.state)
|
||||
} = useSongList(toRef(searchStore.state, 'songs'))
|
||||
|
||||
const decodedQ = computed(() => decodeURIComponent(q.value))
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ import { settingStore, sharedStore } from '@/stores'
|
|||
import { alerts, forceReloadWindow, hideOverlay, parseValidationError, showOverlay } from '@/utils'
|
||||
import router from '@/router'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const Btn = defineAsyncComponent(() => import('@/components/ui/btn.vue'))
|
||||
|
||||
const state = settingStore.state
|
||||
|
|
|
@ -67,10 +67,10 @@ import { UploadFile, validMediaMimeTypes } from '@/config'
|
|||
import { upload } from '@/services'
|
||||
|
||||
import UploadItem from '@/components/ui/upload/upload-item.vue'
|
||||
import BtnGroup from '@/components/ui/btn-group.vue'
|
||||
import BtnGroup from '@/components/ui/BtnGroup.vue'
|
||||
import Btn from '@/components/ui/btn.vue'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const ScreenPlaceholder = defineAsyncComponent(() => import('@/components/ui/screen-placeholder.vue'))
|
||||
|
||||
const mediaPath = toRef(settingStore.state, 'media_path')
|
||||
|
|
|
@ -29,10 +29,10 @@ import isMobile from 'ismobilejs'
|
|||
import { userStore } from '@/stores'
|
||||
import { eventBus } from '@/utils'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ControlsToggler = defineAsyncComponent(() => import('@/components/ui/screen-controls-toggler.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const ControlsToggler = defineAsyncComponent(() => import('@/components/ui/ScreenControlsToggler.vue'))
|
||||
const Btn = defineAsyncComponent(() => import('@/components/ui/btn.vue'))
|
||||
const BtnGroup = defineAsyncComponent(() => import('@/components/ui/btn-group.vue'))
|
||||
const BtnGroup = defineAsyncComponent(() => import('@/components/ui/BtnGroup.vue'))
|
||||
const UserCard = defineAsyncComponent(() => import('@/components/user/card.vue'))
|
||||
|
||||
const state = reactive(userStore.state)
|
||||
|
|
|
@ -25,7 +25,7 @@ import createYouTubePlayer from 'youtube-player'
|
|||
|
||||
let player: YouTubePlayer|null = null
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/screen-header.vue'))
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const ScreenPlaceholder = defineAsyncComponent(() => import('@/components/ui/screen-placeholder.vue'))
|
||||
|
||||
const title = ref('YouTube Video')
|
||||
|
|
|
@ -72,7 +72,6 @@ import {
|
|||
getCurrentInstance,
|
||||
nextTick,
|
||||
onMounted,
|
||||
PropType,
|
||||
ref,
|
||||
toRefs,
|
||||
watch
|
||||
|
@ -182,7 +181,8 @@ const render = () => {
|
|||
|
||||
watch(items, () => render())
|
||||
|
||||
watch(selectedSongs, () => eventBus.emit('SET_SELECTED_SONGS', selectedSongs, getCurrentInstance()?.parent))
|
||||
const vm = getCurrentInstance()
|
||||
watch(selectedSongs, () => eventBus.emit('SET_SELECTED_SONGS', selectedSongs.value, vm?.parent))
|
||||
|
||||
const handleDelete = () => {
|
||||
if (!selectedSongs.value.length) {
|
|
@ -4,23 +4,23 @@
|
|||
<template v-if="mergedConfig.play">
|
||||
<template v-if="altPressed">
|
||||
<Btn
|
||||
@click.prevent="playAll"
|
||||
v-if="selectedSongs.length < 2 && songs.length"
|
||||
class="btn-play-all"
|
||||
data-test="btn-play-all"
|
||||
orange
|
||||
title="Play all songs"
|
||||
v-if="selectedSongs.length < 2 && songs.length"
|
||||
data-test="btn-play-all"
|
||||
@click.prevent="playAll"
|
||||
>
|
||||
<i class="fa fa-play"></i> All
|
||||
</Btn>
|
||||
|
||||
<Btn
|
||||
@click.prevent="playSelected"
|
||||
v-if="selectedSongs.length > 1"
|
||||
class="btn-play-selected"
|
||||
data-test="btn-play-selected"
|
||||
orange
|
||||
title="Play selected songs"
|
||||
v-if="selectedSongs.length > 1"
|
||||
data-test="btn-play-selected"
|
||||
@click.prevent="playSelected"
|
||||
>
|
||||
<i class="fa fa-play"></i> Selected
|
||||
</Btn>
|
||||
|
@ -28,23 +28,23 @@
|
|||
|
||||
<template v-else>
|
||||
<Btn
|
||||
@click.prevent="shuffle"
|
||||
v-if="selectedSongs.length < 2 && songs.length"
|
||||
class="btn-shuffle-all"
|
||||
data-test="btn-shuffle-all"
|
||||
orange
|
||||
title="Shuffle all songs"
|
||||
v-if="selectedSongs.length < 2 && songs.length"
|
||||
data-test="btn-shuffle-all"
|
||||
@click.prevent="shuffle"
|
||||
>
|
||||
<i class="fa fa-random"></i> All
|
||||
</Btn>
|
||||
|
||||
<Btn
|
||||
@click.prevent="shuffleSelected"
|
||||
v-if="selectedSongs.length > 1"
|
||||
class="btn-shuffle-selected"
|
||||
data-test="btn-shuffle-selected"
|
||||
orange
|
||||
title="Shuffle selected songs"
|
||||
v-if="selectedSongs.length > 1"
|
||||
data-test="btn-shuffle-selected"
|
||||
@click.prevent="shuffleSelected"
|
||||
>
|
||||
<i class="fa fa-random"></i> Selected
|
||||
</Btn>
|
||||
|
@ -63,21 +63,21 @@
|
|||
</Btn>
|
||||
|
||||
<Btn
|
||||
@click.prevent="clearQueue"
|
||||
v-if="showClearQueueButton"
|
||||
class="btn-clear-queue"
|
||||
red
|
||||
v-if="showClearQueueButton"
|
||||
title="Clear current queue"
|
||||
@click.prevent="clearQueue"
|
||||
>
|
||||
Clear
|
||||
</Btn>
|
||||
|
||||
<Btn
|
||||
@click.prevent="deletePlaylist"
|
||||
v-if="showDeletePlaylistButton"
|
||||
class="del btn-delete-playlist"
|
||||
red
|
||||
title="Delete this playlist"
|
||||
v-if="showDeletePlaylistButton"
|
||||
@click.prevent="deletePlaylist"
|
||||
>
|
||||
<i class="fa fa-times"></i> Playlist
|
||||
</Btn>
|
||||
|
@ -85,11 +85,11 @@
|
|||
</BtnGroup>
|
||||
|
||||
<AddToMenu
|
||||
@closing="closeAddToMenu"
|
||||
:config="mergedConfig.addTo"
|
||||
:songs="selectedSongs"
|
||||
:showing="showingAddToMenu"
|
||||
v-koel-clickaway="closeAddToMenu"
|
||||
:config="mergedConfig.addTo"
|
||||
:showing="showingAddToMenu"
|
||||
:songs="selectedSongs"
|
||||
@closing="closeAddToMenu"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -99,17 +99,20 @@ import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref,
|
|||
|
||||
const AddToMenu = defineAsyncComponent(() => import('./AddToMenu.vue'))
|
||||
const Btn = defineAsyncComponent(() => import('@/components/ui/btn.vue'))
|
||||
const BtnGroup = defineAsyncComponent(() => import('@/components/ui/btn-group.vue'))
|
||||
const BtnGroup = defineAsyncComponent(() => import('@/components/ui/BtnGroup.vue'))
|
||||
|
||||
const props = withDefaults(defineProps<{ songs: Song[], selectedSongs: Song[], config: Partial<SongListControlsConfig> }>(), {
|
||||
songs: () => [],
|
||||
selectedSongs: () => [],
|
||||
config: () => ({})
|
||||
})
|
||||
const props = withDefaults(
|
||||
defineProps<{ songs: Song[], selectedSongs: Song[], config: Partial<SongListControlsConfig> }>(),
|
||||
{
|
||||
songs: () => [],
|
||||
selectedSongs: () => [],
|
||||
config: () => ({})
|
||||
}
|
||||
)
|
||||
|
||||
const { config, songs, selectedSongs } = toRefs(props)
|
||||
|
||||
const el = ref(null as unknown as HTMLElement)
|
||||
const el = ref<HTMLElement>()
|
||||
const showingAddToMenu = ref(false)
|
||||
const numberOfQueuedSongs = ref(0)
|
||||
const altPressed = ref(false)
|
||||
|
@ -124,7 +127,7 @@ const mergedConfig = computed((): SongListControlsConfig => Object.assign({
|
|||
},
|
||||
clearQueue: false,
|
||||
deletePlaylist: false
|
||||
}, config)
|
||||
}, config.value)
|
||||
)
|
||||
|
||||
const showClearQueueButton = computed(() => mergedConfig.value.clearQueue)
|
||||
|
@ -151,9 +154,9 @@ const toggleAddToMenu = async () => {
|
|||
|
||||
await nextTick()
|
||||
|
||||
const btnAddTo = el.value.querySelector<HTMLButtonElement>('.btn-add-to')!
|
||||
const btnAddTo = el.value?.querySelector<HTMLButtonElement>('.btn-add-to')!
|
||||
const { left: btnLeft, bottom: btnBottom, width: btnWidth } = btnAddTo.getBoundingClientRect()
|
||||
const contextMenu = el.value.querySelector<HTMLElement>('.add-to')!
|
||||
const contextMenu = el.value?.querySelector<HTMLElement>('.add-to')!
|
||||
const menuWidth = contextMenu.getBoundingClientRect().width
|
||||
contextMenu.style.top = `${btnBottom + 10}px`
|
||||
contextMenu.style.left = `${btnLeft + btnWidth / 2 - menuWidth / 2}px`
|
|
@ -14,7 +14,7 @@ import { defineAsyncComponent } from 'vue'
|
|||
const Btn = defineAsyncComponent(() => import('@/components/ui/btn.vue'))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
.btn-group {
|
||||
display: flex;
|
||||
position: relative;
|
|
@ -1,20 +1,20 @@
|
|||
/**
|
||||
* Add necessary functionalities into a view that contains a song-list component.
|
||||
*/
|
||||
import { ComponentInternalInstance, getCurrentInstance, reactive, ref, watchEffect } from 'vue'
|
||||
import { ComponentInternalInstance, getCurrentInstance, reactive, Ref, ref, watchEffect } from 'vue'
|
||||
import isMobile from 'ismobilejs'
|
||||
|
||||
import { playback } from '@/services'
|
||||
import { eventBus } from '@/utils'
|
||||
|
||||
import ControlsToggler from '@/components/ui/screen-controls-toggler.vue'
|
||||
import SongList from '@/components/song/list.vue'
|
||||
import SongListControls from '@/components/song/list-controls.vue'
|
||||
import ControlsToggler from '@/components/ui/ScreenControlsToggler.vue'
|
||||
import SongList from '@/components/song/SongList.vue'
|
||||
import SongListControls from '@/components/song/SongListControls.vue'
|
||||
import { songStore } from '@/stores'
|
||||
|
||||
export const useSongList = (controlsConfig: Partial<SongListControlsConfig> = {}) => {
|
||||
export const useSongList = (songs: Ref<Song[]>, controlsConfig: Partial<SongListControlsConfig> = {}) => {
|
||||
const songList = ref<InstanceType<typeof SongList>>()
|
||||
const state = reactive<SongListState>({ songs: [] })
|
||||
const vm = getCurrentInstance()
|
||||
|
||||
const meta = reactive<SongListMeta>({
|
||||
songCount: 0,
|
||||
|
@ -27,12 +27,12 @@ export const useSongList = (controlsConfig: Partial<SongListControlsConfig> = {}
|
|||
const isPhone = isMobile.phone
|
||||
|
||||
watchEffect(() => {
|
||||
if (!state.songs.length) {
|
||||
if (!songs.value.length) {
|
||||
return
|
||||
}
|
||||
|
||||
meta.songCount = state.songs.length
|
||||
meta.totalLength = songStore.getFormattedLength(state.songs)
|
||||
meta.songCount = songs.value.length
|
||||
meta.totalLength = songStore.getFormattedLength(songs.value)
|
||||
})
|
||||
|
||||
const getSongsToPlay = (): Song[] => songList.value.getAllSongsWithSort()
|
||||
|
@ -42,7 +42,7 @@ export const useSongList = (controlsConfig: Partial<SongListControlsConfig> = {}
|
|||
|
||||
eventBus.on({
|
||||
SET_SELECTED_SONGS (songs: Song[], target: ComponentInternalInstance) {
|
||||
target === getCurrentInstance() && (selectedSongs.value = songs)
|
||||
target === vm && (selectedSongs.value = songs)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -50,8 +50,8 @@ export const useSongList = (controlsConfig: Partial<SongListControlsConfig> = {}
|
|||
SongList,
|
||||
SongListControls,
|
||||
ControlsToggler,
|
||||
songs,
|
||||
songList,
|
||||
state,
|
||||
meta,
|
||||
selectedSongs,
|
||||
showingControls,
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import { difference, union } from 'lodash'
|
||||
import { http } from '@/services'
|
||||
import { arrayify } from '@/utils'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export const favoriteStore = {
|
||||
state: {
|
||||
state: reactive({
|
||||
songs: [] as Song[],
|
||||
length: 0,
|
||||
fmtLength: ''
|
||||
},
|
||||
}),
|
||||
|
||||
get all (): Song[] {
|
||||
get all () {
|
||||
return this.state.songs
|
||||
},
|
||||
|
||||
|
@ -17,7 +18,7 @@ export const favoriteStore = {
|
|||
this.state.songs = value
|
||||
},
|
||||
|
||||
async toggleOne (song: Song): Promise<void> {
|
||||
async toggleOne (song: Song) {
|
||||
// Don't wait for the HTTP response to update the status, just toggle right away.
|
||||
// This may cause a minor problem if the request fails somehow, but do we care?
|
||||
song.liked = !song.liked
|
||||
|
@ -26,17 +27,11 @@ export const favoriteStore = {
|
|||
await http.post<Song>('interaction/like', { song: song.id })
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a song/songs into the store.
|
||||
*/
|
||||
add (songs: Song | Song[]): void {
|
||||
add (songs: Song | Song[]) {
|
||||
this.all = union(this.all, arrayify(songs))
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a song/songs from the store.
|
||||
*/
|
||||
remove (songs: Song | Song[]): void {
|
||||
remove (songs: Song | Song[]) {
|
||||
this.all = difference(this.all, arrayify(songs))
|
||||
},
|
||||
|
||||
|
@ -44,7 +39,7 @@ export const favoriteStore = {
|
|||
this.all = []
|
||||
},
|
||||
|
||||
async like (songs: Song[]): Promise<void> {
|
||||
async like (songs: Song[]) {
|
||||
// Don't wait for the HTTP response to update the status, just set them to Liked right away.
|
||||
// This may cause a minor problem if the request fails somehow, but do we care?
|
||||
songs.forEach(song => { song.liked = true })
|
||||
|
@ -53,7 +48,7 @@ export const favoriteStore = {
|
|||
await http.post('interaction/batch/like', { songs: songs.map(song => song.id) })
|
||||
},
|
||||
|
||||
async unlike (songs: Song[]): Promise<void> {
|
||||
async unlike (songs: Song[]) {
|
||||
songs.forEach(song => { song.liked = false })
|
||||
this.remove(songs)
|
||||
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
import { songStore } from '.'
|
||||
import { http } from '@/services'
|
||||
import { remove } from 'lodash'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
const EXCERPT_COUNT = 7
|
||||
|
||||
export const recentlyPlayedStore = {
|
||||
excerptState: {
|
||||
excerptState: reactive({
|
||||
songs: [] as Song[]
|
||||
},
|
||||
}),
|
||||
|
||||
state: {
|
||||
state: reactive({
|
||||
songs: [] as Song[]
|
||||
},
|
||||
}),
|
||||
|
||||
fetched: false,
|
||||
|
||||
initExcerpt (songIds: string[]): void {
|
||||
initExcerpt (songIds: string[]) {
|
||||
this.excerptState.songs = songStore.byIds(songIds)
|
||||
},
|
||||
|
||||
async fetchAll (): Promise<Song[]> {
|
||||
async fetchAll () {
|
||||
if (!this.fetched) {
|
||||
this.state.songs = songStore.byIds(await http.get<string[]>(`interaction/recently-played`))
|
||||
this.fetched = true
|
||||
|
@ -28,8 +29,8 @@ export const recentlyPlayedStore = {
|
|||
return this.state.songs
|
||||
},
|
||||
|
||||
add (song: Song): void {
|
||||
[this.state, this.excerptState].forEach((state): void => {
|
||||
add (song: Song) {
|
||||
[this.state, this.excerptState].forEach(state => {
|
||||
// make sure there's no duplicate
|
||||
remove(state.songs, s => s.id === song.id)
|
||||
state.songs.unshift(song)
|
||||
|
|
|
@ -2,6 +2,7 @@ import { http } from '@/services'
|
|||
import { songStore } from '@/stores/song'
|
||||
import { albumStore } from '@/stores/album'
|
||||
import { artistStore } from '@/stores/artist'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
interface ExcerptSearchResult {
|
||||
songs: Array<string>
|
||||
|
@ -14,27 +15,25 @@ interface SongSearchResult {
|
|||
}
|
||||
|
||||
export const searchStore = {
|
||||
state: {
|
||||
state: reactive({
|
||||
excerpt: {
|
||||
songs: [] as Song[],
|
||||
albums: [] as Album[],
|
||||
artists: [] as Artist[]
|
||||
},
|
||||
songs: [] as Song[],
|
||||
songs: [] as Song[]
|
||||
}),
|
||||
|
||||
async excerptSearch (q: string) {
|
||||
const { results } = await http.get<{ [key: string]: ExcerptSearchResult }>(`search?q=${q}`)
|
||||
this.state.excerpt.songs = songStore.byIds(results.songs)
|
||||
this.state.excerpt.albums = albumStore.byIds(results.albums)
|
||||
this.state.excerpt.artists = artistStore.byIds(results.artists)
|
||||
},
|
||||
|
||||
excerptSearch (q: string) {
|
||||
http.get<{ [key: string]: ExcerptSearchResult }>(`search?q=${q}`).then(({ results }) => {
|
||||
this.state.excerpt.songs = songStore.byIds(results.songs)
|
||||
this.state.excerpt.albums = albumStore.byIds(results.albums)
|
||||
this.state.excerpt.artists = artistStore.byIds(results.artists)
|
||||
})
|
||||
},
|
||||
|
||||
songSearch (q: string) {
|
||||
http.get<SongSearchResult>(`search/songs?q=${q}`).then(({ songs }) => {
|
||||
this.state.songs = this.state.songs.concat(songStore.byIds(songs))
|
||||
})
|
||||
async songSearch (q: string) {
|
||||
const { songs } = await http.get<SongSearchResult>(`search/songs?q=${q}`)
|
||||
this.state.songs = this.state.songs.concat(songStore.byIds(songs))
|
||||
},
|
||||
|
||||
resetSongResultState () {
|
||||
|
|
Loading…
Reference in a new issue