[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

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>