[web] Add the possibility to remove past search queries

This commit is contained in:
Alain Nussbaumer 2024-04-09 15:39:06 +02:00
parent e5e7702fc5
commit 0362896bfb
5 changed files with 212 additions and 257 deletions

View File

@ -10,7 +10,7 @@
'is-active': $route.name === 'search-library' 'is-active': $route.name === 'search-library'
}" }"
> >
<a @click="search_library"> <a @click="$emit('search-library')">
<mdicon class="icon is-small" name="bookshelf" size="16" /> <mdicon class="icon is-small" name="bookshelf" size="16" />
<span v-text="$t('page.search.tabs.library')" /> <span v-text="$t('page.search.tabs.library')" />
</a> </a>
@ -20,7 +20,7 @@
'is-active': $route.name === 'search-spotify' 'is-active': $route.name === 'search-spotify'
}" }"
> >
<a @click="search_spotify"> <a @click="$emit('search-spotify')">
<mdicon class="icon is-small" name="spotify" size="16" /> <mdicon class="icon is-small" name="spotify" size="16" />
<span v-text="$t('page.search.tabs.spotify')" /> <span v-text="$t('page.search.tabs.spotify')" />
</a> </a>
@ -36,39 +36,12 @@
<script> <script>
export default { export default {
name: 'TabsSearch', name: 'TabsSearch',
props: { query: { default: '', type: String } }, emits: ['search-library', 'search-spotify'],
computed: { computed: {
route_query() {
if (this.query) {
return {
limit: 3,
offset: 0,
query: this.query,
type: 'track,artist,album,composer,playlist,audiobook,podcast'
}
}
return null
},
spotify_enabled() { spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid return this.$store.state.spotify.webapi_token_valid
} }
},
methods: {
search_library() {
this.$router.push({
name: 'search-library',
query: this.route_query
})
},
search_spotify() {
this.$router.push({
name: 'search-spotify',
query: this.route_query
})
}
} }
} }
</script> </script>

View File

@ -3,7 +3,7 @@
<div class="container"> <div class="container">
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-four-fifths"> <div class="column is-four-fifths">
<form @submit.prevent="new_search"> <form @submit.prevent="search">
<div class="field"> <div class="field">
<p class="control has-icons-left"> <p class="control has-icons-left">
<input <input
@ -32,43 +32,42 @@
</i18n-t> </i18n-t>
</div> </div>
</form> </form>
<div class="tags mt-4"> <div class="field is-grouped is-grouped-multiline mt-4">
<a <div v-for="query in recent_searches" :key="query" class="control">
v-for="recent_search in recent_searches" <div class="tags has-addons">
:key="recent_search" <a class="tag" @click="open_search(query)" v-text="query" />
class="tag" <a class="tag is-delete" @click="remove_search(query)"></a>
@click="open_recent_search(recent_search)" </div>
v-text="recent_search" </div>
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<tabs-search :query="search_query" /> <tabs-search @search-library="search" @search-spotify="search_spotify" />
<template v-for="type in search_types" :key="type"> <template v-for="[type, items] in results" :key="type">
<content-with-heading v-if="show(type)" class="pt-0"> <content-with-heading class="pt-0">
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t(`page.search.${type}s`)" /> <p class="title is-4" v-text="$t(`page.search.${type}s`)" />
</template> </template>
<template #content> <template #content>
<component :is="components[type]" :items="results[type]" /> <component :is="components[type]" :items="items" />
</template> </template>
<template #footer> <template v-if="!expanded" #footer>
<nav v-if="show_all_button(type)" class="level"> <nav v-if="show_all_button(items)" 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(type)" @click="expand(type)"
v-text=" v-text="
$t(`page.search.show-${type}s`, results[type].total, { $t(`page.search.show-${type}s`, items.total, {
count: $filters.number(results[type].total) count: $filters.number(items.total)
}) })
" "
/> />
</p> </p>
</nav> </nav>
<p v-if="!results[type].total" class="has-text-centered-mobile"> <p v-if="!items.total" class="has-text-centered-mobile">
<i v-text="$t('page.search.no-results')" /> <i v-text="$t('page.search.no-results')" />
</p> </p>
</template> </template>
@ -88,6 +87,17 @@ import ListTracks from '@/components/ListTracks.vue'
import TabsSearch from '@/components/TabsSearch.vue' import TabsSearch from '@/components/TabsSearch.vue'
import webapi from '@/webapi' import webapi from '@/webapi'
const PAGE_SIZE = 3,
SEARCH_TYPES = [
'track',
'artist',
'album',
'composer',
'playlist',
'audiobook',
'podcast'
]
export default { export default {
name: 'PageSearchLibrary', name: 'PageSearchLibrary',
components: { components: {
@ -111,139 +121,98 @@ export default {
podcast: ListAlbums.name, podcast: ListAlbums.name,
track: ListTracks.name track: ListTracks.name
}, },
results: { results: new Map(),
album: new GroupedList(), search_limit: {},
artist: new GroupedList(),
audiobook: new GroupedList(),
composer: new GroupedList(),
playlist: new GroupedList(),
podcast: new GroupedList(),
track: new GroupedList()
},
search_query: '', search_query: '',
search_types: [ search_types: SEARCH_TYPES
'track',
'artist',
'album',
'composer',
'playlist',
'audiobook',
'podcast'
],
tracks: new GroupedList()
} }
}, },
computed: { computed: {
expanded() {
return this.search_types.length === 1
},
recent_searches() { recent_searches() {
return this.$store.state.recent_searches return this.$store.state.recent_searches
} }
}, },
watch: { watch: {
$route(to, from) { search_query() {
this.search(to) this.$store.commit(types.SEARCH_QUERY, this.search_query)
} }
}, },
mounted() { mounted() {
this.$store.commit(types.SEARCH_SOURCE, this.$route.name) this.$store.commit(types.SEARCH_SOURCE, this.$route.name)
this.search_query = this.$store.state.search_query
this.search_limit = PAGE_SIZE
this.search() this.search()
}, },
methods: { methods: {
new_search() { expand(type) {
if (!this.search_query) { this.search_query = this.$store.state.search_query
return this.search_types = [type]
} this.search_limit = -1
this.$router.push({ this.search()
query: {
limit: 3,
offset: 0,
query: this.search_query,
type: this.search_types.join()
}
})
this.$refs.search_field.blur()
}, },
open_recent_search(query) { open_search(query) {
this.search_query = query this.search_query = query
this.new_search() this.search_types = SEARCH_TYPES
this.search_limit = PAGE_SIZE
this.search()
}, },
open_search(type) { remove_search(query) {
this.$router.push({ this.$store.dispatch('remove_recent_search', query)
query: { query: this.$route.query.query, type } },
reset() {
this.results.clear()
this.search_types.forEach((type) => {
this.results.set(type, new GroupedList())
}) })
}, },
search() { search(event) {
this.search_query = this.$route.query.query?.trim() if (event) {
this.search_types = SEARCH_TYPES
this.search_limit = PAGE_SIZE
}
if (!this.search_query || !this.search_query.replace(/^query:/u, '')) { if (!this.search_query || !this.search_query.replace(/^query:/u, '')) {
this.$refs.search_field.focus() this.$refs.search_field.focus()
return return
} }
this.$route.query.query = this.search_query this.reset()
this.searchMusic(this.$route.query) this.search_types.forEach((type) => {
this.searchType(this.$route.query, 'audiobook') this.search_items(type)
this.searchType(this.$route.query, 'podcast') })
this.$store.dispatch('add_recent_search', this.search_query) this.$store.dispatch('add_recent_search', this.search_query)
}, },
searchMusic(query) { search_items(type) {
if ( const music = type !== 'audiobook' && type !== 'podcast',
!query.type.includes('track') && kind = music ? 'music' : type,
!query.type.includes('artist') && parameters = {
!query.type.includes('album') && type: music ? type : 'album',
!query.type.includes('playlist') && limit: this.search_limit
!query.type.includes('composer')
) {
return
} }
const parameters = { if (this.search_query.startsWith('query:')) {
type: query.type parameters.expression = `(${this.search_query.replace(/^query:/u, '').trim()}) and media_kind is ${kind}`
} } else if (music) {
if (query.query.startsWith('query:')) { parameters.query = this.search_query
parameters.expression = `(${query.query.replace(/^query:/u, '').trim()}) and media_kind is music` parameters.media_kind = kind
} else { } else {
parameters.query = query.query parameters.expression = `(album includes "${this.search_query}" or artist includes "${this.search_query}") and media_kind is ${kind}`
parameters.media_kind = 'music'
}
if (query.limit) {
parameters.limit = query.limit
parameters.offset = query.offset
} }
webapi.search(parameters).then(({ data }) => { webapi.search(parameters).then(({ data }) => {
this.results.track = new GroupedList(data.tracks) this.results.set(type, new GroupedList(data[`${parameters.type}s`]))
this.results.artist = new GroupedList(data.artists)
this.results.album = new GroupedList(data.albums)
this.results.composer = new GroupedList(data.composers)
this.results.playlist = new GroupedList(data.playlists)
}) })
}, },
searchType(query, type) { search_spotify() {
if (!query.type.includes(type)) { this.$router.push({ name: 'search-spotify' })
return
}
const parameters = {
type: 'album'
}
if (query.query.startsWith('query:')) {
parameters.expression = query.query.replace(/^query:/u, '').trim()
} else {
parameters.expression = `album includes "${query.query}" or artist includes "${query.query}"`
}
parameters.expression = `(${parameters.expression}) and media_kind is ${type}`
if (query.limit) {
parameters.limit = query.limit
parameters.offset = query.offset
}
webapi.search(parameters).then(({ data }) => {
this.results[type] = new GroupedList(data.albums)
})
}, },
show(type) { show(type) {
return this.$route.query.type?.includes(type) ?? false return this.search_types.includes(type)
}, },
show_all_button(type) { show_all_button(items) {
const items = this.results[type]
return items.total > items.items.length return items.total > items.items.length
} }
} }

View File

@ -3,7 +3,7 @@
<div class="container"> <div class="container">
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-four-fifths"> <div class="column is-four-fifths">
<form @submit.prevent="new_search"> <form @submit.prevent="search">
<div class="field"> <div class="field">
<p class="control has-icons-left"> <p class="control has-icons-left">
<input <input
@ -18,28 +18,27 @@
</p> </p>
</div> </div>
</form> </form>
<div class="tags mt-4"> <div class="field is-grouped is-grouped-multiline mt-4">
<a <div v-for="query in recent_searches" :key="query" class="control">
v-for="recent_search in recent_searches" <div class="tags has-addons">
:key="recent_search" <a class="tag" @click="open_search(query)" v-text="query" />
class="tag" <a class="tag is-delete" @click="remove_search(query)"></a>
@click="open_recent_search(recent_search)" </div>
v-text="recent_search" </div>
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<tabs-search :query="search_query" /> <tabs-search @search-library="search_library" @search-spotify="search" />
<template v-for="type in search_types" :key="type"> <template v-for="[type, items] in results" :key="type">
<content-with-heading v-if="show(type)" class="pt-0"> <content-with-heading class="pt-0">
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t(`page.spotify.search.${type}s`)" /> <p class="title is-4" v-text="$t(`page.spotify.search.${type}s`)" />
</template> </template>
<template #content> <template #content>
<component :is="components[type]" :items="results[type].items" /> <component :is="components[type]" :items="items.items" />
<VueEternalLoading v-if="$route.query.type === type" :load="search_next"> <VueEternalLoading v-if="expanded" :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">
@ -50,21 +49,21 @@
<template #no-more>&nbsp;</template> <template #no-more>&nbsp;</template>
</VueEternalLoading> </VueEternalLoading>
</template> </template>
<template #footer> <template v-if="!expanded" #footer>
<nav v-if="show_all_button(type)" class="level"> <nav v-if="show_all_button(items)" 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(type)" @click="expand(type)"
v-text=" v-text="
$t(`page.spotify.search.show-${type}s`, results[type].total, { $t(`page.spotify.search.show-${type}s`, items.total, {
count: $filters.number(results[type].total) count: $filters.number(items.total)
}) })
" "
/> />
</p> </p>
</nav> </nav>
<p v-if="!results[type].total" class="has-text-centered-mobile"> <p v-if="!items.total" class="has-text-centered-mobile">
<i v-text="$t(`page.spotify.search.no-results`)" /> <i v-text="$t(`page.spotify.search.no-results`)" />
</p> </p>
</template> </template>
@ -84,7 +83,9 @@ import TabsSearch from '@/components/TabsSearch.vue'
import { VueEternalLoading } from '@ts-pro/vue-eternal-loading' import { VueEternalLoading } from '@ts-pro/vue-eternal-loading'
import webapi from '@/webapi' import webapi from '@/webapi'
const PAGE_SIZE = 50 const PAGE_SIZE = 3,
PAGE_SIZE_EXPANDED = 50,
SEARCH_TYPES = ['track', 'artist', 'album', 'playlist']
export default { export default {
name: 'PageSearchSpotify', name: 'PageSearchSpotify',
@ -106,114 +107,115 @@ export default {
playlist: ListPlaylistsSpotify.name, playlist: ListPlaylistsSpotify.name,
track: ListTracksSpotify.name track: ListTracksSpotify.name
}, },
results: { results: new Map(),
album: { items: [], total: 0 }, search_parameters: {},
artist: { items: [], total: 0 },
playlist: { items: [], total: 0 },
track: { items: [], total: 0 }
},
search_param: {},
search_query: '', search_query: '',
search_types: ['track', 'artist', 'album', 'playlist'] search_types: SEARCH_TYPES
} }
}, },
computed: { computed: {
expanded() {
return this.search_types.length === 1
},
recent_searches() { recent_searches() {
return this.$store.state.recent_searches.filter( return this.$store.state.recent_searches.filter(
(search) => !search.startsWith('query:') (query) => !query.startsWith('query:')
) )
} }
}, },
watch: { watch: {
$route(to, from) { search_query() {
this.search() this.$store.commit(types.SEARCH_QUERY, this.search_query)
} }
}, },
mounted() { mounted() {
this.$store.commit(types.SEARCH_SOURCE, this.$route.name) this.$store.commit(types.SEARCH_SOURCE, this.$route.name)
this.search_query = this.$store.state.search_query
this.search_parameters.limit = PAGE_SIZE
this.search() this.search()
}, },
methods: { methods: {
new_search() { expand(type) {
if (!this.search_query) { this.search_query = this.$store.state.search_query
return this.search_types = [type]
} this.search_parameters.limit = PAGE_SIZE_EXPANDED
this.$router.push({ this.search_parameters.offset = 0
query: {
limit: 3, this.search()
offset: 0,
query: this.search_query,
type: this.search_types.join()
}
})
this.$refs.search_field.blur()
}, },
open_recent_search(query) { open_search(query) {
this.search_query = query this.search_query = query
this.new_search() this.search_types = SEARCH_TYPES
this.search_parameters.limit = PAGE_SIZE
this.search_parameters.offset = 0
this.search()
}, },
open_search(type) { remove_search(query) {
this.$router.push({ this.$store.dispatch('remove_recent_search', query)
query: { query: this.$route.query.query, type }
})
}, },
reset() { reset() {
Object.entries(this.results).forEach( this.results.clear()
(key) => (this.results[key] = { items: [], total: 0 }) this.search_types.forEach((type) => {
) this.results.set(type, { items: [], total: 0 })
})
}, },
search() { search(event) {
this.reset() if (event) {
this.search_query = this.$route.query.query?.trim() this.search_types = SEARCH_TYPES
if (!this.search_query || this.search_query.startsWith('query:')) { this.search_parameters.limit = PAGE_SIZE
this.search_query = '' }
if (!this.search_query) {
this.$refs.search_field.focus() this.$refs.search_field.focus()
return return
} }
this.$route.query.query = this.search_query this.reset()
this.search_all() this.search_items().then((data) => {
this.search_types.forEach((type) => {
this.results.set(type, data[`${type}s`])
})
})
this.$store.dispatch('add_recent_search', this.search_query) this.$store.dispatch('add_recent_search', this.search_query)
}, },
search_all() { search_items() {
this.search_param.limit = this.$route.query.limit ?? PAGE_SIZE return webapi.spotify().then(({ data }) => {
this.search_param.offset = this.$route.query.offset ?? 0 this.search_parameters.market = data.webapi_country
const types = this.$route.query.type const spotifyApi = new SpotifyWebApi()
.split(',') spotifyApi.setAccessToken(data.webapi_token)
.filter((type) => this.search_types.includes(type)) return spotifyApi.search(
this.search_spotify(types).then((data) => { this.search_query,
this.results.track = data.tracks ?? { items: [], total: 0 } this.search_types,
this.results.artist = data.artists ?? { items: [], total: 0 } this.search_parameters
this.results.album = data.albums ?? { items: [], total: 0 } )
this.results.playlist = data.playlists ?? { items: [], total: 0 } })
},
search_library() {
this.$router.push({
name: 'search-library'
}) })
}, },
search_next({ loaded }) { search_next({ loaded }) {
const items = this.results[this.$route.query.type] const [type] = this.search_types,
this.search_spotify([this.$route.query.type]).then((data) => { items = this.results.get(type)
this.search_parameters.limit = PAGE_SIZE_EXPANDED
this.search_items().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 items.total = next.total
this.search_param.offset += next.limit if (!this.search_parameters.offset) {
loaded(next.items.length, PAGE_SIZE) this.search_parameters.offset = 0
}) }
}, this.search_parameters.offset += next.limit
search_spotify(types) { loaded(next.items.length, PAGE_SIZE_EXPANDED)
return webapi.spotify().then(({ data }) => {
this.search_param.market = data.webapi_country
const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(data.webapi_token)
return spotifyApi.search(this.$route.query.query, types, this.search_param)
}) })
}, },
show(type) { show(type) {
return this.$route.query.type?.includes(type) ?? false return this.search_types.includes(type)
}, },
show_all_button(type) { show_all_button(items) {
const items = this.results[type]
return items.total > items.items.length return items.total > items.items.length
} }
} }

View File

@ -54,6 +54,7 @@ export default createStore({
}, },
recent_searches: [], recent_searches: [],
rss_count: {}, rss_count: {},
search_query: '',
search_source: 'search-library', search_source: 'search-library',
settings: { settings: {
categories: [] categories: []
@ -130,6 +131,9 @@ export default createStore({
[types.UPDATE_PAIRING](state, pairing) { [types.UPDATE_PAIRING](state, pairing) {
state.pairing = pairing state.pairing = pairing
}, },
[types.SEARCH_QUERY](state, query) {
state.search_query = query
},
[types.SEARCH_SOURCE](state, searchSource) { [types.SEARCH_SOURCE](state, searchSource) {
state.search_source = searchSource state.search_source = searchSource
}, },
@ -200,8 +204,8 @@ export default createStore({
} }
}, },
add_recent_search({ commit, state }, query) { add_recent_search({ commit, state }, query) {
const index = state.recent_searches.findIndex((elem) => elem === query) const index = state.recent_searches.indexOf(query)
if (index >= 0) { if (index !== -1) {
state.recent_searches.splice(index, 1) state.recent_searches.splice(index, 1)
} }
state.recent_searches.splice(0, 0, query) state.recent_searches.splice(0, 0, query)
@ -209,6 +213,12 @@ export default createStore({
state.recent_searches.pop() state.recent_searches.pop()
} }
}, },
remove_recent_search({ commit, state }, query) {
const index = state.recent_searches.indexOf(query)
if (index !== -1) {
state.recent_searches.splice(index, 1)
}
},
delete_notification({ commit, state }, notification) { delete_notification({ commit, state }, notification) {
const index = state.notifications.list.indexOf(notification) const index = state.notifications.list.indexOf(notification)
if (index !== -1) { if (index !== -1) {

View File

@ -1,25 +1,26 @@
export const UPDATE_CONFIG = 'UPDATE_CONFIG' export const ALBUMS_SORT = 'ALBUMS_SORT',
export const UPDATE_SETTINGS = 'UPDATE_SETTINGS' ARTISTS_SORT = 'ARTISTS_SORT',
export const UPDATE_LIBRARY_STATS = 'UPDATE_LIBRARY_STATS' ARTIST_ALBUMS_SORT = 'ARTIST_ALBUMS_SORT',
export const UPDATE_LIBRARY_RSS_COUNT = 'UPDATE_LIBRARY_RSS_COUNT' ARTIST_TRACKS_SORT = 'ARTIST_TRACKS_SORT',
export const UPDATE_OUTPUTS = 'UPDATE_OUTPUTS' COMPOSER_TRACKS_SORT = 'COMPOSER_TRACKS_SORT',
export const UPDATE_PLAYER_STATUS = 'UPDATE_PLAYER_STATUS' GENRE_TRACKS_SORT = 'GENRE_TRACKS_SORT',
export const UPDATE_QUEUE = 'UPDATE_QUEUE' HIDE_SINGLES = 'HIDE_SINGLES',
export const UPDATE_LYRICS = 'UPDATE_LYRICS' HIDE_SPOTIFY = 'HIDE_SPOTIFY',
export const UPDATE_LASTFM = 'UPDATE_LASTFM' SEARCH_QUERY = 'SEARCH_QUERY',
export const UPDATE_SPOTIFY = 'UPDATE_SPOTIFY' SEARCH_SOURCE = 'SEARCH_SOURCE',
export const UPDATE_PAIRING = 'UPDATE_PAIRING' SHOW_BURGER_MENU = 'SHOW_BURGER_MENU',
export const SEARCH_SOURCE = 'SEARCH_SOURCE' SHOW_ONLY_NEXT_ITEMS = 'SHOW_ONLY_NEXT_ITEMS',
export const COMPOSER_TRACKS_SORT = 'COMPOSER_TRACKS_SORT' SHOW_PLAYER_MENU = 'SHOW_PLAYER_MENU',
export const GENRE_TRACKS_SORT = 'GENRE_TRACKS_SORT' SHOW_UPDATE_DIALOG = 'SHOW_UPDATE_DIALOG',
export const HIDE_SINGLES = 'HIDE_SINGLES' UPDATE_CONFIG = 'UPDATE_CONFIG',
export const HIDE_SPOTIFY = 'HIDE_SPOTIFY' UPDATE_DIALOG_SCAN_KIND = 'UPDATE_DIALOG_SCAN_KIND',
export const ARTISTS_SORT = 'ARTISTS_SORT' UPDATE_LASTFM = 'UPDATE_LASTFM',
export const ARTIST_ALBUMS_SORT = 'ARTIST_ALBUMS_SORT' UPDATE_LIBRARY_RSS_COUNT = 'UPDATE_LIBRARY_RSS_COUNT',
export const ARTIST_TRACKS_SORT = 'ARTIST_TRACKS_SORT' UPDATE_LIBRARY_STATS = 'UPDATE_LIBRARY_STATS',
export const ALBUMS_SORT = 'ALBUMS_SORT' UPDATE_LYRICS = 'UPDATE_LYRICS',
export const SHOW_ONLY_NEXT_ITEMS = 'SHOW_ONLY_NEXT_ITEMS' UPDATE_OUTPUTS = 'UPDATE_OUTPUTS',
export const SHOW_BURGER_MENU = 'SHOW_BURGER_MENU' UPDATE_PAIRING = 'UPDATE_PAIRING',
export const SHOW_PLAYER_MENU = 'SHOW_PLAYER_MENU' UPDATE_PLAYER_STATUS = 'UPDATE_PLAYER_STATUS',
export const SHOW_UPDATE_DIALOG = 'SHOW_UPDATE_DIALOG' UPDATE_QUEUE = 'UPDATE_QUEUE',
export const UPDATE_DIALOG_SCAN_KIND = 'UPDATE_DIALOG_SCAN_KIND' UPDATE_SETTINGS = 'UPDATE_SETTINGS',
UPDATE_SPOTIFY = 'UPDATE_SPOTIFY'