[web] Refactor the heading title in the pages

This commit is contained in:
Alain Nussbaumer 2025-03-02 18:03:17 +01:00
parent ef3e64b9c9
commit bdad6d61bf
69 changed files with 903 additions and 823 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,11 @@
<template> <template>
<button <button
class="button is-small is-rounded" class="button is-small is-rounded"
:disabled="disabled" :disabled="button.disabled"
@click="handler" @click="button.handler"
> >
<mdicon v-if="icon" class="icon" :name="icon" size="16" /> <mdicon v-if="button.icon" class="icon" :name="button.icon" size="16" />
<span v-if="label" v-text="$t(label)" /> <span v-if="button.key" v-text="$t(button.key)" />
</button> </button>
</template> </template>
@ -13,10 +13,7 @@
export default { export default {
name: 'ControlButton', name: 'ControlButton',
props: { props: {
disabled: { default: false, type: Boolean }, button: { required: true, type: Object }
handler: { required: true, type: Function },
icon: { default: '', type: String },
label: { default: '', type: String }
} }
} }
</script> </script>

View File

@ -21,7 +21,7 @@
/> />
<span <span
:class="{ 'is-hidden-mobile': link.icon }" :class="{ 'is-hidden-mobile': link.icon }"
v-text="$t(link.label)" v-text="$t(link.key)"
/> />
</a> </a>
</li> </li>

View File

@ -0,0 +1,24 @@
<template>
<div v-if="content.title" class="title is-4" v-text="content.title" />
<div class="is-size-7 is-uppercase">
<template v-for="(part, index) in content.subtitle" :key="index">
<a
v-if="part.handler"
@click="part.handler"
v-text="$t(part.key, { count: $n(part.count) }, part.count)"
/>
<span
v-else
v-text="$t(part.key, { count: $n(part.count) }, part.count)"
/>
<span v-if="index !== content.subtitle.length - 1">&nbsp;|&nbsp;</span>
</template>
</div>
</template>
<script>
export default {
name: 'HeadingTitle',
props: { content: { required: true, type: Object } }
}
</script>

View File

@ -100,16 +100,8 @@ export default {
computed: { computed: {
actions() { actions() {
return [ return [
{ { key: 'page.podcast.cancel', handler: 'cancel', icon: 'cancel' },
label: this.$t('page.podcast.cancel'), { key: 'page.podcast.remove', handler: 'remove', icon: 'delete' }
handler: 'cancel',
icon: 'cancel'
},
{
label: this.$t('page.podcast.remove'),
handler: 'remove',
icon: 'delete'
}
] ]
}, },
media_kind_resolved() { media_kind_resolved() {

View File

@ -13,10 +13,10 @@
<slot v-if="$slots.buttons" name="buttons" /> <slot v-if="$slots.buttons" name="buttons" />
<div <div
v-for="property in item.properties?.filter((p) => p.value)" v-for="property in item.properties?.filter((p) => p.value)"
:key="property.label" :key="property.key"
class="mb-3" class="mb-3"
> >
<div class="is-size-7 is-uppercase" v-text="$t(property.label)" /> <div class="is-size-7 is-uppercase" v-text="$t(property.key)" />
<div class="title is-6"> <div class="title is-6">
<a <a
v-if="property.handler" v-if="property.handler"

View File

@ -11,13 +11,13 @@
<footer v-if="actions.length" class="card-footer"> <footer v-if="actions.length" class="card-footer">
<a <a
v-for="action in actions" v-for="action in actions"
:key="action.label" :key="action.key"
class="card-footer-item" class="card-footer-item"
:class="{ 'is-disabled': action.disabled }" :class="{ 'is-disabled': action.disabled }"
@click="action.handler" @click="action.handler"
> >
<mdicon class="icon" :name="action.icon" size="16" /> <mdicon class="icon" :name="action.icon" size="16" />
<span class="is-size-7" v-text="action.label" /> <span class="is-size-7" v-text="$t(action.key)" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -37,16 +37,12 @@ export default {
computed: { computed: {
actions() { actions() {
if (this.loading) { if (this.loading) {
return [{ label: this.$t('dialog.add.rss.processing'), icon: 'web' }] return [{ key: 'dialog.add.rss.processing', icon: 'web' }]
} }
return [ return [
{ key: 'dialog.add.rss.cancel', handler: this.cancel, icon: 'cancel' },
{ {
label: this.$t('dialog.add.rss.cancel'), key: 'dialog.add.rss.add',
handler: this.cancel,
icon: 'cancel'
},
{
label: this.$t('dialog.add.rss.add'),
disabled: this.disabled, disabled: this.disabled,
handler: this.add, handler: this.add,
icon: 'playlist-plus' icon: 'playlist-plus'

View File

@ -38,22 +38,22 @@ export default {
computed: { computed: {
actions() { actions() {
if (this.loading) { if (this.loading) {
return [{ label: this.$t('dialog.add.stream.processing'), icon: 'web' }] return [{ key: 'dialog.add.stream.processing', icon: 'web' }]
} }
return [ return [
{ {
label: this.$t('dialog.add.stream.cancel'), key: 'dialog.add.stream.cancel',
handler: this.cancel, handler: this.cancel,
icon: 'cancel' icon: 'cancel'
}, },
{ {
label: this.$t('dialog.add.stream.add'), key: 'dialog.add.stream.add',
disabled: this.disabled, disabled: this.disabled,
handler: this.add, handler: this.add,
icon: 'playlist-plus' icon: 'playlist-plus'
}, },
{ {
label: this.$t('dialog.add.stream.play'), key: 'dialog.add.stream.play',
disabled: this.disabled, disabled: this.disabled,
handler: this.play, handler: this.play,
icon: 'play' icon: 'play'

View File

@ -25,15 +25,12 @@ export default {
if (this.media_kind_resolved === 'podcast') { if (this.media_kind_resolved === 'podcast') {
if (this.item.data_kind === 'url') { if (this.item.data_kind === 'url') {
return [ return [
{ label: 'dialog.album.mark-as-played', handler: this.mark_played }, { key: 'dialog.album.mark-as-played', handler: this.mark_played },
{ { key: 'dialog.album.remove-podcast', handler: this.remove_podcast }
label: 'dialog.album.remove-podcast',
handler: this.remove_podcast
}
] ]
} }
return [ return [
{ label: 'dialog.album.mark-as-played', handler: this.mark_played } { key: 'dialog.album.mark-as-played', handler: this.mark_played }
] ]
} }
return [] return []
@ -49,26 +46,26 @@ export default {
uri: this.item.uri, uri: this.item.uri,
properties: [ properties: [
{ {
label: 'property.artist', key: 'property.artist',
value: this.item.artist, value: this.item.artist,
handler: this.open_artist handler: this.open_artist
}, },
{ {
label: 'property.release-date', key: 'property.release-date',
value: this.$filters.toDate(this.item.date_released) value: this.$filters.toDate(this.item.date_released)
}, },
{ label: 'property.year', value: this.item.year }, { key: 'property.year', value: this.item.year },
{ label: 'property.tracks', value: this.item.track_count }, { key: 'property.tracks', value: this.item.track_count },
{ {
label: 'property.duration', key: 'property.duration',
value: this.$filters.toTimecode(this.item.length_ms) value: this.$filters.toTimecode(this.item.length_ms)
}, },
{ {
label: 'property.type', key: 'property.type',
value: `${this.$t(`media.kind.${this.item.media_kind}`)} - ${this.$t(`data.kind.${this.item.data_kind}`)}` value: `${this.$t(`media.kind.${this.item.media_kind}`)} - ${this.$t(`data.kind.${this.item.data_kind}`)}`
}, },
{ {
label: 'property.added-on', key: 'property.added-on',
value: this.$filters.toDateTime(this.item.time_added) value: this.$filters.toDateTime(this.item.time_added)
} }
] ]

View File

@ -23,18 +23,15 @@ export default {
uri: this.item.uri, uri: this.item.uri,
properties: [ properties: [
{ {
label: 'property.artist', key: 'property.artist',
value: this.item?.artists?.[0]?.name, value: this.item?.artists?.[0]?.name,
handler: this.open_artist handler: this.open_artist
}, },
{ {
label: 'property.release-date', key: 'property.release-date',
value: this.$filters.toDate(this.item.release_date) value: this.$filters.toDate(this.item.release_date)
}, },
{ { key: 'property.type', value: this.item.album_type }
label: 'property.type',
value: this.item.album_type
}
] ]
} }
} }

View File

@ -21,14 +21,14 @@ export default {
handler: this.open, handler: this.open,
uri: this.item.uri, uri: this.item.uri,
properties: [ properties: [
{ label: 'property.albums', value: this.item.album_count }, { key: 'property.albums', value: this.item.album_count },
{ label: 'property.tracks', value: this.item.track_count }, { key: 'property.tracks', value: this.item.track_count },
{ {
label: 'property.type', key: 'property.type',
value: this.$t(`data.kind.${this.item.data_kind}`) value: this.$t(`data.kind.${this.item.data_kind}`)
}, },
{ {
label: 'property.added-on', key: 'property.added-on',
value: this.$filters.toDateTime(this.item.time_added) value: this.$filters.toDateTime(this.item.time_added)
} }
] ]

View File

@ -22,15 +22,12 @@ export default {
uri: this.item.uri, uri: this.item.uri,
properties: [ properties: [
{ {
label: 'property.popularity', key: 'property.popularity',
value: [this.item.popularity, this.item.followers?.total].join( value: [this.item.popularity, this.item.followers?.total].join(
' / ' ' / '
) )
}, },
{ { key: 'property.genres', value: this.item.genres?.join(', ') }
label: 'property.genres',
value: this.item.genres?.join(', ')
}
] ]
} }
} }

View File

@ -22,17 +22,17 @@ export default {
expression: `composer is "${this.item.name}" and media_kind is music`, expression: `composer is "${this.item.name}" and media_kind is music`,
properties: [ properties: [
{ {
label: 'property.albums', key: 'property.albums',
value: this.item.album_count, value: this.item.album_count,
handler: this.open_albums handler: this.open_albums
}, },
{ {
label: 'property.tracks', key: 'property.tracks',
value: this.item.track_count, value: this.item.track_count,
handler: this.open_tracks handler: this.open_tracks
}, },
{ {
label: 'property.duration', key: 'property.duration',
value: this.$filters.toTimecode(this.item.length_ms) value: this.$filters.toTimecode(this.item.length_ms)
} }
] ]

View File

@ -25,16 +25,10 @@ export default {
handler: this.open, handler: this.open,
expression: `genre is "${this.item.name}" and media_kind is ${this.media_kind}`, expression: `genre is "${this.item.name}" and media_kind is ${this.media_kind}`,
properties: [ properties: [
{ key: 'property.albums', value: this.item.album_count },
{ key: 'property.tracks', value: this.item.track_count },
{ {
label: 'property.albums', key: 'property.duration',
value: this.item.album_count
},
{
label: 'property.tracks',
value: this.item.track_count
},
{
label: 'property.duration',
value: this.$filters.toTimecode(this.item.length_ms) value: this.$filters.toTimecode(this.item.length_ms)
} }
] ]

View File

@ -6,9 +6,8 @@
<div class="buttons"> <div class="buttons">
<control-button <control-button
v-for="button in buttons" v-for="button in buttons"
:key="button.label" :key="button.key"
:handler="button.handler" :button="button"
label="button.label"
/> />
</div> </div>
</template> </template>
@ -36,20 +35,16 @@ export default {
actions() { actions() {
return [ return [
{ {
label: this.$t('dialog.playable.add'), key: 'dialog.playable.add',
handler: this.queue_add, handler: this.queue_add,
icon: 'playlist-plus' icon: 'playlist-plus'
}, },
{ {
label: this.$t('dialog.playable.add-next'), key: 'dialog.playable.add-next',
handler: this.queue_add_next, handler: this.queue_add_next,
icon: 'playlist-play' icon: 'playlist-play'
}, },
{ { key: 'dialog.playable.play', handler: this.play, icon: 'play' }
label: this.$t('dialog.playable.play'),
handler: this.play,
icon: 'play'
}
] ]
} }
}, },

View File

@ -26,12 +26,12 @@ export default {
uri: this.item.uri, uri: this.item.uri,
uris: this.uris, uris: this.uris,
properties: [ properties: [
{ label: 'property.tracks', value: this.item.item_count }, { key: 'property.tracks', value: this.item.item_count },
{ {
label: 'property.type', key: 'property.type',
value: this.$t(`playlist.type.${this.item.type}`) value: this.$t(`playlist.type.${this.item.type}`)
}, },
{ label: 'property.path', value: this.item.path } { key: 'property.path', value: this.item.path }
] ]
} }
} }

View File

@ -47,16 +47,16 @@ export default {
computed: { computed: {
actions() { actions() {
if (this.loading) { if (this.loading) {
return [{ label: this.$t('dialog.playlist.save.saving'), icon: 'web' }] return [{ key: 'dialog.playlist.save.saving', icon: 'web' }]
} }
return [ return [
{ {
label: this.$t('dialog.playlist.save.cancel'), key: 'dialog.playlist.save.cancel',
handler: this.cancel, handler: this.cancel,
icon: 'cancel' icon: 'cancel'
}, },
{ {
label: this.$t('dialog.playlist.save.save'), key: 'dialog.playlist.save.save',
disabled: this.disabled, disabled: this.disabled,
handler: this.save, handler: this.save,
icon: 'download' icon: 'download'

View File

@ -21,15 +21,9 @@ export default {
handler: this.open, handler: this.open,
uri: this.item.uri, uri: this.item.uri,
properties: [ properties: [
{ { key: 'property.owner', value: this.item.owner?.display_name },
label: 'property.owner', { key: 'property.tracks', value: this.item.tracks?.total },
value: this.item.owner?.display_name { key: 'property.path', value: this.item.uri }
},
{
label: 'property.tracks',
value: this.item.tracks?.total
},
{ label: 'property.path', value: this.item.uri }
] ]
} }
} }

View File

@ -30,12 +30,12 @@ export default {
actions() { actions() {
return [ return [
{ {
label: this.$t('dialog.queue-item.remove'), key: this.$t('dialog.queue-item.remove'),
handler: this.remove, handler: this.remove,
icon: 'delete' icon: 'delete'
}, },
{ {
label: this.$t('dialog.queue-item.play'), key: this.$t('dialog.queue-item.play'),
handler: this.play, handler: this.play,
icon: 'play' icon: 'play'
} }
@ -47,37 +47,37 @@ export default {
uri: this.item.uri, uri: this.item.uri,
properties: [ properties: [
{ {
label: 'property.album', key: 'property.album',
value: this.item.album, value: this.item.album,
handler: this.open_album handler: this.open_album
}, },
{ {
label: 'property.album-artist', key: 'property.album-artist',
value: this.item.album_artist, value: this.item.album_artist,
handler: this.open_album_artist handler: this.open_album_artist
}, },
{ label: 'property.composer', value: this.item.composer }, { key: 'property.composer', value: this.item.composer },
{ label: 'property.year', value: this.item.year }, { key: 'property.year', value: this.item.year },
{ {
label: 'property.genre', key: 'property.genre',
value: this.item.genre, value: this.item.genre,
handler: this.open_genre handler: this.open_genre
}, },
{ {
label: 'property.position', key: 'property.position',
value: [this.item.disc_number, this.item.track_number].join(' / ') value: [this.item.disc_number, this.item.track_number].join(' / ')
}, },
{ {
label: 'property.duration', key: 'property.duration',
value: this.$filters.toTimecode(this.item.length_ms) value: this.$filters.toTimecode(this.item.length_ms)
}, },
{ label: 'property.path', value: this.item.path }, { key: 'property.path', value: this.item.path },
{ {
label: 'property.type', key: 'property.type',
value: `${this.$t(`media.kind.${this.item.media_kind}`)} - ${this.$t(`data.kind.${this.item.data_kind}`)}` value: `${this.$t(`media.kind.${this.item.media_kind}`)} - ${this.$t(`data.kind.${this.item.data_kind}`)}`
}, },
{ {
label: 'property.quality', key: 'property.quality',
value: this.$t('dialog.track.quality-value', { value: this.$t('dialog.track.quality-value', {
format: this.item.type, format: this.item.type,
bitrate: this.item.bitrate, bitrate: this.item.bitrate,

View File

@ -47,12 +47,12 @@ export default {
actions() { actions() {
return [ return [
{ {
label: this.$t('dialog.remote-pairing.cancel'), key: 'dialog.remote-pairing.cancel',
handler: this.cancel, handler: this.cancel,
icon: 'cancel' icon: 'cancel'
}, },
{ {
label: this.$t('dialog.remote-pairing.pair'), key: 'dialog.remote-pairing.pair',
handler: this.pair, handler: this.pair,
icon: 'cellphone' icon: 'cellphone'
} }

View File

@ -22,8 +22,8 @@ export default {
return [] return []
} }
return this.item.play_count > 0 return this.item.play_count > 0
? [{ label: 'dialog.track.mark-as-new', handler: this.mark_new }] ? [{ key: 'dialog.track.mark-as-new', handler: this.mark_new }]
: [{ label: 'dialog.track.mark-as-played', handler: this.mark_played }] : [{ key: 'dialog.track.mark-as-played', handler: this.mark_played }]
}, },
playable() { playable() {
return { return {
@ -31,36 +31,36 @@ export default {
uri: this.item.uri, uri: this.item.uri,
properties: [ properties: [
{ {
label: 'property.album', key: 'property.album',
value: this.item.album, value: this.item.album,
handler: this.open_album handler: this.open_album
}, },
{ {
label: 'property.album-artist', key: 'property.album-artist',
value: this.item.album_artist, value: this.item.album_artist,
handler: this.open_artist handler: this.open_artist
}, },
{ label: 'property.composer', value: this.item.composer }, { key: 'property.composer', value: this.item.composer },
{ {
label: 'property.release-date', key: 'property.release-date',
value: this.$filters.toDate(this.item.date_released) value: this.$filters.toDate(this.item.date_released)
}, },
{ label: 'property.year', value: this.item.year }, { key: 'property.year', value: this.item.year },
{ label: 'property.genre', value: this.item.genre }, { key: 'property.genre', value: this.item.genre },
{ {
label: 'property.position', key: 'property.position',
value: [this.item.disc_number, this.item.track_number].join(' / ') value: [this.item.disc_number, this.item.track_number].join(' / ')
}, },
{ {
label: 'property.duration', key: 'property.duration',
value: this.$filters.toTimecode(this.item.length_ms) value: this.$filters.toTimecode(this.item.length_ms)
}, },
{ {
label: 'property.type', key: 'property.type',
value: `${this.$t(`media.kind.${this.item.media_kind}`)} - ${this.$t(`data.kind.${this.item.data_kind}`)}` value: `${this.$t(`media.kind.${this.item.media_kind}`)} - ${this.$t(`data.kind.${this.item.data_kind}`)}`
}, },
{ {
label: 'property.quality', key: 'property.quality',
value: value:
this.item.data_kind !== 'spotify' && this.item.data_kind !== 'spotify' &&
this.$t('dialog.track.quality-value', { this.$t('dialog.track.quality-value', {
@ -71,17 +71,17 @@ export default {
}) })
}, },
{ {
label: 'property.added-on', key: 'property.added-on',
value: this.$filters.toDateTime(this.item.time_added) value: this.$filters.toDateTime(this.item.time_added)
}, },
{ {
label: 'property.rating', key: 'property.rating',
value: this.$t('dialog.track.rating-value', { value: this.$t('dialog.track.rating-value', {
rating: Math.floor(this.item.rating / 10) rating: Math.floor(this.item.rating / 10)
}) })
}, },
{ label: 'property.comment', value: this.item.comment }, { key: 'property.comment', value: this.item.comment },
{ label: 'property.path', value: this.item.path } { key: 'property.path', value: this.item.path }
] ]
} }
} }

View File

@ -24,28 +24,28 @@ export default {
uri: this.item.uri, uri: this.item.uri,
properties: [ properties: [
{ {
label: 'property.album', key: 'property.album',
value: this.item.album.name, value: this.item.album.name,
handler: this.open_album handler: this.open_album
}, },
{ {
label: 'property.album-artist', key: 'property.album-artist',
value: this.item.artists[0]?.name, value: this.item.artists[0]?.name,
handler: this.open_artist handler: this.open_artist
}, },
{ {
label: 'property.release-date', key: 'property.release-date',
value: this.$filters.toDate(this.item.album.release_date) value: this.$filters.toDate(this.item.album.release_date)
}, },
{ {
label: 'property.position', key: 'property.position',
value: [this.item.disc_number, this.item.track_number].join(' / ') value: [this.item.disc_number, this.item.track_number].join(' / ')
}, },
{ {
label: 'property.duration', key: 'property.duration',
value: this.$filters.toTimecode(this.item.duration_ms) value: this.$filters.toTimecode(this.item.duration_ms)
}, },
{ label: 'property.path', value: this.item.uri } { key: 'property.path', value: this.item.uri }
] ]
} }
} }

View File

@ -68,14 +68,14 @@ export default {
actions() { actions() {
const actions = [ const actions = [
{ {
label: this.$t('dialog.update.cancel'), key: 'dialog.update.cancel',
handler: this.cancel, handler: this.cancel,
icon: 'cancel' icon: 'cancel'
} }
] ]
if (!this.libraryStore.updating) { if (!this.libraryStore.updating) {
actions.push({ actions.push({
label: this.$t('dialog.update.rescan'), key: 'dialog.update.rescan',
handler: this.analyse, handler: this.analyse,
icon: 'check' icon: 'check'
}) })

View File

@ -33,7 +33,7 @@
v-else-if="menu.action" v-else-if="menu.action"
class="dropdown-item" class="dropdown-item"
@click.stop.prevent="menu.action" @click.stop.prevent="menu.action"
v-text="$t(menu.label)" v-text="$t(menu.key)"
/> />
<control-link <control-link
v-else v-else
@ -48,7 +48,7 @@
'pl-5': menu.sub, 'pl-5': menu.sub,
'has-text-weight-semibold': menu.icon 'has-text-weight-semibold': menu.icon
}" }"
v-text="$t(menu.label)" v-text="$t(menu.key)"
/> />
</control-link> </control-link>
</template> </template>
@ -82,82 +82,82 @@ export default {
return [ return [
{ {
name: 'playlists', name: 'playlists',
label: 'navigation.playlists', key: 'navigation.playlists',
icon: 'music-box-multiple', icon: 'music-box-multiple',
show: this.settingsStore.show_menu_item_playlists show: this.settingsStore.show_menu_item_playlists
}, },
{ {
name: 'music', name: 'music',
label: 'navigation.music', key: 'navigation.music',
icon: 'music', icon: 'music',
show: this.settingsStore.show_menu_item_music show: this.settingsStore.show_menu_item_music
}, },
{ {
name: 'music-artists', name: 'music-artists',
label: 'navigation.artists', key: 'navigation.artists',
show: true, show: true,
sub: true sub: true
}, },
{ {
name: 'music-albums', name: 'music-albums',
label: 'navigation.albums', key: 'navigation.albums',
show: true, show: true,
sub: true sub: true
}, },
{ {
name: 'music-genres', name: 'music-genres',
label: 'navigation.genres', key: 'navigation.genres',
show: true, show: true,
sub: true sub: true
}, },
{ {
name: 'music-spotify', name: 'music-spotify',
label: 'navigation.spotify', key: 'navigation.spotify',
show: this.servicesStore.spotify.webapi_token_valid, show: this.servicesStore.spotify.webapi_token_valid,
sub: true sub: true
}, },
{ {
name: 'podcasts', name: 'podcasts',
label: 'navigation.podcasts', key: 'navigation.podcasts',
icon: 'microphone', icon: 'microphone',
show: this.settingsStore.show_menu_item_podcasts show: this.settingsStore.show_menu_item_podcasts
}, },
{ {
name: 'audiobooks', name: 'audiobooks',
label: 'navigation.audiobooks', key: 'navigation.audiobooks',
icon: 'book-open-variant', icon: 'book-open-variant',
show: this.settingsStore.show_menu_item_audiobooks show: this.settingsStore.show_menu_item_audiobooks
}, },
{ {
name: 'radio', name: 'radio',
label: 'navigation.radio', key: 'navigation.radio',
icon: 'radio', icon: 'radio',
show: this.settingsStore.show_menu_item_radio show: this.settingsStore.show_menu_item_radio
}, },
{ {
name: 'files', name: 'files',
label: 'navigation.files', key: 'navigation.files',
icon: 'folder-open', icon: 'folder-open',
show: this.settingsStore.show_menu_item_files show: this.settingsStore.show_menu_item_files
}, },
{ {
name: this.searchStore.search_source, name: this.searchStore.search_source,
label: 'navigation.search', key: 'navigation.search',
icon: 'magnify', icon: 'magnify',
show: this.settingsStore.show_menu_item_search show: this.settingsStore.show_menu_item_search
}, },
{ separator: true, show: true }, { separator: true, show: true },
{ {
name: 'settings-webinterface', name: 'settings-webinterface',
label: 'navigation.settings', key: 'navigation.settings',
show: true show: true
}, },
{ {
label: 'navigation.update-library', key: 'navigation.update-library',
action: this.open_update_dialog, action: this.open_update_dialog,
show: true show: true
}, },
{ name: 'about', label: 'navigation.about', show: true } { name: 'about', key: 'navigation.about', show: true }
] ]
}, },
zindex() { zindex() {

View File

@ -14,17 +14,17 @@ export default {
{ {
to: { name: 'audiobooks-artists' }, to: { name: 'audiobooks-artists' },
icon: 'account-music', icon: 'account-music',
label: 'page.audiobooks.tabs.authors' key: 'page.audiobooks.tabs.authors'
}, },
{ {
to: { name: 'audiobooks-albums' }, to: { name: 'audiobooks-albums' },
icon: 'album', icon: 'album',
label: 'page.audiobooks.tabs.audiobooks' key: 'page.audiobooks.tabs.audiobooks'
}, },
{ {
to: { name: 'audiobooks-genres' }, to: { name: 'audiobooks-genres' },
icon: 'speaker', icon: 'speaker',
label: 'page.audiobooks.tabs.genres' key: 'page.audiobooks.tabs.genres'
} }
] ]
} }

View File

@ -18,34 +18,34 @@ export default {
{ {
to: { name: 'music-history' }, to: { name: 'music-history' },
icon: 'history', icon: 'history',
label: 'page.music.tabs.history' key: 'page.music.tabs.history'
}, },
{ {
to: { name: 'music-artists' }, to: { name: 'music-artists' },
icon: 'account-music', icon: 'account-music',
label: 'page.music.tabs.artists' key: 'page.music.tabs.artists'
}, },
{ {
to: { name: 'music-albums' }, to: { name: 'music-albums' },
icon: 'album', icon: 'album',
label: 'page.music.tabs.albums' key: 'page.music.tabs.albums'
}, },
{ {
to: { name: 'music-genres' }, to: { name: 'music-genres' },
icon: 'speaker', icon: 'speaker',
label: 'page.music.tabs.genres' key: 'page.music.tabs.genres'
}, },
{ {
to: { name: 'music-composers' }, to: { name: 'music-composers' },
icon: 'book-open-page-variant', icon: 'book-open-page-variant',
label: 'page.music.tabs.composers' key: 'page.music.tabs.composers'
} }
] ]
if (this.servicesStore.spotify.webapi_token_valid) { if (this.servicesStore.spotify.webapi_token_valid) {
links.push({ links.push({
to: { name: 'music-spotify' }, to: { name: 'music-spotify' },
icon: 'spotify', icon: 'spotify',
label: 'page.music.tabs.spotify' key: 'page.music.tabs.spotify'
}) })
} }
return links return links

View File

@ -12,20 +12,20 @@ export default {
links() { links() {
return [ return [
{ {
to: { name: 'settings-webinterface' }, key: 'page.settings.tabs.general',
label: 'page.settings.tabs.general' to: { name: 'settings-webinterface' }
}, },
{ {
to: { name: 'settings-remotes-outputs' }, key: 'page.settings.tabs.remotes-and-outputs',
label: 'page.settings.tabs.remotes-and-outputs' to: { name: 'settings-remotes-outputs' }
}, },
{ {
to: { name: 'settings-artwork' }, key: 'page.settings.tabs.artwork',
label: 'page.settings.tabs.artwork' to: { name: 'settings-artwork' }
}, },
{ {
to: { name: 'settings-online-services' }, key: 'page.settings.tabs.online-services',
label: 'page.settings.tabs.online-services' to: { name: 'settings-online-services' }
} }
] ]
} }

View File

@ -1,33 +1,29 @@
<template> <template>
<section class="section"> <div>
<div class="container"> <content-with-heading>
<div class="columns is-centered"> <template #heading-left>
<div class="column is-four-fifths"> <heading-title :content="{ title: $t('page.about.library') }" />
<div class="content"> </template>
<nav class="level"> <template #heading-right>
<div class="level-left">
<div class="level-item">
<p class="title is-4" v-text="$t('page.about.library')" />
</div>
</div>
<div class="level-right">
<control-button <control-button
:class="{ 'is-loading': libraryStore.updating }" :class="{ 'is-loading': libraryStore.updating }"
:disabled="libraryStore.updating" :button="{
:handler="showUpdateDialog" disabled: libraryStore.updating,
icon="refresh" handler: showUpdateDialog,
label="page.about.update" icon: 'refresh',
key: 'page.about.update'
}"
/> />
</div> </template>
</nav> <template #content>
<div <div
v-for="property in properties" v-for="property in properties"
:key="property.label" :key="property.key"
class="media is-align-items-center mb-0" class="media is-align-items-center mb-0"
> >
<div <div
class="media-content has-text-weight-bold" class="media-content has-text-weight-bold"
v-text="$t(property.label)" v-text="$t(property.key)"
/> />
<div class="media-right"> <div class="media-right">
<span v-text="property.value" /> <span v-text="property.value" />
@ -38,25 +34,17 @@
/> />
</div> </div>
</div> </div>
</div> </template>
</div> <template #footer>
</div> <div
</div> class="is-size-7 mt-6"
</section>
<section class="section">
<div class="container">
<div class="columns is-centered">
<div class="column is-four-fifths">
<div class="content has-text-centered-mobile">
<p
class="is-size-7"
v-text=" v-text="
$t('page.about.version', { $t('page.about.version', {
version: configurationStore.version version: configurationStore.version
}) })
" "
/> />
<p <div
class="is-size-7" class="is-size-7"
v-text=" v-text="
$t('page.about.compiled-with', { $t('page.about.compiled-with', {
@ -65,7 +53,7 @@
" "
/> />
<i18n-t <i18n-t
tag="p" tag="div"
class="is-size-7" class="is-size-7"
keypath="page.about.built-with" keypath="page.about.built-with"
scope="global" scope="global"
@ -91,22 +79,22 @@
/> />
</template> </template>
</i18n-t> </i18n-t>
</template>
</content-with-heading>
</div> </div>
</div>
</div>
</div>
</section>
</template> </template>
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import { useConfigurationStore } from '@/stores/configuration' import { useConfigurationStore } from '@/stores/configuration'
import { useLibraryStore } from '@/stores/library' import { useLibraryStore } from '@/stores/library'
import { useUIStore } from '@/stores/ui' import { useUIStore } from '@/stores/ui'
export default { export default {
name: 'PageAbout', name: 'PageAbout',
components: { ControlButton }, components: { ContentWithHeading, ControlButton, HeadingTitle },
setup() { setup() {
return { return {
configurationStore: useConfigurationStore(), configurationStore: useConfigurationStore(),
@ -118,32 +106,32 @@ export default {
properties() { properties() {
return [ return [
{ {
label: 'property.name', key: 'property.name',
value: this.configurationStore.library_name value: this.configurationStore.library_name
}, },
{ {
label: 'property.artists', key: 'property.artists',
value: this.$n(this.libraryStore.artists) value: this.$n(this.libraryStore.artists)
}, },
{ {
label: 'property.albums', key: 'property.albums',
value: this.$n(this.libraryStore.albums) value: this.$n(this.libraryStore.albums)
}, },
{ {
label: 'property.tracks', key: 'property.tracks',
value: this.$n(this.libraryStore.songs) value: this.$n(this.libraryStore.songs)
}, },
{ {
label: 'property.playtime', key: 'property.playtime',
value: this.$filters.toDuration(this.libraryStore.db_playtime) value: this.$filters.toDuration(this.libraryStore.db_playtime)
}, },
{ {
label: 'property.updated', key: 'property.updated',
value: this.$filters.toRelativeDuration(this.libraryStore.updated_at), value: this.$filters.toRelativeDuration(this.libraryStore.updated_at),
alternate: this.$filters.toDateTime(this.libraryStore.updated_at) alternate: this.$filters.toDateTime(this.libraryStore.updated_at)
}, },
{ {
label: 'property.uptime', key: 'property.uptime',
value: this.$filters.toDurationToNow(this.libraryStore.started_at), value: this.$filters.toDurationToNow(this.libraryStore.started_at),
alternate: this.$filters.toDateTime(this.libraryStore.started_at) alternate: this.$filters.toDateTime(this.libraryStore.started_at)
} }

View File

@ -12,11 +12,18 @@
/> />
<div class="buttons is-centered-mobile mt-5"> <div class="buttons is-centered-mobile mt-5">
<control-button <control-button
:handler="play" :button="{
icon="shuffle" handler: play,
label="page.album.shuffle" icon: 'shuffle',
key: 'page.album.shuffle'
}"
/>
<control-button
:button="{
handler: showDetails,
icon: 'dots-horizontal'
}"
/> />
<control-button :handler="showDetails" icon="dots-horizontal" />
</div> </div>
</template> </template>
<template #heading-right> <template #heading-right>

View File

@ -12,11 +12,15 @@
/> />
<div class="buttons is-centered-mobile mt-5"> <div class="buttons is-centered-mobile mt-5">
<control-button <control-button
:handler="play" :button="{
icon="shuffle" handler: play,
label="page.spotify.album.shuffle" icon: 'shuffle',
key: 'page.spotify.album.shuffle'
}"
/>
<control-button
:button="{ handler: showDetails, icon: 'dots-horizontal' }"
/> />
<control-button :handler="showDetails" icon="dots-horizontal" />
</div> </div>
</template> </template>
<template #heading-right> <template #heading-right>

View File

@ -43,11 +43,7 @@
</div> </div>
</template> </template>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="$t('page.albums.title')" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.albums', { count: albums.count })"
/>
</template> </template>
<template #content> <template #content>
<list-albums :items="albums" /> <list-albums :items="albums" />
@ -61,6 +57,7 @@ import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import ControlSwitch from '@/components/ControlSwitch.vue' import ControlSwitch from '@/components/ControlSwitch.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
@ -72,7 +69,6 @@ const dataObject = {
load(to) { load(to) {
return webapi.library_albums('music') return webapi.library_albums('music')
}, },
set(vm, response) { set(vm, response) {
vm.albums_list = new GroupedList(response.data) vm.albums_list = new GroupedList(response.data)
} }
@ -84,21 +80,19 @@ export default {
ContentWithHeading, ContentWithHeading,
ControlDropdown, ControlDropdown,
ControlSwitch, ControlSwitch,
HeadingTitle,
IndexButtonList, IndexButtonList,
ListAlbums, ListAlbums,
TabsMusic TabsMusic
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
setup() { setup() {
return { uiStore: useUIStore(), servicesStore: useServicesStore() } return { uiStore: useUIStore(), servicesStore: useServicesStore() }
}, },
data() { data() {
return { return {
albums_list: new GroupedList(), albums_list: new GroupedList(),
@ -149,7 +143,6 @@ export default {
] ]
} }
}, },
computed: { computed: {
albums() { albums() {
const { options } = this.groupings.find( const { options } = this.groupings.find(
@ -161,6 +154,12 @@ export default {
] ]
return this.albums_list.group(options) return this.albums_list.group(options)
}, },
heading() {
return {
title: this.$t('page.albums.title'),
subtitle: [{ key: 'count.albums', count: this.albums.count }]
}
},
spotify_enabled() { spotify_enabled() {
return this.servicesStore.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
} }

View File

@ -33,25 +33,19 @@
</div> </div>
</template> </template>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="artist.name" /> <heading-title :content="heading" />
<div class="is-size-7 is-uppercase">
<span v-text="$t('count.albums', { count: albums.count })" />
<span>&nbsp;|&nbsp;</span>
<a
@click="open_tracks"
v-text="$t('count.tracks', { count: track_count })"
/>
</div>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button <control-button
:handler="play" :button="{ handler: showDetails, icon: 'dots-horizontal' }"
icon="shuffle" />
label="page.artist.shuffle" <control-button
:button="{
handler: play,
icon: 'shuffle',
key: 'page.artist.shuffle'
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-albums :items="albums" /> <list-albums :items="albums" />
@ -71,6 +65,7 @@ import ControlButton from '@/components/ControlButton.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import ControlSwitch from '@/components/ControlSwitch.vue' import ControlSwitch from '@/components/ControlSwitch.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '../components/HeadingTitle.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogArtist from '@/components/ModalDialogArtist.vue' import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
import { useServicesStore } from '@/stores/services' import { useServicesStore } from '@/stores/services'
@ -84,7 +79,6 @@ const dataObject = {
webapi.library_artist_albums(to.params.id) webapi.library_artist_albums(to.params.id)
]) ])
}, },
set(vm, response) { set(vm, response) {
vm.artist = response[0].data vm.artist = response[0].data
vm.albums_list = new GroupedList(response[1].data) vm.albums_list = new GroupedList(response[1].data)
@ -98,20 +92,18 @@ export default {
ControlButton, ControlButton,
ControlDropdown, ControlDropdown,
ControlSwitch, ControlSwitch,
HeadingTitle,
ListAlbums, ListAlbums,
ModalDialogArtist ModalDialogArtist
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
setup() { setup() {
return { servicesStore: useServicesStore(), uiStore: useUIStore() } return { servicesStore: useServicesStore(), uiStore: useUIStore() }
}, },
data() { data() {
return { return {
albums_list: new GroupedList(), albums_list: new GroupedList(),
@ -131,7 +123,6 @@ export default {
show_details_modal: false show_details_modal: false
} }
}, },
computed: { computed: {
albums() { albums() {
const { options } = this.groupings.find( const { options } = this.groupings.find(
@ -142,6 +133,19 @@ export default {
] ]
return this.albums_list.group(options) return this.albums_list.group(options)
}, },
heading() {
return {
title: this.artist.name,
subtitle: [
{ key: 'count.albums', count: this.albums.count },
{
handler: this.open_tracks,
key: 'count.tracks',
count: this.track_count
}
]
}
},
spotify_enabled() { spotify_enabled() {
return this.servicesStore.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
}, },
@ -153,7 +157,6 @@ export default {
) )
} }
}, },
methods: { methods: {
open_tracks() { open_tracks() {
this.$router.push({ this.$router.push({

View File

@ -2,21 +2,19 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="artist.name" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.albums', { count: total })"
/>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button <control-button
:handler="play" :button="{ handler: showDetails, icon: 'dots-horizontal' }"
icon="shuffle" />
label="page.spotify.artist.shuffle" <control-button
:button="{
handler: play,
icon: 'shuffle',
key: 'page.spotify.artist.shuffle'
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-albums-spotify :items="albums" /> <list-albums-spotify :items="albums" />
@ -48,6 +46,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbumsSpotify from '@/components/ListAlbumsSpotify.vue' import ListAlbumsSpotify from '@/components/ListAlbumsSpotify.vue'
import ModalDialogArtistSpotify from '@/components/ModalDialogArtistSpotify.vue' import ModalDialogArtistSpotify from '@/components/ModalDialogArtistSpotify.vue'
import SpotifyWebApi from 'spotify-web-api-js' import SpotifyWebApi from 'spotify-web-api-js'
@ -71,7 +70,6 @@ const dataObject = {
}) })
]) ])
}, },
set(vm, response) { set(vm, response) {
vm.artist = response.shift() vm.artist = response.shift()
vm.albums = [] vm.albums = []
@ -86,21 +84,19 @@ export default {
components: { components: {
ContentWithHeading, ContentWithHeading,
ControlButton, ControlButton,
HeadingTitle,
ListAlbumsSpotify, ListAlbumsSpotify,
ModalDialogArtistSpotify, ModalDialogArtistSpotify,
VueEternalLoading VueEternalLoading
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
setup() { setup() {
return { servicesStore: useServicesStore() } return { servicesStore: useServicesStore() }
}, },
data() { data() {
return { return {
albums: [], albums: [],
@ -110,7 +106,14 @@ export default {
total: 0 total: 0
} }
}, },
computed: {
heading() {
return {
title: this.$t('artist.name'),
subtitle: [{ key: 'count.albums', count: this.total }]
}
}
},
methods: { methods: {
append_albums(data) { append_albums(data) {
this.albums = this.albums.concat(data.items) this.albums = this.albums.concat(data.items)

View File

@ -34,25 +34,19 @@
</div> </div>
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="artist.name" /> <heading-title :content="heading" />
<div class="is-size-7 is-uppercase">
<a
@click="open_artist"
v-text="$t('count.albums', { count: album_count })"
/>
<span>&nbsp;|&nbsp;</span>
<span v-text="$t('count.tracks', { count: tracks.count })" />
</div>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button <control-button
:handler="play" :button="{ handler: showDetails, icon: 'dots-horizontal' }"
icon="shuffle" />
label="page.artist.shuffle" <control-button
:button="{
handler: play,
icon: 'shuffle',
key: 'page.artist.shuffle'
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-tracks :items="tracks" :uris="track_uris" /> <list-tracks :items="tracks" :uris="track_uris" />
@ -72,6 +66,7 @@ import ControlButton from '@/components/ControlButton.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import ControlSwitch from '@/components/ControlSwitch.vue' import ControlSwitch from '@/components/ControlSwitch.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import ModalDialogArtist from '@/components/ModalDialogArtist.vue' import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
@ -86,7 +81,6 @@ const dataObject = {
webapi.library_artist_tracks(to.params.id) webapi.library_artist_tracks(to.params.id)
]) ])
}, },
set(vm, response) { set(vm, response) {
vm.artist = response[0].data vm.artist = response[0].data
vm.tracks_list = new GroupedList(response[1].data.tracks) vm.tracks_list = new GroupedList(response[1].data.tracks)
@ -100,21 +94,19 @@ export default {
ControlButton, ControlButton,
ControlDropdown, ControlDropdown,
ControlSwitch, ControlSwitch,
HeadingTitle,
IndexButtonList, IndexButtonList,
ListTracks, ListTracks,
ModalDialogArtist ModalDialogArtist
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
setup() { setup() {
return { servicesStore: useServicesStore(), uiStore: useUIStore() } return { servicesStore: useServicesStore(), uiStore: useUIStore() }
}, },
data() { data() {
return { return {
artist: {}, artist: {},
@ -137,7 +129,6 @@ export default {
tracks_list: new GroupedList() tracks_list: new GroupedList()
} }
}, },
computed: { computed: {
album_count() { album_count() {
return new Set( return new Set(
@ -146,6 +137,19 @@ export default {
.map((track) => track.item.album_id) .map((track) => track.item.album_id)
).size ).size
}, },
heading() {
return {
title: this.artist.name,
subtitle: [
{
handler: this.openArtist,
key: 'count.albums',
count: this.album_count
},
{ key: 'count.tracks', count: this.tracks.count }
]
}
},
spotify_enabled() { spotify_enabled() {
return this.servicesStore.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
}, },
@ -162,9 +166,8 @@ export default {
return this.tracks_list.group(options) return this.tracks_list.group(options)
} }
}, },
methods: { methods: {
open_artist() { openArtist() {
this.show_details_modal = false this.show_details_modal = false
this.$router.push({ this.$router.push({
name: 'music-artist', name: 'music-artist',

View File

@ -42,11 +42,7 @@
</div> </div>
</template> </template>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="$t('page.artists.title')" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.artists', { count: artists.count })"
/>
</template> </template>
<template #content> <template #content>
<list-artists :items="artists" /> <list-artists :items="artists" />
@ -60,6 +56,7 @@ import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import ControlSwitch from '@/components/ControlSwitch.vue' import ControlSwitch from '@/components/ControlSwitch.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListArtists from '@/components/ListArtists.vue' import ListArtists from '@/components/ListArtists.vue'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
@ -83,21 +80,19 @@ export default {
ContentWithHeading, ContentWithHeading,
ControlDropdown, ControlDropdown,
ControlSwitch, ControlSwitch,
HeadingTitle,
IndexButtonList, IndexButtonList,
ListArtists, ListArtists,
TabsMusic TabsMusic
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
setup() { setup() {
return { servicesStore: useServicesStore(), uiStore: useUIStore() } return { servicesStore: useServicesStore(), uiStore: useUIStore() }
}, },
data() { data() {
return { return {
artists_list: new GroupedList(), artists_list: new GroupedList(),
@ -118,7 +113,6 @@ export default {
] ]
} }
}, },
computed: { computed: {
artists() { artists() {
const { options } = this.groupings.find( const { options } = this.groupings.find(
@ -132,6 +126,12 @@ export default {
] ]
return this.artists_list.group(options) return this.artists_list.group(options)
}, },
heading() {
return {
title: this.$t('page.artists.title'),
subtitle: [{ key: 'count.artists', count: this.artists.count }]
}
},
spotify_enabled() { spotify_enabled() {
return this.servicesStore.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
} }

View File

@ -4,7 +4,7 @@
<template #heading-left> <template #heading-left>
<div class="title is-5" v-text="album.name" /> <div class="title is-5" v-text="album.name" />
<div class="subtitle is-6"> <div class="subtitle is-6">
<a @click="open_artist" v-text="album.artist" /> <a @click="openArtist" v-text="album.artist" />
</div> </div>
<div <div
class="is-size-7 is-uppercase has-text-centered-mobile" class="is-size-7 is-uppercase has-text-centered-mobile"
@ -12,11 +12,15 @@
/> />
<div class="buttons is-centered-mobile mt-5"> <div class="buttons is-centered-mobile mt-5">
<control-button <control-button
:handler="play" :button="{
icon="play" handler: play,
label="page.audiobooks.album.play" icon: 'play',
key: 'page.audiobooks.album.play'
}"
/>
<control-button
:button="{ handler: showDetails, icon: 'dots-horizontal' }"
/> />
<control-button :handler="showDetails" icon="dots-horizontal" />
</div> </div>
</template> </template>
<template #heading-right> <template #heading-right>
@ -57,7 +61,6 @@ const dataObject = {
webapi.library_album_tracks(to.params.id) webapi.library_album_tracks(to.params.id)
]) ])
}, },
set(vm, response) { set(vm, response) {
vm.album = response[0].data vm.album = response[0].data
vm.tracks = new GroupedList(response[1].data) vm.tracks = new GroupedList(response[1].data)
@ -86,7 +89,7 @@ export default {
} }
}, },
methods: { methods: {
open_artist() { openArtist() {
this.show_details_modal = false this.show_details_modal = false
this.$router.push({ this.$router.push({
name: 'audiobooks-artist', name: 'audiobooks-artist',

View File

@ -6,11 +6,7 @@
<index-button-list :indices="albums.indices" /> <index-button-list :indices="albums.indices" />
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.audiobooks.albums.title')" /> <heading-title :content="heading" />
<p
class="is-size-7 is-uppercase"
v-text="$t('count.audiobooks', { count: albums.count })"
/>
</template> </template>
<template #content> <template #content>
<list-albums :items="albums" /> <list-albums :items="albums" />
@ -22,6 +18,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import TabsAudiobooks from '@/components/TabsAudiobooks.vue' import TabsAudiobooks from '@/components/TabsAudiobooks.vue'
@ -31,7 +28,6 @@ const dataObject = {
load(to) { load(to) {
return webapi.library_albums('audiobook') return webapi.library_albums('audiobook')
}, },
set(vm, response) { set(vm, response) {
vm.albums = new GroupedList(response.data, { vm.albums = new GroupedList(response.data, {
index: { field: 'name_sort', type: String } index: { field: 'name_sort', type: String }
@ -45,19 +41,26 @@ export default {
ContentWithHeading, ContentWithHeading,
IndexButtonList, IndexButtonList,
ListAlbums, ListAlbums,
TabsAudiobooks TabsAudiobooks,
HeadingTitle
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
albums: new GroupedList() albums: new GroupedList()
} }
},
computed: {
heading() {
return {
title: this.$t('page.audiobooks.albums.title'),
subtitle: [{ key: 'count.audiobooks', count: this.albums.count }]
}
}
} }
} }
</script> </script>

View File

@ -2,21 +2,19 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="artist.name" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.audiobooks', { count: artist.album_count })"
/>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button <control-button
:handler="play" :button="{ handler: showDetails, icon: 'dots-horizontal' }"
icon="play" />
label="page.audiobooks.artist.play" <control-button
:button="{
handler: play,
icon: 'play',
key: 'page.audiobooks.artist.play'
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-albums :items="albums" /> <list-albums :items="albums" />
@ -34,6 +32,7 @@
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogArtist from '@/components/ModalDialogArtist.vue' import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
import webapi from '@/webapi' import webapi from '@/webapi'
@ -56,6 +55,7 @@ export default {
components: { components: {
ContentWithHeading, ContentWithHeading,
ControlButton, ControlButton,
HeadingTitle,
ListAlbums, ListAlbums,
ModalDialogArtist ModalDialogArtist
}, },
@ -71,6 +71,19 @@ export default {
show_details_modal: false show_details_modal: false
} }
}, },
computed: {
heading() {
if (this.artist.name) {
return {
title: this.artist.name,
subtitle: [
{ key: 'count.audiobooks', count: this.artist.album_count }
]
}
}
return {}
}
},
methods: { methods: {
play() { play() {
webapi.player_play_uri( webapi.player_play_uri(

View File

@ -6,11 +6,7 @@
<index-button-list :indices="artists.indices" /> <index-button-list :indices="artists.indices" />
</template> </template>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="$t('page.audiobooks.artists.title')" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.authors', { count: artists.count })"
/>
</template> </template>
<template #content> <template #content>
<list-artists :items="artists" /> <list-artists :items="artists" />
@ -22,6 +18,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListArtists from '@/components/ListArtists.vue' import ListArtists from '@/components/ListArtists.vue'
import TabsAudiobooks from '@/components/TabsAudiobooks.vue' import TabsAudiobooks from '@/components/TabsAudiobooks.vue'
@ -31,7 +28,6 @@ const dataObject = {
load(to) { load(to) {
return webapi.library_artists('audiobook') return webapi.library_artists('audiobook')
}, },
set(vm, response) { set(vm, response) {
vm.artists = new GroupedList(response.data, { vm.artists = new GroupedList(response.data, {
index: { field: 'name_sort', type: String } index: { field: 'name_sort', type: String }
@ -43,21 +39,28 @@ export default {
name: 'PageAudiobooksArtists', name: 'PageAudiobooksArtists',
components: { components: {
ContentWithHeading, ContentWithHeading,
HeadingTitle,
IndexButtonList, IndexButtonList,
ListArtists, ListArtists,
TabsAudiobooks TabsAudiobooks
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
artists: new GroupedList() artists: new GroupedList()
} }
},
computed: {
heading() {
return {
title: this.$t('page.audiobooks.artists.title'),
subtitle: [{ key: 'count.authors', count: this.artists.count }]
}
}
} }
} }
</script> </script>

View File

@ -6,11 +6,7 @@
<index-button-list :indices="genres.indices" /> <index-button-list :indices="genres.indices" />
</template> </template>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="$t('page.genres.title')" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.genres', { count: genres.total })"
/>
</template> </template>
<template #content> <template #content>
<list-genres :items="genres" :media_kind="'audiobook'" /> <list-genres :items="genres" :media_kind="'audiobook'" />
@ -22,6 +18,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListGenres from '@/components/ListGenres.vue' import ListGenres from '@/components/ListGenres.vue'
import TabsAudiobooks from '@/components/TabsAudiobooks.vue' import TabsAudiobooks from '@/components/TabsAudiobooks.vue'
@ -31,7 +28,6 @@ const dataObject = {
load(to) { load(to) {
return webapi.library_genres('audiobook') return webapi.library_genres('audiobook')
}, },
set(vm, response) { set(vm, response) {
vm.genres = new GroupedList(response.data.genres, { vm.genres = new GroupedList(response.data.genres, {
index: { field: 'name_sort', type: String } index: { field: 'name_sort', type: String }
@ -43,21 +39,28 @@ export default {
name: 'PageAudiobooksGenres', name: 'PageAudiobooksGenres',
components: { components: {
ContentWithHeading, ContentWithHeading,
HeadingTitle,
IndexButtonList, IndexButtonList,
ListGenres, ListGenres,
TabsAudiobooks TabsAudiobooks
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
genres: new GroupedList() genres: new GroupedList()
} }
},
computed: {
heading() {
return {
title: this.$t('page.genres.title'),
subtitle: [{ key: 'count.genres', count: this.genres.total }]
}
}
} }
} }
</script> </script>

View File

@ -2,25 +2,19 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="composer.name" /> <heading-title :content="heading" />
<div class="is-size-7 is-uppercase">
<span v-text="$t('count.albums', { count: composer.album_count })" />
<span>&nbsp;|&nbsp;</span>
<a
@click="open_tracks"
v-text="$t('count.tracks', { count: composer.track_count })"
/>
</div>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button <control-button
:handler="play" :button="{ handler: showDetails, icon: 'dots-horizontal' }"
icon="shuffle" />
label="page.composer.shuffle" <control-button
:button="{
handler: play,
icon: 'shuffle',
key: 'page.composer.shuffle'
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-albums :items="albums" /> <list-albums :items="albums" />
@ -38,6 +32,7 @@
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogComposer from '@/components/ModalDialogComposer.vue' import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
import webapi from '@/webapi' import webapi from '@/webapi'
@ -49,7 +44,6 @@ const dataObject = {
webapi.library_composer_albums(to.params.name) webapi.library_composer_albums(to.params.name)
]) ])
}, },
set(vm, response) { set(vm, response) {
vm.composer = response[0].data vm.composer = response[0].data
vm.albums = new GroupedList(response[1].data.albums) vm.albums = new GroupedList(response[1].data.albums)
@ -61,6 +55,7 @@ export default {
components: { components: {
ContentWithHeading, ContentWithHeading,
ControlButton, ControlButton,
HeadingTitle,
ListAlbums, ListAlbums,
ModalDialogComposer ModalDialogComposer
}, },
@ -79,6 +74,19 @@ export default {
computed: { computed: {
expression() { expression() {
return `composer is "${this.composer.name}" and media_kind is music` return `composer is "${this.composer.name}" and media_kind is music`
},
heading() {
return {
title: this.composer.name,
subtitle: [
{ key: 'count.albums', count: this.composer.album_count },
{
handler: this.open_tracks,
key: 'count.tracks',
count: this.composer.track_count
}
]
}
} }
}, },
methods: { methods: {

View File

@ -17,25 +17,19 @@
</div> </div>
</template> </template>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="composer.name" /> <heading-title :content="heading" />
<div class="is-size-7 is-uppercase">
<a
@click="open_albums"
v-text="$t('count.albums', { count: composer.album_count })"
/>
<span>&nbsp;|&nbsp;</span>
<span v-text="$t('count.tracks', { count: composer.track_count })" />
</div>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button <control-button
:handler="play" :button="{ handler: showDetails, icon: 'dots-horizontal' }"
icon="shuffle" />
label="page.composer.shuffle" <control-button
:button="{
handler: play,
icon: 'shuffle',
key: 'page.composer.shuffle'
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-tracks :items="tracks" :expression="expression" /> <list-tracks :items="tracks" :expression="expression" />
@ -54,6 +48,7 @@ import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import ModalDialogComposer from '@/components/ModalDialogComposer.vue' import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
@ -80,6 +75,7 @@ export default {
ContentWithHeading, ContentWithHeading,
ControlButton, ControlButton,
ControlDropdown, ControlDropdown,
HeadingTitle,
IndexButtonList, IndexButtonList,
ListTracks, ListTracks,
ModalDialogComposer ModalDialogComposer
@ -118,6 +114,19 @@ export default {
expression() { expression() {
return `composer is "${this.composer.name}" and media_kind is music` return `composer is "${this.composer.name}" and media_kind is music`
}, },
heading() {
return {
title: this.composer.name,
subtitle: [
{
handler: this.open_albums,
key: 'count.albums',
count: this.composer.album_count
},
{ key: 'count.tracks', count: composer.track_count }
]
}
},
tracks() { tracks() {
const { options } = this.groupings.find( const { options } = this.groupings.find(
(grouping) => grouping.id === this.uiStore.composer_tracks_sort (grouping) => grouping.id === this.uiStore.composer_tracks_sort

View File

@ -6,11 +6,7 @@
<index-button-list :indices="composers.indices" /> <index-button-list :indices="composers.indices" />
</template> </template>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="$t('page.composers.title')" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.composers', { count: composers.total })"
/>
</template> </template>
<template #content> <template #content>
<list-composers :items="composers" /> <list-composers :items="composers" />
@ -22,6 +18,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListComposers from '@/components/ListComposers.vue' import ListComposers from '@/components/ListComposers.vue'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
@ -31,7 +28,6 @@ const dataObject = {
load(to) { load(to) {
return webapi.library_composers('music') return webapi.library_composers('music')
}, },
set(vm, response) { set(vm, response) {
vm.composers = new GroupedList(response.data, { vm.composers = new GroupedList(response.data, {
index: { field: 'name_sort', type: String } index: { field: 'name_sort', type: String }
@ -41,18 +37,30 @@ const dataObject = {
export default { export default {
name: 'PageComposers', name: 'PageComposers',
components: { ContentWithHeading, IndexButtonList, ListComposers, TabsMusic }, components: {
ContentWithHeading,
HeadingTitle,
IndexButtonList,
ListComposers,
TabsMusic
},
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
composers: new GroupedList() composers: new GroupedList()
} }
},
computed: {
heading() {
return {
title: this.$t('page.composers.title'),
subtitle: [{ key: 'count.composers', count: this.composers.total }]
}
}
} }
} }
</script> </script>

View File

@ -2,13 +2,15 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="name" /> <heading-title :content="{ title: name }" />
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <control-button
<control-button :handler="showDetails" icon="dots-horizontal" /> :button="{ handler: showDetails, icon: 'dots-horizontal' }"
<control-button :handler="play" icon="play" label="page.files.play" /> />
</div> <control-button
:button="{ handler: play, icon: 'play', key: 'page.files.play' }"
/>
</template> </template>
<template #content> <template #content>
<list-directories :items="directories" /> <list-directories :items="directories" />
@ -32,6 +34,7 @@
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListDirectories from '@/components/ListDirectories.vue' import ListDirectories from '@/components/ListDirectories.vue'
import ListPlaylists from '@/components/ListPlaylists.vue' import ListPlaylists from '@/components/ListPlaylists.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
@ -72,6 +75,7 @@ export default {
components: { components: {
ContentWithHeading, ContentWithHeading,
ControlButton, ControlButton,
HeadingTitle,
ListDirectories, ListDirectories,
ListPlaylists, ListPlaylists,
ListTracks, ListTracks,
@ -105,14 +109,14 @@ export default {
current() { current() {
return this.$route.query?.directory || '/' return this.$route.query?.directory || '/'
}, },
expression() {
return `path starts with "${this.current}" order by path asc`
},
name() { name() {
if (this.current !== '/') { if (this.current !== '/') {
return this.current?.slice(this.current.lastIndexOf('/') + 1) return this.current?.slice(this.current.lastIndexOf('/') + 1)
} }
return this.$t('page.files.title') return this.$t('page.files.title')
},
expression() {
return `path starts with "${this.current}" order by path asc`
} }
}, },
methods: { methods: {

View File

@ -5,25 +5,19 @@
<index-button-list :indices="albums.indices" /> <index-button-list :indices="albums.indices" />
</template> </template>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="genre.name" /> <heading-title :content="heading" />
<div class="is-size-7 is-uppercase">
<span v-text="$t('count.albums', { count: genre.album_count })" />
<span>&nbsp;|&nbsp;</span>
<a
@click="open_tracks"
v-text="$t('count.tracks', { count: genre.track_count })"
/>
</div>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button <control-button
:handler="play" :button="{ handler: showDetails, icon: 'dots-horizontal' }"
icon="shuffle" />
label="page.genre.shuffle" <control-button
:button="{
handler: play,
icon: 'shuffle',
key: 'page.genre.shuffle'
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-albums :items="albums" /> <list-albums :items="albums" />
@ -42,6 +36,7 @@
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogGenre from '@/components/ModalDialogGenre.vue' import ModalDialogGenre from '@/components/ModalDialogGenre.vue'
@ -67,6 +62,7 @@ export default {
components: { components: {
ContentWithHeading, ContentWithHeading,
ControlButton, ControlButton,
HeadingTitle,
IndexButtonList, IndexButtonList,
ListAlbums, ListAlbums,
ModalDialogGenre ModalDialogGenre
@ -84,6 +80,21 @@ export default {
show_details_modal: false show_details_modal: false
} }
}, },
computed: {
heading() {
return {
title: this.genre.name,
subtitle: [
{ key: 'count.albums', count: this.genre.album_count },
{
handler: this.open_tracks,
key: 'count.tracks',
count: this.genre.track_count
}
]
}
}
},
methods: { methods: {
open_tracks() { open_tracks() {
this.show_details_modal = false this.show_details_modal = false

View File

@ -17,25 +17,19 @@
</div> </div>
</template> </template>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="genre.name" /> <heading-title :content="heading" />
<div class="is-size-7 is-uppercase">
<a
@click="open_genre"
v-text="$t('count.albums', { count: genre.album_count })"
/>
<span>&nbsp;|&nbsp;</span>
<span v-text="$t('count.tracks', { count: genre.track_count })" />
</div>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button <control-button
:handler="play" :button="{ handler: showDetails, icon: 'dots-horizontal' }"
icon="shuffle" />
label="page.genre.shuffle" <control-button
:button="{
handler: play,
icon: 'shuffle',
key: 'page.genre.shuffle'
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-tracks :items="tracks" :expression="expression" /> <list-tracks :items="tracks" :expression="expression" />
@ -55,6 +49,7 @@ import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import ModalDialogGenre from '@/components/ModalDialogGenre.vue' import ModalDialogGenre from '@/components/ModalDialogGenre.vue'
@ -68,7 +63,6 @@ const dataObject = {
webapi.library_genre_tracks(to.params.name, to.query.media_kind) webapi.library_genre_tracks(to.params.name, to.query.media_kind)
]) ])
}, },
set(vm, response) { set(vm, response) {
vm.genre = response[0].data.genres.items.shift() vm.genre = response[0].data.genres.items.shift()
vm.tracks_list = new GroupedList(response[1].data.tracks) vm.tracks_list = new GroupedList(response[1].data.tracks)
@ -81,6 +75,7 @@ export default {
ContentWithHeading, ContentWithHeading,
ControlButton, ControlButton,
ControlDropdown, ControlDropdown,
HeadingTitle,
IndexButtonList, IndexButtonList,
ListTracks, ListTracks,
ModalDialogGenre ModalDialogGenre
@ -120,6 +115,19 @@ export default {
expression() { expression() {
return `genre is "${this.genre.name}" and media_kind is ${this.media_kind}` return `genre is "${this.genre.name}" and media_kind is ${this.media_kind}`
}, },
heading() {
return {
title: this.genre.name,
subtitle: [
{
handler: this.openGenre,
key: 'count.albums',
count: this.genre.album_count
},
{ key: 'count.tracks', count: this.genre.track_count }
]
}
},
tracks() { tracks() {
const { options } = this.groupings.find( const { options } = this.groupings.find(
(grouping) => grouping.id === this.uiStore.genre_tracks_sort (grouping) => grouping.id === this.uiStore.genre_tracks_sort
@ -128,7 +136,7 @@ export default {
} }
}, },
methods: { methods: {
open_genre() { openGenre() {
this.show_details_modal = false this.show_details_modal = false
this.$router.push({ this.$router.push({
name: 'genre-albums', name: 'genre-albums',

View File

@ -6,11 +6,7 @@
<index-button-list :indices="genres.indices" /> <index-button-list :indices="genres.indices" />
</template> </template>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="$t('page.genres.title')" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.genres', { count: genres.total })"
/>
</template> </template>
<template #content> <template #content>
<list-genres :items="genres" :media_kind="'music'" /> <list-genres :items="genres" :media_kind="'music'" />
@ -22,6 +18,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListGenres from '@/components/ListGenres.vue' import ListGenres from '@/components/ListGenres.vue'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
@ -31,7 +28,6 @@ const dataObject = {
load(to) { load(to) {
return webapi.library_genres('music') return webapi.library_genres('music')
}, },
set(vm, response) { set(vm, response) {
vm.genres = new GroupedList(response.data.genres, { vm.genres = new GroupedList(response.data.genres, {
index: { field: 'name_sort', type: String } index: { field: 'name_sort', type: String }
@ -43,21 +39,28 @@ export default {
name: 'PageGenres', name: 'PageGenres',
components: { components: {
ContentWithHeading, ContentWithHeading,
HeadingTitle,
IndexButtonList, IndexButtonList,
ListGenres, ListGenres,
TabsMusic TabsMusic
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
genres: new GroupedList() genres: new GroupedList()
} }
},
computed: {
heading() {
return {
title: this.$t('page.genres.title'),
subtitle: [{ key: 'count.genres', count: this.genres.total }]
}
}
} }
} }
</script> </script>

View File

@ -3,42 +3,38 @@
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.music.recently-added.title')" /> <heading-title
:content="{ title: $t('page.music.recently-added.title') }"
/>
</template> </template>
<template #content> <template #content>
<list-albums :items="albums" /> <list-albums :items="albums" />
</template> </template>
<template #footer> <template #footer>
<nav class="level">
<p class="level-item">
<router-link <router-link
class="button is-small is-rounded" class="button is-small is-rounded"
:to="{ name: 'music-recently-added' }" :to="{ name: 'music-recently-added' }"
> >
{{ $t('page.music.show-more') }} {{ $t('page.music.show-more') }}
</router-link> </router-link>
</p>
</nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.music.recently-played.title')" /> <heading-title
:content="{ title: $t('page.music.recently-played.title') }"
/>
</template> </template>
<template #content> <template #content>
<list-tracks :items="tracks" /> <list-tracks :items="tracks" />
</template> </template>
<template #footer> <template #footer>
<nav class="level">
<p class="level-item">
<router-link <router-link
class="button is-small is-rounded" class="button is-small is-rounded"
:to="{ name: 'music-recently-played' }" :to="{ name: 'music-recently-played' }"
> >
{{ $t('page.music.show-more') }} {{ $t('page.music.show-more') }}
</router-link> </router-link>
</p>
</nav>
</template> </template>
</content-with-heading> </content-with-heading>
</div> </div>
@ -47,6 +43,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
@ -69,7 +66,6 @@ const dataObject = {
}) })
]) ])
}, },
set(vm, response) { set(vm, response) {
vm.albums = new GroupedList(response[0].data.albums) vm.albums = new GroupedList(response[0].data.albums)
vm.tracks = new GroupedList(response[1].data.tracks) vm.tracks = new GroupedList(response[1].data.tracks)
@ -78,14 +74,18 @@ const dataObject = {
export default { export default {
name: 'PageMusic', name: 'PageMusic',
components: { ContentWithHeading, ListAlbums, ListTracks, TabsMusic }, components: {
ContentWithHeading,
HeadingTitle,
ListAlbums,
ListTracks,
TabsMusic
},
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
albums: [], albums: [],

View File

@ -3,7 +3,9 @@
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.music.recently-added.title')" /> <heading-title
:content="{ title: $t('page.music.recently-added.title') }"
/>
</template> </template>
<template #content> <template #content>
<list-albums :items="albums" /> <list-albums :items="albums" />
@ -15,6 +17,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
import { useSettingsStore } from '@/stores/settings' import { useSettingsStore } from '@/stores/settings'
@ -30,7 +33,6 @@ const dataObject = {
type: 'album' type: 'album'
}) })
}, },
set(vm, response) { set(vm, response) {
vm.albums = new GroupedList(response.data.albums, { vm.albums = new GroupedList(response.data.albums, {
criteria: [{ field: 'time_added', order: -1, type: Date }], criteria: [{ field: 'time_added', order: -1, type: Date }],
@ -41,20 +43,17 @@ const dataObject = {
export default { export default {
name: 'PageMusicRecentlyAdded', name: 'PageMusicRecentlyAdded',
components: { ContentWithHeading, ListAlbums, TabsMusic }, components: { ContentWithHeading, HeadingTitle, ListAlbums, TabsMusic },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
setup() { setup() {
return { return {
settingsStore: useSettingsStore() settingsStore: useSettingsStore()
} }
}, },
data() { data() {
return { return {
albums: new GroupedList() albums: new GroupedList()

View File

@ -3,7 +3,9 @@
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.music.recently-played.title')" /> <heading-title
:content="{ title: $t('page.music.recently-played.title') }"
/>
</template> </template>
<template #content> <template #content>
<list-tracks :items="tracks" /> <list-tracks :items="tracks" />
@ -15,6 +17,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
import webapi from '@/webapi' import webapi from '@/webapi'
@ -28,7 +31,6 @@ const dataObject = {
type: 'track' type: 'track'
}) })
}, },
set(vm, response) { set(vm, response) {
vm.tracks = new GroupedList(response.data.tracks) vm.tracks = new GroupedList(response.data.tracks)
} }
@ -36,14 +38,12 @@ const dataObject = {
export default { export default {
name: 'PageMusicRecentlyPlayed', name: 'PageMusicRecentlyPlayed',
components: { ContentWithHeading, ListTracks, TabsMusic }, components: { ContentWithHeading, HeadingTitle, ListTracks, TabsMusic },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
tracks: {} tracks: {}

View File

@ -3,45 +3,38 @@
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.spotify.music.new-releases')" /> <heading-title
:content="{ title: $t('page.spotify.music.new-releases') }"
/>
</template> </template>
<template #content> <template #content>
<list-albums-spotify :items="albums" /> <list-albums-spotify :items="albums" />
</template> </template>
<template #footer> <template #footer>
<nav class="level">
<p class="level-item">
<router-link <router-link
:to="{ name: 'music-spotify-new-releases' }" :to="{ name: 'music-spotify-new-releases' }"
class="button is-small is-rounded" class="button is-small is-rounded"
> >
{{ $t('page.spotify.music.show-more') }} {{ $t('page.spotify.music.show-more') }}
</router-link> </router-link>
</p>
</nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p <heading-title
class="title is-4" :content="{ title: $t('page.spotify.music.featured-playlists') }"
v-text="$t('page.spotify.music.featured-playlists')"
/> />
</template> </template>
<template #content> <template #content>
<list-playlists-spotify :items="playlists" /> <list-playlists-spotify :items="playlists" />
</template> </template>
<template #footer> <template #footer>
<nav class="level">
<p class="level-item">
<router-link <router-link
:to="{ name: 'music-spotify-featured-playlists' }" :to="{ name: 'music-spotify-featured-playlists' }"
class="button is-small is-rounded" class="button is-small is-rounded"
> >
{{ $t('page.spotify.music.show-more') }} {{ $t('page.spotify.music.show-more') }}
</router-link> </router-link>
</p>
</nav>
</template> </template>
</content-with-heading> </content-with-heading>
</div> </div>
@ -49,6 +42,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbumsSpotify from '@/components/ListAlbumsSpotify.vue' import ListAlbumsSpotify from '@/components/ListAlbumsSpotify.vue'
import ListPlaylistsSpotify from '@/components/ListPlaylistsSpotify.vue' import ListPlaylistsSpotify from '@/components/ListPlaylistsSpotify.vue'
import SpotifyWebApi from 'spotify-web-api-js' import SpotifyWebApi from 'spotify-web-api-js'
@ -72,7 +66,6 @@ const dataObject = {
]) ])
}) })
}, },
set(vm, response) { set(vm, response) {
vm.albums = response[0].albums.items vm.albums = response[0].albums.items
vm.playlists = response[1].playlists.items vm.playlists = response[1].playlists.items
@ -83,17 +76,16 @@ export default {
name: 'PageMusicSpotify', name: 'PageMusicSpotify',
components: { components: {
ContentWithHeading, ContentWithHeading,
HeadingTitle,
ListAlbumsSpotify, ListAlbumsSpotify,
ListPlaylistsSpotify, ListPlaylistsSpotify,
TabsMusic TabsMusic
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
playlists: [], playlists: [],

View File

@ -3,10 +3,7 @@
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p <heading-title :content="heading" />
class="title is-4"
v-text="$t('page.spotify.music.featured-playlists')"
/>
</template> </template>
<template #content> <template #content>
<list-playlists-spotify :items="playlists" /> <list-playlists-spotify :items="playlists" />
@ -17,6 +14,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListPlaylistsSpotify from '@/components/ListPlaylistsSpotify.vue' import ListPlaylistsSpotify from '@/components/ListPlaylistsSpotify.vue'
import SpotifyWebApi from 'spotify-web-api-js' import SpotifyWebApi from 'spotify-web-api-js'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
@ -33,7 +31,6 @@ const dataObject = {
}) })
}) })
}, },
set(vm, response) { set(vm, response) {
vm.playlists = response.playlists.items vm.playlists = response.playlists.items
} }
@ -43,20 +40,24 @@ export default {
name: 'PageMusicSpotifyFeaturedPlaylists', name: 'PageMusicSpotifyFeaturedPlaylists',
components: { components: {
ContentWithHeading, ContentWithHeading,
HeadingTitle,
ListPlaylistsSpotify, ListPlaylistsSpotify,
TabsMusic TabsMusic
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
playlists: [] playlists: []
} }
},
computed: {
heading() {
return { title: this.$t('page.spotify.music.featured-playlists') }
}
} }
} }
</script> </script>

View File

@ -3,7 +3,7 @@
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.spotify.music.new-releases')" /> <heading-title :content="heading" />
</template> </template>
<template #content> <template #content>
<list-albums-spotify :items="albums" /> <list-albums-spotify :items="albums" />
@ -14,6 +14,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbumsSpotify from '@/components/ListAlbumsSpotify.vue' import ListAlbumsSpotify from '@/components/ListAlbumsSpotify.vue'
import SpotifyWebApi from 'spotify-web-api-js' import SpotifyWebApi from 'spotify-web-api-js'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
@ -30,7 +31,6 @@ const dataObject = {
}) })
}) })
}, },
set(vm, response) { set(vm, response) {
vm.albums = response.albums.items vm.albums = response.albums.items
} }
@ -40,20 +40,24 @@ export default {
name: 'PageMusicSpotifyNewReleases', name: 'PageMusicSpotifyNewReleases',
components: { components: {
ContentWithHeading, ContentWithHeading,
HeadingTitle,
ListAlbumsSpotify, ListAlbumsSpotify,
TabsMusic TabsMusic
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
albums: [] albums: []
} }
},
computed: {
heading() {
return { title: this.$t('page.spotify.music.new-releases') }
}
} }
} }
</script> </script>

View File

@ -2,16 +2,7 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div <heading-title :content="heading" />
class="title is-4"
v-text="
playlist.id === 0 ? $t('page.playlists.title') : playlist.name
"
/>
<div
class="is-size-7 is-uppercase"
v-text="$t('count.playlists', { count: playlists.count })"
/>
</template> </template>
<template #content> <template #content>
<list-playlists :items="playlists" /> <list-playlists :items="playlists" />
@ -23,6 +14,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListPlaylists from '@/components/ListPlaylists.vue' import ListPlaylists from '@/components/ListPlaylists.vue'
import { useConfigurationStore } from '@/stores/configuration' import { useConfigurationStore } from '@/stores/configuration'
import webapi from '@/webapi' import webapi from '@/webapi'
@ -34,7 +26,6 @@ const dataObject = {
webapi.library_playlist_folder(to.params.id) webapi.library_playlist_folder(to.params.id)
]) ])
}, },
set(vm, response) { set(vm, response) {
vm.playlist = response[0].data vm.playlist = response[0].data
vm.playlists_list = new GroupedList(response[1].data) vm.playlists_list = new GroupedList(response[1].data)
@ -43,35 +34,39 @@ const dataObject = {
export default { export default {
name: 'PagePlaylistFolder', name: 'PagePlaylistFolder',
components: { ContentWithHeading, ListPlaylists }, components: { ContentWithHeading, ListPlaylists, HeadingTitle },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
beforeRouteUpdate(to, from, next) { beforeRouteUpdate(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
dataObject.set(this, response) dataObject.set(this, response)
next() next()
}) })
}, },
setup() { setup() {
return { return {
configurationStore: useConfigurationStore() configurationStore: useConfigurationStore()
} }
}, },
data() { data() {
return { return {
playlist: {}, playlist: {},
playlists_list: new GroupedList() playlists_list: new GroupedList()
} }
}, },
computed: { computed: {
heading() {
return {
title:
this.playlists.count === 0
? this.$t('page.playlists.title')
: this.playlist.name,
subtitle: [{ key: 'count.playlists', count: this.playlists.count }]
}
},
playlists() { playlists() {
return this.playlists_list.group({ return this.playlists_list.group({
filters: [ filters: [

View File

@ -2,22 +2,20 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="playlist.name" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.tracks', { count: tracks.count })"
/>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button <control-button
:disabled="tracks.count === 0" :button="{ handler: showDetails, icon: 'dots-horizontal' }"
:handler="play" />
icon="shuffle" <control-button
label="page.playlist.shuffle" :button="{
handler: play,
icon: 'shuffle',
key: 'page.playlist.shuffle',
disabled: tracks.count === 0
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-tracks :items="tracks" :uris="uris" /> <list-tracks :items="tracks" :uris="uris" />
@ -36,6 +34,7 @@
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist.vue' import ModalDialogPlaylist from '@/components/ModalDialogPlaylist.vue'
import webapi from '@/webapi' import webapi from '@/webapi'
@ -47,7 +46,6 @@ const dataObject = {
webapi.library_playlist_tracks(to.params.id) webapi.library_playlist_tracks(to.params.id)
]) ])
}, },
set(vm, response) { set(vm, response) {
vm.playlist = response[0].data vm.playlist = response[0].data
vm.tracks = new GroupedList(response[1].data) vm.tracks = new GroupedList(response[1].data)
@ -59,16 +57,15 @@ export default {
components: { components: {
ContentWithHeading, ContentWithHeading,
ControlButton, ControlButton,
HeadingTitle,
ListTracks, ListTracks,
ModalDialogPlaylist ModalDialogPlaylist
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
playlist: {}, playlist: {},
@ -76,8 +73,13 @@ export default {
tracks: new GroupedList() tracks: new GroupedList()
} }
}, },
computed: { computed: {
heading() {
return {
title: this.playlist.name,
subtitle: [{ key: 'count.tracks', count: this.tracks.count }]
}
},
uris() { uris() {
if (this.playlist.random) { if (this.playlist.random) {
return this.tracks.map((item) => item.uri).join() return this.tracks.map((item) => item.uri).join()
@ -85,7 +87,6 @@ export default {
return this.playlist.uri return this.playlist.uri
} }
}, },
methods: { methods: {
play() { play() {
webapi.player_play_uri(this.uris, true) webapi.player_play_uri(this.uris, true)

View File

@ -2,22 +2,20 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="playlist.name" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.playlists', { count: playlist.tracks.total })"
/>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button <control-button
:disabled="playlist.tracks.total === 0" :button="{ handler: showDetails, icon: 'dots-horizontal' }"
:handler="play" />
icon="shuffle" <control-button
label="page.spotify.playlist.shuffle" :button="{
handler: play,
icon: 'shuffle',
key: 'page.spotify.playlist.shuffle',
disabled: playlist.tracks.total === 0
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-tracks-spotify :items="tracks" :context_uri="playlist.uri" /> <list-tracks-spotify :items="tracks" :context_uri="playlist.uri" />
@ -46,6 +44,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListTracksSpotify from '@/components/ListTracksSpotify.vue' import ListTracksSpotify from '@/components/ListTracksSpotify.vue'
import ModalDialogPlaylistSpotify from '@/components/ModalDialogPlaylistSpotify.vue' import ModalDialogPlaylistSpotify from '@/components/ModalDialogPlaylistSpotify.vue'
import SpotifyWebApi from 'spotify-web-api-js' import SpotifyWebApi from 'spotify-web-api-js'
@ -68,7 +67,6 @@ const dataObject = {
}) })
]) ])
}, },
set(vm, response) { set(vm, response) {
vm.playlist = response.shift() vm.playlist = response.shift()
vm.tracks = [] vm.tracks = []
@ -85,19 +83,17 @@ export default {
ControlButton, ControlButton,
ListTracksSpotify, ListTracksSpotify,
ModalDialogPlaylistSpotify, ModalDialogPlaylistSpotify,
HeadingTitle,
VueEternalLoading VueEternalLoading
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
setup() { setup() {
return { servicesStore: useServicesStore() } return { servicesStore: useServicesStore() }
}, },
data() { data() {
return { return {
offset: 0, offset: 0,
@ -107,7 +103,16 @@ export default {
tracks: [] tracks: []
} }
}, },
computed: {
heading() {
return {
title: this.playlist.name,
subtitle: [
{ key: 'count.playlists', count: this.playlist.tracks.total }
]
}
}
},
methods: { methods: {
append_tracks(data) { append_tracks(data) {
let position = Math.max( let position = Math.max(

View File

@ -12,11 +12,11 @@
/> />
<div class="buttons is-centered-mobile mt-5"> <div class="buttons is-centered-mobile mt-5">
<control-button <control-button
:handler="play" :button="{ handler: play, icon: 'play', key: 'page.podcast.play' }"
icon="play" />
label="page.podcast.play" <control-button
:button="{ handler: showDetails, icon: 'dots-horizontal' }"
/> />
<control-button :handler="showDetails" icon="dots-horizontal" />
</div> </div>
</template> </template>
<template #heading-right> <template #heading-right>
@ -119,12 +119,12 @@ export default {
actions() { actions() {
return [ return [
{ {
label: this.$t('page.podcast.cancel'), key: this.$t('page.podcast.cancel'),
handler: 'cancel', handler: 'cancel',
icon: 'cancel' icon: 'cancel'
}, },
{ {
label: this.$t('page.podcast.remove'), key: this.$t('page.podcast.remove'),
handler: 'remove', handler: 'remove',
icon: 'delete' icon: 'delete'
} }

View File

@ -2,58 +2,56 @@
<div> <div>
<content-with-heading v-if="tracks.items.length > 0"> <content-with-heading v-if="tracks.items.length > 0">
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.podcasts.new-episodes')" /> <heading-title :content="{ title: $t('page.podcasts.new-episodes') }" />
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button <control-button
:handler="mark_all_played" :button="{
icon="pencil" handler: markAllAsPlayed,
label="page.podcasts.mark-all-played" icon: 'pencil',
key: 'page.podcasts.mark-all-played'
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-tracks <list-tracks
:items="tracks" :items="tracks"
:show_progress="true" :show_progress="true"
@play-count-changed="reload_new_episodes" @play-count-changed="reloadNewEpisodes"
/> />
</template> </template>
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="$t('page.podcasts.title')" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.podcasts', { count: albums.total })"
/>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button <control-button
v-if="rss.tracks > 0" v-if="rss.tracks > 0"
:handler="update_rss" :button="{
icon="refresh" handler: updateRss,
label="page.podcasts.update" icon: 'refresh',
key: 'page.podcasts.update'
}"
/> />
<control-button <control-button
:handler="open_add_podcast_dialog" :button="{
icon="rss" handler: 'openAddPodcastDialog',
label="page.podcasts.add" icon: 'rss',
key: 'page.podcasts.add'
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<list-albums <list-albums
:items="albums" :items="albums"
@play-count-changed="reload_new_episodes()" @play-count-changed="reloadNewEpisodes()"
@podcast-deleted="reload_podcasts()" @podcast-deleted="reloadPodcasts()"
/> />
<modal-dialog-add-rss <modal-dialog-add-rss
:show="show_url_modal" :show="show_url_modal"
@close="show_url_modal = false" @close="show_url_modal = false"
@podcast-added="reload_podcasts()" @podcast-added="reloadPodcasts()"
/> />
</template> </template>
</content-with-heading> </content-with-heading>
@ -64,6 +62,7 @@
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import ModalDialogAddRss from '@/components/ModalDialogAddRss.vue' import ModalDialogAddRss from '@/components/ModalDialogAddRss.vue'
@ -78,7 +77,6 @@ const dataObject = {
webapi.library_podcasts_new_episodes() webapi.library_podcasts_new_episodes()
]) ])
}, },
set(vm, response) { set(vm, response) {
vm.albums = new GroupedList(response[0].data) vm.albums = new GroupedList(response[0].data)
vm.tracks = new GroupedList(response[1].data.tracks) vm.tracks = new GroupedList(response[1].data.tracks)
@ -90,21 +88,19 @@ export default {
components: { components: {
ContentWithHeading, ContentWithHeading,
ControlButton, ControlButton,
HeadingTitle,
ListAlbums, ListAlbums,
ListTracks, ListTracks,
ModalDialogAddRss ModalDialogAddRss
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
setup() { setup() {
return { libraryStore: useLibraryStore(), uiStore: useUIStore() } return { libraryStore: useLibraryStore(), uiStore: useUIStore() }
}, },
data() { data() {
return { return {
albums: [], albums: [],
@ -112,39 +108,42 @@ export default {
show_url_modal: false show_url_modal: false
} }
}, },
computed: { computed: {
heading() {
if (this.albums.total) {
return {
title: this.$t('page.podcasts.title'),
subtitle: [{ key: 'count.podcasts', count: this.albums.count }]
}
}
return {}
},
rss() { rss() {
return this.libraryStore.rss return this.libraryStore.rss
} }
}, },
methods: { methods: {
mark_all_played() { markAllAsPlayed() {
this.tracks.items.forEach((ep) => { this.tracks.items.forEach((ep) => {
webapi.library_track_update(ep.id, { play_count: 'increment' }) webapi.library_track_update(ep.id, { play_count: 'increment' })
}) })
this.tracks.items = {} this.tracks.items = {}
}, },
openAddPodcastDialog() {
open_add_podcast_dialog() {
this.show_url_modal = true this.show_url_modal = true
}, },
reloadNewEpisodes() {
reload_new_episodes() {
webapi.library_podcasts_new_episodes().then(({ data }) => { webapi.library_podcasts_new_episodes().then(({ data }) => {
this.tracks = new GroupedList(data.tracks) this.tracks = new GroupedList(data.tracks)
}) })
}, },
reloadPodcasts() {
reload_podcasts() {
webapi.library_albums('podcast').then(({ data }) => { webapi.library_albums('podcast').then(({ data }) => {
this.albums = new GroupedList(data) this.albums = new GroupedList(data)
this.reload_new_episodes() this.reloadNewEpisodes()
}) })
}, },
updateRss() {
update_rss() {
this.libraryStore.update_dialog_scan_kind = 'rss' this.libraryStore.update_dialog_scan_kind = 'rss'
this.uiStore.show_update_dialog = true this.uiStore.show_update_dialog = true
} }

View File

@ -2,46 +2,50 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="$t('page.queue.title')" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.tracks', { count: queue.count })"
/>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered">
<control-button <control-button
:handler="update_show_next_items" :button="{
:class="{ 'is-dark': show_only_next_items }" handler: update_show_next_items,
icon="eye-off-outline" icon: 'eye-off-outline',
label="page.queue.hide-previous" key: 'page.queue.hide-previous',
class: { 'is-dark': show_only_next_items }
}"
/> />
<control-button <control-button
:handler="open_add_stream_dialog" :button="{
icon="web" handler: open_add_stream_dialog,
label="page.queue.add-stream" icon: 'web',
key: 'page.queue.add-stream'
}"
/> />
<control-button <control-button
:class="{ 'is-dark': edit_mode }" :button="{
:disabled="queue_items.length === 0" handler: toggleEdit,
:handler="toggleEdit" icon: 'pencil',
icon="pencil" key: 'page.queue.edit',
label="page.queue.edit" disabled: queue_items.length === 0,
class: { 'is-dark': edit_mode }
}"
/> />
<control-button <control-button
:disabled="queue_items.length === 0" :button="{
:handler="queue_clear" handler: queue_clear,
icon="delete-empty" icon: 'delete-empty',
label="page.queue.clear" key: 'page.queue.clear',
disabled: queue_items.length === 0
}"
/> />
<control-button <control-button
v-if="is_queue_save_allowed" v-if="is_queue_save_allowed"
:disabled="queue_items.length === 0" :button="{
:handler="save_dialog" handler: save_dialog,
icon="download" icon: 'download',
label="page.queue.save" key: 'page.queue.save',
disabled: queue_items.length === 0
}"
/> />
</div>
</template> </template>
<template #content> <template #content>
<draggable v-model="queue_items" item-key="id" @end="move_item"> <draggable v-model="queue_items" item-key="id" @end="move_item">
@ -93,6 +97,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue' import ControlButton from '@/components/ControlButton.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListItemQueueItem from '@/components/ListItemQueueItem.vue' import ListItemQueueItem from '@/components/ListItemQueueItem.vue'
import ModalDialogAddStream from '@/components/ModalDialogAddStream.vue' import ModalDialogAddStream from '@/components/ModalDialogAddStream.vue'
import ModalDialogPlaylistSave from '@/components/ModalDialogPlaylistSave.vue' import ModalDialogPlaylistSave from '@/components/ModalDialogPlaylistSave.vue'
@ -113,9 +118,9 @@ export default {
ModalDialogAddStream, ModalDialogAddStream,
ModalDialogPlaylistSave, ModalDialogPlaylistSave,
ModalDialogQueueItem, ModalDialogQueueItem,
HeadingTitle,
draggable draggable
}, },
setup() { setup() {
return { return {
configurationStore: useConfigurationStore(), configurationStore: useConfigurationStore(),
@ -124,7 +129,6 @@ export default {
uiStore: useUIStore() uiStore: useUIStore()
} }
}, },
data() { data() {
return { return {
edit_mode: false, edit_mode: false,
@ -134,11 +138,16 @@ export default {
show_url_modal: false show_url_modal: false
} }
}, },
computed: { computed: {
current_position() { current_position() {
return this.queue.current?.position ?? -1 return this.queue.current?.position ?? -1
}, },
heading() {
return {
title: this.$t('page.queue.title'),
subtitle: [{ key: 'count.tracks', count: this.queue.count }]
}
},
is_queue_save_allowed() { is_queue_save_allowed() {
return ( return (
this.configurationStore.allow_modifying_stored_playlists && this.configurationStore.allow_modifying_stored_playlists &&

View File

@ -2,11 +2,7 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="$t('page.radio.title')" /> <heading-title :content="heading" />
<div
class="is-size-7 is-uppercase"
v-text="$t('count.stations', { count: tracks.total })"
/>
</template> </template>
<template #content> <template #content>
<list-tracks :items="tracks" /> <list-tracks :items="tracks" />
@ -18,6 +14,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import webapi from '@/webapi' import webapi from '@/webapi'
@ -25,7 +22,6 @@ const dataObject = {
load(to) { load(to) {
return webapi.library_radio_streams() return webapi.library_radio_streams()
}, },
set(vm, response) { set(vm, response) {
vm.tracks = new GroupedList(response.data.tracks) vm.tracks = new GroupedList(response.data.tracks)
} }
@ -33,18 +29,24 @@ const dataObject = {
export default { export default {
name: 'PageRadioStreams', name: 'PageRadioStreams',
components: { ContentWithHeading, ListTracks }, components: { ContentWithHeading, ListTracks, HeadingTitle },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => { dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response)) next((vm) => dataObject.set(vm, response))
}) })
}, },
data() { data() {
return { return {
tracks: new GroupedList() tracks: new GroupedList()
} }
},
computed: {
heading() {
return {
title: this.$t('page.radio.title'),
subtitle: [{ key: 'count.stations', count: this.tracks.total }]
}
}
} }
} }
</script> </script>

View File

@ -51,7 +51,7 @@
<template v-for="[type, items] in results" :key="type"> <template v-for="[type, items] in results" :key="type">
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t(`page.search.${type}s`)" /> <heading-title :content="{ title: $t(`page.search.${type}s`) }" />
</template> </template>
<template #content> <template #content>
<component :is="components[type]" :items="items" /> <component :is="components[type]" :items="items" />
@ -83,6 +83,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import ListArtists from '@/components/ListArtists.vue' import ListArtists from '@/components/ListArtists.vue'
import ListComposers from '@/components/ListComposers.vue' import ListComposers from '@/components/ListComposers.vue'
@ -107,6 +108,7 @@ export default {
name: 'PageSearchLibrary', name: 'PageSearchLibrary',
components: { components: {
ContentWithHeading, ContentWithHeading,
HeadingTitle,
ListAlbums, ListAlbums,
ListArtists, ListArtists,
ListComposers, ListComposers,

View File

@ -34,7 +34,9 @@
<template v-for="[type, items] in results" :key="type"> <template v-for="[type, items] in results" :key="type">
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t(`page.spotify.search.${type}s`)" /> <heading-title
:content="{ title: $t(`page.spotify.search.${type}s`) }"
/>
</template> </template>
<template #content> <template #content>
<component :is="components[type]" :items="items.items" /> <component :is="components[type]" :items="items.items" />
@ -77,6 +79,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbumsSpotify from '@/components/ListAlbumsSpotify.vue' import ListAlbumsSpotify from '@/components/ListAlbumsSpotify.vue'
import ListArtistsSpotify from '@/components/ListArtistsSpotify.vue' import ListArtistsSpotify from '@/components/ListArtistsSpotify.vue'
import ListPlaylistsSpotify from '@/components/ListPlaylistsSpotify.vue' import ListPlaylistsSpotify from '@/components/ListPlaylistsSpotify.vue'
@ -95,6 +98,7 @@ export default {
name: 'PageSearchSpotify', name: 'PageSearchSpotify',
components: { components: {
ContentWithHeading, ContentWithHeading,
HeadingTitle,
ListAlbumsSpotify, ListAlbumsSpotify,
ListArtistsSpotify, ListArtistsSpotify,
ListPlaylistsSpotify, ListPlaylistsSpotify,
@ -102,11 +106,9 @@ export default {
TabsSearch, TabsSearch,
VueEternalLoading VueEternalLoading
}, },
setup() { setup() {
return { searchStore: useSearchStore() } return { searchStore: useSearchStore() }
}, },
data() { data() {
return { return {
components: { components: {
@ -121,7 +123,6 @@ export default {
search_types: SEARCH_TYPES search_types: SEARCH_TYPES
} }
}, },
computed: { computed: {
expanded() { expanded() {
return this.search_types.length === 1 return this.search_types.length === 1
@ -132,20 +133,17 @@ export default {
) )
} }
}, },
watch: { watch: {
search_query() { search_query() {
this.searchStore.search_query = this.search_query this.searchStore.search_query = this.search_query
} }
}, },
mounted() { mounted() {
this.searchStore.search_source = this.$route.name this.searchStore.search_source = this.$route.name
this.search_query = this.searchStore.search_query this.search_query = this.searchStore.search_query
this.search_parameters.limit = PAGE_SIZE this.search_parameters.limit = PAGE_SIZE
this.search() this.search()
}, },
methods: { methods: {
expand(type) { expand(type) {
this.search_query = this.searchStore.search_query this.search_query = this.searchStore.search_query

View File

@ -3,7 +3,9 @@
<tabs-settings /> <tabs-settings />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="$t('page.settings.artwork.title')" /> <heading-title
:content="{ title: $t('page.settings.artwork.title') }"
/>
</template> </template>
<template #content> <template #content>
<div <div
@ -79,17 +81,21 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlSettingSwitch from '@/components/ControlSettingSwitch.vue' import ControlSettingSwitch from '@/components/ControlSettingSwitch.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import TabsSettings from '@/components/TabsSettings.vue' import TabsSettings from '@/components/TabsSettings.vue'
import { useServicesStore } from '@/stores/services' import { useServicesStore } from '@/stores/services'
export default { export default {
name: 'PageSettingsArtwork', name: 'PageSettingsArtwork',
components: { ContentWithHeading, ControlSettingSwitch, TabsSettings }, components: {
ContentWithHeading,
ControlSettingSwitch,
HeadingTitle,
TabsSettings
},
setup() { setup() {
return { servicesStore: useServicesStore() } return { servicesStore: useServicesStore() }
}, },
computed: { computed: {
spotify() { spotify() {
return this.servicesStore.spotify return this.servicesStore.spotify

View File

@ -3,9 +3,8 @@
<tabs-settings /> <tabs-settings />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div <heading-title
class="title is-4" :content="{ title: $t('page.settings.services.spotify.title') }"
v-text="$t('page.settings.services.spotify.title')"
/> />
</template> </template>
<template #content> <template #content>
@ -59,9 +58,8 @@
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div <heading-title
class="title is-4" :content="{ title: $t('page.settings.services.lastfm.title') }"
v-text="$t('page.settings.services.lastfm.title')"
/> />
</template> </template>
<template #content> <template #content>
@ -127,18 +125,17 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import TabsSettings from '@/components/TabsSettings.vue' import TabsSettings from '@/components/TabsSettings.vue'
import { useServicesStore } from '@/stores/services' import { useServicesStore } from '@/stores/services'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'PageSettingsOnlineServices', name: 'PageSettingsOnlineServices',
components: { ContentWithHeading, TabsSettings }, components: { ContentWithHeading, HeadingTitle, TabsSettings },
setup() { setup() {
return { servicesStore: useServicesStore() } return { servicesStore: useServicesStore() }
}, },
data() { data() {
return { return {
lastfm_login: { lastfm_login: {
@ -148,7 +145,6 @@ export default {
} }
} }
}, },
computed: { computed: {
lastfm() { lastfm() {
return this.servicesStore.lastfm return this.servicesStore.lastfm
@ -175,7 +171,6 @@ export default {
return [] return []
} }
}, },
methods: { methods: {
login_lastfm() { login_lastfm() {
webapi.lastfm_login(this.lastfm_login).then((response) => { webapi.lastfm_login(this.lastfm_login).then((response) => {

View File

@ -3,7 +3,9 @@
<tabs-settings /> <tabs-settings />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4" v-text="$t('page.settings.devices.pairing')" /> <heading-title
:content="{ title: $t('page.settings.devices.pairing') }"
/>
</template> </template>
<template #content> <template #content>
<div v-if="pairing.active"> <div v-if="pairing.active">
@ -39,9 +41,8 @@
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p <heading-title
class="title is-4" :content="{ title: $t('page.settings.devices.speaker-pairing') }"
v-text="$t('page.settings.devices.speaker-pairing')"
/> />
</template> </template>
<template #content> <template #content>
@ -91,6 +92,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlSwitch from '@/components/ControlSwitch.vue' import ControlSwitch from '@/components/ControlSwitch.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import TabsSettings from '@/components/TabsSettings.vue' import TabsSettings from '@/components/TabsSettings.vue'
import { useOutputsStore } from '@/stores/outputs' import { useOutputsStore } from '@/stores/outputs'
import { useRemotesStore } from '@/stores/remotes' import { useRemotesStore } from '@/stores/remotes'
@ -98,19 +100,16 @@ import webapi from '@/webapi'
export default { export default {
name: 'PageSettingsRemotesOutputs', name: 'PageSettingsRemotesOutputs',
components: { ContentWithHeading, ControlSwitch, TabsSettings }, components: { ContentWithHeading, ControlSwitch, HeadingTitle, TabsSettings },
setup() { setup() {
return { outputsStore: useOutputsStore(), remotesStore: useRemotesStore() } return { outputsStore: useOutputsStore(), remotesStore: useRemotesStore() }
}, },
data() { data() {
return { return {
pairing_req: { pin: '' }, pairing_req: { pin: '' },
verification_req: { pin: '' } verification_req: { pin: '' }
} }
}, },
computed: { computed: {
outputs() { outputs() {
return this.outputsStore.outputs return this.outputsStore.outputs
@ -119,7 +118,6 @@ export default {
return this.remotesStore.pairing return this.remotesStore.pairing
} }
}, },
methods: { methods: {
kickoff_pairing() { kickoff_pairing() {
webapi.pairing_kickoff(this.pairing_req) webapi.pairing_kickoff(this.pairing_req)

View File

@ -3,7 +3,9 @@
<tabs-settings /> <tabs-settings />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4" v-text="$t('page.settings.general.language')" /> <heading-title
:content="{ title: $t('page.settings.general.language') }"
/>
</template> </template>
<template #content> <template #content>
<control-dropdown v-model:value="locale" :options="locales" /> <control-dropdown v-model:value="locale" :options="locales" />
@ -11,9 +13,8 @@
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div <heading-title
class="title is-4" :content="{ title: $t('page.settings.general.navigation-items') }"
v-text="$t('page.settings.general.navigation-items')"
/> />
</template> </template>
<template #content> <template #content>
@ -85,9 +86,8 @@
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div <heading-title
class="title is-4" :content="{ title: $t('page.settings.general.now-playing-page') }"
v-text="$t('page.settings.general.now-playing-page')"
/> />
</template> </template>
<template #content> <template #content>
@ -134,9 +134,8 @@
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div <heading-title
class="title is-4" :content="{ title: $t('page.settings.general.recently-added-page') }"
v-text="$t('page.settings.general.recently-added-page')"
/> />
</template> </template>
<template #content> <template #content>
@ -161,6 +160,7 @@ import ControlDropdown from '@/components/ControlDropdown.vue'
import ControlSettingIntegerField from '@/components/ControlSettingIntegerField.vue' import ControlSettingIntegerField from '@/components/ControlSettingIntegerField.vue'
import ControlSettingSwitch from '@/components/ControlSettingSwitch.vue' import ControlSettingSwitch from '@/components/ControlSettingSwitch.vue'
import ControlSettingTextField from '@/components/ControlSettingTextField.vue' import ControlSettingTextField from '@/components/ControlSettingTextField.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import TabsSettings from '@/components/TabsSettings.vue' import TabsSettings from '@/components/TabsSettings.vue'
import { useSettingsStore } from '@/stores/settings' import { useSettingsStore } from '@/stores/settings'
@ -172,6 +172,7 @@ export default {
ControlSettingIntegerField, ControlSettingIntegerField,
ControlSettingSwitch, ControlSettingSwitch,
ControlSettingTextField, ControlSettingTextField,
HeadingTitle,
TabsSettings TabsSettings
}, },

View File

@ -24,14 +24,21 @@
</div> </div>
</div> </div>
</div> </div>
<div class="level-right has-text-centered-mobile"> <div
v-if="$slots['heading-right']"
class="level-right has-text-centered-mobile"
>
<div class="buttons">
<slot name="heading-right" /> <slot name="heading-right" />
</div> </div>
</div>
</nav> </nav>
<slot name="content" /> <slot name="content" />
<div class="mt-4"> <nav v-if="$slots.footer" class="level mt-4">
<div class="level-item">
<slot name="footer" /> <slot name="footer" />
</div> </div>
</nav>
</div> </div>
</div> </div>
</div> </div>