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

View File

@ -21,7 +21,7 @@
/>
<span
:class="{ 'is-hidden-mobile': link.icon }"
v-text="$t(link.label)"
v-text="$t(link.key)"
/>
</a>
</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: {
actions() {
return [
{
label: this.$t('page.podcast.cancel'),
handler: 'cancel',
icon: 'cancel'
},
{
label: this.$t('page.podcast.remove'),
handler: 'remove',
icon: 'delete'
}
{ key: 'page.podcast.cancel', handler: 'cancel', icon: 'cancel' },
{ key: 'page.podcast.remove', handler: 'remove', icon: 'delete' }
]
},
media_kind_resolved() {

View File

@ -13,10 +13,10 @@
<slot v-if="$slots.buttons" name="buttons" />
<div
v-for="property in item.properties?.filter((p) => p.value)"
:key="property.label"
:key="property.key"
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">
<a
v-if="property.handler"

View File

@ -11,13 +11,13 @@
<footer v-if="actions.length" class="card-footer">
<a
v-for="action in actions"
:key="action.label"
:key="action.key"
class="card-footer-item"
:class="{ 'is-disabled': action.disabled }"
@click="action.handler"
>
<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>
</footer>
</div>

View File

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

View File

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

View File

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

View File

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

View File

@ -21,14 +21,14 @@ export default {
handler: this.open,
uri: this.item.uri,
properties: [
{ label: 'property.albums', value: this.item.album_count },
{ label: 'property.tracks', value: this.item.track_count },
{ key: 'property.albums', value: this.item.album_count },
{ key: 'property.tracks', value: this.item.track_count },
{
label: 'property.type',
key: 'property.type',
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)
}
]

View File

@ -22,15 +22,12 @@ export default {
uri: this.item.uri,
properties: [
{
label: 'property.popularity',
key: 'property.popularity',
value: [this.item.popularity, this.item.followers?.total].join(
' / '
)
},
{
label: 'property.genres',
value: this.item.genres?.join(', ')
}
{ key: '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`,
properties: [
{
label: 'property.albums',
key: 'property.albums',
value: this.item.album_count,
handler: this.open_albums
},
{
label: 'property.tracks',
key: 'property.tracks',
value: this.item.track_count,
handler: this.open_tracks
},
{
label: 'property.duration',
key: 'property.duration',
value: this.$filters.toTimecode(this.item.length_ms)
}
]

View File

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

View File

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

View File

@ -26,12 +26,12 @@ export default {
uri: this.item.uri,
uris: this.uris,
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}`)
},
{ label: 'property.path', value: this.item.path }
{ key: 'property.path', value: this.item.path }
]
}
}

View File

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

View File

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

View File

@ -30,12 +30,12 @@ export default {
actions() {
return [
{
label: this.$t('dialog.queue-item.remove'),
key: this.$t('dialog.queue-item.remove'),
handler: this.remove,
icon: 'delete'
},
{
label: this.$t('dialog.queue-item.play'),
key: this.$t('dialog.queue-item.play'),
handler: this.play,
icon: 'play'
}
@ -47,37 +47,37 @@ export default {
uri: this.item.uri,
properties: [
{
label: 'property.album',
key: 'property.album',
value: this.item.album,
handler: this.open_album
},
{
label: 'property.album-artist',
key: 'property.album-artist',
value: this.item.album_artist,
handler: this.open_album_artist
},
{ label: 'property.composer', value: this.item.composer },
{ label: 'property.year', value: this.item.year },
{ key: 'property.composer', value: this.item.composer },
{ key: 'property.year', value: this.item.year },
{
label: 'property.genre',
key: 'property.genre',
value: this.item.genre,
handler: this.open_genre
},
{
label: 'property.position',
key: 'property.position',
value: [this.item.disc_number, this.item.track_number].join(' / ')
},
{
label: 'property.duration',
key: 'property.duration',
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}`)}`
},
{
label: 'property.quality',
key: 'property.quality',
value: this.$t('dialog.track.quality-value', {
format: this.item.type,
bitrate: this.item.bitrate,

View File

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

View File

@ -22,8 +22,8 @@ export default {
return []
}
return this.item.play_count > 0
? [{ label: 'dialog.track.mark-as-new', handler: this.mark_new }]
: [{ label: 'dialog.track.mark-as-played', handler: this.mark_played }]
? [{ key: 'dialog.track.mark-as-new', handler: this.mark_new }]
: [{ key: 'dialog.track.mark-as-played', handler: this.mark_played }]
},
playable() {
return {
@ -31,36 +31,36 @@ export default {
uri: this.item.uri,
properties: [
{
label: 'property.album',
key: 'property.album',
value: this.item.album,
handler: this.open_album
},
{
label: 'property.album-artist',
key: 'property.album-artist',
value: this.item.album_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)
},
{ label: 'property.year', value: this.item.year },
{ label: 'property.genre', value: this.item.genre },
{ key: 'property.year', value: this.item.year },
{ key: 'property.genre', value: this.item.genre },
{
label: 'property.position',
key: 'property.position',
value: [this.item.disc_number, this.item.track_number].join(' / ')
},
{
label: 'property.duration',
key: 'property.duration',
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}`)}`
},
{
label: 'property.quality',
key: 'property.quality',
value:
this.item.data_kind !== 'spotify' &&
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)
},
{
label: 'property.rating',
key: 'property.rating',
value: this.$t('dialog.track.rating-value', {
rating: Math.floor(this.item.rating / 10)
})
},
{ label: 'property.comment', value: this.item.comment },
{ label: 'property.path', value: this.item.path }
{ key: 'property.comment', value: this.item.comment },
{ key: 'property.path', value: this.item.path }
]
}
}

View File

@ -24,28 +24,28 @@ export default {
uri: this.item.uri,
properties: [
{
label: 'property.album',
key: 'property.album',
value: this.item.album.name,
handler: this.open_album
},
{
label: 'property.album-artist',
key: 'property.album-artist',
value: this.item.artists[0]?.name,
handler: this.open_artist
},
{
label: 'property.release-date',
key: 'property.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(' / ')
},
{
label: 'property.duration',
key: 'property.duration',
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() {
const actions = [
{
label: this.$t('dialog.update.cancel'),
key: 'dialog.update.cancel',
handler: this.cancel,
icon: 'cancel'
}
]
if (!this.libraryStore.updating) {
actions.push({
label: this.$t('dialog.update.rescan'),
key: 'dialog.update.rescan',
handler: this.analyse,
icon: 'check'
})

View File

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

View File

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

View File

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

View File

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

View File

@ -1,112 +1,100 @@
<template>
<section class="section">
<div class="container">
<div class="columns is-centered">
<div class="column is-four-fifths">
<div class="content">
<nav class="level">
<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
:class="{ 'is-loading': libraryStore.updating }"
:disabled="libraryStore.updating"
:handler="showUpdateDialog"
icon="refresh"
label="page.about.update"
/>
</div>
</nav>
<div
v-for="property in properties"
:key="property.label"
class="media is-align-items-center mb-0"
>
<div
class="media-content has-text-weight-bold"
v-text="$t(property.label)"
/>
<div class="media-right">
<span v-text="property.value" />
<span
v-if="property.alternate"
class="has-text-grey"
v-text="` (${property.alternate})`"
/>
</div>
</div>
<div>
<content-with-heading>
<template #heading-left>
<heading-title :content="{ title: $t('page.about.library') }" />
</template>
<template #heading-right>
<control-button
:class="{ 'is-loading': libraryStore.updating }"
:button="{
disabled: libraryStore.updating,
handler: showUpdateDialog,
icon: 'refresh',
key: 'page.about.update'
}"
/>
</template>
<template #content>
<div
v-for="property in properties"
:key="property.key"
class="media is-align-items-center mb-0"
>
<div
class="media-content has-text-weight-bold"
v-text="$t(property.key)"
/>
<div class="media-right">
<span v-text="property.value" />
<span
v-if="property.alternate"
class="has-text-grey"
v-text="` (${property.alternate})`"
/>
</div>
</div>
</div>
</div>
</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="
$t('page.about.version', {
version: configurationStore.version
})
"
</template>
<template #footer>
<div
class="is-size-7 mt-6"
v-text="
$t('page.about.version', {
version: configurationStore.version
})
"
/>
<div
class="is-size-7"
v-text="
$t('page.about.compiled-with', {
options: configurationStore.buildoptions.join(', ')
})
"
/>
<i18n-t
tag="div"
class="is-size-7"
keypath="page.about.built-with"
scope="global"
>
<template #bulma>
<a href="https://bulma.io">Bulma</a>
</template>
<template #mdi>
<a href="https://pictogrammers.com/library/mdi/">
Material Design Icons
</a>
</template>
<template #vuejs>
<a href="https://vuejs.org/">Vue.js</a>
</template>
<template #axios>
<a href="https://github.com/axios/axios">axios</a>
</template>
<template #others>
<a
href="https://github.com/owntone/owntone-server/network/dependencies"
v-text="$t('page.about.more')"
/>
<p
class="is-size-7"
v-text="
$t('page.about.compiled-with', {
options: configurationStore.buildoptions.join(', ')
})
"
/>
<i18n-t
tag="p"
class="is-size-7"
keypath="page.about.built-with"
scope="global"
>
<template #bulma>
<a href="https://bulma.io">Bulma</a>
</template>
<template #mdi>
<a href="https://pictogrammers.com/library/mdi/">
Material Design Icons
</a>
</template>
<template #vuejs>
<a href="https://vuejs.org/">Vue.js</a>
</template>
<template #axios>
<a href="https://github.com/axios/axios">axios</a>
</template>
<template #others>
<a
href="https://github.com/owntone/owntone-server/network/dependencies"
v-text="$t('page.about.more')"
/>
</template>
</i18n-t>
</div>
</div>
</div>
</div>
</section>
</template>
</i18n-t>
</template>
</content-with-heading>
</div>
</template>
<script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import { useConfigurationStore } from '@/stores/configuration'
import { useLibraryStore } from '@/stores/library'
import { useUIStore } from '@/stores/ui'
export default {
name: 'PageAbout',
components: { ControlButton },
components: { ContentWithHeading, ControlButton, HeadingTitle },
setup() {
return {
configurationStore: useConfigurationStore(),
@ -118,32 +106,32 @@ export default {
properties() {
return [
{
label: 'property.name',
key: 'property.name',
value: this.configurationStore.library_name
},
{
label: 'property.artists',
key: 'property.artists',
value: this.$n(this.libraryStore.artists)
},
{
label: 'property.albums',
key: 'property.albums',
value: this.$n(this.libraryStore.albums)
},
{
label: 'property.tracks',
key: 'property.tracks',
value: this.$n(this.libraryStore.songs)
},
{
label: 'property.playtime',
key: 'property.playtime',
value: this.$filters.toDuration(this.libraryStore.db_playtime)
},
{
label: 'property.updated',
key: 'property.updated',
value: this.$filters.toRelativeDuration(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),
alternate: this.$filters.toDateTime(this.libraryStore.started_at)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,25 +2,19 @@
<div>
<content-with-heading>
<template #heading-left>
<div class="title is-4" v-text="composer.name" />
<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>
<heading-title :content="heading" />
</template>
<template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button
:handler="play"
icon="shuffle"
label="page.composer.shuffle"
/>
</div>
<control-button
:button="{ handler: showDetails, icon: 'dots-horizontal' }"
/>
<control-button
:button="{
handler: play,
icon: 'shuffle',
key: 'page.composer.shuffle'
}"
/>
</template>
<template #content>
<list-albums :items="albums" />
@ -38,6 +32,7 @@
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue'
import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
import webapi from '@/webapi'
@ -49,7 +44,6 @@ const dataObject = {
webapi.library_composer_albums(to.params.name)
])
},
set(vm, response) {
vm.composer = response[0].data
vm.albums = new GroupedList(response[1].data.albums)
@ -61,6 +55,7 @@ export default {
components: {
ContentWithHeading,
ControlButton,
HeadingTitle,
ListAlbums,
ModalDialogComposer
},
@ -79,6 +74,19 @@ export default {
computed: {
expression() {
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: {

View File

@ -17,25 +17,19 @@
</div>
</template>
<template #heading-left>
<div class="title is-4" v-text="composer.name" />
<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>
<heading-title :content="heading" />
</template>
<template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button
:handler="play"
icon="shuffle"
label="page.composer.shuffle"
/>
</div>
<control-button
:button="{ handler: showDetails, icon: 'dots-horizontal' }"
/>
<control-button
:button="{
handler: play,
icon: 'shuffle',
key: 'page.composer.shuffle'
}"
/>
</template>
<template #content>
<list-tracks :items="tracks" :expression="expression" />
@ -54,6 +48,7 @@ import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue'
import ControlDropdown from '@/components/ControlDropdown.vue'
import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue'
import ListTracks from '@/components/ListTracks.vue'
import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
@ -80,6 +75,7 @@ export default {
ContentWithHeading,
ControlButton,
ControlDropdown,
HeadingTitle,
IndexButtonList,
ListTracks,
ModalDialogComposer
@ -118,6 +114,19 @@ export default {
expression() {
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() {
const { options } = this.groupings.find(
(grouping) => grouping.id === this.uiStore.composer_tracks_sort

View File

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

View File

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

View File

@ -5,25 +5,19 @@
<index-button-list :indices="albums.indices" />
</template>
<template #heading-left>
<div class="title is-4" v-text="genre.name" />
<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>
<heading-title :content="heading" />
</template>
<template #heading-right>
<div class="buttons is-centered">
<control-button :handler="showDetails" icon="dots-horizontal" />
<control-button
:handler="play"
icon="shuffle"
label="page.genre.shuffle"
/>
</div>
<control-button
:button="{ handler: showDetails, icon: 'dots-horizontal' }"
/>
<control-button
:button="{
handler: play,
icon: 'shuffle',
key: 'page.genre.shuffle'
}"
/>
</template>
<template #content>
<list-albums :items="albums" />
@ -42,6 +36,7 @@
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlButton from '@/components/ControlButton.vue'
import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import IndexButtonList from '@/components/IndexButtonList.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogGenre from '@/components/ModalDialogGenre.vue'
@ -67,6 +62,7 @@ export default {
components: {
ContentWithHeading,
ControlButton,
HeadingTitle,
IndexButtonList,
ListAlbums,
ModalDialogGenre
@ -84,6 +80,21 @@ export default {
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: {
open_tracks() {
this.show_details_modal = false

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,9 @@
<tabs-music />
<content-with-heading>
<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 #content>
<list-tracks :items="tracks" />
@ -15,6 +17,7 @@
<script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListTracks from '@/components/ListTracks.vue'
import TabsMusic from '@/components/TabsMusic.vue'
import webapi from '@/webapi'
@ -28,7 +31,6 @@ const dataObject = {
type: 'track'
})
},
set(vm, response) {
vm.tracks = new GroupedList(response.data.tracks)
}
@ -36,14 +38,12 @@ const dataObject = {
export default {
name: 'PageMusicRecentlyPlayed',
components: { ContentWithHeading, ListTracks, TabsMusic },
components: { ContentWithHeading, HeadingTitle, ListTracks, TabsMusic },
beforeRouteEnter(to, from, next) {
dataObject.load(to).then((response) => {
next((vm) => dataObject.set(vm, response))
})
},
data() {
return {
tracks: {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -51,7 +51,7 @@
<template v-for="[type, items] in results" :key="type">
<content-with-heading>
<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 #content>
<component :is="components[type]" :items="items" />
@ -83,6 +83,7 @@
<script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList'
import HeadingTitle from '@/components/HeadingTitle.vue'
import ListAlbums from '@/components/ListAlbums.vue'
import ListArtists from '@/components/ListArtists.vue'
import ListComposers from '@/components/ListComposers.vue'
@ -107,6 +108,7 @@ export default {
name: 'PageSearchLibrary',
components: {
ContentWithHeading,
HeadingTitle,
ListAlbums,
ListArtists,
ListComposers,

View File

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

View File

@ -3,7 +3,9 @@
<tabs-settings />
<content-with-heading>
<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 #content>
<div
@ -79,17 +81,21 @@
<script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlSettingSwitch from '@/components/ControlSettingSwitch.vue'
import HeadingTitle from '@/components/HeadingTitle.vue'
import TabsSettings from '@/components/TabsSettings.vue'
import { useServicesStore } from '@/stores/services'
export default {
name: 'PageSettingsArtwork',
components: { ContentWithHeading, ControlSettingSwitch, TabsSettings },
components: {
ContentWithHeading,
ControlSettingSwitch,
HeadingTitle,
TabsSettings
},
setup() {
return { servicesStore: useServicesStore() }
},
computed: {
spotify() {
return this.servicesStore.spotify

View File

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

View File

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

View File

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

View File

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