[web] Refactor Spotify search page

This commit is contained in:
Alain Nussbaumer 2024-03-25 17:30:59 +01:00
parent 7adde0340e
commit a6b2f93f41

View File

@ -1,51 +1,50 @@
<template> <template>
<div> <section class="section pb-0">
<!-- Search field + recent searches --> <div class="container">
<section class="section pb-0"> <div class="columns is-centered">
<div class="container"> <div class="column is-four-fifths">
<div class="columns is-centered"> <form @submit.prevent="new_search">
<div class="column is-four-fifths"> <div class="field">
<form @submit.prevent="new_search"> <p class="control has-icons-left">
<div class="field"> <input
<p class="control has-icons-left"> ref="search_field"
<input v-model="search_query"
ref="search_field" class="input is-rounded is-shadowless"
v-model="search_query" type="text"
class="input is-rounded is-shadowless" :placeholder="$t('page.spotify.search.placeholder')"
type="text" autocomplete="off"
:placeholder="$t('page.spotify.search.placeholder')" />
autocomplete="off" <mdicon class="icon is-left" name="magnify" size="16" />
/> </p>
<mdicon class="icon is-left" name="magnify" size="16" />
</p>
</div>
</form>
<div class="tags mt-4">
<a
v-for="recent_search in recent_searches"
:key="recent_search"
class="tag"
@click="open_recent_search(recent_search)"
v-text="recent_search"
/>
</div> </div>
</form>
<div class="tags mt-4">
<a
v-for="recent_search in recent_searches"
:key="recent_search"
class="tag"
@click="open_recent_search(recent_search)"
v-text="recent_search"
/>
</div> </div>
</div> </div>
</div> </div>
</section> </div>
<tabs-search :query="search_query" /> </section>
<!-- Tracks --> <tabs-search :query="search_query" />
<content-with-heading v-if="show('track') && tracks.total" class="pt-0"> <template v-for="type in validSearchTypes" :key="type">
<content-with-heading v-if="show(type)" class="pt-0">
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.spotify.search.tracks')" /> <p class="title is-4" v-text="$t(`page.spotify.search.${type}s`)" />
</template> </template>
<template #content> <template #content>
<list-item-track-spotify <component
v-for="track in tracks.items" :is="components[type]"
:key="track.id" v-for="item in results[type].items"
:item="track" :key="item.id"
:item="item"
/> />
<VueEternalLoading v-if="query.type === 'track'" :load="search_next"> <VueEternalLoading v-if="query.type === type" :load="search_next">
<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">
@ -57,160 +56,32 @@
</VueEternalLoading> </VueEternalLoading>
</template> </template>
<template #footer> <template #footer>
<nav v-if="show_all_button(tracks)" class="level"> <nav v-if="show_all_button(type)" class="level">
<p class="level-item"> <p class="level-item">
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search('track')" @click="open_search(type)"
v-text=" v-text="
$t('page.spotify.search.show-all-tracks', tracks.total, { $t(
count: $filters.number(tracks.total) `page.spotify.search.show-all-${type}s`,
}) results[type].total,
{
count: $filters.number(results[type].total)
}
)
" "
/> />
</p> </p>
</nav> </nav>
<p v-if="!results[type].total" class="has-text-centered-mobile">
<i v-text="$t(`page.spotify.search.no-${type}s`)" />
</p>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show('track') && !tracks.total" class="pt-0"> </template>
<template #content>
<p><i v-text="$t('page.spotify.search.no-tracks')" /></p>
</template>
</content-text>
<!-- Artists -->
<content-with-heading v-if="show('artist') && artists.total">
<template #heading-left>
<p class="title is-4" v-text="$t('page.spotify.search.artists')" />
</template>
<template #content>
<list-item-artist-spotify
v-for="artist in artists.items"
:key="artist.id"
:item="artist"
/>
<VueEternalLoading v-if="query.type === 'artist'" :load="search_next">
<template #loading>
<div class="columns is-centered">
<div class="column has-text-centered">
<mdicon class="icon mdi-spin" name="loading" />
</div>
</div>
</template>
<template #no-more>&nbsp;</template>
</VueEternalLoading>
</template>
<template #footer>
<nav v-if="show_all_button(artists)" class="level">
<p class="level-item">
<a
class="button is-light is-small is-rounded"
@click="open_search('artist')"
v-text="
$t('page.spotify.search.show-all-artists', artists.total, {
count: $filters.number(artists.total)
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show('artist') && !artists.total">
<template #content>
<p><i v-text="$t('page.spotify.search.no-artists')" /></p>
</template>
</content-text>
<!-- Albums -->
<content-with-heading v-if="show('album') && albums.total">
<template #heading-left>
<p class="title is-4" v-text="$t('page.spotify.search.albums')" />
</template>
<template #content>
<list-item-album-spotify
v-for="album in albums.items"
:key="album.id"
:item="album"
/>
<VueEternalLoading v-if="query.type === 'album'" :load="search_next">
<template #loading>
<div class="columns is-centered">
<div class="column has-text-centered">
<mdicon class="icon mdi-spin" name="loading" />
</div>
</div>
</template>
<template #no-more>&nbsp;</template>
</VueEternalLoading>
</template>
<template #footer>
<nav v-if="show_all_button(albums)" class="level">
<p class="level-item">
<a
class="button is-light is-small is-rounded"
@click="open_search('album')"
v-text="
$t('page.spotify.search.show-all-albums', albums.total, {
count: $filters.number(albums.total)
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show('album') && !albums.total">
<template #content>
<p><i v-text="$t('page.spotify.search.no-albums')" /></p>
</template>
</content-text>
<!-- Playlists -->
<content-with-heading v-if="show('playlist') && playlists.total">
<template #heading-left>
<p class="title is-4" v-text="$t('page.spotify.search.playlists')" />
</template>
<template #content>
<list-item-playlist-spotify
v-for="playlist in playlists.items"
:key="playlist.id"
:item="playlist"
/>
<VueEternalLoading v-if="query.type === 'playlist'" :load="search_next">
<template #loading>
<div class="columns is-centered">
<div class="column has-text-centered">
<mdicon class="icon mdi-spin" name="loading" />
</div>
</div>
</template>
<template #no-more>&nbsp;</template>
</VueEternalLoading>
</template>
<template #footer>
<nav v-if="show_all_button(playlists)" class="level">
<p class="level-item">
<a
class="button is-light is-small is-rounded"
@click="open_search('playlist')"
v-text="
$t('page.spotify.search.show-all-playlists', playlists.total, {
count: $filters.number(playlists.total)
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show('playlist') && !playlists.total">
<template #content>
<p><i v-text="$t('page.spotify.search.no-playlists')" /></p>
</template>
</content-text>
</div>
</template> </template>
<script> <script>
import ContentText from '@/templates/ContentText.vue'
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListItemAlbumSpotify from '@/components/ListItemAlbumSpotify.vue' import ListItemAlbumSpotify from '@/components/ListItemAlbumSpotify.vue'
import ListItemArtistSpotify from '@/components/ListItemArtistSpotify.vue' import ListItemArtistSpotify from '@/components/ListItemArtistSpotify.vue'
@ -226,7 +97,6 @@ const PAGE_SIZE = 50
export default { export default {
name: 'PageSearchSpotify', name: 'PageSearchSpotify',
components: { components: {
ContentText,
ContentWithHeading, ContentWithHeading,
ListItemAlbumSpotify, ListItemAlbumSpotify,
ListItemArtistSpotify, ListItemArtistSpotify,
@ -238,13 +108,21 @@ export default {
data() { data() {
return { return {
albums: { items: [], total: 0 }, components: {
artists: { items: [], total: 0 }, album: ListItemAlbumSpotify.name,
playlists: { items: [], total: 0 }, artist: ListItemArtistSpotify.name,
playlist: ListItemPlaylistSpotify.name,
track: ListItemTrackSpotify.name
},
query: {}, query: {},
results: {
album: { items: [], total: 0 },
artist: { items: [], total: 0 },
playlist: { items: [], total: 0 },
track: { items: [], total: 0 }
},
search_param: {}, search_param: {},
search_query: '', search_query: '',
tracks: { items: [], total: 0 },
validSearchTypes: ['track', 'artist', 'album', 'playlist'] validSearchTypes: ['track', 'artist', 'album', 'playlist']
} }
}, },
@ -299,10 +177,9 @@ export default {
}) })
}, },
reset() { reset() {
this.tracks = { items: [], total: 0 } Object.entries(this.results).forEach(
this.artists = { items: [], total: 0 } (key) => (this.results[key] = { items: [], total: 0 })
this.albums = { items: [], total: 0 } )
this.playlists = { items: [], total: 0 }
}, },
search() { search() {
this.reset() this.reset()
@ -319,38 +196,39 @@ export default {
this.search_all() this.search_all()
}, },
search_all() { search_all() {
this.spotify_search().then((data) => { const types = this.query.type
this.tracks = data.tracks ?? { items: [], total: 0 } .split(',')
this.artists = data.artists ?? { items: [], total: 0 } .filter((type) => this.validSearchTypes.includes(type))
this.albums = data.albums ?? { items: [], total: 0 } this.spotify_search(types).then((data) => {
this.playlists = data.playlists ?? { items: [], total: 0 } this.results.track = data.tracks ?? { items: [], total: 0 }
this.results.artist = data.artists ?? { items: [], total: 0 }
this.results.album = data.albums ?? { items: [], total: 0 }
this.results.playlist = data.playlists ?? { items: [], total: 0 }
}) })
}, },
search_next(obj) { search_next({ loaded }) {
const items = this[`${this.query.type}s`] const items = this.results[this.query.type]
this.spotify_search().then((data) => { this.spotify_search([this.query.type]).then((data) => {
const newItems = data[`${this.query.type}s`] const next = Object.values(data)[0]
items.items = items.items.concat(newItems.items) items.items = items.items.concat(next.items)
items.total = newItems.total items.total = next.total
this.search_param.offset += newItems.limit this.search_param.offset += next.limit
obj.loaded(newItems.items.length, PAGE_SIZE) loaded(next.items.length, PAGE_SIZE)
}) })
}, },
show(type) { show(type) {
return this.$route.query.type?.includes(type) ?? false return this.$route.query.type?.includes(type) ?? false
}, },
show_all_button(items) { show_all_button(type) {
const items = this.results[type]
return items.total > items.items.length return items.total > items.items.length
}, },
spotify_search() { spotify_search(types) {
return webapi.spotify().then(({ data }) => { return webapi.spotify().then(({ data }) => {
this.search_param.market = data.webapi_country this.search_param.market = data.webapi_country
const spotifyApi = new SpotifyWebApi() const api = new SpotifyWebApi()
spotifyApi.setAccessToken(data.webapi_token) api.setAccessToken(data.webapi_token)
const types = this.query.type return api.search(this.query.query, types, this.search_param)
.split(',')
.filter((type) => this.validSearchTypes.includes(type))
return spotifyApi.search(this.query.query, types, this.search_param)
}) })
} }
} }