[web] Fix bug when adding playable items to queue

This commit is contained in:
Alain Nussbaumer 2025-02-13 21:02:13 +01:00
parent d6d5912de1
commit a93777aeec
33 changed files with 122 additions and 198 deletions

View File

@ -3,18 +3,14 @@
<div
class="media is-align-items-center is-clickable mb-0"
@click="open(item)"
>
<div
v-if="settingsStore.show_cover_artwork_in_album_lists"
class="media-left"
>
<cover-artwork
:url="artwork_url(item)"
v-if="settingsStore.show_cover_artwork_in_album_lists"
:url="item.images?.[0]?.url ?? ''"
:artist="item.artist"
:album="item.name"
class="is-small"
class="media-left is-small"
/>
</div>
<div class="media-content">
<div class="is-size-6 has-text-weight-bold" v-text="item.name" />
<div
@ -51,19 +47,13 @@ export default {
name: 'ListAlbumsSpotify',
components: { CoverArtwork, ModalDialogAlbumSpotify },
props: { items: { required: true, type: Object } },
setup() {
return { settingsStore: useSettingsStore() }
},
data() {
return { selected_item: {}, show_details_modal: false }
},
methods: {
artwork_url(item) {
return item.images?.[0]?.url ?? ''
},
open(item) {
this.$router.push({
name: 'music-spotify-album',

View File

@ -1,14 +1,12 @@
<template>
<template v-for="item in items" :key="item.itemId">
<div v-if="!item.isItem" class="py-5">
<div class="media-content">
<span
:id="`index_${item.index}`"
class="tag is-small has-text-weight-bold"
v-text="item.index"
/>
</div>
</div>
<div
v-else
class="media is-align-items-center is-clickable mb-0"
@ -39,15 +37,10 @@ import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
export default {
name: 'ListArtists',
components: { ModalDialogArtist },
props: {
items: { required: true, type: Object }
},
props: { items: { required: true, type: Object } },
data() {
return {
selected_item: {},
show_details_modal: false
}
return { selected_item: {}, show_details_modal: false }
},
methods: {

View File

@ -39,15 +39,10 @@ import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
export default {
name: 'ListComposers',
components: { ModalDialogComposer },
props: {
items: { required: true, type: Object }
},
props: { items: { required: true, type: Object } },
data() {
return {
selected_item: {},
show_details_modal: false
}
return { selected_item: {}, show_details_modal: false }
},
methods: {

View File

@ -54,10 +54,7 @@ export default {
props: { items: { required: true, type: Array } },
data() {
return {
selected_item: '',
show_details_modal: false
}
return { selected_item: '', show_details_modal: false }
},
computed: {

View File

@ -45,10 +45,7 @@ export default {
media_kind: { required: true, type: String }
},
data() {
return {
selected_item: {},
show_details_modal: false
}
return { selected_item: {}, show_details_modal: false }
},
methods: {
open(item) {

View File

@ -60,9 +60,7 @@ export default {
},
setup() {
return {
playerStore: usePlayerStore()
}
return { playerStore: usePlayerStore() }
},
computed: {

View File

@ -33,10 +33,7 @@ export default {
props: { items: { required: true, type: Object } },
data() {
return {
selected_item: {},
show_details_modal: false
}
return { selected_item: {}, show_details_modal: false }
},
methods: {

View File

@ -69,10 +69,7 @@ export default {
emits: ['play-count-changed'],
data() {
return {
selected_item: {},
show_details_modal: false
}
return { selected_item: {}, show_details_modal: false }
},
methods: {

View File

@ -46,8 +46,7 @@ export default {
name: this.item.name,
handler: this.open,
image: this.item.artwork_url,
artist: this.item.artist,
album: this.item.name,
uri: this.item.uri,
properties: [
{
label: 'property.artist',

View File

@ -19,12 +19,11 @@ export default {
return {
name: this.item.name || '',
image: this.item?.images?.[0]?.url || '',
artist: this.item.artist || '',
album: this.item.name || '',
handler: this.open,
uri: this.item.uri,
properties: [
{
label: 'property.album-artist',
label: 'property.artist',
value: this.item?.artists?.[0]?.name,
handler: this.open_artist
},

View File

@ -23,6 +23,7 @@ export default {
return {
name: this.item.name,
handler: this.open,
uri: this.item.uri,
uris: this.uris,
properties: [
{ label: 'property.tracks', value: this.item.item_count },

View File

@ -19,6 +19,7 @@ export default {
return {
name: this.item.name,
handler: this.open,
uri: this.item.uri,
properties: [
{
label: 'property.owner',

View File

@ -44,6 +44,7 @@ export default {
playable() {
return {
name: this.item.title,
uri: this.item.uri,
properties: [
{
label: 'property.album',

View File

@ -43,6 +43,7 @@ export default {
playable() {
return {
name: this.item.title,
uri: this.item.uri,
properties: [
{
label: 'property.album',

View File

@ -16,9 +16,12 @@ export default {
emits: ['close'],
computed: {
playable() {
if (!this.item.artists) {
return {}
}
return {
name: this.item.name,
subtitle: this.item.artists[0].name,
uri: this.item.uri,
properties: [
{
label: 'property.album',
@ -27,20 +30,20 @@ export default {
},
{
label: 'property.album-artist',
value: this.item.artists[0].name,
value: this.item.artists[0]?.name,
handler: this.open_artist
},
{
label: 'property.release-date',
value: this.$filters.date(item.album.release_date)
value: this.$filters.date(this.item.album.release_date)
},
{
label: 'property.position',
value: [item.disc_number, item.track_number].join(' / ')
value: [this.item.disc_number, this.item.track_number].join(' / ')
},
{
label: 'property.duration',
value: this.$filters.durationInHours(item.duration_ms)
value: this.$filters.durationInHours(this.item.duration_ms)
},
{ label: 'property.path', value: this.item.uri }
]

View File

@ -23,9 +23,7 @@ export default {
name: 'NotificationList',
setup() {
return {
notificationsStore: useNotificationsStore()
}
return { notificationsStore: useNotificationsStore() }
},
computed: {

View File

@ -41,9 +41,7 @@ export default {
emits: ['search-library', 'search-spotify'],
setup() {
return {
servicesStore: useServicesStore()
}
return { servicesStore: useServicesStore() }
},
computed: {

View File

@ -7,12 +7,12 @@
<a @click="open_artist" v-text="album.artists[0].name" />
</div>
<div class="buttons is-centered-mobile mt-5">
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.spotify.album.shuffle')" />
</a>
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
@ -21,7 +21,7 @@
</template>
<template #heading-right>
<cover-artwork
:url="artwork_url(album)"
:url="album.images?.[0]?.url ?? ''"
:artist="album.artists[0].name"
:album="album.name"
class="is-clickable is-medium"
@ -63,7 +63,6 @@ const dataObject = {
market: useServicesStore().spotify.webapi_country
})
},
set(vm, response) {
vm.album = response
}
@ -77,26 +76,20 @@ export default {
ListTracksSpotify,
ModalDialogAlbumSpotify
},
beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response))
})
},
setup() {
return {
servicesStore: useServicesStore()
}
return { servicesStore: useServicesStore() }
},
data() {
return {
album: { artists: [{}], tracks: {} },
show_details_modal: false
}
},
computed: {
tracks() {
const { album } = this
@ -107,9 +100,6 @@ export default {
}
},
methods: {
artwork_url(album) {
return album.images?.[0]?.url ?? ''
},
open_artist() {
this.$router.push({
name: 'music-spotify-artist',

View File

@ -48,12 +48,12 @@
<template #heading-right>
<div class="buttons is-centered">
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.artist.shuffle')" />
</a>

View File

@ -11,12 +11,12 @@
<template #heading-right>
<div class="buttons is-centered">
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.spotify.artist.shuffle')" />
</a>

View File

@ -49,12 +49,12 @@
<template #heading-right>
<div class="buttons is-centered">
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.artist.shuffle')" />
</a>

View File

@ -7,12 +7,12 @@
<a @click="open_artist" v-text="album.artist" />
</div>
<div class="buttons is-centered-mobile mt-5">
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="play" size="16" />
<span v-text="$t('page.audiobooks.album.play')" />
</a>
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />

View File

@ -15,12 +15,12 @@
<template #heading-right>
<div class="buttons is-centered">
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="play" size="16" />
<span v-text="$t('page.audiobooks.artist.play')" />
</a>
@ -52,7 +52,6 @@ const dataObject = {
webapi.library_artist_albums(to.params.id)
])
},
set(vm, response) {
vm.artist = response[0].data
vm.albums = new GroupedList(response[1].data)
@ -62,13 +61,11 @@ const dataObject = {
export default {
name: 'PageAudiobooksArtist',
components: { ContentWithHeading, ListAlbums, ModalDialogArtist },
beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response))
})
},
data() {
return {
albums: new GroupedList(),
@ -76,7 +73,6 @@ export default {
show_details_modal: false
}
},
methods: {
play() {
webapi.player_play_uri(

View File

@ -21,12 +21,12 @@
<template #heading-right>
<div class="buttons is-centered">
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.composer.shuffle')" />
</a>
@ -72,13 +72,11 @@ export default {
ListAlbums,
ModalDialogComposer
},
beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response))
})
},
data() {
return {
albums: new GroupedList(),
@ -86,7 +84,11 @@ export default {
show_details_modal: false
}
},
computed: {
expression() {
return `composer is "${this.composer.name}" and media_kind is music`
}
},
methods: {
open_tracks() {
this.$router.push({
@ -95,10 +97,7 @@ export default {
})
},
play() {
webapi.player_play_expression(
`composer is "${this.composer.name}" and media_kind is music`,
true
)
webapi.player_play_expression(this.expression, true)
}
}
}

View File

@ -38,12 +38,12 @@
<template #heading-right>
<div class="buttons is-centered">
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.composer.shuffle')" />
</a>
@ -94,17 +94,14 @@ export default {
ListTracks,
ModalDialogComposer
},
beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response))
})
},
setup() {
return { uiStore: useUIStore() }
},
data() {
return {
composer: {},
@ -127,7 +124,6 @@ export default {
tracks_list: new GroupedList()
}
},
computed: {
expression() {
return `composer is "${this.composer.name}" and media_kind is music`
@ -139,10 +135,8 @@ export default {
return this.tracks_list.group(options)
}
},
methods: {
open_albums() {
this.show_details_modal = false
this.$router.push({
name: 'music-composer-albums',
params: { name: this.composer.name }

View File

@ -7,12 +7,12 @@
<template #heading-right>
<div class="buttons is-centered">
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="play" size="16" />
<span v-text="$t('page.files.play')" />
</a>
@ -22,7 +22,7 @@
<list-directories :items="directories" />
<list-playlists :items="playlists" />
<list-tracks
:expression="play_expression"
:expression="expression"
:items="tracks"
:show_icon="true"
/>
@ -83,26 +83,22 @@ export default {
ListTracks,
ModalDialogDirectory
},
beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response))
})
},
beforeRouteUpdate(to, from, next) {
dataObject.load(to).then((response) => {
dataObject.set(this, response)
next()
})
},
setup() {
return {
configurationStore: useConfigurationStore()
}
},
data() {
return {
directories: [],
@ -111,7 +107,6 @@ export default {
tracks: new GroupedList()
}
},
computed: {
current() {
return this.$route.query?.directory || '/'
@ -122,14 +117,13 @@ export default {
}
return this.$t('page.files.title')
},
play_expression() {
expression() {
return `path starts with "${this.current}" order by path asc`
}
},
methods: {
play() {
webapi.player_play_expression(this.play_expression, false)
webapi.player_play_expression(this.expression, false)
},
transform(path) {
return { name: path.slice(path.lastIndexOf('/') + 1), path }

View File

@ -20,12 +20,12 @@
<template #heading-right>
<div class="buttons is-centered">
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.genre.shuffle')" />
</a>
@ -59,7 +59,6 @@ const dataObject = {
webapi.library_genre_albums(to.params.name, to.query.media_kind)
])
},
set(vm, response) {
vm.genre = response[0].data.genres.items.shift()
vm.albums = new GroupedList(response[1].data.albums, {
@ -81,7 +80,6 @@ export default {
next((vm) => dataObject.set(vm, response))
})
},
data() {
return {
albums: new GroupedList(),

View File

@ -32,12 +32,12 @@
<template #heading-right>
<div class="buttons is-centered">
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.genre.shuffle')" />
</a>
@ -89,17 +89,14 @@ export default {
ListTracks,
ModalDialogGenre
},
beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response))
})
},
setup() {
return { uiStore: useUIStore() }
},
data() {
return {
genre: {},
@ -123,7 +120,6 @@ export default {
tracks_list: new GroupedList()
}
},
computed: {
expression() {
return `genre is "${this.genre.name}" and media_kind is ${this.media_kind}`
@ -135,7 +131,6 @@ export default {
return this.tracks_list.group(options)
}
},
methods: {
open_genre() {
this.show_details_modal = false

View File

@ -11,12 +11,12 @@
<template #heading-right>
<div class="buttons is-centered">
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.playlist.shuffle')" />
</a>

View File

@ -13,12 +13,12 @@
<template #heading-right>
<div class="buttons is-centered">
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_playlist_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.spotify.playlist.shuffle')" />
</a>

View File

@ -7,12 +7,12 @@
<br />
</div>
<div class="buttons is-centered-mobile mt-5">
<a class="button is-small is-dark is-rounded" @click="play">
<a class="button is-small is-rounded" @click="play">
<mdicon class="icon" name="play" size="16" />
<span v-text="$t('page.podcast.play')" />
</a>
<a
class="button is-small is-light is-rounded"
class="button is-small is-rounded"
@click="show_details_modal = true"
>
<mdicon class="icon" name="dots-horizontal" size="16" />

View File

@ -22,7 +22,7 @@
<p v-text="$t('page.settings.services.spotify.requirements')" />
<p v-text="spotify_required_scope.join(', ')" />
</div>
<p v-if="spotify.webapi_token_valid">
<p v-if="spotify.webapi_token_valid" class="content">
<span v-text="$t('page.settings.services.spotify.user')" />
<code v-text="spotify.webapi_user" />
</p>

View File

@ -315,53 +315,50 @@ export default {
return axios.get('./api/queue')
},
queue_add(uri) {
return axios.post(`./api/queue/items/add?uris=${uri}`).then((response) => {
async queue_add(uri) {
const response = await axios.post(`./api/queue/items/add?uris=${uri}`)
useNotificationsStore().add({
text: t('server.appended-tracks', { count: response.data.count }),
timeout: 2000,
type: 'info'
})
return Promise.resolve(response)
})
return await Promise.resolve(response)
},
queue_add_next(uri) {
async queue_add_next(uri) {
let position = 0
const { current } = useQueueStore()
if (current?.id) {
position = current.position + 1
}
return axios
.post(`./api/queue/items/add?uris=${uri}&position=${position}`)
.then((response) => {
const response = await axios.post(
`./api/queue/items/add?uris=${uri}&position=${position}`
)
useNotificationsStore().add({
text: t('server.appended-tracks', { count: response.data.count }),
timeout: 2000,
type: 'info'
})
return Promise.resolve(response)
})
return await Promise.resolve(response)
},
queue_clear() {
return axios.put('./api/queue/clear')
},
queue_expression_add(expression) {
return axios
.post('./api/queue/items/add', null, { params: { expression } })
.then((response) => {
async queue_expression_add(expression) {
const response = await axios.post('./api/queue/items/add', null, {
params: { expression }
})
useNotificationsStore().add({
text: t('server.appended-tracks', { count: response.data.count }),
timeout: 2000,
type: 'info'
})
return Promise.resolve(response)
})
return await Promise.resolve(response)
},
queue_expression_add_next(expression) {
async queue_expression_add_next(expression) {
const params = {}
params.expression = expression
params.position = 0
@ -369,16 +366,13 @@ export default {
if (current?.id) {
params.position = current.position + 1
}
return axios
.post('./api/queue/items/add', null, { params })
.then((response) => {
const response = await axios.post('./api/queue/items/add', null, { params })
useNotificationsStore().add({
text: t('server.appended-tracks', { count: response.data.count }),
timeout: 2000,
type: 'info'
})
return Promise.resolve(response)
})
return await Promise.resolve(response)
},
queue_move(itemId, position) {
@ -389,17 +383,16 @@ export default {
return axios.delete(`./api/queue/items/${itemId}`)
},
queue_save_playlist(name) {
return axios
.post('./api/queue/save', null, { params: { name } })
.then((response) => {
async queue_save_playlist(name) {
const response = await axios.post('./api/queue/save', null, {
params: { name }
})
useNotificationsStore().add({
text: t('server.queue-saved', { name }),
timeout: 2000,
type: 'info'
})
return Promise.resolve(response)
})
return await Promise.resolve(response)
},
search(params) {