[web] Fix a bug in the Spotify search

This commit is contained in:
Alain Nussbaumer 2025-05-27 23:46:33 +02:00
parent eb33a25ce7
commit 4adb623c3f
15 changed files with 44 additions and 88 deletions

View File

@ -34,7 +34,6 @@ export default {
props: { props: {
items: { required: true, type: Object }, items: { required: true, type: Object },
load: { default: null, type: Function }, load: { default: null, type: Function },
loaded: { default: true, type: Boolean },
mediaKind: { default: '', type: String } mediaKind: { default: '', type: String }
}, },
emits: ['play-count-changed', 'podcast-deleted'], emits: ['play-count-changed', 'podcast-deleted'],

View File

@ -13,7 +13,7 @@
@open="open(item)" @open="open(item)"
@open-details="openDetails(item)" @open-details="openDetails(item)"
/> />
<loader-list-item :load="load" :loaded="loaded" /> <loader-list-item :load="load" />
<modal-dialog-album-spotify <modal-dialog-album-spotify
:item="selectedItem" :item="selectedItem"
:show="showDetailsModal" :show="showDetailsModal"
@ -32,8 +32,7 @@ export default {
components: { ListItem, LoaderListItem, ModalDialogAlbumSpotify }, components: { ListItem, LoaderListItem, ModalDialogAlbumSpotify },
props: { props: {
items: { required: true, type: Object }, items: { required: true, type: Object },
load: { default: null, type: Function }, load: { default: null, type: Function }
loaded: { default: true, type: Boolean }
}, },
setup() { setup() {
return { settingsStore: useSettingsStore() } return { settingsStore: useSettingsStore() }

View File

@ -24,8 +24,7 @@ export default {
components: { ListItem, ModalDialogArtist }, components: { ListItem, ModalDialogArtist },
props: { props: {
items: { required: true, type: Object }, items: { required: true, type: Object },
load: { default: null, type: Function }, load: { default: null, type: Function }
loaded: { default: true, type: Boolean }
}, },
data() { data() {
return { selectedItem: {}, showDetailsModal: false } return { selectedItem: {}, showDetailsModal: false }

View File

@ -8,7 +8,7 @@
@open="open(item)" @open="open(item)"
@open-details="openDetails(item)" @open-details="openDetails(item)"
/> />
<loader-list-item :load="load" :loaded="loaded" /> <loader-list-item :load="load" />
<modal-dialog-artist-spotify <modal-dialog-artist-spotify
:item="selectedItem" :item="selectedItem"
:show="showDetailsModal" :show="showDetailsModal"
@ -26,8 +26,7 @@ export default {
components: { ListItem, LoaderListItem, ModalDialogArtistSpotify }, components: { ListItem, LoaderListItem, ModalDialogArtistSpotify },
props: { props: {
items: { required: true, type: Object }, items: { required: true, type: Object },
load: { default: null, type: Function }, load: { default: null, type: Function }
loaded: { default: true, type: Boolean }
}, },
data() { data() {
return { selectedItem: {}, showDetailsModal: false } return { selectedItem: {}, showDetailsModal: false }

View File

@ -24,8 +24,7 @@ export default {
components: { ListItem, ModalDialogComposer }, components: { ListItem, ModalDialogComposer },
props: { props: {
items: { required: true, type: Object }, items: { required: true, type: Object },
load: { default: null, type: Function }, load: { default: null, type: Function }
loaded: { default: true, type: Boolean }
}, },
data() { data() {
return { selectedItem: {}, showDetailsModal: false } return { selectedItem: {}, showDetailsModal: false }

View File

@ -25,8 +25,7 @@ export default {
components: { ListItem, ModalDialogPlaylist }, components: { ListItem, ModalDialogPlaylist },
props: { props: {
items: { required: true, type: Object }, items: { required: true, type: Object },
load: { default: null, type: Function }, load: { default: null, type: Function }
loaded: { default: true, type: Boolean }
}, },
data() { data() {
return { selectedItem: {}, showDetailsModal: false } return { selectedItem: {}, showDetailsModal: false }

View File

@ -8,7 +8,7 @@
@open="open(item)" @open="open(item)"
@open-details="openDetails(item)" @open-details="openDetails(item)"
/> />
<loader-list-item :load="load" :loaded="loaded" /> <loader-list-item :load="load" />
<modal-dialog-playlist-spotify <modal-dialog-playlist-spotify
:item="selectedItem" :item="selectedItem"
:show="showDetailsModal" :show="showDetailsModal"
@ -26,8 +26,7 @@ export default {
components: { ListItem, LoaderListItem, ModalDialogPlaylistSpotify }, components: { ListItem, LoaderListItem, ModalDialogPlaylistSpotify },
props: { props: {
items: { required: true, type: Object }, items: { required: true, type: Object },
load: { default: null, type: Function }, load: { default: null, type: Function }
loaded: { default: true, type: Boolean }
}, },
data() { data() {

View File

@ -32,7 +32,6 @@ export default {
icon: { default: null, type: String }, icon: { default: null, type: String },
items: { default: null, type: Object }, items: { default: null, type: Object },
load: { default: null, type: Function }, load: { default: null, type: Function },
loaded: { default: true, type: Boolean },
showProgress: { default: false, type: Boolean }, showProgress: { default: false, type: Boolean },
uris: { default: '', type: String } uris: { default: '', type: String }
}, },

View File

@ -19,7 +19,7 @@
/>) />)
</template> </template>
</list-item> </list-item>
<loader-list-item :load="load" :loaded="loaded" /> <loader-list-item :load="load" />
<modal-dialog-track-spotify <modal-dialog-track-spotify
:item="selectedItem" :item="selectedItem"
:show="showDetailsModal" :show="showDetailsModal"
@ -39,8 +39,7 @@ export default {
props: { props: {
contextUri: { default: '', type: String }, contextUri: { default: '', type: String },
items: { required: true, type: Object }, items: { required: true, type: Object },
load: { default: null, type: Function }, load: { default: null, type: Function }
loaded: { default: true, type: Boolean }
}, },
data() { data() {
return { selectedItem: {}, showDetailsModal: false } return { selectedItem: {}, showDetailsModal: false }

View File

@ -1,5 +1,5 @@
<template> <template>
<vue-eternal-loading v-if="load && !loaded" :load="load"> <vue-eternal-loading v-if="load" :load="load">
<template #loading> <template #loading>
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column has-text-centered"> <div class="column has-text-centered">
@ -23,8 +23,7 @@ export default {
name: 'LoaderListItem', name: 'LoaderListItem',
components: { VueEternalLoading }, components: { VueEternalLoading },
props: { props: {
load: { default: null, type: Function }, load: { default: null, type: Function }
loaded: { default: true, type: Boolean }
} }
} }
</script> </script>

View File

@ -12,7 +12,7 @@
/> />
</template> </template>
<template #content> <template #content>
<list-albums-spotify :items="albums" :load="load" :loaded="loaded" /> <list-albums-spotify :items="albums" :load="load" />
</template> </template>
</content-with-heading> </content-with-heading>
<modal-dialog-artist-spotify <modal-dialog-artist-spotify
@ -84,9 +84,6 @@ export default {
subtitle: [{ count: this.total, key: 'data.albums' }], subtitle: [{ count: this.total, key: 'data.albums' }],
title: this.artist.name title: this.artist.name
} }
},
loaded() {
return !(this.offset < this.total)
} }
}, },
methods: { methods: {

View File

@ -21,7 +21,6 @@
:context-uri="playlist.uri" :context-uri="playlist.uri"
:items="tracks" :items="tracks"
:load="load" :load="load"
:loaded="loaded"
/> />
</template> </template>
</content-with-heading> </content-with-heading>
@ -96,9 +95,6 @@ export default {
} }
} }
return {} return {}
},
loaded() {
return !(this.offset < this.total)
} }
}, },
methods: { methods: {

View File

@ -44,8 +44,8 @@ import ListTracks from '@/components/ListTracks.vue'
import library from '@/api/library' import library from '@/api/library'
import { useSearchStore } from '@/stores/search' import { useSearchStore } from '@/stores/search'
const PAGE_SIZE = 3, const PAGE_SIZE = 3
SEARCH_TYPES = [ const SEARCH_TYPES = [
'track', 'track',
'artist', 'artist',
'album', 'album',
@ -72,7 +72,7 @@ export default {
}, },
data() { data() {
return { return {
limit: {}, limit: PAGE_SIZE,
results: new Map(), results: new Map(),
types: SEARCH_TYPES types: SEARCH_TYPES
} }
@ -86,23 +86,17 @@ export default {
} }
}, },
mounted() { mounted() {
this.searchStore.source = this.$route.name
this.limit = PAGE_SIZE
this.search() this.search()
}, },
methods: { methods: {
expand(type) { expand(type) {
this.types = [type] this.search([type], -1)
this.limit = -1
this.search()
}, },
getItems(items) { getItems(items) {
return items return items
}, },
openSearch(query) { openSearch(query) {
this.searchStore.query = query this.searchStore.query = query
this.types = SEARCH_TYPES
this.limit = PAGE_SIZE
this.search() this.search()
}, },
reset() { reset() {
@ -111,12 +105,10 @@ export default {
this.results.set(type, new GroupedList()) this.results.set(type, new GroupedList())
}) })
}, },
search(event) { search(types = SEARCH_TYPES, limit = PAGE_SIZE) {
if (this.searchStore.query) { if (this.searchStore.query) {
if (event) { this.types = types
this.types = SEARCH_TYPES this.limit = limit
this.limit = PAGE_SIZE
}
this.searchStore.query = this.searchStore.query.trim() this.searchStore.query = this.searchStore.query.trim()
this.reset() this.reset()
this.types.forEach((type) => { this.types.forEach((type) => {

View File

@ -4,6 +4,7 @@
:expanded="expanded" :expanded="expanded"
:get-items="getItems" :get-items="getItems"
:history="history" :history="history"
:load="(expanded && searchNext) || null"
:results="results" :results="results"
@search="search" @search="search"
@search-library="searchLibrary" @search-library="searchLibrary"
@ -23,9 +24,9 @@ import SpotifyWebApi from 'spotify-web-api-js'
import services from '@/api/services' import services from '@/api/services'
import { useSearchStore } from '@/stores/search' import { useSearchStore } from '@/stores/search'
const PAGE_SIZE = 3, const PAGE_SIZE = 3
PAGE_SIZE_EXPANDED = 50, const PAGE_SIZE_EXPANDED = 50
SEARCH_TYPES = ['track', 'artist', 'album', 'playlist'] const SEARCH_TYPES = ['track', 'artist', 'album', 'playlist']
export default { export default {
name: 'PageSearchSpotify', name: 'PageSearchSpotify',
@ -43,8 +44,8 @@ export default {
}, },
data() { data() {
return { return {
results: new Map(),
parameters: {}, parameters: {},
results: new Map(),
types: SEARCH_TYPES types: SEARCH_TYPES
} }
}, },
@ -59,25 +60,17 @@ export default {
} }
}, },
mounted() { mounted() {
this.searchStore.source = this.$route.name
this.parameters.limit = PAGE_SIZE
this.search() this.search()
}, },
methods: { methods: {
expand(type) { expand(type) {
this.types = [type] this.search([type], PAGE_SIZE_EXPANDED)
this.parameters.limit = PAGE_SIZE_EXPANDED
this.parameters.offset = 0
this.search()
}, },
getItems(items) { getItems(items) {
return items.items return items.items
}, },
openSearch(query) { openSearch(query) {
this.searchStore.query = query this.searchStore.query = query
this.types = SEARCH_TYPES
this.parameters.limit = PAGE_SIZE
this.parameters.offset = 0
this.search() this.search()
}, },
reset() { reset() {
@ -86,12 +79,11 @@ export default {
this.results.set(type, { items: [], total: 0 }) this.results.set(type, { items: [], total: 0 })
}) })
}, },
search(event) { search(types = SEARCH_TYPES, limit = PAGE_SIZE, offset = 0) {
if (this.searchStore.query) { if (this.searchStore.query) {
if (event) { this.types = types
this.types = SEARCH_TYPES this.parameters.limit = limit
this.parameters.limit = PAGE_SIZE this.parameters.offset = offset
}
this.searchStore.query = this.searchStore.query.trim() this.searchStore.query = this.searchStore.query.trim()
this.reset() this.reset()
this.searchItems().then((data) => { this.searchItems().then((data) => {
@ -123,9 +115,8 @@ export default {
this.searchItems().then((data) => { this.searchItems().then((data) => {
const [next] = Object.values(data) const [next] = Object.values(data)
items.items.push(...next.items) items.items.push(...next.items)
items.total = next.total this.parameters.offset += next.items.length
this.parameters.offset = (this.parameters.offset || 0) + next.limit loaded(Number(next.next && next.limit), PAGE_SIZE_EXPANDED)
loaded(next.items.length, PAGE_SIZE_EXPANDED)
}) })
} }
} }

View File

@ -43,16 +43,11 @@
<pane-title :content="{ title: $t(`page.search.${type}s`) }" /> <pane-title :content="{ title: $t(`page.search.${type}s`) }" />
</template> </template>
<template #content> <template #content>
<component <component :is="components[type]" :items="getItems(items)" :load="load" />
:is="components[type]"
:items="getItems(items)"
:load="load"
:loaded="!expanded"
/>
</template> </template>
<template v-if="!expanded" #footer> <template v-if="!expanded" #footer>
<control-button <control-button
v-if="showAllButton(items)" v-if="items.total"
:button="{ :button="{
handler: () => $emit('expand', type), handler: () => $emit('expand', type),
title: $t( title: $t(
@ -62,7 +57,7 @@
) )
}" }"
/> />
<div v-if="!items.total" class="has-text-centered-mobile"> <div v-else class="has-text-centered-mobile">
<i v-text="$t('page.search.no-results')" /> <i v-text="$t('page.search.no-results')" />
</div> </div>
</template> </template>
@ -98,11 +93,6 @@ export default {
return { return {
searchStore: useSearchStore() searchStore: useSearchStore()
} }
},
methods: {
showAllButton(items) {
return items.total > items.items.length
}
} }
} }
</script> </script>