mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-01 08:56:00 -05:00
6a2f85e04f
If a Spotify playlist is containing unplayable tracks (e.g., wrong market), the position of the subsequent tracks in the playlist must be changed accordingly. Moreover, the album property was provided for no reason.
579 lines
17 KiB
Vue
579 lines
17 KiB
Vue
<template>
|
|
<div>
|
|
<!-- Search field + recent searches -->
|
|
<section class="section fd-remove-padding-bottom">
|
|
<div class="container">
|
|
<div class="columns is-centered">
|
|
<div class="column is-four-fifths">
|
|
<form @submit.prevent="new_search">
|
|
<div class="field">
|
|
<p class="control is-expanded has-icons-left">
|
|
<input
|
|
ref="search_field"
|
|
v-model="search_query"
|
|
class="input is-rounded is-shadowless"
|
|
type="text"
|
|
placeholder="Search"
|
|
autocomplete="off"
|
|
/>
|
|
<span class="icon is-left"
|
|
><mdicon name="magnify" size="16"
|
|
/></span>
|
|
</p>
|
|
</div>
|
|
</form>
|
|
<div class="tags" style="margin-top: 16px">
|
|
<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>
|
|
</section>
|
|
<tabs-search :query="search_query" />
|
|
<!-- Tracks -->
|
|
<content-with-heading v-if="show_tracks && tracks.total">
|
|
<template #heading-left>
|
|
<p class="title is-4" v-text="$t('page.spotify.search.tracks')" />
|
|
</template>
|
|
<template #content>
|
|
<spotify-list-item-track
|
|
v-for="track in tracks.items"
|
|
:key="track.id"
|
|
:track="track"
|
|
:position="0"
|
|
:context_uri="track.uri"
|
|
>
|
|
<template #actions>
|
|
<a @click.prevent.stop="open_track_dialog(track)">
|
|
<span class="icon has-text-dark"
|
|
><mdicon name="dots-vertical" size="16"
|
|
/></span>
|
|
</a>
|
|
</template>
|
|
</spotify-list-item-track>
|
|
<VueEternalLoading
|
|
v-if="query.type === 'track'"
|
|
:load="search_tracks_next"
|
|
>
|
|
<template #no-more> . </template>
|
|
</VueEternalLoading>
|
|
<spotify-modal-dialog-track
|
|
:show="show_track_details_modal"
|
|
:track="selected_track"
|
|
:album="selected_track.album"
|
|
@close="show_track_details_modal = false"
|
|
/>
|
|
</template>
|
|
<template #footer>
|
|
<nav v-if="show_all_tracks_button" class="level">
|
|
<p class="level-item">
|
|
<a
|
|
class="button is-light is-small is-rounded"
|
|
@click="open_search_tracks"
|
|
v-text="
|
|
$t('page.spotify.search.show-all-tracks', {
|
|
count: tracks.total.toLocaleString($i18n.locale)
|
|
})
|
|
"
|
|
/>
|
|
</p>
|
|
</nav>
|
|
</template>
|
|
</content-with-heading>
|
|
<content-text v-if="show_tracks && !tracks.total" class="mt-6">
|
|
<template #content>
|
|
<p><i v-text="$t('page.spotify.search.no-tracks')" /></p>
|
|
</template>
|
|
</content-text>
|
|
<!-- Artists -->
|
|
<content-with-heading v-if="show_artists && artists.total">
|
|
<template #heading-left>
|
|
<p class="title is-4" v-text="$t('page.spotify.search.artists')" />
|
|
</template>
|
|
<template #content>
|
|
<spotify-list-item-artist
|
|
v-for="artist in artists.items"
|
|
:key="artist.id"
|
|
:artist="artist"
|
|
>
|
|
<template #actions>
|
|
<a @click.prevent.stop="open_artist_dialog(artist)">
|
|
<span class="icon has-text-dark"
|
|
><mdicon name="dots-vertical" size="16"
|
|
/></span>
|
|
</a>
|
|
</template>
|
|
</spotify-list-item-artist>
|
|
<VueEternalLoading
|
|
v-if="query.type === 'artist'"
|
|
:load="search_artists_next"
|
|
>
|
|
<template #no-more> . </template>
|
|
</VueEternalLoading>
|
|
<spotify-modal-dialog-artist
|
|
:show="show_artist_details_modal"
|
|
:artist="selected_artist"
|
|
@close="show_artist_details_modal = false"
|
|
/>
|
|
</template>
|
|
<template #footer>
|
|
<nav v-if="show_all_artists_button" class="level">
|
|
<p class="level-item">
|
|
<a
|
|
class="button is-light is-small is-rounded"
|
|
@click="open_search_artists"
|
|
v-text="
|
|
$t('page.spotify.search.show-all-artists', {
|
|
count: artists.total.toLocaleString($i18n.locale)
|
|
})
|
|
"
|
|
/>
|
|
</p>
|
|
</nav>
|
|
</template>
|
|
</content-with-heading>
|
|
<content-text v-if="show_artists && !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_albums && albums.total">
|
|
<template #heading-left>
|
|
<p class="title is-4" v-text="$t('page.spotify.search.albums')" />
|
|
</template>
|
|
<template #content>
|
|
<spotify-list-item-album
|
|
v-for="album in albums.items"
|
|
:key="album.id"
|
|
:album="album"
|
|
@click="open_album(album)"
|
|
>
|
|
<template v-if="is_visible_artwork" #artwork>
|
|
<cover-artwork
|
|
:artwork_url="artwork_url(album)"
|
|
:artist="album.artist"
|
|
:album="album.name"
|
|
class="fd-has-action fd-has-shadow fd-cover fd-cover-small-image"
|
|
:maxwidth="64"
|
|
:maxheight="64"
|
|
/>
|
|
</template>
|
|
<template #actions>
|
|
<a @click.prevent.stop="open_album_dialog(album)">
|
|
<span class="icon has-text-dark"
|
|
><mdicon name="dots-vertical" size="16"
|
|
/></span>
|
|
</a>
|
|
</template>
|
|
</spotify-list-item-album>
|
|
<VueEternalLoading
|
|
v-if="query.type === 'album'"
|
|
:load="search_albums_next"
|
|
>
|
|
<template #no-more> . </template>
|
|
</VueEternalLoading>
|
|
<spotify-modal-dialog-album
|
|
:show="show_album_details_modal"
|
|
:album="selected_album"
|
|
@close="show_album_details_modal = false"
|
|
/>
|
|
</template>
|
|
<template #footer>
|
|
<nav v-if="show_all_albums_button" class="level">
|
|
<p class="level-item">
|
|
<a
|
|
class="button is-light is-small is-rounded"
|
|
@click="open_search_albums"
|
|
v-text="
|
|
$t('page.spotify.search.show-all-albums', {
|
|
count: albums.total.toLocaleString($i18n.locale)
|
|
})
|
|
"
|
|
/>
|
|
</p>
|
|
</nav>
|
|
</template>
|
|
</content-with-heading>
|
|
<content-text v-if="show_albums && !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_playlists && playlists.total">
|
|
<template #heading-left>
|
|
<p class="title is-4" v-text="$t('page.spotify.search.playlists')" />
|
|
</template>
|
|
<template #content>
|
|
<spotify-list-item-playlist
|
|
v-for="playlist in playlists.items"
|
|
:key="playlist.id"
|
|
:playlist="playlist"
|
|
>
|
|
<template #actions>
|
|
<a @click.prevent.stop="open_playlist_dialog(playlist)">
|
|
<span class="icon has-text-dark"
|
|
><mdicon name="dots-vertical" size="16"
|
|
/></span>
|
|
</a>
|
|
</template>
|
|
</spotify-list-item-playlist>
|
|
<VueEternalLoading
|
|
v-if="query.type === 'playlist'"
|
|
:load="search_playlists_next"
|
|
>
|
|
<template #no-more> . </template>
|
|
</VueEternalLoading>
|
|
<spotify-modal-dialog-playlist
|
|
:show="show_playlist_details_modal"
|
|
:playlist="selected_playlist"
|
|
@close="show_playlist_details_modal = false"
|
|
/>
|
|
</template>
|
|
<template #footer>
|
|
<nav v-if="show_all_playlists_button" class="level">
|
|
<p class="level-item">
|
|
<a
|
|
class="button is-light is-small is-rounded"
|
|
@click="open_search_playlists"
|
|
v-text="
|
|
$t('page.spotify.search.show-all-playlists', {
|
|
count: playlists.total.toLocaleString($i18n.locale)
|
|
})
|
|
"
|
|
/>
|
|
</p>
|
|
</nav>
|
|
</template>
|
|
</content-with-heading>
|
|
<content-text v-if="show_playlists && !playlists.total">
|
|
<template #content>
|
|
<p><i v-text="$t('page.spotify.search.no-playlists')" /></p>
|
|
</template>
|
|
</content-text>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
|
import ContentText from '@/templates/ContentText.vue'
|
|
import TabsSearch from '@/components/TabsSearch.vue'
|
|
import SpotifyListItemTrack from '@/components/SpotifyListItemTrack.vue'
|
|
import SpotifyListItemArtist from '@/components/SpotifyListItemArtist.vue'
|
|
import SpotifyListItemAlbum from '@/components/SpotifyListItemAlbum.vue'
|
|
import SpotifyListItemPlaylist from '@/components/SpotifyListItemPlaylist.vue'
|
|
import SpotifyModalDialogTrack from '@/components/SpotifyModalDialogTrack.vue'
|
|
import SpotifyModalDialogArtist from '@/components/SpotifyModalDialogArtist.vue'
|
|
import SpotifyModalDialogAlbum from '@/components/SpotifyModalDialogAlbum.vue'
|
|
import SpotifyModalDialogPlaylist from '@/components/SpotifyModalDialogPlaylist.vue'
|
|
import CoverArtwork from '@/components/CoverArtwork.vue'
|
|
import SpotifyWebApi from 'spotify-web-api-js'
|
|
import webapi from '@/webapi'
|
|
import * as types from '@/store/mutation_types'
|
|
import { VueEternalLoading } from '@ts-pro/vue-eternal-loading'
|
|
|
|
const PAGE_SIZE = 50
|
|
|
|
export default {
|
|
name: 'SpotifyPageSearch',
|
|
components: {
|
|
ContentWithHeading,
|
|
ContentText,
|
|
TabsSearch,
|
|
SpotifyListItemTrack,
|
|
SpotifyListItemArtist,
|
|
SpotifyListItemAlbum,
|
|
SpotifyListItemPlaylist,
|
|
SpotifyModalDialogTrack,
|
|
SpotifyModalDialogArtist,
|
|
SpotifyModalDialogAlbum,
|
|
SpotifyModalDialogPlaylist,
|
|
VueEternalLoading,
|
|
CoverArtwork
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
search_query: '',
|
|
tracks: { items: [], total: 0 },
|
|
artists: { items: [], total: 0 },
|
|
albums: { items: [], total: 0 },
|
|
playlists: { items: [], total: 0 },
|
|
|
|
query: {},
|
|
search_param: {},
|
|
|
|
show_track_details_modal: false,
|
|
selected_track: {},
|
|
|
|
show_album_details_modal: false,
|
|
selected_album: {},
|
|
|
|
show_artist_details_modal: false,
|
|
selected_artist: {},
|
|
|
|
show_playlist_details_modal: false,
|
|
selected_playlist: {},
|
|
|
|
validSearchTypes: ['track', 'artist', 'album', 'playlist']
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
recent_searches() {
|
|
return this.$store.state.recent_searches.filter(
|
|
(search) => !search.startsWith('query:')
|
|
)
|
|
},
|
|
|
|
show_tracks() {
|
|
return this.$route.query.type && this.$route.query.type.includes('track')
|
|
},
|
|
show_all_tracks_button() {
|
|
return this.tracks.total > this.tracks.items.length
|
|
},
|
|
|
|
show_artists() {
|
|
return this.$route.query.type && this.$route.query.type.includes('artist')
|
|
},
|
|
show_all_artists_button() {
|
|
return this.artists.total > this.artists.items.length
|
|
},
|
|
|
|
show_albums() {
|
|
return this.$route.query.type && this.$route.query.type.includes('album')
|
|
},
|
|
show_all_albums_button() {
|
|
return this.albums.total > this.albums.items.length
|
|
},
|
|
|
|
show_playlists() {
|
|
return (
|
|
this.$route.query.type && this.$route.query.type.includes('playlist')
|
|
)
|
|
},
|
|
show_all_playlists_button() {
|
|
return this.playlists.total > this.playlists.items.length
|
|
},
|
|
|
|
is_visible_artwork() {
|
|
return this.$store.getters.settings_option(
|
|
'webinterface',
|
|
'show_cover_artwork_in_album_lists'
|
|
).value
|
|
}
|
|
},
|
|
|
|
watch: {
|
|
$route(to, from) {
|
|
this.query = to.query
|
|
this.search()
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
this.query = this.$route.query
|
|
this.search()
|
|
},
|
|
|
|
methods: {
|
|
reset() {
|
|
this.tracks = { items: [], total: 0 }
|
|
this.artists = { items: [], total: 0 }
|
|
this.albums = { items: [], total: 0 }
|
|
this.playlists = { items: [], total: 0 }
|
|
},
|
|
|
|
search() {
|
|
this.reset()
|
|
|
|
// If no search query present reset and focus search field
|
|
if (
|
|
!this.query.query ||
|
|
this.query.query === '' ||
|
|
this.query.query.startsWith('query:')
|
|
) {
|
|
this.search_query = ''
|
|
this.$refs.search_field.focus()
|
|
return
|
|
}
|
|
|
|
this.search_query = this.query.query
|
|
this.search_param.limit = this.query.limit ? this.query.limit : PAGE_SIZE
|
|
this.search_param.offset = this.query.offset ? this.query.offset : 0
|
|
|
|
this.$store.commit(types.ADD_RECENT_SEARCH, this.query.query)
|
|
|
|
this.search_all()
|
|
},
|
|
|
|
spotify_search() {
|
|
return webapi.spotify().then(({ data }) => {
|
|
this.search_param.market = data.webapi_country
|
|
|
|
const spotifyApi = new SpotifyWebApi()
|
|
spotifyApi.setAccessToken(data.webapi_token)
|
|
|
|
const types = this.query.type
|
|
.split(',')
|
|
.filter((type) => this.validSearchTypes.includes(type))
|
|
return spotifyApi.search(this.query.query, types, this.search_param)
|
|
})
|
|
},
|
|
|
|
search_all() {
|
|
this.spotify_search().then((data) => {
|
|
this.tracks = data.tracks ? data.tracks : { items: [], total: 0 }
|
|
this.artists = data.artists ? data.artists : { items: [], total: 0 }
|
|
this.albums = data.albums ? data.albums : { items: [], total: 0 }
|
|
this.playlists = data.playlists
|
|
? data.playlists
|
|
: { items: [], total: 0 }
|
|
})
|
|
},
|
|
|
|
search_tracks_next({ loaded }) {
|
|
this.spotify_search().then((data) => {
|
|
this.tracks.items = this.tracks.items.concat(data.tracks.items)
|
|
this.tracks.total = data.tracks.total
|
|
this.search_param.offset += data.tracks.limit
|
|
|
|
loaded(data.tracks.items.length, PAGE_SIZE)
|
|
})
|
|
},
|
|
|
|
search_artists_next({ loaded }) {
|
|
this.spotify_search().then((data) => {
|
|
this.artists.items = this.artists.items.concat(data.artists.items)
|
|
this.artists.total = data.artists.total
|
|
this.search_param.offset += data.artists.limit
|
|
|
|
loaded(data.artists.items.length, PAGE_SIZE)
|
|
})
|
|
},
|
|
|
|
search_albums_next({ loaded }) {
|
|
this.spotify_search().then((data) => {
|
|
this.albums.items = this.albums.items.concat(data.albums.items)
|
|
this.albums.total = data.albums.total
|
|
this.search_param.offset += data.albums.limit
|
|
|
|
loaded(data.albums.items.length, PAGE_SIZE)
|
|
})
|
|
},
|
|
|
|
search_playlists_next({ loaded }) {
|
|
this.spotify_search().then((data) => {
|
|
this.playlists.items = this.playlists.items.concat(data.playlists.items)
|
|
this.playlists.total = data.playlists.total
|
|
this.search_param.offset += data.playlists.limit
|
|
|
|
loaded(data.playlists.items.length, PAGE_SIZE)
|
|
})
|
|
},
|
|
|
|
new_search() {
|
|
if (!this.search_query) {
|
|
return
|
|
}
|
|
|
|
this.$router.push({
|
|
path: '/search/spotify',
|
|
query: {
|
|
type: 'track,artist,album,playlist,audiobook,podcast',
|
|
query: this.search_query,
|
|
limit: 3,
|
|
offset: 0
|
|
}
|
|
})
|
|
this.$refs.search_field.blur()
|
|
},
|
|
|
|
open_search_tracks() {
|
|
this.$router.push({
|
|
path: '/search/spotify',
|
|
query: {
|
|
type: 'track',
|
|
query: this.$route.query.query
|
|
}
|
|
})
|
|
},
|
|
|
|
open_search_artists() {
|
|
this.$router.push({
|
|
path: '/search/spotify',
|
|
query: {
|
|
type: 'artist',
|
|
query: this.$route.query.query
|
|
}
|
|
})
|
|
},
|
|
|
|
open_search_albums() {
|
|
this.$router.push({
|
|
path: '/search/spotify',
|
|
query: {
|
|
type: 'album',
|
|
query: this.$route.query.query
|
|
}
|
|
})
|
|
},
|
|
|
|
open_search_playlists() {
|
|
this.$router.push({
|
|
path: '/search/spotify',
|
|
query: {
|
|
type: 'playlist',
|
|
query: this.$route.query.query
|
|
}
|
|
})
|
|
},
|
|
|
|
open_recent_search(query) {
|
|
this.search_query = query
|
|
this.new_search()
|
|
},
|
|
|
|
open_track_dialog(track) {
|
|
this.selected_track = track
|
|
this.show_track_details_modal = true
|
|
},
|
|
|
|
open_album_dialog(album) {
|
|
this.selected_album = album
|
|
this.show_album_details_modal = true
|
|
},
|
|
|
|
open_artist_dialog(artist) {
|
|
this.selected_artist = artist
|
|
this.show_artist_details_modal = true
|
|
},
|
|
|
|
open_playlist_dialog(playlist) {
|
|
this.selected_playlist = playlist
|
|
this.show_playlist_details_modal = true
|
|
},
|
|
|
|
open_album(album) {
|
|
this.$router.push({ path: '/music/spotify/albums/' + album.id })
|
|
},
|
|
|
|
artwork_url(album) {
|
|
if (album.images && album.images.length > 0) {
|
|
return album.images[0].url
|
|
}
|
|
return ''
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style></style>
|