Merge pull request #1476 from hacketiwack/i18n

Translation of Web UI
This commit is contained in:
Christian Meffert 2022-06-03 07:03:51 +02:00 committed by GitHub
commit ccdcc0cbf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 6624 additions and 1971 deletions

File diff suppressed because one or more lines are too long

3979
web-src/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,13 @@
"name": "owntone-web", "name": "owntone-web",
"version": "2.0.0", "version": "2.0.0",
"scripts": { "scripts": {
"dev": "vite",
"serve": "vite", "serve": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src", "lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
"format": "prettier . --write" "dev": "vite",
"format": "prettier . --write",
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"",
"preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@aacassandra/vue3-progressbar": "^1.0.3", "@aacassandra/vue3-progressbar": "^1.0.3",
@ -23,6 +24,7 @@
"spotify-web-api-js": "^1.5.2", "spotify-web-api-js": "^1.5.2",
"string-to-color": "^2.2.2", "string-to-color": "^2.2.2",
"vue": "^3.2.33", "vue": "^3.2.33",
"vue-i18n": "^9.1.0",
"vue-router": "^4.0.14", "vue-router": "^4.0.14",
"vue-scrollto": "^2.20.0", "vue-scrollto": "^2.20.0",
"vue3-click-away": "^1.2.4", "vue3-click-away": "^1.2.4",
@ -31,12 +33,13 @@
"vuex": "^4.0.2" "vuex": "^4.0.2"
}, },
"devDependencies": { "devDependencies": {
"@intlify/vite-plugin-vue-i18n": "^3.4.0",
"@vitejs/plugin-vue": "^2.3.1", "@vitejs/plugin-vue": "^2.3.1",
"eslint": "^8.13.0", "eslint": "^8.13.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-vue": "^8.6.0", "eslint-plugin-vue": "^8.6.0",
"prettier": "2.6.2", "prettier": "2.6.2",
"sass": "^1.50.0", "sass": "^1.50.0",
"vite": "^2.9.5" "vite": "^2.9.8"
} }
} }

View File

@ -116,15 +116,6 @@ export default {
methods: { methods: {
connect: function () { connect: function () {
/*
this.$store.dispatch('add_notification', {
text: 'Connecting to OwnTone server',
type: 'info',
topic: 'connection',
timeout: 2000
})
*/
webapi webapi
.config() .config()
.then(({ data }) => { .then(({ data }) => {
@ -137,7 +128,7 @@ export default {
}) })
.catch(() => { .catch(() => {
this.$store.dispatch('add_notification', { this.$store.dispatch('add_notification', {
text: 'Failed to connect to OwnTone server', text: this.$t('server.connection.failed'),
type: 'danger', type: 'danger',
topic: 'connection' topic: 'connection'
}) })
@ -147,7 +138,7 @@ export default {
open_ws: function () { open_ws: function () {
if (this.$store.state.config.websocket_port <= 0) { if (this.$store.state.config.websocket_port <= 0) {
this.$store.dispatch('add_notification', { this.$store.dispatch('add_notification', {
text: 'Missing websocket port', text: this.$t('server.missing-port'),
type: 'danger' type: 'danger'
}) })
return return
@ -183,14 +174,6 @@ export default {
}) })
socket.onopen = function () { socket.onopen = function () {
/*
vm.$store.dispatch('add_notification', {
text: 'Connection to server established',
type: 'primary',
topic: 'connection',
timeout: 2000
})
*/
vm.reconnect_attempts = 0 vm.reconnect_attempts = 0
socket.send( socket.send(
JSON.stringify({ JSON.stringify({
@ -221,17 +204,6 @@ export default {
socket.onclose = function () { socket.onclose = function () {
// vm.$store.dispatch('add_notification', { text: 'Connection closed', type: 'danger', timeout: 2000 }) // vm.$store.dispatch('add_notification', { text: 'Connection closed', type: 'danger', timeout: 2000 })
} }
/*
socket.onerror = function () {
vm.reconnect_attempts++
vm.$store.dispatch('add_notification', {
text:
'Connection lost. Reconnecting ... (' + vm.reconnect_attempts + ')',
type: 'danger',
topic: 'connection'
})
}
*/
// When the app becomes active, force an update of all information, because we // When the app becomes active, force an update of all information, because we
// may have missed notifications while the app was inactive. // may have missed notifications while the app was inactive.

View File

@ -11,23 +11,20 @@
aria-controls="dropdown-menu" aria-controls="dropdown-menu"
@click="is_active = !is_active" @click="is_active = !is_active"
> >
<span>{{ modelValue }}</span> <span v-text="option.name" />
<span class="icon"> <mdicon class="icon" name="chevron-down" size="16" />
<mdicon name="chevron-down" size="16" />
</span>
</button> </button>
</div> </div>
<div id="dropdown-menu" class="dropdown-menu" role="menu"> <div id="dropdown-menu" class="dropdown-menu" role="menu">
<div class="dropdown-content"> <div class="dropdown-content">
<a <a
v-for="option in options" v-for="o in options"
:key="option" :key="o.id"
class="dropdown-item" class="dropdown-item"
:class="{ 'is-active': modelValue === option }" :class="{ 'is-active': modelValue === o.id }"
@click="select(option)" @click="select(o)"
> v-text="o.name"
{{ option }} />
</a>
</div> </div>
</div> </div>
</div> </div>
@ -36,8 +33,6 @@
<script> <script>
export default { export default {
name: 'DropdownMenu', name: 'DropdownMenu',
// eslint-disable-next-line vue/prop-name-casing
props: ['modelValue', 'options'], props: ['modelValue', 'options'],
emits: ['update:modelValue'], emits: ['update:modelValue'],
@ -47,6 +42,14 @@ export default {
} }
}, },
computed: {
option: {
get() {
return this.options.find((option) => option.id === this.modelValue)
}
}
},
methods: { methods: {
onClickOutside(event) { onClickOutside(event) {
this.is_active = false this.is_active = false
@ -54,7 +57,7 @@ export default {
select(option) { select(option) {
this.is_active = false this.is_active = false
this.$emit('update:modelValue', option) this.$emit('update:modelValue', option.id)
} }
} }
} }

View File

@ -6,8 +6,8 @@
:key="char" :key="char"
class="button is-small" class="button is-small"
@click="nav(char)" @click="nav(char)"
>{{ char }}</a v-text="char"
> />
</nav> </nav>
</section> </section>
</template> </template>
@ -15,9 +15,7 @@
<script> <script>
export default { export default {
name: 'IndexButtonList', name: 'IndexButtonList',
props: ['index'], props: ['index'],
computed: { computed: {
filtered_index() { filtered_index() {
if (!this.index) { if (!this.index) {
@ -27,7 +25,6 @@ export default {
return this.index.filter((c) => !specialChars.includes(c)) return this.index.filter((c) => !specialChars.includes(c))
} }
}, },
methods: { methods: {
nav: function (id) { nav: function (id) {
this.$router.push({ hash: '#index_' + id }) this.$router.push({ hash: '#index_' + id })

View File

@ -4,8 +4,8 @@
<span <span
:id="'index_' + album.groupKey" :id="'index_' + album.groupKey"
class="tag is-info is-light is-small has-text-weight-bold" class="tag is-info is-light is-small has-text-weight-bold"
>{{ album.groupKey }}</span v-text="album.groupKey"
> />
</div> </div>
<div v-else-if="album.isItem" class="media" @click="open_album(album.item)"> <div v-else-if="album.isItem" class="media" @click="open_album(album.item)">
<div v-if="is_visible_artwork" class="media-left fd-has-action"> <div v-if="is_visible_artwork" class="media-left fd-has-action">
@ -24,25 +24,20 @@
</div> </div>
<div class="media-content fd-has-action is-clipped"> <div class="media-content fd-has-action is-clipped">
<div style="margin-top: 0.7rem"> <div style="margin-top: 0.7rem">
<h1 class="title is-6"> <h1 class="title is-6" v-text="album.item.name" />
{{ album.item.name }}
</h1>
<h2 class="subtitle is-7 has-text-grey"> <h2 class="subtitle is-7 has-text-grey">
<b>{{ album.item.artist }}</b> <b v-text="album.item.artist" />
</h2> </h2>
<h2 <h2
v-if="album.item.date_released && album.item.media_kind === 'music'" v-if="album.item.date_released && album.item.media_kind === 'music'"
class="subtitle is-7 has-text-grey has-text-weight-normal" class="subtitle is-7 has-text-grey has-text-weight-normal"
> v-text="$filters.date(album.item.date_released)"
{{ $filters.date(album.item.date_released) }} />
</h2>
</div> </div>
</div> </div>
<div class="media-right" style="padding-top: 0.7rem"> <div class="media-right" style="padding-top: 0.7rem">
<a @click.prevent.stop="open_dialog(album.item)"> <a @click.prevent.stop="open_dialog(album.item)">
<span class="icon has-text-dark" <mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
><mdicon name="dots-vertical" size="16"
/></span>
</a> </a>
</div> </div>
</div> </div>
@ -64,11 +59,10 @@
@delete="remove_podcast" @delete="remove_podcast"
> >
<template #modal-content> <template #modal-content>
<p>Permanently remove this podcast from your library?</p> <p v-text="$t('list.albums.info-1')" />
<p class="is-size-7"> <p class="is-size-7">
(This will also remove the RSS playlist (<span v-text="$t('list.albums.info-2')" />
<b>{{ rss_playlist_to_remove.name }}</b <b v-text="rss_playlist_to_remove.name" />)
>.)
</p> </p>
</template> </template>
</modal-dialog> </modal-dialog>
@ -152,7 +146,7 @@ export default {
const rssPlaylists = data.items.filter((pl) => pl.type === 'rss') const rssPlaylists = data.items.filter((pl) => pl.type === 'rss')
if (rssPlaylists.length !== 1) { if (rssPlaylists.length !== 1) {
this.$store.dispatch('add_notification', { this.$store.dispatch('add_notification', {
text: 'Podcast cannot be removed. Probably it was not added as an RSS playlist.', text: this.$t('list.albums.notification'),
type: 'danger' type: 'danger'
}) })
return return

View File

@ -5,8 +5,8 @@
<span <span
:id="'index_' + artist.groupKey" :id="'index_' + artist.groupKey"
class="tag is-info is-light is-small has-text-weight-bold" class="tag is-info is-light is-small has-text-weight-bold"
>{{ artist.groupKey }}</span v-text="artist.groupKey"
> />
</div> </div>
</div> </div>
<div <div
@ -15,15 +15,11 @@
@click="open_artist(artist.item)" @click="open_artist(artist.item)"
> >
<div class="media-content fd-has-action is-clipped"> <div class="media-content fd-has-action is-clipped">
<h1 class="title is-6"> <h1 class="title is-6" v-text="artist.item.name" />
{{ artist.item.name }}
</h1>
</div> </div>
<div class="media-right"> <div class="media-right">
<a @click.prevent.stop="open_dialog(artist.item)"> <a @click.prevent.stop="open_dialog(artist.item)">
<span class="icon has-text-dark" <mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
><mdicon name="dots-vertical" size="16"
/></span>
</a> </a>
</div> </div>
</div> </div>

View File

@ -5,8 +5,8 @@
<span <span
:id="'index_' + composer.groupKey" :id="'index_' + composer.groupKey"
class="tag is-info is-light is-small has-text-weight-bold" class="tag is-info is-light is-small has-text-weight-bold"
>{{ composer.groupKey }}</span v-text="composer.groupKey"
> />
</div> </div>
</div> </div>
<div <div
@ -15,15 +15,11 @@
@click="open_composer(composer.item)" @click="open_composer(composer.item)"
> >
<div class="media-content fd-has-action is-clipped"> <div class="media-content fd-has-action is-clipped">
<h1 class="title is-6"> <h1 class="title is-6" v-text="composer.item.name" />
{{ composer.item.name }}
</h1>
</div> </div>
<div class="media-right"> <div class="media-right">
<a @click.prevent.stop="open_dialog(composer.item)"> <a @click.prevent.stop="open_dialog(composer.item)">
<span class="icon has-text-dark" <mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
><mdicon name="dots-vertical" size="16"
/></span>
</a> </a>
</div> </div>
</div> </div>
@ -44,7 +40,6 @@ import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
export default { export default {
name: 'ListComposers', name: 'ListComposers',
components: { ModalDialogComposer }, components: { ModalDialogComposer },
props: ['composers', 'media_kind', 'hide_group_title'], props: ['composers', 'media_kind', 'hide_group_title'],
data() { data() {

View File

@ -5,9 +5,7 @@
@click="open_parent_directory()" @click="open_parent_directory()"
> >
<figure class="media-left fd-has-action"> <figure class="media-left fd-has-action">
<span class="icon"> <mdicon class="icon" name="subdirectory-arrow-left" size="16" />
<mdicon name="subdirectory-arrow-left" size="16" />
</span>
</figure> </figure>
<div class="media-content fd-has-action is-clipped"> <div class="media-content fd-has-action is-clipped">
<h1 class="title is-6">..</h1> <h1 class="title is-6">..</h1>
@ -19,23 +17,18 @@
<template v-for="directory in directories" :key="directory.path"> <template v-for="directory in directories" :key="directory.path">
<div class="media" @click="open_directory(directory)"> <div class="media" @click="open_directory(directory)">
<figure class="media-left fd-has-action"> <figure class="media-left fd-has-action">
<span class="icon"> <mdicon class="icon" name="folder" size="16" />
<mdicon name="folder" size="16" />
</span>
</figure> </figure>
<div class="media-content fd-has-action is-clipped"> <div class="media-content fd-has-action is-clipped">
<h1 class="title is-6"> <h1
{{ directory.path.substring(directory.path.lastIndexOf('/') + 1) }} class="title is-6"
</h1> v-text="directory.path.substring(directory.path.lastIndexOf('/') + 1)"
<h2 class="subtitle is-7 has-text-grey-light"> />
{{ directory.path }} <h2 class="subtitle is-7 has-text-grey-light" v-text="directory.path" />
</h2>
</div> </div>
<div class="media-right"> <div class="media-right">
<a @click.prevent.stop="open_dialog(directory)"> <a @click.prevent.stop="open_dialog(directory)">
<span class="icon has-text-dark" <mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
><mdicon name="dots-vertical" size="16"
/></span>
</a> </a>
</div> </div>
</div> </div>

View File

@ -5,21 +5,17 @@
<span <span
:id="'index_' + genre.groupKey" :id="'index_' + genre.groupKey"
class="tag is-info is-light is-small has-text-weight-bold" class="tag is-info is-light is-small has-text-weight-bold"
>{{ genre.groupKey }}</span v-text="genre.groupKey"
> />
</div> </div>
</div> </div>
<div v-else-if="genre.isItem" class="media" @click="open_genre(genre.item)"> <div v-else-if="genre.isItem" class="media" @click="open_genre(genre.item)">
<div class="media-content fd-has-action is-clipped"> <div class="media-content fd-has-action is-clipped">
<h1 class="title is-6"> <h1 class="title is-6" v-text="genre.item.name" />
{{ genre.item.name }}
</h1>
</div> </div>
<div class="media-right"> <div class="media-right">
<a @click.prevent.stop="open_dialog(genre.item)"> <a @click.prevent.stop="open_dialog(genre.item)">
<span class="icon has-text-dark" <mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
><mdicon name="dots-vertical" size="16"
/></span>
</a> </a>
</div> </div>
</div> </div>

View File

@ -1,11 +1,12 @@
<template> <template>
<div v-if="is_next || !show_only_next_items" class="media"> <div v-if="is_next || !show_only_next_items" class="media">
<div v-if="edit_mode" class="media-left"> <div v-if="edit_mode" class="media-left">
<span class="icon has-text-grey fd-is-movable handle" <mdicon
><mdicon name="drag-horizontal" size="16" class="icon has-text-grey fd-is-movable handle"
/></span> name="drag-horizontal"
size="16"
/>
</div> </div>
<div class="media-content fd-has-action is-clipped" @click="play"> <div class="media-content fd-has-action is-clipped" @click="play">
<h1 <h1
class="title is-6" class="title is-6"
@ -13,9 +14,8 @@
'has-text-primary': item.id === state.item_id, 'has-text-primary': item.id === state.item_id,
'has-text-grey-light': !is_next 'has-text-grey-light': !is_next
}" }"
> v-text="item.title"
{{ item.title }} />
</h1>
<h2 <h2
class="subtitle is-7" class="subtitle is-7"
:class="{ :class="{
@ -24,7 +24,7 @@
'has-text-grey': is_next && item.id !== state.item_id 'has-text-grey': is_next && item.id !== state.item_id
}" }"
> >
<b>{{ item.artist }}</b> <b v-text="item.artist" />
</h2> </h2>
<h2 <h2
class="subtitle is-7" class="subtitle is-7"
@ -33,9 +33,8 @@
'has-text-grey-light': !is_next, 'has-text-grey-light': !is_next,
'has-text-grey': is_next && item.id !== state.item_id 'has-text-grey': is_next && item.id !== state.item_id
}" }"
> v-text="item.album"
{{ item.album }} />
</h2>
</div> </div>
<div class="media-right"> <div class="media-right">
<slot name="actions" /> <slot name="actions" />

View File

@ -7,20 +7,14 @@
@click="open_playlist(playlist.item)" @click="open_playlist(playlist.item)"
> >
<figure class="media-left fd-has-action"> <figure class="media-left fd-has-action">
<span class="icon"> <mdicon class="icon" :name="icon_name(playlist.item)" size="16" />
<mdicon :name="icon_name(playlist.item)" size="16" />
</span>
</figure> </figure>
<div class="media-content fd-has-action is-clipped"> <div class="media-content fd-has-action is-clipped">
<h1 class="title is-6"> <h1 class="title is-6" v-text="playlist.item.name" />
{{ playlist.item.name }}
</h1>
</div> </div>
<div class="media-right"> <div class="media-right">
<a @click.prevent.stop="open_dialog(playlist.item)"> <a @click.prevent.stop="open_dialog(playlist.item)">
<span class="icon has-text-dark" <mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
><mdicon name="dots-vertical" size="16"
/></span>
</a> </a>
</div> </div>
</div> </div>

View File

@ -8,9 +8,7 @@
@click="play_track(index, track)" @click="play_track(index, track)"
> >
<figure v-if="show_icon" class="media-left fd-has-action"> <figure v-if="show_icon" class="media-left fd-has-action">
<span class="icon"> <mdicon class="icon" name="file-outline" size="16" />
<mdicon name="file-outline" size="16" />
</span>
</figure> </figure>
<div class="media-content fd-has-action is-clipped"> <div class="media-content fd-has-action is-clipped">
<h1 <h1
@ -19,15 +17,10 @@
'has-text-grey': 'has-text-grey':
track.media_kind === 'podcast' && track.play_count > 0 track.media_kind === 'podcast' && track.play_count > 0
}" }"
> v-text="track.title"
{{ track.title }} />
</h1> <h2 class="subtitle is-7 has-text-grey" v-text="track.artist" />
<h2 class="subtitle is-7 has-text-grey"> <h2 class="subtitle is-7 has-text-grey" v-text="track.album" />
<b>{{ track.artist }}</b>
</h2>
<h2 class="subtitle is-7 has-text-grey">
{{ track.album }}
</h2>
<progress-bar <progress-bar
v-if="show_progress" v-if="show_progress"
:max="track.length_ms" :max="track.length_ms"
@ -36,13 +29,10 @@
</div> </div>
<div class="media-right"> <div class="media-right">
<a @click.prevent.stop="open_dialog(track)"> <a @click.prevent.stop="open_dialog(track)">
<span class="icon has-text-dark" <mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
><mdicon name="dots-vertical" size="16"
/></span>
</a> </a>
</div> </div>
</div> </div>
<teleport to="#app"> <teleport to="#app">
<modal-dialog-track <modal-dialog-track
:show="show_details_modal" :show="show_details_modal"

View File

@ -6,33 +6,32 @@
<div class="modal-content fd-modal-card"> <div class="modal-content fd-modal-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p v-if="title" class="title is-4"> <p v-if="title" class="title is-4" v-text="title" />
{{ title }}
</p>
<slot name="modal-content" /> <slot name="modal-content" />
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="$emit('close')"> <a class="card-footer-item has-text-dark" @click="$emit('close')">
<span class="icon"><mdicon name="cancel" size="16" /></span> <mdicon class="icon" name="cancel" size="16" />
<span class="is-size-7">{{ <span
close_action ? close_action : 'Cancel' class="is-size-7"
}}</span> v-text="close_action ? close_action : t('dialog.cancel')"
/>
</a> </a>
<a <a
v-if="delete_action" v-if="delete_action"
class="card-footer-item has-background-danger has-text-white has-text-weight-bold" class="card-footer-item has-background-danger has-text-white has-text-weight-bold"
@click="$emit('delete')" @click="$emit('delete')"
> >
<span class="icon"><mdicon name="delete" size="16" /></span> <mdicon class="icon" name="delete" size="16" />
<span class="is-size-7">{{ delete_action }}</span> <span class="is-size-7" v-text="delete_action" />
</a> </a>
<a <a
v-if="ok_action" v-if="ok_action"
class="card-footer-item has-background-info has-text-white has-text-weight-bold" class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="$emit('ok')" @click="$emit('ok')"
> >
<span class="icon"><mdicon name="check" size="16" /></span> <mdicon class="icon" name="check" size="16" />
<span class="is-size-7">{{ ok_action }}</span> <span class="is-size-7" v-text="ok_action" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -6,7 +6,7 @@
<div class="modal-content fd-modal-card"> <div class="modal-content fd-modal-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4">Add Podcast RSS feed URL</p> <p class="title is-4" v-text="$t('dialog.add.rss.title')" />
<form @submit.prevent="add_stream"> <form @submit.prevent="add_stream">
<div class="field"> <div class="field">
<p class="control is-expanded has-icons-left"> <p class="control is-expanded has-icons-left">
@ -15,24 +15,22 @@
v-model="url" v-model="url"
class="input is-shadowless" class="input is-shadowless"
type="text" type="text"
placeholder="http://url-to-rss" :placeholder="$t('dialog.add.rss.placeholder')"
:disabled="loading" :disabled="loading"
/> />
<span class="icon is-left"> <mdicon class="icon is-left" name="rss" size="16" />
<mdicon name="rss" size="16" />
</span>
</p>
<p class="help">
Adding a podcast includes creating an RSS playlist, that
will allow OwnTone to manage the podcast subscription.
</p> </p>
<p class="help" v-text="$t('dialog.add.rss.help')" />
</div> </div>
</form> </form>
</div> </div>
<footer v-if="loading" class="card-footer"> <footer v-if="loading" class="card-footer">
<a class="card-footer-item button is-loading"> <a class="card-footer-item button is-loading">
<span class="icon"><mdicon name="web" size="16" /></span> <mdicon class="icon" name="web" size="16" />
<span class="is-size-7">Processing ...</span> <span
class="is-size-7"
v-text="$t('dialog.add.rss.processing')"
/>
</a> </a>
</footer> </footer>
<footer v-else class="card-footer"> <footer v-else class="card-footer">
@ -40,17 +38,15 @@
class="card-footer-item has-text-danger" class="card-footer-item has-text-danger"
@click="$emit('close')" @click="$emit('close')"
> >
<span class="icon"><mdicon name="cancel" size="16" /></span> <mdicon class="icon" name="cancel" size="16" />
<span class="is-size-7">Cancel</span> <span class="is-size-7" v-text="$t('dialog.add.rss.cancel')" />
</a> </a>
<a <a
class="card-footer-item has-background-info has-text-white has-text-weight-bold" class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="add_stream" @click="add_stream"
> >
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span class="is-size-7" v-text="$t('dialog.add.rss.add')" />
/></span>
<span class="is-size-7">Add</span>
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -6,7 +6,7 @@
<div class="modal-content fd-modal-card"> <div class="modal-content fd-modal-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4">Add stream URL</p> <p class="title is-4" v-text="$t('dialog.add.stream.title')" />
<form class="fd-has-margin-bottom" @submit.prevent="play"> <form class="fd-has-margin-bottom" @submit.prevent="play">
<div class="field"> <div class="field">
<p class="control is-expanded has-icons-left"> <p class="control is-expanded has-icons-left">
@ -15,20 +15,21 @@
v-model="url" v-model="url"
class="input is-shadowless" class="input is-shadowless"
type="text" type="text"
placeholder="http://url-to-stream" :placeholder="$t('dialog.add.stream.placeholder')"
:disabled="loading" :disabled="loading"
/> />
<span class="icon is-left"> <mdicon class="icon is-left" name="web" size="16" />
<mdicon name="web" size="16" />
</span>
</p> </p>
</div> </div>
</form> </form>
</div> </div>
<footer v-if="loading" class="card-footer"> <footer v-if="loading" class="card-footer">
<a class="card-footer-item has-text-dark"> <a class="card-footer-item has-text-dark">
<span class="icon"><mdicon name="web" size="16" /></span> <mdicon class="icon" name="web" size="16" />
<span class="is-size-7">Loading ...</span> <span
class="is-size-7"
v-text="$t('dialog.add.stream.loading')"
/>
</a> </a>
</footer> </footer>
<footer v-else class="card-footer"> <footer v-else class="card-footer">
@ -36,21 +37,22 @@
class="card-footer-item has-text-danger" class="card-footer-item has-text-danger"
@click="$emit('close')" @click="$emit('close')"
> >
<span class="icon"><mdicon name="cancel" size="16" /></span> <mdicon class="icon" name="cancel" size="16" />
<span class="is-size-7">Cancel</span> <span
class="is-size-7"
v-text="$t('dialog.add.stream.cancel')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="add_stream"> <a class="card-footer-item has-text-dark" @click="add_stream">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span class="is-size-7" v-text="$t('dialog.add.stream.add')" />
/></span>
<span class="is-size-7">Add</span>
</a> </a>
<a <a
class="card-footer-item has-background-info has-text-white has-text-weight-bold" class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="play" @click="play"
> >
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span class="is-size-7" v-text="$t('dialog.add.stream.play')" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -13,75 +13,91 @@
class="image is-square fd-has-margin-bottom fd-has-shadow" class="image is-square fd-has-margin-bottom fd-has-shadow"
/> />
<p class="title is-4"> <p class="title is-4">
<a class="has-text-link" @click="open_album">{{ <a
album.name class="has-text-link"
}}</a> @click="open_album"
v-text="album.name"
/>
</p> </p>
<div v-if="media_kind_resolved === 'podcast'" class="buttons"> <div v-if="media_kind_resolved === 'podcast'" class="buttons">
<a class="button is-small" @click="mark_played" <a
>Mark as played</a class="button is-small"
> @click="mark_played"
<a class="button is-small" @click="$emit('remove-podcast')" v-text="$t('dialog.album.mark-as-played')"
>Remove podcast</a />
> <a
class="button is-small"
@click="$emit('remove-podcast')"
v-text="$t('dialog.album.remove-podcast')"
/>
</div> </div>
<div class="content is-small"> <div class="content is-small">
<p v-if="album.artist"> <p v-if="album.artist">
<span class="heading">Album artist</span> <span class="heading" v-text="$t('dialog.album.artist')" />
<a class="title is-6 has-text-link" @click="open_artist">{{ <a
album.artist class="title is-6 has-text-link"
}}</a> @click="open_artist"
v-text="album.artist"
/>
</p> </p>
<p v-if="album.date_released"> <p v-if="album.date_released">
<span class="heading">Release date</span> <span
<span class="title is-6">{{ class="heading"
$filters.date(album.date_released) v-text="$t('dialog.album.release-date')"
}}</span> />
<span
class="title is-6"
v-text="$filters.date(album.date_released)"
/>
</p> </p>
<p v-else-if="album.year > 0"> <p v-else-if="album.year > 0">
<span class="heading">Year</span> <span class="heading" v-text="$t('dialog.album.year')" />
<span class="title is-6">{{ album.year }}</span> <span class="title is-6" v-text="album.year" />
</p> </p>
<p> <p>
<span class="heading">Tracks</span> <span class="heading" v-text="$t('dialog.album.tracks')" />
<span class="title is-6">{{ album.track_count }}</span> <span class="title is-6" v-text="album.track_count" />
</p> </p>
<p> <p>
<span class="heading">Length</span> <span class="heading" v-text="$t('dialog.album.duration')" />
<span class="title is-6">{{ <span
$filters.durationInHours(album.length_ms) class="title is-6"
}}</span> v-text="$filters.durationInHours(album.length_ms)"
/>
</p> </p>
<p> <p>
<span class="heading">Type</span> <span class="heading" v-text="$t('dialog.album.type')" />
<span class="title is-6" <span
>{{ album.media_kind }} - {{ album.data_kind }}</span class="title is-6"
> v-text="
[
t('media.kind.' + album.media_kind),
t('data.kind.' + album.data_kind)
].join(' - ')
"
/>
</p> </p>
<p> <p>
<span class="heading">Added at</span> <span class="heading" v-text="$t('dialog.album.added-on')" />
<span class="title is-6">{{ <span
$filters.datetime(album.time_added) class="title is-6"
}}</span> v-text="$filters.datetime(album.time_added)"
/>
</p> </p>
</div> </div>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span class="is-size-7" v-text="$t('dialog.album.add')" />
/></span>
<span class="is-size-7">Add</span>
</a> </a>
<a class="card-footer-item has-text-dark" @click="queue_add_next"> <a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon" <mdicon class="icon" name="playlist-play" size="16" />
><mdicon name="playlist-play" size="16" <span class="is-size-7" v-text="$t('dialog.album.add-next')" />
/></span>
<span class="is-size-7">Add Next</span>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play"> <a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span class="is-size-7" v-text="$t('dialog.album.play')" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -7,47 +7,49 @@
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4"> <p class="title is-4">
<a class="has-text-link" @click="open_artist">{{ <a
artist.name class="has-text-link"
}}</a> @click="open_artist"
v-text="artist.name"
/>
</p> </p>
<div class="content is-small"> <div class="content is-small">
<p> <p>
<span class="heading">Albums</span> <span class="heading" v-text="$t('dialog.artist.albums')" />
<span class="title is-6">{{ artist.album_count }}</span> <span class="title is-6" v-text="artist.album_count" />
</p> </p>
<p> <p>
<span class="heading">Tracks</span> <span class="heading" v-text="$t('dialog.artist.tracks')" />
<span class="title is-6">{{ artist.track_count }}</span> <span class="title is-6" v-text="artist.track_count" />
</p> </p>
<p> <p>
<span class="heading">Type</span> <span class="heading" v-text="$t('dialog.artist.type')" />
<span class="title is-6">{{ artist.data_kind }}</span> <span
class="title is-6"
v-text="$t('data.kind.' + artist.data_kind)"
/>
</p> </p>
<p> <p>
<span class="heading">Added at</span> <span class="heading" v-text="$t('dialog.artist.added-on')" />
<span class="title is-6">{{ <span
$filters.datetime(artist.time_added) class="title is-6"
}}</span> v-text="$filters.datetime(artist.time_added)"
/>
</p> </p>
</div> </div>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span class="is-size-7" v-text="$t('dialog.artist.add')" />
/></span>
<span class="is-size-7">Add</span>
</a> </a>
<a class="card-footer-item has-text-dark" @click="queue_add_next"> <a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon" <mdicon class="icon" name="playlist-play" size="16" />
><mdicon name="playlist-play" size="16" <span class="is-size-7" v-text="$t('dialog.artist.add-next')" />
/></span>
<span class="is-size-7">Add Next</span>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play"> <a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span class="is-size-7" v-text="$t('dialog.artist.play')" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -7,45 +7,51 @@
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4"> <p class="title is-4">
<a class="has-text-link" @click="open_albums">{{ <a
composer.name class="has-text-link"
}}</a> @click="open_albums"
v-text="composer.name"
/>
</p> </p>
<p> <p>
<span class="heading">Albums</span> <span class="heading" v-text="$t('dialog.composer.albums')" />
<a class="has-text-link is-6" @click="open_albums">{{ <a
composer.album_count class="has-text-link is-6"
}}</a> @click="open_albums"
v-text="composer.album_count"
/>
</p> </p>
<p> <p>
<span class="heading">Tracks</span> <span class="heading" v-text="$t('dialog.composer.tracks')" />
<a class="has-text-link is-6" @click="open_tracks">{{ <a
composer.track_count class="has-text-link is-6"
}}</a> @click="open_tracks"
v-text="composer.track_count"
/>
</p> </p>
<p> <p>
<span class="heading">Length</span> <span class="heading" v-text="$t('dialog.composer.duration')" />
<span class="title is-6">{{ <span
$filters.durationInHours(composer.length_ms) class="title is-6"
}}</span> v-text="$filters.durationInHours(composer.length_ms)"
/>
</p> </p>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span class="is-size-7" v-text="$t('dialog.composer.add')" />
/></span>
<span class="is-size-7">Add</span>
</a> </a>
<a class="card-footer-item has-text-dark" @click="queue_add_next"> <a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon" <mdicon class="icon" name="playlist-play" size="16" />
><mdicon name="playlist-play" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Add Next</span> v-text="$t('dialog.composer.add-next')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play"> <a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span class="is-size-7" v-text="$t('dialog.composer.play')" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -6,26 +6,23 @@
<div class="modal-content fd-modal-card"> <div class="modal-content fd-modal-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4"> <p class="title is-4" v-text="directory.path" />
{{ directory.path }}
</p>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span class="is-size-7" v-text="$t('dialog.directory.add')" />
/></span>
<span class="is-size-7">Add</span>
</a> </a>
<a class="card-footer-item has-text-dark" @click="queue_add_next"> <a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon" <mdicon class="icon" name="playlist-play" size="16" />
><mdicon name="playlist-play" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Add Next</span> v-text="$t('dialog.directory.add-next')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play"> <a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span class="is-size-7" v-text="$t('dialog.directory.play')" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -7,43 +7,42 @@
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4"> <p class="title is-4">
<a class="has-text-link" @click="open_genre">{{ <a
genre.name class="has-text-link"
}}</a> @click="open_genre"
v-text="genre.name"
/>
</p> </p>
<div class="content is-small"> <div class="content is-small">
<p> <p>
<span class="heading">Albums</span> <span class="heading" v-text="$t('dialog.genre.albums')" />
<span class="title is-6">{{ genre.album_count }}</span> <span class="title is-6" v-text="genre.album_count" />
</p> </p>
<p> <p>
<span class="heading">Tracks</span> <span class="heading" v-text="$t('dialog.genre.tracks')" />
<span class="title is-6">{{ genre.track_count }}</span> <span class="title is-6" v-text="genre.track_count" />
</p> </p>
<p> <p>
<span class="heading">Length</span> <span class="heading" v-text="$t('dialog.genre.duration')" />
<span class="title is-6">{{ <span
$filters.durationInHours(genre.length_ms) class="title is-6"
}}</span> v-text="$filters.durationInHours(genre.length_ms)"
/>
</p> </p>
</div> </div>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span class="is-size-7" v-text="$t('dialog.genre.add')" />
/></span>
<span class="is-size-7">Add</span>
</a> </a>
<a class="card-footer-item has-text-dark" @click="queue_add_next"> <a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon" <mdicon class="icon" name="playlist-play" size="16" />
><mdicon name="playlist-play" size="16" <span class="is-size-7" v-text="$t('dialog.genre.add-next')" />
/></span>
<span class="is-size-7">Add Next</span>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play"> <a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span class="is-size-7" v-text="$t('dialog.genre.play')" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -7,41 +7,45 @@
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4"> <p class="title is-4">
<a class="has-text-link" @click="open_playlist">{{ <a
playlist.name class="has-text-link"
}}</a> @click="open_playlist"
v-text="playlist.name"
/>
</p> </p>
<div class="content is-small"> <div class="content is-small">
<p> <p>
<span class="heading">Path</span> <span class="heading">Path</span>
<span class="title is-6">{{ playlist.path }}</span> <span class="title is-6" v-text="playlist.path" />
</p> </p>
<p> <p>
<span class="heading">Type</span> <span class="heading" v-text="$t('dialog.playlist.type')" />
<span class="title is-6">{{ playlist.type }}</span> <span class="title is-6" v-text="playlist.type" />
</p> </p>
<p v-if="!playlist.folder"> <p v-if="!playlist.folder">
<span class="heading">Track count</span> <span
<span class="title is-6">{{ playlist.item_count }}</span> class="heading"
v-text="$t('dialog.playlist.track-count')"
/>
<span class="title is-6" v-text="playlist.item_count" />
</p> </p>
</div> </div>
</div> </div>
<footer v-if="!playlist.folder" class="card-footer"> <footer v-if="!playlist.folder" class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span class="is-size-7" v-text="$t('dialog.playlist.add')" />
/></span>
<span class="is-size-7">Add</span>
</a> </a>
<a class="card-footer-item has-text-dark" @click="queue_add_next"> <a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon" <mdicon class="icon" name="playlist-play" size="16" />
><mdicon name="playlist-play" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Add Next</span> v-text="$t('dialog.playlist.add-next')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play"> <a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span class="is-size-7" v-text="$t('dialog.playlist.play')" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -6,7 +6,7 @@
<div class="modal-content fd-modal-card"> <div class="modal-content fd-modal-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4">Save queue to playlist</p> <p class="title is-4" v-text="$t('dialog.playlist.save.title')" />
<form class="fd-has-margin-bottom" @submit.prevent="save"> <form class="fd-has-margin-bottom" @submit.prevent="save">
<div class="field"> <div class="field">
<p class="control is-expanded has-icons-left"> <p class="control is-expanded has-icons-left">
@ -18,17 +18,18 @@
placeholder="Playlist name" placeholder="Playlist name"
:disabled="loading" :disabled="loading"
/> />
<span class="icon is-left"> <mdicon class="icon is-left" name="file-music" size="16" />
<mdicon name="file-music" size="16" />
</span>
</p> </p>
</div> </div>
</form> </form>
</div> </div>
<footer v-if="loading" class="card-footer"> <footer v-if="loading" class="card-footer">
<a class="card-footer-item has-text-dark"> <a class="card-footer-item has-text-dark">
<span class="icon"><mdicon name="web" size="16" /></span> <mdicon class="icon" name="web" size="16" />
<span class="is-size-7">Saving ...</span> <span
class="is-size-7"
v-text="$t('dialog.playlist.save.saving')"
/>
</a> </a>
</footer> </footer>
<footer v-else class="card-footer"> <footer v-else class="card-footer">
@ -36,17 +37,21 @@
class="card-footer-item has-text-danger" class="card-footer-item has-text-danger"
@click="$emit('close')" @click="$emit('close')"
> >
<span class="icon"><mdicon name="cancel" size="16" /></span> <mdicon class="icon" name="cancel" size="16" />
<span class="is-size-7">Cancel</span> <span
class="is-size-7"
v-text="$t('dialog.playlist.save.cancel')"
/>
</a> </a>
<a <a
class="card-footer-item has-background-info has-text-white has-text-weight-bold" class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="save" @click="save"
> >
<span class="icon" <mdicon class="icon" name="content-save" size="16" />
><mdicon name="content-save" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Save</span> v-text="$t('dialog.playlist.save.save')"
/>
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -6,98 +6,146 @@
<div class="modal-content fd-modal-card"> <div class="modal-content fd-modal-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4"> <p class="title is-4" v-text="item.title" />
{{ item.title }} <p class="subtitle" v-text="item.artist" />
</p>
<p class="subtitle">
{{ item.artist }}
</p>
<div class="content is-small"> <div class="content is-small">
<p> <p>
<span class="heading">Album</span> <span
class="heading"
v-text="$t('dialog.queue-item.album')"
/>
<a <a
v-if="item.album_id" v-if="item.album_id"
class="title is-6 has-text-link" class="title is-6 has-text-link"
@click="open_album" @click="open_album"
>{{ item.album }}</a v-text="item.album"
> />
<span v-else class="title is-6">{{ item.album }}</span> <span v-else class="title is-6" v-text="item.album" />
</p> </p>
<p v-if="item.album_artist"> <p v-if="item.album_artist">
<span class="heading">Album artist</span> <span
class="heading"
v-text="$t('dialog.queue-item.album-artist')"
/>
<a <a
v-if="item.album_artist_id" v-if="item.album_artist_id"
class="title is-6 has-text-link" class="title is-6 has-text-link"
@click="open_album_artist" @click="open_album_artist"
>{{ item.album_artist }}</a v-text="item.album_artist"
> />
<span v-else class="title is-6">{{ item.album_artist }}</span> <span v-else class="title is-6" v-text="item.album_artist" />
</p> </p>
<p v-if="item.composer"> <p v-if="item.composer">
<span class="heading">Composer</span> <span
<span class="title is-6">{{ item.composer }}</span> class="heading"
v-text="$t('dialog.queue-item.composer')"
/>
<span class="title is-6" v-text="item.composer" />
</p> </p>
<p v-if="item.year > 0"> <p v-if="item.year > 0">
<span class="heading">Year</span> <span class="heading" v-text="$t('dialog.queue-item.year')" />
<span class="title is-6">{{ item.year }}</span> <span class="title is-6" v-text="item.year" />
</p> </p>
<p v-if="item.genre"> <p v-if="item.genre">
<span class="heading">Genre</span> <span
<a class="title is-6 has-text-link" @click="open_genre">{{ class="heading"
item.genre v-text="$t('dialog.queue-item.genre')"
}}</a> />
<a
class="title is-6 has-text-link"
@click="open_genre"
v-text="item.genre"
/>
</p> </p>
<p> <p>
<span class="heading">Track / Disc</span> <span
<span class="title is-6" class="heading"
>{{ item.track_number }} / {{ item.disc_number }}</span v-text="$t('dialog.queue-item.position')"
> />
<span
class="title is-6"
v-text="[item.disc_number, item.track_number].join(' / ')"
/>
</p> </p>
<p> <p>
<span class="heading">Length</span> <span
<span class="title is-6">{{ class="heading"
$filters.durationInHours(item.length_ms) v-text="$t('dialog.queue-item.duration')"
}}</span> />
<span
class="title is-6"
v-text="$filters.durationInHours(item.length_ms)"
/>
</p> </p>
<p> <p>
<span class="heading">Path</span> <span class="heading" v-text="$t('dialog.queue-item.path')" />
<span class="title is-6">{{ item.path }}</span> <span class="title is-6" v-text="item.path" />
</p> </p>
<p> <p>
<span class="heading">Type</span> <span class="heading" v-text="$t('dialog.queue-item.type')" />
<span class="title is-6" <span class="title is-6">
>{{ item.media_kind }} - {{ item.data_kind }} <span
v-text="[item.media_kind, item.data_kind].join(' - ')"
/>
<span <span
v-if="item.data_kind === 'spotify'" v-if="item.data_kind === 'spotify'"
class="has-text-weight-normal" class="has-text-weight-normal"
>(<a @click="open_spotify_artist">artist</a>, >
<a @click="open_spotify_album">album</a>)</span (<a
></span @click="open_spotify_artist"
> v-text="$t('dialog.queue-item.spotify-artist')"
/>,
<a
@click="open_spotify_album"
v-text="$t('dialog.queue-item.spotify-album')"
/>)
</span>
</span>
</p> </p>
<p> <p>
<span class="heading">Quality</span> <span
class="heading"
v-text="$t('dialog.queue-item.quality')"
/>
<span class="title is-6"> <span class="title is-6">
{{ item.type }} <span v-text="item.type" />
<span v-if="item.samplerate"> <span
| {{ item.samplerate }} Hz</span v-if="item.samplerate"
> v-text="
<span v-if="item.channels"> $t('dialog.queue-item.samplerate', {
| {{ $filters.channels(item.channels) }}</span rate: item.samplerate
> })
<span v-if="item.bitrate"> | {{ item.bitrate }} Kb/s</span> "
/>
<span
v-if="item.channels"
v-text="
$t('dialog.queue-item.channels', {
channels: $filters.channels(item.channels)
})
"
/>
<span
v-if="item.bitrate"
v-text="
$t('dialog.queue-item.bitrate', { rate: item.bitrate })
"
/>
</span> </span>
</p> </p>
</div> </div>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="remove"> <a class="card-footer-item has-text-dark" @click="remove">
<span class="icon"><mdicon name="delete" size="16" /></span> <mdicon class="icon" name="delete" size="16" />
<span class="is-size-7">Remove</span> <span
class="is-size-7"
v-text="$t('dialog.queue-item.remove')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play"> <a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span class="is-size-7" v-text="$t('dialog.queue-item.play')" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -6,11 +6,12 @@
<div class="modal-content fd-modal-card"> <div class="modal-content fd-modal-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4">Remote pairing request</p> <p
class="title is-4"
v-text="$t('dialog.remote-pairing.title')"
/>
<form @submit.prevent="kickoff_pairing"> <form @submit.prevent="kickoff_pairing">
<label class="label"> <label class="label" v-text="pairing.remote" />
{{ pairing.remote }}
</label>
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input <input
@ -29,15 +30,21 @@
class="card-footer-item has-text-danger" class="card-footer-item has-text-danger"
@click="$emit('close')" @click="$emit('close')"
> >
<span class="icon"><mdicon name="cancel" size="16" /></span> <mdicon class="icon" name="cancel" size="16" />
<span class="is-size-7">Cancel</span> <span
class="is-size-7"
v-text="$t('dialog.remote-pairing.cancel')"
/>
</a> </a>
<a <a
class="card-footer-item has-background-info has-text-white has-text-weight-bold" class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="kickoff_pairing" @click="kickoff_pairing"
> >
<span class="icon"><mdicon name="cellphone" size="16" /></span> <mdicon class="icon" name="cellphone" size="16" />
<span class="is-size-7">Pair Remote</span> <span
class="is-size-7"
v-text="$t('dialog.remote-pairing.pair')"
/>
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -6,138 +6,173 @@
<div class="modal-content fd-modal-card"> <div class="modal-content fd-modal-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4"> <p class="title is-4" v-text="track.title" />
{{ track.title }} <p class="subtitle" v-text="track.artist" />
</p>
<p class="subtitle">
{{ track.artist }}
</p>
<div v-if="track.media_kind === 'podcast'" class="buttons"> <div v-if="track.media_kind === 'podcast'" class="buttons">
<a <a
v-if="track.play_count > 0" v-if="track.play_count > 0"
class="button is-small" class="button is-small"
@click="mark_new" @click="mark_new"
>Mark as new</a v-text="$t('dialog.track.mark-as-new')"
> />
<a <a
v-if="track.play_count === 0" v-if="track.play_count === 0"
class="button is-small" class="button is-small"
@click="mark_played" @click="mark_played"
>Mark as played</a v-text="$t('dialog.track.mark-as-played')"
> />
</div> </div>
<div class="content is-small"> <div class="content is-small">
<p> <p>
<span class="heading">Album</span> <span class="heading" v-text="$t('dialog.track.album')" />
<a class="title is-6 has-text-link" @click="open_album">{{ <a
track.album class="title is-6 has-text-link"
}}</a> @click="open_album"
v-text="track.album"
/>
</p> </p>
<p <p
v-if="track.album_artist && track.media_kind !== 'audiobook'" v-if="track.album_artist && track.media_kind !== 'audiobook'"
> >
<span class="heading">Album artist</span> <span
<a class="title is-6 has-text-link" @click="open_artist">{{ class="heading"
track.album_artist v-text="$t('dialog.track.album-artist')"
}}</a> />
<a
class="title is-6 has-text-link"
@click="open_artist"
v-text="track.album_artist"
/>
</p> </p>
<p v-if="track.composer"> <p v-if="track.composer">
<span class="heading">Composer</span> <span class="heading" v-text="$t('dialog.track.composer')" />
<span class="title is-6">{{ track.composer }}</span> <span class="title is-6" v-text="track.composer" />
</p> </p>
<p v-if="track.date_released"> <p v-if="track.date_released">
<span class="heading">Release date</span> <span
<span class="title is-6">{{ class="heading"
$filters.date(track.date_released) v-text="$t('dialog.track.release-date')"
}}</span> />
<span
class="title is-6"
v-text="$filters.date(track.date_released)"
/>
</p> </p>
<p v-else-if="track.year > 0"> <p v-else-if="track.year > 0">
<span class="heading">Year</span> <span class="heading" v-text="$t('dialog.track.year')" />
<span class="title is-6">{{ track.year }}</span> <span class="title is-6" v-text="track.year" />
</p> </p>
<p v-if="track.genre"> <p v-if="track.genre">
<span class="heading">Genre</span> <span class="heading" v-text="$t('dialog.track.genre')" />
<a class="title is-6 has-text-link" @click="open_genre">{{ <a
track.genre class="title is-6 has-text-link"
}}</a> @click="open_genre"
v-text="track.genre"
/>
</p> </p>
<p> <p>
<span class="heading">Track / Disc</span> <span class="heading" v-text="$t('dialog.track.position')" />
<span class="title is-6" <span
>{{ track.track_number }} / {{ track.disc_number }}</span class="title is-6"
> v-text="[track.disc_number, track.track_number].join(' / ')"
/>
</p> </p>
<p> <p>
<span class="heading">Length</span> <span class="heading" v-text="$t('dialog.track.duration')" />
<span class="title is-6">{{ <span
$filters.durationInHours(track.length_ms) class="title is-6"
}}</span> v-text="$filters.durationInHours(track.length_ms)"
/>
</p> </p>
<p> <p>
<span class="heading">Path</span> <span class="heading" v-text="$t('dialog.track.path')" />
<span class="title is-6">{{ track.path }}</span> <span class="title is-6" v-text="track.path" />
</p> </p>
<p> <p>
<span class="heading">Type</span> <span class="heading" v-text="$t('dialog.track.type')" />
<span class="title is-6" <span class="title is-6">
>{{ track.media_kind }} - {{ track.data_kind }} <span
v-text="[track.media_kind, track.data_kind].join(' - ')"
/>
<span <span
v-if="track.data_kind === 'spotify'" v-if="track.data_kind === 'spotify'"
class="has-text-weight-normal" class="has-text-weight-normal"
>(<a @click="open_spotify_artist">artist</a>,
<a @click="open_spotify_album">album</a>)</span
></span
>
</p>
<p>
<span class="heading">Quality</span>
<span class="title is-6">
{{ track.type }}
<span v-if="track.samplerate">
| {{ track.samplerate }} Hz</span
>
<span v-if="track.channels">
| {{ $filters.channels(track.channels) }}</span
>
<span v-if="track.bitrate">
| {{ track.bitrate }} Kb/s</span
> >
(<a
@click="open_spotify_artist"
v-text="$t('dialog.track.spotify-artist')"
/>,
<a
@click="open_spotify_album"
v-text="$t('dialog.track.spotify-album')"
/>)
</span>
</span> </span>
</p> </p>
<p> <p>
<span class="heading">Added at</span> <span class="heading" v-text="$t('dialog.track.quality')" />
<span class="title is-6">{{ <span class="title is-6">
$filters.datetime(track.time_added) <span v-text="track.type" />
}}</span> <span
v-if="track.samplerate"
v-text="
$t('dialog.track.samplerate', {
rate: track.samplerate
})
"
/>
<span
v-if="track.channels"
v-text="
$t('dialog.track.channels', {
channels: $filters.channels(track.channels)
})
"
/>
<span
v-if="track.bitrate"
v-text="
$t('dialog.track.bitrate', { rate: track.bitrate })
"
/>
</span>
</p> </p>
<p> <p>
<span class="heading">Rating</span> <span class="heading" v-text="$t('dialog.track.added-on')" />
<span class="title is-6" <span
>{{ Math.floor(track.rating / 10) }} / 10</span class="title is-6"
> v-text="$filters.datetime(track.time_added)"
/>
</p>
<p>
<span class="heading" v-text="$t('dialog.track.rating')" />
<span
class="title is-6"
v-text="
$t('dialog.track.rating-value', {
rating: Math.floor(track.rating / 10)
})
"
/>
</p> </p>
<p v-if="track.comment"> <p v-if="track.comment">
<span class="heading">Comment</span> <span class="heading" v-text="$t('dialog.track.comment')" />
<span class="title is-6">{{ track.comment }}</span> <span class="title is-6" v-text="track.comment" />
</p> </p>
</div> </div>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span class="is-size-7" v-text="$t('dialog.track.add')" />
/></span>
<span class="is-size-7">Add</span>
</a> </a>
<a class="card-footer-item has-text-dark" @click="queue_add_next"> <a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon" <mdicon class="icon" name="playlist-play" size="16" />
><mdicon name="playlist-play" size="16" <span class="is-size-7" v-text="$t('dialog.track.add-next')" />
/></span>
<span class="is-size-7">Add Next</span>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play_track"> <a class="card-footer-item has-text-dark" @click="play_track">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span class="is-size-7" v-text="$t('dialog.track.play')" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -1,40 +1,48 @@
<template> <template>
<modal-dialog <modal-dialog
:show="show" :show="show"
title="Update library" :title="$t('dialog.update.title')"
:ok_action="library.updating ? '' : 'Rescan'" :ok_action="library.updating ? '' : $t('dialog.update.rescan')"
close_action="Close" :close_action="$t('dialog.update.cancel')"
@ok="update_library" @ok="update_library"
@close="close()" @close="close()"
> >
<template #modal-content> <template #modal-content>
<div v-if="!library.updating"> <div v-if="!library.updating">
<p class="mb-3">Scan for new, deleted and modified files</p> <p class="mb-3" v-text="$t('dialog.update.info')" />
<div v-if="spotify_enabled || rss.tracks > 0" class="field"> <div v-if="spotify_enabled || rss.tracks > 0" class="field">
<div class="control"> <div class="control">
<div class="select is-small"> <div class="select is-small">
<select v-model="update_dialog_scan_kind"> <select v-model="update_dialog_scan_kind">
<option value="">Update everything</option> <option value="" v-text="$t('dialog.update.all')" />
<option value="files">Only update local library</option> <option value="files" v-text="$t('dialog.update.local')" />
<option v-if="spotify_enabled" value="spotify"> <option
Only update Spotify v-if="spotify_enabled"
</option> value="spotify"
<option v-if="rss.tracks > 0" value="rss"> v-text="$t('dialog.update.spotify')"
Only update RSS feeds />
</option> <option
v-if="rss.tracks > 0"
value="rss"
v-text="$t('dialog.update.feeds')"
/>
</select> </select>
</div> </div>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<label class="checkbox is-size-7 is-small"> <label class="checkbox is-size-7 is-small">
<input v-model="rescan_metadata" type="checkbox" /> <input
Rescan metadata for unmodified files v-model="rescan_metadata"
type="checkbox"
style="margin-right: 5px"
/>
<span v-text="$t('dialog.update.rescan-metadata')" />
</label> </label>
</div> </div>
</div> </div>
<div v-else> <div v-else>
<p class="mb-3">Library update in progress ...</p> <p class="mb-3" v-text="$t('dialog.update.progress')" />
</div> </div>
</template> </template>
</modal-dialog> </modal-dialog>

View File

@ -12,9 +12,8 @@
<div class="navbar-brand fd-expanded"> <div class="navbar-brand fd-expanded">
<!-- Link to queue --> <!-- Link to queue -->
<navbar-item-link to="/" exact> <navbar-item-link to="/" exact>
<span class="icon"><mdicon name="playlist-play" size="24" /></span> <mdicon class="icon" name="playlist-play" size="24" />
</navbar-item-link> </navbar-item-link>
<!-- Now playing artist/title (not visible on "now playing" page) --> <!-- Now playing artist/title (not visible on "now playing" page) -->
<router-link <router-link
v-if="!is_now_playing_page" v-if="!is_now_playing_page"
@ -25,16 +24,18 @@
> >
<div class="is-clipped"> <div class="is-clipped">
<p class="is-size-7 fd-is-text-clipped"> <p class="is-size-7 fd-is-text-clipped">
<strong>{{ now_playing.title }}</strong <strong v-text="now_playing.title" />
><br /> <br />
{{ now_playing.artist <span v-text="now_playing.artist" />
}}<span v-if="now_playing.data_kind === 'url'"> <span
- {{ now_playing.album }}</span v-if="now_playing.data_kind === 'url'"
> v-text="
$t('navigation.now-playing', { album: now_playing.album })
"
/>
</p> </p>
</div> </div>
</router-link> </router-link>
<!-- Skip previous (not visible on "now playing" page) --> <!-- Skip previous (not visible on "now playing" page) -->
<player-button-previous <player-button-previous
v-if="is_now_playing_page" v-if="is_now_playing_page"
@ -65,19 +66,17 @@
class="navbar-item" class="navbar-item"
:icon_size="24" :icon_size="24"
/> />
<!-- Player menu button (only visible on mobile and tablet) --> <!-- Player menu button (only visible on mobile and tablet) -->
<a <a
class="navbar-item fd-margin-left-auto is-hidden-desktop" class="navbar-item fd-margin-left-auto is-hidden-desktop"
@click="show_player_menu = !show_player_menu" @click="show_player_menu = !show_player_menu"
> >
<span class="icon" <mdicon
><mdicon class="icon"
:name="show_player_menu ? 'chevron-down' : 'chevron-up'" :name="show_player_menu ? 'chevron-down' : 'chevron-up'"
size="18" size="18"
/></span> />
</a> </a>
<!-- Player menu dropup menu (only visible on desktop) --> <!-- Player menu dropup menu (only visible on desktop) -->
<div <div
class="navbar-item has-dropdown has-dropdown-up fd-margin-left-auto is-hidden-touch" class="navbar-item has-dropdown has-dropdown-up fd-margin-left-auto is-hidden-touch"
@ -87,13 +86,12 @@
class="navbar-link is-arrowless" class="navbar-link is-arrowless"
@click="show_player_menu = !show_player_menu" @click="show_player_menu = !show_player_menu"
> >
<span class="icon" <mdicon
><mdicon class="icon"
:name="show_player_menu ? 'chevron-down' : 'chevron-up'" :name="show_player_menu ? 'chevron-down' : 'chevron-up'"
size="18" size="18"
/></span> />
</a> </a>
<div <div
class="navbar-dropdown is-right is-boxed" class="navbar-dropdown is-right is-boxed"
style="margin-right: 6px; margin-bottom: 6px; border-radius: 6px" style="margin-right: 6px; margin-bottom: 6px; border-radius: 6px"
@ -107,16 +105,16 @@
class="button is-white is-small" class="button is-white is-small"
@click="toggle_mute_volume" @click="toggle_mute_volume"
> >
<span class="icon" <mdicon
><mdicon class="icon"
:name="player.volume > 0 ? 'volume-high' : 'volume-off'" :name="player.volume > 0 ? 'volume-high' : 'volume-off'"
size="18" size="18"
/></span> />
</a> </a>
</div> </div>
<div class="level-item fd-expanded"> <div class="level-item fd-expanded">
<div class="fd-expanded"> <div class="fd-expanded">
<p class="heading">Volume</p> <p class="heading" v-text="$t('navigation.volume')" />
<Slider <Slider
v-model="player.volume" v-model="player.volume"
:min="0" :min="0"
@ -126,20 +124,11 @@
:classes="{ target: 'slider' }" :classes="{ target: 'slider' }"
@change="set_volume" @change="set_volume"
/> />
<!--range-slider
class="slider fd-has-action"
min="0"
max="100"
step="1"
:value="player.volume"
@change="set_volume">
</range-slider-->
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Outputs: master volume --> <!-- Outputs: master volume -->
<hr class="fd-navbar-divider" /> <hr class="fd-navbar-divider" />
<navbar-item-output <navbar-item-output
@ -147,7 +136,6 @@
:key="output.id" :key="output.id"
:output="output" :output="output"
/> />
<!-- Outputs: stream volume --> <!-- Outputs: stream volume -->
<hr class="fd-navbar-divider" /> <hr class="fd-navbar-divider" />
<div class="navbar-item"> <div class="navbar-item">
@ -157,15 +145,18 @@
<a <a
class="button is-white is-small" class="button is-white is-small"
:class="{ 'is-loading': loading }" :class="{ 'is-loading': loading }"
><span >
<span
class="icon fd-has-action" class="icon fd-has-action"
:class="{ :class="{
'has-text-grey-light': !playing && !loading, 'has-text-grey-light': !playing && !loading,
'is-loading': loading 'is-loading': loading
}" }"
@click="togglePlay" @click="togglePlay"
><mdicon name="radio-tower" size="18" /></span >
></a> <mdicon name="broadcast" size="18" />
</span>
</a>
</div> </div>
<div class="level-item fd-expanded"> <div class="level-item fd-expanded">
<div class="fd-expanded"> <div class="fd-expanded">
@ -173,10 +164,19 @@
class="heading" class="heading"
:class="{ 'has-text-grey-light': !playing }" :class="{ 'has-text-grey-light': !playing }"
> >
HTTP stream <span v-text="$t('navigation.stream')" />
<a href="stream.mp3" <a
><span class="is-lowercase">(stream.mp3)</span></a href="stream.mp3"
style="margin-left: 5px"
target="_blank"
> >
<mdicon
class="icon"
name="open-in-new"
size="16"
style="vertical-align: middle"
/>
</a>
</p> </p>
<Slider <Slider
v-model="stream_volume" v-model="stream_volume"
@ -188,21 +188,11 @@
:classes="{ target: 'slider' }" :classes="{ target: 'slider' }"
@change="set_stream_volume" @change="set_stream_volume"
/> />
<!--range-slider
class="slider fd-has-action"
min="0"
max="100"
step="1"
:disabled="!playing"
:value="stream_volume"
@change="set_stream_volume">
</range-slider-->
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Playback controls --> <!-- Playback controls -->
<hr class="fd-navbar-divider" /> <hr class="fd-navbar-divider" />
<div class="navbar-item"> <div class="navbar-item">
@ -219,7 +209,6 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Player menu (only visible on mobile and tablet) --> <!-- Player menu (only visible on mobile and tablet) -->
<div <div
class="navbar-menu is-hidden-desktop" class="navbar-menu is-hidden-desktop"
@ -235,25 +224,23 @@
<player-button-consume class="button" :icon_size="18" /> <player-button-consume class="button" :icon_size="18" />
</div> </div>
</div> </div>
<hr class="fd-navbar-divider" /> <hr class="fd-navbar-divider" />
<!-- Outputs: master volume --> <!-- Outputs: master volume -->
<div class="navbar-item"> <div class="navbar-item">
<div class="level is-mobile"> <div class="level is-mobile">
<div class="level-left fd-expanded"> <div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0"> <div class="level-item" style="flex-grow: 0">
<a class="button is-white is-small" @click="toggle_mute_volume"> <a class="button is-white is-small" @click="toggle_mute_volume">
<span class="icon" <mdicon
><mdicon class="icon"
:name="player.volume > 0 ? 'volume-high' : 'volume-off'" :name="player.volume > 0 ? 'volume-high' : 'volume-off'"
size="18" size="18"
/></span> />
</a> </a>
</div> </div>
<div class="level-item fd-expanded"> <div class="level-item fd-expanded">
<div class="fd-expanded"> <div class="fd-expanded">
<p class="heading">Volume</p> <p class="heading" v-text="$t('navigation.volume')" />
<Slider <Slider
v-model="player.volume" v-model="player.volume"
:min="0" :min="0"
@ -263,27 +250,17 @@
:classes="{ target: 'slider' }" :classes="{ target: 'slider' }"
@change="set_volume" @change="set_volume"
/> />
<!--range-slider
class="slider fd-has-action"
min="0"
max="100"
step="1"
:value="player.volume"
@change="set_volume">
</range-slider-->
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Outputs: speaker volumes --> <!-- Outputs: speaker volumes -->
<navbar-item-output <navbar-item-output
v-for="output in outputs" v-for="output in outputs"
:key="output.id" :key="output.id"
:output="output" :output="output"
/> />
<!-- Outputs: stream volume --> <!-- Outputs: stream volume -->
<hr class="fd-navbar-divider" /> <hr class="fd-navbar-divider" />
<div class="navbar-item fd-has-margin-bottom"> <div class="navbar-item fd-has-margin-bottom">
@ -301,7 +278,8 @@
'is-loading': loading 'is-loading': loading
}" }"
@click="togglePlay" @click="togglePlay"
><mdicon name="radio-tower" size="16" /> >
<mdicon name="broadcast" size="16" />
</span> </span>
</a> </a>
</div> </div>
@ -311,10 +289,19 @@
class="heading" class="heading"
:class="{ 'has-text-grey-light': !playing }" :class="{ 'has-text-grey-light': !playing }"
> >
HTTP stream <span v-text="$t('navigation.stream')" />
<a href="stream.mp3" <a
><span class="is-lowercase">(stream.mp3)</span></a href="stream.mp3"
style="margin-left: 5px"
target="_blank"
> >
<mdicon
class="icon"
name="open-in-new"
size="16"
style="vertical-align: middle"
/>
</a>
</p> </p>
<Slider <Slider
v-model="stream_volume" v-model="stream_volume"
@ -326,15 +313,6 @@
:classes="{ target: 'slider' }" :classes="{ target: 'slider' }"
@change="set_stream_volume" @change="set_stream_volume"
/> />
<!-- range-slider
class="slider fd-has-action"
min="0"
max="100"
step="1"
:disabled="!playing"
:value="stream_volume"
@change="set_stream_volume">
</range-slider-->
</div> </div>
</div> </div>
</div> </div>
@ -358,7 +336,6 @@ import PlayerButtonConsume from '@/components/PlayerButtonConsume.vue'
import PlayerButtonRepeat from '@/components/PlayerButtonRepeat.vue' import PlayerButtonRepeat from '@/components/PlayerButtonRepeat.vue'
import PlayerButtonSeekBack from '@/components/PlayerButtonSeekBack.vue' import PlayerButtonSeekBack from '@/components/PlayerButtonSeekBack.vue'
import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward.vue' import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward.vue'
//import RangeSlider from 'vue-range-slider'
import Slider from '@vueform/slider' import Slider from '@vueform/slider'
import * as types from '@/store/mutation_types' import * as types from '@/store/mutation_types'
@ -367,7 +344,6 @@ export default {
components: { components: {
NavbarItemLink, NavbarItemLink,
NavbarItemOutput, NavbarItemOutput,
//RangeSlider,
Slider, Slider,
PlayerButtonPlayPause, PlayerButtonPlayPause,
PlayerButtonNext, PlayerButtonNext,
@ -382,11 +358,9 @@ export default {
data() { data() {
return { return {
old_volume: 0, old_volume: 0,
playing: false, playing: false,
loading: false, loading: false,
stream_volume: 10, stream_volume: 10,
show_outputs_menu: false, show_outputs_menu: false,
show_desktop_outputs_menu: false show_desktop_outputs_menu: false
} }
@ -488,7 +462,7 @@ export default {
a.addEventListener('error', (e) => { a.addEventListener('error', (e) => {
this.closeAudio() this.closeAudio()
this.$store.dispatch('add_notification', { this.$store.dispatch('add_notification', {
text: 'HTTP stream error: failed to load stream or stopped loading due to network problem', text: this.$t('navigation.stream-error'),
type: 'danger' type: 'danger'
}) })
this.playing = false this.playing = false

View File

@ -18,9 +18,8 @@
<p <p
class="heading" class="heading"
:class="{ 'has-text-grey-light': !output.selected }" :class="{ 'has-text-grey-light': !output.selected }"
> v-text="output.name"
{{ output.name }} />
</p>
<Slider <Slider
v-model="volume" v-model="volume"
:min="0" :min="0"
@ -31,15 +30,6 @@
:classes="{ target: 'slider' }" :classes="{ target: 'slider' }"
@change="set_volume" @change="set_volume"
/> />
<!--range-slider
class="slider fd-has-action"
min="0"
max="100"
step="1"
:disabled="!output.selected"
:value="volume"
@change="set_volume" >
</range-slider-->
</div> </div>
</div> </div>
</div> </div>
@ -48,14 +38,12 @@
</template> </template>
<script> <script>
//import RangeSlider from 'vue-range-slider'
import Slider from '@vueform/slider' import Slider from '@vueform/slider'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'NavbarItemOutput', name: 'NavbarItemOutput',
components: { components: {
// RangeSlider
Slider Slider
}, },

View File

@ -7,27 +7,26 @@
> >
<div class="navbar-brand"> <div class="navbar-brand">
<navbar-item-link v-if="is_visible_playlists" to="/playlists"> <navbar-item-link v-if="is_visible_playlists" to="/playlists">
<span class="icon"><mdicon name="music-box-multiple" size="16" /></span> <mdicon class="icon" name="music-box-multiple" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="is_visible_music" to="/music"> <navbar-item-link v-if="is_visible_music" to="/music">
<span class="icon"><mdicon name="music" size="16" /></span> <mdicon class="icon" name="music" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="is_visible_podcasts" to="/podcasts"> <navbar-item-link v-if="is_visible_podcasts" to="/podcasts">
<span class="icon"><mdicon name="microphone" size="16" /></span> <mdicon class="icon" name="podcast" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="is_visible_audiobooks" to="/audiobooks"> <navbar-item-link v-if="is_visible_audiobooks" to="/audiobooks">
<span class="icon"><mdicon name="book-open-variant" size="16" /></span> <mdicon class="icon" name="book-open-variant" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="is_visible_radio" to="/radio"> <navbar-item-link v-if="is_visible_radio" to="/radio">
<span class="icon"><mdicon name="radio" size="16" /></span> <mdicon class="icon" name="radio-tower" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="is_visible_files" to="/files"> <navbar-item-link v-if="is_visible_files" to="/files">
<span class="icon"><mdicon name="folder-open" size="16" /></span> <mdicon class="icon" name="folder-open" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="is_visible_search" to="/search"> <navbar-item-link v-if="is_visible_search" to="/search">
<span class="icon"><mdicon name="magnify" size="16" /></span> <mdicon class="icon" name="magnify" size="16" />
</navbar-item-link> </navbar-item-link>
<div <div
class="navbar-burger" class="navbar-burger"
:class="{ 'is-active': show_burger_menu }" :class="{ 'is-active': show_burger_menu }"
@ -38,10 +37,8 @@
<span /> <span />
</div> </div>
</div> </div>
<div class="navbar-menu" :class="{ 'is-active': show_burger_menu }"> <div class="navbar-menu" :class="{ 'is-active': show_burger_menu }">
<div class="navbar-start" /> <div class="navbar-start" />
<div class="navbar-end"> <div class="navbar-end">
<!-- Burger menu entries --> <!-- Burger menu entries -->
<div <div
@ -50,67 +47,76 @@
@click="on_click_outside_settings" @click="on_click_outside_settings"
> >
<a class="navbar-link is-arrowless"> <a class="navbar-link is-arrowless">
<span class="icon is-hidden-touch" <mdicon class="icon is-hidden-touch" name="menu" size="24" />
><mdicon name="menu" size="24" <span
/></span> class="is-hidden-desktop has-text-weight-bold"
<span class="is-hidden-desktop has-text-weight-bold">OwnTone</span> v-text="$t('navigation.title')"
/>
</a> </a>
<div class="navbar-dropdown is-right"> <div class="navbar-dropdown is-right">
<navbar-item-link to="/playlists"> <navbar-item-link to="/playlists">
<span class="icon" <mdicon class="icon" name="music-box-multiple" size="16" />
><mdicon name="music-box-multiple" size="16" <b v-text="$t('navigation.playlists')" />
/></span>
<b>Playlists</b>
</navbar-item-link> </navbar-item-link>
<navbar-item-link to="/music" exact> <navbar-item-link to="/music" exact>
<span class="icon"><mdicon name="music" size="16" /></span> <mdicon class="icon" name="music" size="16" />
<b>Music</b> <b v-text="$t('navigation.music')" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link to="/music/artists"> <navbar-item-link to="/music/artists">
<span class="fd-navbar-item-level2">Artists</span> <span
class="fd-navbar-item-level2"
v-text="$t('navigation.artists')"
/>
</navbar-item-link> </navbar-item-link>
<navbar-item-link to="/music/albums"> <navbar-item-link to="/music/albums">
<span class="fd-navbar-item-level2">Albums</span> <span
class="fd-navbar-item-level2"
v-text="$t('navigation.albums')"
/>
</navbar-item-link> </navbar-item-link>
<navbar-item-link to="/music/genres"> <navbar-item-link to="/music/genres">
<span class="fd-navbar-item-level2">Genres</span> <span
class="fd-navbar-item-level2"
v-text="$t('navigation.genres')"
/>
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="spotify_enabled" to="/music/spotify"> <navbar-item-link v-if="spotify_enabled" to="/music/spotify">
<span class="fd-navbar-item-level2">Spotify</span> <span
class="fd-navbar-item-level2"
v-text="$t('navigation.spotify')"
/>
</navbar-item-link> </navbar-item-link>
<navbar-item-link to="/podcasts"> <navbar-item-link to="/podcasts">
<span class="icon"><mdicon name="microphone" size="16" /></span> <mdicon class="icon" name="podcast" size="16" />
<b>Podcasts</b> <b v-text="$t('navigation.podcasts')" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link to="/audiobooks"> <navbar-item-link to="/audiobooks">
<span class="icon" <mdicon class="icon" name="book-open-variant" size="16" />
><mdicon name="book-open-variant" size="16" <b v-text="$t('navigation.audiobooks')" />
/></span>
<b>Audiobooks</b>
</navbar-item-link> </navbar-item-link>
<navbar-item-link to="/radio"> <navbar-item-link to="/radio">
<span class="icon"><mdicon name="radio" size="16" /></span> <mdicon class="icon" name="radio-tower" size="16" />
<b>Radio</b> <b v-text="$t('navigation.radio')" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link to="/files"> <navbar-item-link to="/files">
<span class="icon"><mdicon name="folder-open" size="16" /></span> <mdicon class="icon" name="folder-open" size="16" />
<b>Files</b> <b v-text="$t('navigation.files')" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link to="/search"> <navbar-item-link to="/search">
<span class="icon"><mdicon name="magnify" size="16" /></span> <mdicon class="icon" name="magnify" size="16" />
<b>Search</b> <b v-text="$t('navigation.search')" />
</navbar-item-link> </navbar-item-link>
<hr class="fd-navbar-divider" /> <hr class="fd-navbar-divider" />
<navbar-item-link
<navbar-item-link to="/settings/webinterface"> to="/settings/webinterface"
Settings v-text="$t('navigation.settings')"
</navbar-item-link> />
<a class="navbar-item" @click.stop.prevent="open_update_dialog()"> <a
Update Library class="navbar-item"
</a> @click.stop.prevent="open_update_dialog()"
<navbar-item-link to="/about"> About </navbar-item-link> v-text="$t('navigation.update-library')"
/>
<navbar-item-link to="/about" v-text="$t('navigation.about')" />
<div <div
class="navbar-item is-hidden-desktop" class="navbar-item is-hidden-desktop"
style="margin-bottom: 2.5rem" style="margin-bottom: 2.5rem"
@ -119,7 +125,6 @@
</div> </div>
</div> </div>
</div> </div>
<div <div
v-show="show_settings_menu" v-show="show_settings_menu"
class="is-overlay" class="is-overlay"

View File

@ -12,7 +12,7 @@
]" ]"
> >
<button class="delete" @click="remove(notification)" /> <button class="delete" @click="remove(notification)" />
{{ notification.text }} <span v-text="notification.text" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
<template> <template>
<a :class="{ 'is-warning': is_consume }" @click="toggle_consume_mode"> <a :class="{ 'is-warning': is_consume }" @click="toggle_consume_mode">
<span class="icon"><mdicon name="fire" :size="icon_size" /></span> <mdicon class="icon" name="fire" :size="icon_size" />
</a> </a>
</template> </template>

View File

@ -1,6 +1,6 @@
<template> <template>
<a :disabled="disabled" @click="play_next"> <a :disabled="disabled" @click="play_next">
<span class="icon"><mdicon name="skip-forward" :size="icon_size" /></span> <mdicon class="icon" name="skip-forward" :size="icon_size" />
</a> </a>
</template> </template>

View File

@ -1,6 +1,6 @@
<template> <template>
<a :disabled="disabled" @click="toggle_play_pause"> <a :disabled="disabled" @click="toggle_play_pause">
<span class="icon"><mdicon :name="icon_name" :size="icon_size" /></span> <mdicon class="icon" :name="icon_name" :size="icon_size" />
</a> </a>
</template> </template>
@ -49,7 +49,7 @@ export default {
if (this.disabled) { if (this.disabled) {
if (this.show_disabled_message) { if (this.show_disabled_message) {
this.$store.dispatch('add_notification', { this.$store.dispatch('add_notification', {
text: 'Queue is empty', text: this.$t('server.empty-queue'),
type: 'info', type: 'info',
topic: 'connection', topic: 'connection',
timeout: 2000 timeout: 2000

View File

@ -1,6 +1,6 @@
<template> <template>
<a :disabled="disabled" @click="play_previous"> <a :disabled="disabled" @click="play_previous">
<span class="icon"><mdicon name="skip-backward" :size="icon_size" /></span> <mdicon class="icon" name="skip-backward" :size="icon_size" />
</a> </a>
</template> </template>

View File

@ -1,6 +1,6 @@
<template> <template>
<a :class="{ 'is-warning': !is_repeat_off }" @click="toggle_repeat_mode"> <a :class="{ 'is-warning': !is_repeat_off }" @click="toggle_repeat_mode">
<span class="icon"><mdicon :name="icon_name" :size="icon_size" /></span> <mdicon class="icon" :name="icon_name" :size="icon_size" />
</a> </a>
</template> </template>

View File

@ -1,6 +1,6 @@
<template> <template>
<a v-if="visible" :disabled="disabled" @click="seek"> <a v-if="visible" :disabled="disabled" @click="seek">
<span class="icon"><mdicon name="rewind" :size="icon_size" /></span> <mdicon class="icon" name="rewind" :size="icon_size" />
</a> </a>
</template> </template>

View File

@ -1,6 +1,6 @@
<template> <template>
<a v-if="visible" :disabled="disabled" @click="seek"> <a v-if="visible" :disabled="disabled" @click="seek">
<span class="icon"><mdicon name="fast-forward" :size="icon_size" /></span> <mdicon class="icon" name="fast-forward" :size="icon_size" />
</a> </a>
</template> </template>

View File

@ -1,6 +1,6 @@
<template> <template>
<a :class="{ 'is-warning': is_shuffle }" @click="toggle_shuffle_mode"> <a :class="{ 'is-warning': is_shuffle }" @click="toggle_shuffle_mode">
<span class="icon"><mdicon :name="icon_name" :size="icon_size" /></span> <mdicon class="icon" :name="icon_name" :size="icon_size" />
</a> </a>
</template> </template>

View File

@ -1,22 +1,19 @@
<template> <template>
<div class="field"> <div class="field">
<label class="checkbox"> <label class="switch">
<input <input
ref="settings_checkbox" ref="setting"
type="checkbox" type="checkbox"
:checked="value" :checked="value"
style="margin-right: 5px"
@change="set_update_timer" @change="set_update_timer"
/> />
<slot name="label" /> <slot name="label" />
<i <i
class="is-size-7" class="is-size-7"
:class="{ :class="{ 'has-text-info': is_success, 'has-text-danger': is_error }"
'has-text-info': statusUpdate === 'success', v-text="info"
'has-text-danger': statusUpdate === 'error' />
}"
>
{{ info }}</i
>
</label> </label>
<p v-if="$slots['info']" class="help"> <p v-if="$slots['info']" class="help">
<slot name="info" /> <slot name="info" />
@ -30,15 +27,12 @@ import * as types from '@/store/mutation_types'
export default { export default {
name: 'SettingsCheckbox', name: 'SettingsCheckbox',
props: ['category_name', 'option_name'], props: ['category_name', 'option_name'],
data() { data() {
return { return {
timerDelay: 2000, timerDelay: 2000,
timerId: -1, timerId: -1,
// <empty>: default/no changes, 'success': update succesful, 'error': update failed
statusUpdate: '' statusUpdate: ''
} }
}, },
@ -65,11 +59,19 @@ export default {
info() { info() {
if (this.statusUpdate === 'success') { if (this.statusUpdate === 'success') {
return '(setting saved)' return this.$t('setting.saved')
} else if (this.statusUpdate === 'error') { } else if (this.statusUpdate === 'error') {
return '(error saving setting)' return this.$t('setting.not-saved')
} }
return '' return ''
},
is_success() {
return this.statusUpdate === 'success'
},
is_error() {
return this.statusUpdate === 'error'
} }
}, },
@ -81,7 +83,7 @@ export default {
} }
this.statusUpdate = '' this.statusUpdate = ''
const newValue = this.$refs.settings_checkbox.checked const newValue = this.$refs.setting.checked
if (newValue !== this.value) { if (newValue !== this.value) {
this.timerId = window.setTimeout(this.update_setting, this.timerDelay) this.timerId = window.setTimeout(this.update_setting, this.timerDelay)
} }
@ -90,8 +92,8 @@ export default {
update_setting() { update_setting() {
this.timerId = -1 this.timerId = -1
const newValue = this.$refs.settings_checkbox.checked const newValue = this.$refs.setting.checked
console.log(this.$refs.settings_checkbox) console.log(this.$refs.setting)
if (newValue === this.value) { if (newValue === this.value) {
this.statusUpdate = '' this.statusUpdate = ''
return return
@ -110,7 +112,7 @@ export default {
}) })
.catch(() => { .catch(() => {
this.statusUpdate = 'error' this.statusUpdate = 'error'
this.$refs.settings_checkbox.checked = this.value this.$refs.setting.checked = this.value
}) })
.finally(() => { .finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay) this.timerId = window.setTimeout(this.clear_status, this.timerDelay)

View File

@ -5,17 +5,13 @@
<slot name="label" /> <slot name="label" />
<i <i
class="is-size-7" class="is-size-7"
:class="{ :class="{ 'has-text-info': is_success, 'has-text-danger': is_error }"
'has-text-info': statusUpdate === 'success', v-text="info"
'has-text-danger': statusUpdate === 'error' />
}"
>
{{ info }}</i
>
</label> </label>
<div class="control"> <div class="control">
<input <input
ref="settings_number" ref="setting"
class="input" class="input"
type="number" type="number"
min="0" min="0"
@ -38,14 +34,12 @@ import * as types from '@/store/mutation_types'
export default { export default {
name: 'SettingsIntfield', name: 'SettingsIntfield',
props: ['category_name', 'option_name', 'placeholder', 'disabled'], props: ['category_name', 'option_name', 'placeholder', 'disabled'],
data() { data() {
return { return {
timerDelay: 2000, timerDelay: 2000,
timerId: -1, timerId: -1,
statusUpdate: '' statusUpdate: ''
} }
}, },
@ -72,11 +66,19 @@ export default {
info() { info() {
if (this.statusUpdate === 'success') { if (this.statusUpdate === 'success') {
return '(setting saved)' return this.$t('setting.saved')
} else if (this.statusUpdate === 'error') { } else if (this.statusUpdate === 'error') {
return '(error saving setting)' return this.$t('setting.not-saved')
} }
return '' return ''
},
is_success() {
return this.statusUpdate === 'success'
},
is_error() {
return this.statusUpdate === 'error'
} }
}, },
@ -88,7 +90,7 @@ export default {
} }
this.statusUpdate = '' this.statusUpdate = ''
const newValue = this.$refs.settings_number.value const newValue = this.$refs.setting.value
if (newValue !== this.value) { if (newValue !== this.value) {
this.timerId = window.setTimeout(this.update_setting, this.timerDelay) this.timerId = window.setTimeout(this.update_setting, this.timerDelay)
} }
@ -97,7 +99,7 @@ export default {
update_setting() { update_setting() {
this.timerId = -1 this.timerId = -1
const newValue = this.$refs.settings_number.value const newValue = this.$refs.setting.value
if (newValue === this.value) { if (newValue === this.value) {
this.statusUpdate = '' this.statusUpdate = ''
return return
@ -116,7 +118,7 @@ export default {
}) })
.catch(() => { .catch(() => {
this.statusUpdate = 'error' this.statusUpdate = 'error'
this.$refs.settings_number.value = this.value this.$refs.setting.value = this.value
}) })
.finally(() => { .finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay) this.timerId = window.setTimeout(this.clear_status, this.timerDelay)

View File

@ -5,17 +5,13 @@
<slot name="label" /> <slot name="label" />
<i <i
class="is-size-7" class="is-size-7"
:class="{ :class="{ 'has-text-info': is_success, 'has-text-danger': is_error }"
'has-text-info': statusUpdate === 'success', v-text="info"
'has-text-danger': statusUpdate === 'error' />
}"
>
{{ info }}</i
>
</label> </label>
<div class="control"> <div class="control">
<input <input
ref="settings_text" ref="setting"
class="input" class="input"
type="text" type="text"
:placeholder="placeholder" :placeholder="placeholder"
@ -43,8 +39,6 @@ export default {
return { return {
timerDelay: 2000, timerDelay: 2000,
timerId: -1, timerId: -1,
// <empty>: default/no changes, 'success': update succesful, 'error': update failed
statusUpdate: '' statusUpdate: ''
} }
}, },
@ -71,11 +65,19 @@ export default {
info() { info() {
if (this.statusUpdate === 'success') { if (this.statusUpdate === 'success') {
return '(setting saved)' return this.$t('setting.saved')
} else if (this.statusUpdate === 'error') { } else if (this.statusUpdate === 'error') {
return '(error saving setting)' return this.$t('setting.not-saved')
} }
return '' return ''
},
is_success() {
return this.statusUpdate === 'success'
},
is_error() {
return this.statusUpdate === 'error'
} }
}, },
@ -87,7 +89,7 @@ export default {
} }
this.statusUpdate = '' this.statusUpdate = ''
const newValue = this.$refs.settings_text.value const newValue = this.$refs.setting.value
if (newValue !== this.value) { if (newValue !== this.value) {
this.timerId = window.setTimeout(this.update_setting, this.timerDelay) this.timerId = window.setTimeout(this.update_setting, this.timerDelay)
} }
@ -96,7 +98,7 @@ export default {
update_setting() { update_setting() {
this.timerId = -1 this.timerId = -1
const newValue = this.$refs.settings_text.value const newValue = this.$refs.setting.value
if (newValue === this.value) { if (newValue === this.value) {
this.statusUpdate = '' this.statusUpdate = ''
return return
@ -115,7 +117,7 @@ export default {
}) })
.catch(() => { .catch(() => {
this.statusUpdate = 'error' this.statusUpdate = 'error'
this.$refs.settings_text.value = this.value this.$refs.setting.value = this.value
}) })
.finally(() => { .finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay) this.timerId = window.setTimeout(this.clear_status, this.timerDelay)

View File

@ -4,15 +4,16 @@
<slot name="artwork" /> <slot name="artwork" />
</div> </div>
<div class="media-content fd-has-action is-clipped"> <div class="media-content fd-has-action is-clipped">
<h1 class="title is-6"> <h1 class="title is-6" v-text="album.name" />
{{ album.name }}
</h1>
<h2 class="subtitle is-7 has-text-grey"> <h2 class="subtitle is-7 has-text-grey">
<b>{{ album.artists[0].name }}</b> <b v-text="album.artists[0].name" />
</h2>
<h2 class="subtitle is-7 has-text-grey has-text-weight-normal">
({{ album.album_type }}, {{ $filters.date(album.release_date) }})
</h2> </h2>
<h2
class="subtitle is-7 has-text-grey has-text-weight-normal"
v-text="
[album.album_type, $filters.date(album.release_date)].join(', ')
"
/>
</div> </div>
<div class="media-right"> <div class="media-right">
<slot name="actions" /> <slot name="actions" />

View File

@ -1,9 +1,7 @@
<template> <template>
<div class="media"> <div class="media">
<div class="media-content fd-has-action is-clipped" @click="open_artist"> <div class="media-content fd-has-action is-clipped" @click="open_artist">
<h1 class="title is-6"> <h1 class="title is-6" v-text="artist.name" />
{{ artist.name }}
</h1>
</div> </div>
<div class="media-right"> <div class="media-right">
<slot name="actions" /> <slot name="actions" />

View File

@ -1,12 +1,8 @@
<template> <template>
<div class="media"> <div class="media">
<div class="media-content fd-has-action is-clipped" @click="open_playlist"> <div class="media-content fd-has-action is-clipped" @click="open_playlist">
<h1 class="title is-6"> <h1 class="title is-6" v-text="playlist.name" />
{{ playlist.name }} <h2 class="subtitle is-7" v-text="playlist.owner.display_name" />
</h1>
<h2 class="subtitle is-7">
{{ playlist.owner.display_name }}
</h2>
</div> </div>
<div class="media-right"> <div class="media-right">
<slot name="actions" /> <slot name="actions" />

View File

@ -4,9 +4,8 @@
<h1 <h1
class="title is-6" class="title is-6"
:class="{ 'has-text-grey-light': track.is_playable === false }" :class="{ 'has-text-grey-light': track.is_playable === false }"
> v-text="track.name"
{{ track.name }} />
</h1>
<h2 <h2
class="subtitle is-7" class="subtitle is-7"
:class="{ :class="{
@ -14,13 +13,18 @@
'has-text-grey-light': track.is_playable === false 'has-text-grey-light': track.is_playable === false
}" }"
> >
<b>{{ track.artists[0].name }}</b> <b v-text="track.artists[0].name" />
</h2> </h2>
<h2 v-if="track.is_playable === false" class="subtitle is-7"> <h2 v-if="track.is_playable === false" class="subtitle is-7">
(Track is not playable<span (<span v-text="$t('list.spotify.not-playable-track')" />
<span
v-if="track.restrictions && track.restrictions.reason" v-if="track.restrictions && track.restrictions.reason"
>, restriction reason: {{ track.restrictions.reason }}</span v-text="
>) $t('list.spotify.restriction-reason', {
reason: track.restrictions.reason
})
"
/>)
</h2> </h2>
</div> </div>
<div class="media-right"> <div class="media-right">
@ -34,9 +38,7 @@ import webapi from '@/webapi'
export default { export default {
name: 'SpotifyListItemTrack', name: 'SpotifyListItemTrack',
props: ['track', 'position', 'album', 'context_uri'], props: ['track', 'position', 'album', 'context_uri'],
methods: { methods: {
play: function () { play: function () {
webapi.player_play_uri(this.context_uri, false, this.position) webapi.player_play_uri(this.context_uri, false, this.position)

View File

@ -18,45 +18,64 @@
/> />
</figure> </figure>
<p class="title is-4"> <p class="title is-4">
<a class="has-text-link" @click="open_album">{{ <a
album.name class="has-text-link"
}}</a> @click="open_album"
v-text="album.name"
/>
</p> </p>
<div class="content is-small"> <div class="content is-small">
<p> <p>
<span class="heading">Album artist</span> <span
<a class="title is-6 has-text-link" @click="open_artist">{{ class="heading"
album.artists[0].name v-text="$t('dialog.spotify.album.album-artist')"
}}</a> />
<a
class="title is-6 has-text-link"
@click="open_artist"
v-text="album.artists[0].name"
/>
</p> </p>
<p> <p>
<span class="heading">Release date</span> <span
<span class="title is-6">{{ class="heading"
$filters.date(album.release_date) v-text="$t('dialog.spotify.album.release-date')"
}}</span> />
<span
class="title is-6"
v-text="$filters.date(album.release_date)"
/>
</p> </p>
<p> <p>
<span class="heading">Type</span> <span
<span class="title is-6">{{ album.album_type }}</span> class="heading"
v-text="$t('dialog.spotify.album.type')"
/>
<span class="title is-6" v-text="album.album_type" />
</p> </p>
</div> </div>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Add</span> v-text="$t('dialog.spotify.album.add')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="queue_add_next"> <a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon" <mdicon class="icon" name="playlist-play" size="16" />
><mdicon name="playlist-play" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Add Next</span> v-text="$t('dialog.spotify.album.add-next')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play"> <a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span
class="is-size-7"
v-text="$t('dialog.spotify.album.play')"
/>
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -7,40 +7,55 @@
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4"> <p class="title is-4">
<a class="has-text-link" @click="open_artist">{{ <a
artist.name class="has-text-link"
}}</a> @click="open_artist"
v-text="artist.name"
/>
</p> </p>
<div class="content is-small"> <div class="content is-small">
<p> <p>
<span class="heading">Popularity / Followers</span> <span
<span class="title is-6" class="heading"
>{{ artist.popularity }} / v-text="$t('dialog.spotify.artist.popularity')"
{{ artist.followers.total }}</span />
> <span
class="title is-6"
v-text="
[artist.popularity, artist.followers.total].join(' / ')
"
/>
</p> </p>
<p> <p>
<span class="heading">Genres</span> <span
<span class="title is-6">{{ artist.genres.join(', ') }}</span> class="heading"
v-text="$t('dialog.spotify.artist.genres')"
/>
<span class="title is-6" v-text="artist.genres.join(', ')" />
</p> </p>
</div> </div>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Add</span> v-text="$t('dialog.spotify.artist.add')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="queue_add_next"> <a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon" <mdicon class="icon" name="playlist-play" size="16" />
><mdicon name="playlist-play" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Add Next</span> v-text="$t('dialog.spotify.artist.add-next')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play"> <a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span
class="is-size-7"
v-text="$t('dialog.spotify.artist.play')"
/>
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -7,43 +7,60 @@
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4"> <p class="title is-4">
<a class="has-text-link" @click="open_playlist">{{ <a
playlist.name class="has-text-link"
}}</a> @click="open_playlist"
v-text="playlist.name"
/>
</p> </p>
<div class="content is-small"> <div class="content is-small">
<p> <p>
<span class="heading">Owner</span> <span
<span class="title is-6">{{ class="heading"
playlist.owner.display_name v-text="$t('dialog.spotify.playlist.owner')"
}}</span> />
<span
class="title is-6"
v-text="playlist.owner.display_name"
/>
</p> </p>
<p> <p>
<span class="heading">Tracks</span> <span
<span class="title is-6">{{ playlist.tracks.total }}</span> class="heading"
v-text="$t('dialog.spotify.playlist.tracks')"
/>
<span class="title is-6" v-text="playlist.tracks.total" />
</p> </p>
<p> <p>
<span class="heading">Path</span> <span
<span class="title is-6">{{ playlist.uri }}</span> class="heading"
v-text="$t('dialog.spotify.playlist.path')"
/>
<span class="title is-6" v-text="playlist.uri" />
</p> </p>
</div> </div>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Add</span> v-text="$t('dialog.spotify.playlist.add')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="queue_add_next"> <a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon" <mdicon class="icon" name="playlist-play" size="16" />
><mdicon name="playlist-play" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Add Next</span> v-text="$t('dialog.spotify.playlist.add-next')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play"> <a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span
class="is-size-7"
v-text="$t('dialog.spotify.playlist.play')"
/>
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -6,65 +6,91 @@
<div class="modal-content fd-modal-card"> <div class="modal-content fd-modal-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="title is-4"> <p class="title is-4" v-text="track.name" />
{{ track.name }} <p class="subtitle" v-text="track.artists[0].name" />
</p>
<p class="subtitle">
{{ track.artists[0].name }}
</p>
<div class="content is-small"> <div class="content is-small">
<p> <p>
<span class="heading">Album</span> <span
<a class="title is-6 has-text-link" @click="open_album">{{ class="heading"
album.name v-text="$t('dialog.spotify.track.album')"
}}</a> />
<a
class="title is-6 has-text-link"
@click="open_album"
v-text="album.name"
/>
</p> </p>
<p> <p>
<span class="heading">Album artist</span> <span
<a class="title is-6 has-text-link" @click="open_artist">{{ class="heading"
album.artists[0].name v-text="$t('dialog.spotify.track.album-artist')"
}}</a> />
<a
class="title is-6 has-text-link"
@click="open_artist"
v-text="album.artists[0].name"
/>
</p> </p>
<p> <p>
<span class="heading">Release date</span> <span
<span class="title is-6">{{ class="heading"
$filters.date(album.release_date) v-text="$t('dialog.spotify.track.release-date')"
}}</span> />
<span
class="title is-6"
v-text="$filters.date(album.release_date)"
/>
</p> </p>
<p> <p>
<span class="heading">Track / Disc</span> <span
<span class="title is-6" class="heading"
>{{ track.track_number }} / {{ track.disc_number }}</span v-text="$t('dialog.spotify.track.position')"
> />
<span
class="title is-6"
v-text="[track.disc_number, track.track_number].join(' / ')"
/>
</p> </p>
<p> <p>
<span class="heading">Length</span> <span
<span class="title is-6">{{ class="heading"
$filters.durationInHours(track.duration_ms) v-text="$t('dialog.spotify.track.duration')"
}}</span> />
<span
class="title is-6"
v-text="$filters.durationInHours(track.duration_ms)"
/>
</p> </p>
<p> <p>
<span class="heading">Path</span> <span
<span class="title is-6">{{ track.uri }}</span> class="heading"
v-text="$t('dialog.spotify.track.path')"
/>
<span class="title is-6" v-text="track.uri" />
</p> </p>
</div> </div>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon" <mdicon class="icon" name="playlist-plus" size="16" />
><mdicon name="playlist-plus" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Add</span> v-text="$t('dialog.spotify.track.add')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="queue_add_next"> <a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon" <mdicon class="icon" name="playlist-play" size="16" />
><mdicon name="playlist-play" size="16" <span
/></span> class="is-size-7"
<span class="is-size-7">Add Next</span> v-text="$t('dialog.spotify.track.add-next')"
/>
</a> </a>
<a class="card-footer-item has-text-dark" @click="play"> <a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span class="is-size-7">Play</span> <span
class="is-size-7"
v-text="$t('dialog.spotify.track.play')"
/>
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -12,10 +12,12 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small" <mdicon
><mdicon name="account-music" size="16" class="icon is-small"
/></span> name="account-music"
<span class="">Authors</span> size="16"
/>
<span v-text="$t('tabs.audiobooks.authors')" />
</a> </a>
</li> </li>
</router-link> </router-link>
@ -26,10 +28,8 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small" <mdicon class="icon is-small" name="album" size="16" />
><mdicon name="album" size="16" <span v-text="$t('tabs.audiobooks.audiobooks')" />
/></span>
<span class="">Audiobooks</span>
</a> </a>
</li> </li>
</router-link> </router-link>

View File

@ -12,10 +12,8 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small" <mdicon class="icon is-small" name="web" size="16" />
><mdicon name="web" size="16" <span v-text="$t('page.settings.tabs.music.browse')" />
/></span>
<span class="">Browse</span>
</a> </a>
</li> </li>
</router-link> </router-link>
@ -26,10 +24,12 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small" <mdicon
><mdicon name="account-music" size="16" class="icon is-small"
/></span> name="account-music"
<span class="">Artists</span> size="16"
/>
<span v-text="$t('page.settings.tabs.music.artists')" />
</a> </a>
</li> </li>
</router-link> </router-link>
@ -40,10 +40,8 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small" <mdicon class="icon is-small" name="album" size="16" />
><mdicon name="album" size="16" <span v-text="$t('page.settings.tabs.music.albums')" />
/></span>
<span class="">Albums</span>
</a> </a>
</li> </li>
</router-link> </router-link>
@ -54,10 +52,8 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small" <mdicon class="icon is-small" name="speaker" size="16" />
><mdicon name="speaker" size="16" <span v-text="$t('page.settings.tabs.music.genres')" />
/></span>
<span class="">Genres</span>
</a> </a>
</li> </li>
</router-link> </router-link>
@ -68,10 +64,12 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small" <mdicon
><mdicon name="book-open-page-variant" size="16" class="icon is-small"
/></span> name="book-open-page-variant"
<span class="">Composers</span> size="16"
/>
<span v-text="$t('page.settings.tabs.music.composers')" />
</a> </a>
</li> </li>
</router-link> </router-link>
@ -83,10 +81,8 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small" <mdicon class="icon is-small" name="spotify" size="16" />
><mdicon name="spotify" size="16" <span v-text="$t('page.settings.tabs.music.spotify')" />
/></span>
<span class="">Spotify</span>
</a> </a>
</li> </li>
</router-link> </router-link>

View File

@ -11,10 +11,8 @@
}" }"
> >
<a @click="search_library"> <a @click="search_library">
<span class="icon is-small" <mdicon class="icon is-small" name="bookshelf" size="16" />
><mdicon name="bookshelf" size="16" <span v-text="$t('tabs.search.library')" />
/></span>
<span class="">Library</span>
</a> </a>
</li> </li>
<li <li
@ -23,10 +21,8 @@
}" }"
> >
<a @click="search_spotify"> <a @click="search_spotify">
<span class="icon is-small" <mdicon class="icon is-small" name="spotify" size="16" />
><mdicon name="spotify" size="16" <span v-text="$t('tabs.search.spotify')" />
/></span>
<span class="">Spotify</span>
</a> </a>
</li> </li>
</ul> </ul>

View File

@ -12,7 +12,7 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="">Webinterface</span> <span v-text="$t('page.settings.tabs.settings.general')" />
</a> </a>
</li> </li>
</router-link> </router-link>
@ -23,7 +23,11 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="">Remotes &amp; Outputs</span> <span
v-text="
$t('page.settings.tabs.settings.remotes-and-outputs')
"
/>
</a> </a>
</li> </li>
</router-link> </router-link>
@ -34,7 +38,7 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="">Artwork</span> <span v-text="$t('page.settings.tabs.settings.artwork')" />
</a> </a>
</li> </li>
</router-link> </router-link>
@ -45,7 +49,9 @@
> >
<li :class="{ 'is-active': isActive }"> <li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate"> <a @click="navigate" @keypress.enter="navigate">
<span class="">Online Services</span> <span
v-text="$t('page.settings.tabs.settings.online-services')"
/>
</a> </a>
</li> </li>
</router-link> </router-link>
@ -59,9 +65,7 @@
<script> <script>
export default { export default {
name: 'TabsSettings', name: 'TabsSettings'
computed: {}
} }
</script> </script>

15
web-src/src/i18n.js Normal file
View File

@ -0,0 +1,15 @@
import { createI18n } from 'vue-i18n'
/*
* All i18n resources specified in the plugin `include` option can be loaded
* at once using the import syntax.
*/
import messages from '@intlify/vite-plugin-vue-i18n/messages'
export default createI18n({
legacy: false,
globalInjection: true,
locale: 'en',
fallbackLocale: 'en',
messages
})

View File

@ -2,9 +2,10 @@ import {
mdiAccountMusic, mdiAccountMusic,
mdiAlbum, mdiAlbum,
mdiArrowCollapseDown, mdiArrowCollapseDown,
mdiBookOpenVariant,
mdiBookOpenPageVariant, mdiBookOpenPageVariant,
mdiBookOpenVariant,
mdiBookshelf, mdiBookshelf,
mdiBroadcast,
mdiCancel, mdiCancel,
mdiCast, mdiCast,
mdiCastVariant, mdiCastVariant,
@ -26,16 +27,16 @@ import {
mdiFolderOpen, mdiFolderOpen,
mdiMagnify, mdiMagnify,
mdiMenu, mdiMenu,
mdiMicrophone,
mdiMusic, mdiMusic,
mdiMusicBoxMultiple, mdiMusicBoxMultiple,
mdiOpenInNew,
mdiPause, mdiPause,
mdiPencil, mdiPencil,
mdiPipe, mdiPipe,
mdiPlay, mdiPlay,
mdiPlaylistPlay, mdiPlaylistPlay,
mdiPlaylistPlus, mdiPlaylistPlus,
mdiRadio, mdiPodcast,
mdiRadioTower, mdiRadioTower,
mdiRefresh, mdiRefresh,
mdiRepeat, mdiRepeat,
@ -61,9 +62,10 @@ export const icons = {
mdiAccountMusic, mdiAccountMusic,
mdiAlbum, mdiAlbum,
mdiArrowCollapseDown, mdiArrowCollapseDown,
mdiBookOpenVariant,
mdiBookOpenPageVariant, mdiBookOpenPageVariant,
mdiBookOpenVariant,
mdiBookshelf, mdiBookshelf,
mdiBroadcast,
mdiCancel, mdiCancel,
mdiCast, mdiCast,
mdiCastVariant, mdiCastVariant,
@ -85,16 +87,16 @@ export const icons = {
mdiFolderOpen, mdiFolderOpen,
mdiMagnify, mdiMagnify,
mdiMenu, mdiMenu,
mdiMicrophone,
mdiMusic, mdiMusic,
mdiMusicBoxMultiple, mdiMusicBoxMultiple,
mdiOpenInNew,
mdiPause, mdiPause,
mdiPencil, mdiPencil,
mdiPipe, mdiPipe,
mdiPlay, mdiPlay,
mdiPlaylistPlay, mdiPlaylistPlay,
mdiPlaylistPlus, mdiPlaylistPlus,
mdiRadio, mdiPodcast,
mdiRadioTower, mdiRadioTower,
mdiRefresh, mdiRefresh,
mdiRepeat, mdiRepeat,

554
web-src/src/locales/en.json Normal file
View File

@ -0,0 +1,554 @@
{
"data": {
"kind": {
"file": "File",
"url": "URL",
"spotify": "Spotify",
"pipe": "Stream"
}
},
"dialog": {
"cancel": "Cancel",
"add": {
"rss": {
"add": "Add",
"cancel": "Cancel",
"help": "Adding a podcast includes creating an RSS playlist, that will allow OwnTone to manage the podcast subscription.",
"placeholder": "https://url-to-rss",
"processing": "Processing…",
"title": "Add Podcast RSS feed URL"
},
"stream": {
"add": "Add",
"cancel": "Cancel",
"loading": "Loading…",
"placeholder": "https://url-to-stream",
"play": "Play",
"title": "Add Stream URL"
}
},
"album": {
"add-next": "Add Next",
"add": "Add",
"added-on": "Added on",
"artist": "Album artist",
"duration": "Duration",
"mark-as-played": "Mark as played",
"play": "Play",
"release-date": "Release date",
"remove-podcast": "Remove podcast",
"tracks": "Track count",
"type": "Type",
"year": "Year"
},
"artist": {
"add-next": "Add Next",
"add": "Add",
"added-on": "Added On",
"albums": "Albums",
"play": "Play",
"tracks": "Tracks",
"type": "Type"
},
"composer": {
"add-next": "Add Next",
"add": "Add",
"albums": "Albums",
"duration": "Duration",
"play": "Play",
"tracks": "Tracks"
},
"directory": {
"add-next": "Add Next",
"add": "Add",
"play": "Play"
},
"genre": {
"add-next": "Add Next",
"add": "Add",
"albums": "Albums",
"duration": "Duration",
"play": "Play",
"tracks": "Tracks"
},
"playlist": {
"add-next": "Add Next",
"add": "Add",
"play": "Play",
"track-count": "Track Count",
"type": "Type",
"save": {
"cancel": "Cancel",
"save": "Save",
"saving": "Saving…",
"title": "Save queue to playlist"
}
},
"queue-item": {
"album-artist": "Album Artist",
"album": "Album",
"bitrate": "{'|'} {rate} kbit/s",
"channels": "{'|'} {channels}",
"composer": "Composer",
"duration": "Duration",
"genre": "Genre",
"path": "Path",
"play": "Play",
"position": "Disc / Track",
"quality": "Quality",
"remove": "Remove",
"samplerate": "{'|'} {rate} Hz",
"spotify-album": "album",
"spotify-artist": "artist",
"type": "Type",
"year": "Year"
},
"remote-pairing": {
"cancel": "Cancel",
"pair": "Pair Remote",
"title": "Remote pairing request"
},
"spotify": {
"album": {
"add-next": "Add Next",
"add": "Add",
"album-artist": "Album Artist",
"play": "Play",
"release-date": "Release Date",
"type": "Type"
},
"artist": {
"add-next": "Add Next",
"add": "Add",
"genres": "Genres",
"play": "Play",
"popularity": "Popularity / Followers"
},
"playlist": {
"add-next": "Add Next",
"add": "Add",
"owner": "Owner",
"path": "Path",
"play": "Play",
"tracks": "Tracks"
},
"track": {
"add-next": "Add Next",
"add": "Add",
"album-artist": "Album Artist",
"album": "Album",
"duration": "Duration",
"path": "Path",
"play": "Play",
"position": "Disc / Track",
"release-date": "Release Date"
}
},
"track": {
"add-next": "Add Next",
"add": "Add",
"added-on": "Added On",
"album-artist": "Album Artist",
"album": "Album",
"bitrate": " {'|'} {rate} Kb/s",
"channels": " {'|'} {channels}",
"comment": "Comment",
"composer": "Composer",
"duration": "Duration",
"genre": "Genre",
"mark-as-new": "Mark as new",
"mark-as-played": "Mark as played",
"path": "Path",
"play": "Play",
"position": "Disc / Track",
"quality": "Quality",
"rating-value": "{rating} / 10",
"rating": "Rating",
"release-date": "Release Date",
"samplerate": " {'|'} {rate} Hz",
"spotify-album": "album",
"spotify-artist": "artist",
"type": "Type",
"year": "Year"
},
"update": {
"all": "Update everything",
"cancel": "Cancel",
"feeds": "Only update RSS feeds",
"info": "Scan for new, deleted and modified files",
"local": "Only update local library",
"progress": "Library update in progress…",
"rescan-metadata": "Rescan metadata of unmodified files",
"rescan": "Rescan",
"spotify": "Only update Spotify",
"title": "Library update"
}
},
"language": {
"en": "English",
"fr": "French (Français)"
},
"list": {
"albums": {
"info-1": "Permanently remove this podcast from your library?",
"info-2": "This will also remove the RSS playlist ",
"notification": "Podcast cannot be removed. Probably it was not added as an RSS playlist."
},
"spotify": {
"not-playable-track": "Track is not playable",
"restriction-reason": ", restriction reason: {reason}"
}
},
"media": {
"kind": {
"album": "Album",
"audiobook": "Audiobook",
"music": "Music",
"podcast": "Podcast"
}
},
"navigation": {
"about": "About",
"albums": "Albums",
"artists": "Artists",
"audiobooks": "Audiobooks",
"now-playing": " - {album}",
"stream-error": "HTTP stream error: failed to load stream or stopped loading due to network problem",
"stream": "HTTP stream",
"volume": "Volume",
"files": "Files",
"genres": "Genres",
"music": "Music",
"playlists": "Playlists",
"podcasts": "Podcasts",
"radio": "Radio",
"search": "Search",
"settings": "Settings",
"spotify": "Spotify",
"title": "OwnTone",
"update-library": "Update Library"
},
"page": {
"about": {
"albums": "Albums",
"artists": "Artists",
"built-with": "Web interface built with <a href=\"https://bulma.io\">Bulma</a>, <a href=\"https://materialdesignicons.com/\">Material Design Icons</a>, <a href=\"https://vuejs.org/\">Vue.js</a>, <a href=\"https://github.com/mzabriskie/axios\">axios</a> and <a href=\"https://github.com/owntone/owntone-server/network/dependencies\">more</a>.",
"compiled-with": "Compiled with support for {options}.",
"library": "Library",
"total-playtime": "Total playtime",
"tracks": "Tracks",
"update": "Update",
"updated-on": "{time} ago",
"updated": "Updated",
"uptime": "Uptime",
"version": "Version {version}"
},
"album": {
"shuffle": "Shuffle",
"track-count": "{count} tracks"
},
"albums": {
"count": "{count} albums",
"filter": "Filter",
"hide-singles-help": "If active, hides singles and albums with tracks that only appear in playlists.",
"hide-singles": "Hide singles",
"hide-spotify-help": "If active, hides albums that only appear in your Spotify library.",
"hide-spotify": "Hide albums from Spotify",
"title": "Albums",
"sort-by": {
"title": "Sort by",
"name": "Name",
"recently-added": "Recently added",
"recently-released": "Recently released"
}
},
"artist": {
"album-count": "{count} albums {'|'} ",
"shuffle": "Shuffle",
"track-count": "{count} tracks",
"sort-by": {
"title": "Sort by",
"name": "Name",
"release-date": "Release date"
}
},
"artists": {
"count": "{count} artists",
"filter": "Filter",
"sort-by": {
"title": "Sort by",
"name": "Name",
"recently-added": "Rencently added"
},
"hide-singles-help": "If active, hides artists that only appear on singles or playlists.",
"hide-singles": "Hide singles",
"hide-spotify-help": "If active, hides artists that only appear in your Spotify library.",
"hide-spotify": "Hide artists from Spotify",
"title": "Artists"
},
"audiobooks": {
"album": {
"play": "Play",
"track-count": "{count} tracks"
},
"albums": {
"count": "{count} audiobooks",
"title": "Audiobooks"
},
"artist": {
"albums-count": "{count} albums",
"shuffle": "Shuffle"
},
"artists": {
"count": "{count} authors",
"title": "Authors"
}
},
"browse": {
"albums": "albums",
"show-more": "Show more",
"tracks": "tracks",
"recently-added": {
"albums": "albums",
"title": "Recently added"
},
"recently-played": {
"title": "Recently played",
"tracks": "tracks"
}
},
"composer": {
"album-count": "{count} albums {'|'}",
"shuffle": "Shuffle",
"track-count": "{count} tracks",
"tracks": {
"album-count": "{count} albums",
"track-count": " {'|'} {count} tracks",
"shuffle": "Shuffle"
}
},
"composers": {
"count": "{count} composers",
"title": "Composers"
},
"files": {
"play": "Play",
"title": "Files"
},
"genre": {
"album-count": "{count} albums {'|'} ",
"shuffle": "Shuffle",
"tracks": {
"album-count": "{count} albums {'|'} ",
"count": " {count} tracks",
"shuffle": "Shuffle"
}
},
"genres": {
"count": "{count} genres",
"title": "Genres"
},
"now-playing": {
"info": "Add some tracks by browsing your library",
"title": "Your play queue is empty"
},
"playlist": {
"length": "{length} tracks",
"shuffle": "Shuffle"
},
"playlists": {
"count": "{count} playlists"
},
"podcast": {
"play": "Play",
"remove-error": "Podcast cannot be removed. Probably it was not added as an RSS playlist.",
"remove-info-1": "Permanently remove this podcast from your library?",
"remove-info-2": "This will also remove the RSS playlist ",
"track-count": "{count} tracks"
},
"podcasts": {
"add": "Add",
"count": "{count} podcasts",
"mark-all-played": "Mark All Played",
"new-episodes": "New Episodes",
"title": "Podcasts",
"update": "Update"
},
"queue": {
"add-stream": "Add stream",
"clear": "Clear",
"count": "{count} tracks",
"edit": "Edit",
"hide-previous": "Hide previous",
"title": "Queue"
},
"radio": {
"count": "{count} stations",
"title": "Radio"
},
"search": {
"albums": "Albums",
"artists": "Artists",
"audiobooks": "Audiobooks",
"composers": "Composers",
"help": "Tip: you can search by a smart playlist query language <a href=\"https://github.com/owntone/owntone-server/blob/master/README_SMARTPL.md\" target=\"_blank\">expression</a> if you prefix it with <code>query:</code>.",
"no-albums": "No albums found",
"no-artists": "No artists found",
"no-audiobooks": "No audiobooks found",
"no-composers": "No composers found",
"no-playlists": "No playlists found",
"no-podcasts": "No podcasts found",
"no-tracks": "No tracks found",
"playlists": "Playlists",
"podcasts": "Podcasts",
"show-albums": "Show all {count} albums",
"show-artists": "Show all {count} artists",
"show-audiobooks": "Show all {count} audiobooks",
"show-composers": "Show all {count} composers",
"show-playlists": "Show all {count} playlists",
"show-podcasts": "Show all {count} podcasts",
"show-tracks": "Show all {count} tracks",
"tracks": "Tracks"
},
"settings": {
"artwork": {
"artwork": "Artwork",
"coverartarchive": "Cover Art Archive",
"discogs": "Discogs",
"explanation-1": "OwnTone supports PNG and JPEG artwork which is either placed as separate image files in the library, embedded in the media files or made available online by radio stations.",
"explanation-2": "In addition to that, you can enable fetching artwork from the following artwork providers:",
"spotify": "Spotify"
},
"devices": {
"no-active-pairing": "No active pairing request.",
"pairing-request": "Remote pairing request from ",
"pairing": "Remote Pairing",
"send": "Send",
"speaker-pairing-info": "If your speaker requires pairing then activate it below and enter the PIN that it displays.",
"speaker-pairing": "Speaker pairing and device verification",
"verification-code": "Enter verification code",
"verify": "Verify"
},
"general": {
"album-lists": "Album Lists",
"audiobooks": "Audiobooks",
"files": "Audio Files",
"language": "Language",
"music": "Music",
"navigation-item-selection-info": "If you select more items than can be shown on your screen then the burger menu will disappear.",
"navigation-item-selection": "Select the top navigation bar menu items",
"navigation-items": "Navigation Bar",
"now-playing-page": "Now playing page",
"playlists": "Playlists",
"podcasts": "Podcasts",
"radio": "Radio",
"recently-added-page-info": "Limit the number of albums shown on the \"Recently Added\" page",
"recently-added-page": "Recently added page",
"search": "Search",
"show-composer-genres-info-1": "Comma separated list of genres the composer should be displayed on the \"now playing page\"",
"show-composer-genres-info-2": "Leave empty to always show the composer.",
"show-composer-genres-info-3": "The genre tag of the current track is matched by checking, if one of the defined genres are included. For example setting to \"classical, soundtrack\" will show the composer for tracks with a genre tag of \"Contemporary Classical\"",
"show-composer-genres": "Show composer only for listed genres",
"show-composer-info": "If enabled the composer of the current playing track is shown on the \"now playing page\"",
"show-composer": "Show composer",
"show-coverart": "Show cover artwork in album list"
},
"services": {
"lastfm": {
"grant-access": "<b>Last.fm</b> - Login with your Last.fm username and password to enable scrobbling",
"info": "OwnTone will not store your Last.fm username/password, only the session key. The session key does not expire.",
"title": "Last.fm",
"no-support": "OwnTone was built without support for Last.fm.",
"stop-scrobbling": "Stop scrobbling"
},
"spotify": {
"no-support": "OwnTone was either built without support for Spotify or libspotify is not installed.",
"help": "If you normally log into Spotify with your Facebook account you must first go to Spotify's web site where you can get the Spotify username and password that matches your account.",
"logged-as": "Logged in as ",
"requirements": "You must have a Spotify premium account.",
"scopes": "Access to the Spotify Web API enables scanning of your Spotify library. Required scopes are: ",
"user": "Access granted for ",
"authorize": "Authorize Web API access",
"credentials": " - Login with your Spotify username and password",
"grant-access": "<b>Spotify Web API</b> - Grant access to the Spotify Web API",
"help-1": "libspotify enables OwnTone to play Spotify tracks.",
"help-2": "OwnTone will not store your password, but will still be able to log you in automatically afterwards, because libspotify saves a login token.",
"reauthorize": "Please reauthorize Web API access to grant OwnTone the following additional access rights: ",
"title": "Spotify"
},
"login": "Login",
"logout": "Logout"
},
"tabs": {
"music": {
"albums": "Albums",
"artists": "Artists",
"browse": "Browse",
"composers": "Composers",
"genres": "Genres",
"spotify": "Spotify"
},
"search": {
"library": "Library",
"spotify": "Spotify"
},
"settings": {
"artwork": "Artwork",
"general": "General",
"online-services": "Online Services",
"remotes-and-outputs": "Remotes and Outputs"
}
}
},
"spotify": {
"album": {
"shuffle": "Shuffle",
"track-count": "{count} tracks"
},
"artist": {
"album-count": "{count} albums",
"shuffle": "Shuffle"
},
"browse": {
"featured-playlists": "Featured Playlists",
"new-releases": "New Releases",
"show-more": "Show More"
},
"playlist": {
"count": "{count} tracks",
"shuffle": "Shuffle"
},
"search": {
"albums": "Albums",
"artists": "Artists",
"no-albums": "No albums found",
"no-artists": "No artists found",
"no-playlists": "No playlists found",
"no-tracks": "No tracks found",
"playlists": "Playlists",
"show-all-albums": "Show all {count} albums",
"show-all-artists": "Show all {count} artists",
"show-all-playlists": "Show all {count} playlists",
"show-all-tracks": "Show all {count} tracks",
"tracks": "Tracks"
}
}
},
"setting": {
"not-saved": " (error saving setting)",
"saved": " (setting saved)"
},
"server": {
"connection-failed": "Failed to connect to OwnTone server",
"missing-port": "Missing websocket port",
"request-failed": "Request failed (status: {status} {cause} {url})",
"queue-saved": "Queue saved to playlist {name}",
"appended-tracks": "{count} tracks appended to the queue",
"empty-queue": "Queue is empty"
},
"group-by-list": {
"today": "Today",
"last-week": "Last week",
"last-month": "Last month"
}
}

554
web-src/src/locales/fr.json Normal file
View File

@ -0,0 +1,554 @@
{
"data": {
"kind": {
"file": "Fichier",
"url": "URL",
"spotify": "Spotify",
"pipe": "Flux"
}
},
"dialog": {
"cancel": "Annuler",
"add": {
"rss": {
"add": "Ajouter",
"cancel": "Annuler",
"help": "Lajout dun podcast inclut la création dune liste de lecture RSS, qui permettra à OwnTone de gérer labonnement au podcast.",
"placeholder": "https://url-du-flux-rss",
"processing": "Traitement en cours…",
"title": "Ajouter lURL du flux RSS du podcast"
},
"stream": {
"add": "Ajouter",
"cancel": "Annuler",
"loading": "Chargement…",
"placeholder": "https://url-du-flux",
"play": "Lire",
"title": "Ajouter lURL du flux"
}
},
"album": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"added-on": "Ajouté le",
"artiste": "Album artist",
"duration": "Durée",
"mark-as-played": "Marquer comme lu",
"play": "Lire",
"release-date": "Date de sortie",
"remove-podcast": "Supprimer le podcast",
"pistes": "Pistes",
"type": "Type",
"year": "Année"
},
"artiste": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"added-on": "Ajouté le",
"albums": "Albums",
"play": "Lire",
"pistes": "Pistes",
"type": "Type"
},
"composer": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"albums": "Albums",
"duration": "Durée",
"play": "Lire",
"pistes": "Pistes"
},
"directory": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"play": "Lire"
},
"genre": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"albums": "Albums",
"duration": "Durée",
"play": "Lire",
"pistes": "Pistes"
},
"playlist": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"play": "Lire",
"track-count": "Pistes",
"type": "Type",
"save": {
"cancel": "Annuler",
"save": "Enregistrer",
"saving": "Enregistrement en cours…",
"title": "Enregister la file dattente dans une liste de lecture"
}
},
"queue-item": {
"album-artist": "Artiste de lalbum",
"album": "Album",
"bitrate": "{'|'} {rate} kbit/s",
"channels": "{'|'} {channels}",
"composer": "Compositeur",
"duration": "Durée",
"genre": "Genre",
"path": "Emplacement",
"play": "Lire",
"position": "Disque / Piste",
"quality": "Qualité",
"remove": "Supprimer",
"samplerate": "{'|'} {rate} Hz",
"spotify-album": "album",
"spotify-artist": "artiste",
"type": "Type",
"year": "Année"
},
"remote-pairing": {
"cancel": "Annuler",
"pair": "Jumeler la télécommande",
"title": "Demande de jumelage de télécommande"
},
"spotify": {
"album": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"album-artist": "Artiste de lalbum",
"play": "Lire",
"release-date": "Date de sortie",
"type": "Type"
},
"artiste": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"genres": "Genres",
"play": "Lire",
"popularity": "Popularités / Followers"
},
"playlist": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"owner": "Propriéataire",
"path": "Emplacement",
"play": "Lire",
"pistes": "Pistes"
},
"track": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"album-artist": "Artiste de lalbum",
"album": "Album",
"duration": "Durée",
"path": "Emplacement",
"play": "Lire",
"position": "Disque / Piste",
"release-date": "Date de sortie"
}
},
"track": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"added-on": "Ajouté le",
"album-artist": "Artiste de lalbum",
"album": "Album",
"bitrate": " {'|'} {rate} Kb/s",
"channels": " {'|'} {channels}",
"comment": "Commentaire",
"composer": "Compositeur",
"duration": "Durée",
"genre": "Genre",
"mark-as-new": "Marquer comme nouveau",
"mark-as-played": "Marquer comme lu",
"path": "Emplacement",
"play": "Lire",
"position": "Disque / Piste",
"quality": "Qualité",
"rating-value": "{rating} / 10",
"rating": "Classement",
"release-date": "Date de sortie",
"samplerate": " {'|'} {rate} Hz",
"spotify-album": "album",
"spotify-artist": "artiste",
"type": "Type",
"year": "Année"
},
"update": {
"all": "Tout actualiser",
"cancel": "Annuler",
"feeds": "Actualiser uniquement les flux RSS",
"info": "Recherche les fichiers ajoutés, supprimés et modifiés",
"local": "Actualiser uniquement la bibliothèque locale",
"progress": "Actualisation de la bibliothèque en cours…",
"rescan-metadata": "Analyser les métadonnées des fichiers non modifiés",
"rescan": "Analyser",
"spotify": "Actualiser uniquement Spotify",
"title": "Actualisation de la bibliothèque"
}
},
"language": {
"en": "Anglais (English)",
"fr": "Français"
},
"list": {
"albums": {
"info-1": "Supprimer définitivement ce podcast de votre bibliothèque ?",
"info-2": "Cela supprimera également la liste de lecture RSS ",
"notification": "Le podcast ne peut être supprimé. Il navait probablement pas été ajouté comme une liste de lecture RSS."
},
"spotify": {
"not-playable-track": "La piste ne peut pas être lue",
"restriction-reason": ", raison de la restriction : {reason}"
}
},
"media": {
"kind": {
"album": "Album",
"audiobook": "Livre audio",
"music": "Musique",
"podcast": "Podcast"
}
},
"navigation": {
"about": "À propos",
"albums": "Albums",
"artists": "Artistes",
"audiobooks": "Livres audio",
"now-playing": " - {album}",
"stream-error": "Erreur du flux HTTP : échec du chargement du flux ou arrêt du chargement en raison dun problème réseau",
"stream": "Flux HTTP",
"volume": "Volume",
"files": "Fichiers",
"genres": "Genres",
"music": "Musique",
"playlists": "Listes de lecture",
"podcasts": "Podcasts",
"radio": "Radio",
"search": "Recherche",
"settings": "Réglages",
"spotify": "Spotify",
"title": "OwnTone",
"update-library": "Actualiser la bibliothèque"
},
"page": {
"about": {
"albums": "Albums",
"artists": "Artistes",
"built-with": "Interface utilisateur construite avec <a href=\"https://bulma.io\">Bulma</a>, <a href=\"https://materialdesignicons.com/\">Material Design Icons</a>, <a href=\"https://vuejs.org/\">Vue.js</a>, <a href=\"https://github.com/mzabriskie/axios\">axios</a> et <a href=\"https://github.com/owntone/owntone-server/network/dependencies\">plus</a>.",
"compiled-with": "Compilé avec les options {options}.",
"library": "Bibliothèque",
"total-playtime": "Durée totale de lecture",
"pistes": "Pistes",
"update": "Actualiser",
"updated-on": "il y a {time}",
"updated": "Mis à jour",
"uptime": "Temps de fonctionnement",
"name": "Version {version}"
},
"album": {
"shuffle": "Lecture aléatoire",
"track-count": "{count} pistes"
},
"albums": {
"count": "{count} albums",
"filter": "Filtre",
"hide-singles-help": "Si actif, masque les singles et les albums dont les pistes napparaissent que dans les listes de lecture.",
"hide-singles": "Masquer les singles",
"hide-spotify-help": "Si actif, masque les albums qui napparaissent que dans votre bibliothèque Spotify.",
"hide-spotify": "Masquer les albums de Spotify",
"title": "Albums",
"sort-by": {
"title": "Trier par",
"name": "Nom",
"recently-added": "Ajouts récents",
"recently-released": "Sorties récentes"
}
},
"artist": {
"album-count": "{count} albums {'|'} ",
"shuffle": "Lecture aléatoire",
"track-count": "{count} pistes",
"sort-by": {
"title": "Trier par",
"name": "Nom",
"release-date": "Date de sortie"
}
},
"artists": {
"count": "{count} artists",
"filter": "Filtre",
"sort-by": {
"title": "Trier par",
"name": "Nom",
"recently-added": "Ajouts récents"
},
"hide-singles-help": "Si actif, masque les artistes qui napparaissent que dans des singles ou des listes de lecture.",
"hide-singles": "Masquer les singles",
"hide-spotify-help": "Si actif, masque les artistes qui napparaissent que dans votre bibliothèque Spotify.",
"hide-spotify": "Masquer les artistes de Spotify",
"title": "Artistes"
},
"audiobooks": {
"album": {
"play": "Lire",
"track-count": "{count} pistes"
},
"albums": {
"count": "{count} livres audio",
"title": "Livres audio"
},
"artiste": {
"albums-count": "{count} albums",
"shuffle": "Lecture aléatoire"
},
"artists": {
"count": "{count} auteurs",
"title": "Auteurs"
}
},
"browse": {
"albums": "albums",
"show-more": "Afficher plus",
"pistes": "pistes",
"recently-added": {
"albums": "albums",
"title": "Ajouts récents"
},
"recently-played": {
"title": "Lectures récentes",
"pistes": "pistes"
}
},
"composer": {
"album-count": "{count} albums {'|'}",
"shuffle": "Lecture aléatoire",
"track-count": "{count} pistes",
"pistes": {
"album-count": "{count} albums",
"track-count": " {'|'} {count} pistes",
"shuffle": "Lecture aléatoire"
}
},
"composers": {
"count": "{count} compositeurs",
"title": "Compositeurs"
},
"files": {
"play": "Lire",
"title": "Fichiers"
},
"genre": {
"album-count": "{count} albums {'|'} ",
"shuffle": "Lecture aléatoire",
"pistes": {
"album-count": "{count} albums {'|'} ",
"count": " {count} pistes",
"shuffle": "Lecture aléatoire"
}
},
"genres": {
"count": "{count} genres",
"title": "Genres"
},
"now-playing": {
"info": "Ajoutez des pistes en parcourant votre bibliothèque",
"title": "La file dattente est vide"
},
"playlist": {
"length": "{length} pistes",
"shuffle": "Lecture aléatoire"
},
"playlists": {
"count": "{count} listes de lecture"
},
"podcast": {
"play": "Lire",
"remove-error": "Le podcast ne peut pas être supprimé. Il na probablement pas été ajouté en tant que liste de lecture RSS.",
"remove-info-1": "Supprimer ce podcast de manière permanente de la bibliothèque ?",
"remove-info-2": "Cela supprimera également la liste de lecture RSS ",
"track-count": "{count} pistes"
},
"podcasts": {
"add": "Ajouter",
"count": "{count} podcasts",
"mark-all-played": "Marquer comme lus",
"new-episodes": "Nouveaux épisodes",
"title": "Podcasts",
"update": "Actualiser"
},
"queue": {
"add-stream": "Ajouter un flux",
"clear": "Effacer",
"count": "{count} pistes",
"edit": "Éditer",
"hide-previous": "Masquer lhistorique",
"queue": "File dattente"
},
"radio": {
"count": "{count} stations",
"title": "Radio"
},
"search": {
"albums": "Albums",
"artists": "Artistes",
"audiobooks": "Livres audio",
"composers": "Compositeurs",
"help": "Astuce : en préfixant votre requête avec <code>query:</code>, vous pouvez effectuer une recherche avec une <a href=\"https://github.com/owntone/owntone-server/blob/master/README_SMARTPL.md\" target=\"_blank\">expression</a> du langage de requête de liste de lecture intelligente.",
"no-albums": "Aucun album trouvé",
"no-artists": "Aucun artiste trouvé",
"no-audiobooks": "Aucun livre audio trouvé",
"no-composers": "Aucun compositeur trouvé",
"no-playlists": "Aucune liste de lecture trouvée",
"no-podcasts": "Aucune podcast trouvé",
"no-tracks": "Aucune piste trouvée",
"playlists": "Listes de lecture",
"podcasts": "Podcasts",
"show-albums": "Afficher les {count} albums",
"show-artists": "Afficher les {count} artistes",
"show-audiobooks": "Afficher les {count} livres audio",
"show-composers": "Afficher les {count} compositeurs",
"show-playlists": "Afficher les {count} listes de lecture",
"show-podcasts": "Afficher les {count} podcasts",
"show-tracks": "Afficher les {count} pistes",
"pistes": "Pistes"
},
"settings": {
"artwork": {
"artwork": "Illustrations",
"coverartarchive": "Cover Art Archive",
"discogs": "Discogs",
"explanation-1": "Prend en charge les illustrations au format PNG et JPEG qui sont soit placées dans la bibliothèque en tant que fichiers image séparés, soit intégrées dans les fichiers média, soit mises à disposition en ligne par les stations de radio.",
"explanation-2": "En outre, vous pouvez activer la récupération des illustrations à partir des fournisseurs dillustrations suivants :",
"spotify": "Spotify"
},
"devices": {
"no-active-pairing": "Aucune demande de jumelage active.",
"pairing-request": "Demande de jumelage de télécommande ",
"pairing": "Jumelage de télécommande",
"send": "Envoyer",
"speaker-pairing-info": "Si votre enceinte nécessite un jumelage, activez-la ci-dessous et entrez le code PIN quelle affiche.",
"speaker-pairing": "Jumelage denceinte et vérification dappareil",
"verification-code": "Code de vérification",
"verify": "Vérifier"
},
"general": {
"album-lists": "Listes dalbum",
"audiobooks": "Livres audio",
"files": "Fichiers audio",
"language": "Langue",
"music": "Musique",
"navigation-item-selection-info": "Si vous sélectionnez plus déléments que ce qui peut être affiché sur votre écran, le menu disparaîtra.",
"navigation-item-selection": "Sélectionnez les éléments de la barre de navigation supérieure",
"navigation-items": "Barre de navigation",
"now-playing-page": "Now playing page",
"playlists": "Listes de lecture",
"podcasts": "Podcasts",
"radio": "Radio",
"recently-added-page-info": "Limiter le nombre dalbum affichés dans la section \"Ajouts récents\"",
"recently-added-page": "Recently added page",
"search": "Search",
"show-composer-genres-info-1": "Liste des genres, séparés par des virgules, que le compositeur doit afficher sur la page \"Lecture en cours\".",
"show-composer-genres-info-2": "Laissez vide pour toujours afficher le compositeur.",
"show-composer-genres-info-3": "Létiquette de genre de la piste actuelle est comparée en vérifiant si lun des genres définis est inclus. Par exemple, en choisissant \"classique, bande sonore\", le compositeur pour les pistes dont létiquette de genre est \"classique contemporain\" sera affiché.",
"show-composer-genres": "Afficher le compositeur uniquement pour les genres listés",
"show-composer-info": "Si actif, le compositeur de la piste en cours de lecture est affiché sur la page \"lecture en cours\"",
"show-composer": "Afficher le compositeur",
"show-coverart": "Afficher les illustration dans la liste dalbums"
},
"services": {
"lastfm": {
"grant-access": "<b>Last.fm</b> - Connectez-vous avec votre nom dutilisateur et votre mot de passe Last.fm pour activer le scrobbling.",
"info": "Le nom dutilisateur et le mot de passe Last.fm ne sont pas enregistrés, uniquement la clé de session. La clé de session nexpire pas.",
"title": "Last.fm",
"no-support": "Loption Last.fm nest pas présente.",
"stop-scrobbling": "Arrêter le scrobbling"
},
"spotify": {
"no-support": "Loption Spotify nest pas présente.",
"help": "Si vous vous connectez habituellement à Spotify avec votre compte Facebook, vous devez dabord vous rendre sur le site de Spotify pour obtenir le nom dutilisateur et le mot de passe correspondant à votre compte.",
"logged-as": "Connecté en tant que ",
"requirements": "Vous devez posséder un compte Spotify Premium.",
"scopes": "Laccès à lAPI de Spotify permet lanalyse de votre bibliothèque Spotify. Les champs dapplication requis sont les suivants :",
"user": "Accès autorisé pour ",
"authorize": "Autoriser laccès à lAPI",
"credentials": " - Connectez-vous avec votre nom dutilisateur et mot de passe Spotify",
"grant-access": "<b>Spotify</b> - Accordez laccès à lAPI de Spotify",
"help-1": "La bibliothèque libspotify permet de lire les pistes de Spotify.",
"help-2": "Votre nom dutilisateur et votre mot de passe Spotify ne sont pas enregistrés, uniquement le jeton de connexion.",
"reauthorize": "Veuillez autoriser à nouveau laccès à lAPI pour accorder à OwnTone les droits daccès supplémentaires suivants :",
"title": "Spotify"
},
"login": "Se connecter",
"logout": "Se déconnecter"
},
"tabs": {
"music": {
"albums": "Albums",
"artists": "Artistes",
"browse": "Parcourir",
"composers": "Compositeurs",
"genres": "Genres",
"spotify": "Spotify"
},
"search": {
"library": "Library",
"spotify": "Spotify"
},
"settings": {
"artwork": "Illustrations",
"general": "Général",
"online-services": "Services en ligne",
"remotes-and-outputs": "Télécommandes et sorties"
}
}
},
"spotify": {
"album": {
"shuffle": "Lecture aléatoire",
"track-count": "{count} pistes"
},
"artiste": {
"album-count": "{count} albums",
"shuffle": "Lecture aléatoire"
},
"browse": {
"featured-playlists": "Listes de lecture en vedette",
"new-releases": "Nouvelle sorties",
"show-more": "Afficher plus"
},
"playlist": {
"count": "{count} pistes",
"shuffle": "Lecture aléatoire"
},
"search": {
"albums": "Albums",
"artists": "Artistes",
"no-albums": "Aucun album trouvé",
"no-artists": "Aucun artiste trouvé",
"no-playlists": "Aucune liste de lecture trouvée",
"no-tracks": "Aucune piste trouvée",
"playlists": "Listes de lecture",
"show-all-albums": "Afficher les {count} albums",
"show-all-artists": "Afficher les {count} artistes",
"show-all-playlists": "Afficher les {count} listes de lecture",
"show-all-tracks": "Afficher les {count} pistes",
"pistes": "Pistes"
}
}
},
"setting": {
"not-saved": " (erreur à lenregistrement du réglage)",
"saved": " (réglage enregistré)"
},
"server": {
"connection-failed": "Échec de connexion au serveur",
"missing-port": "Port websocket manquant",
"request-failed": "La requête a échoué (status: {status} {cause} {url})",
"queue-saved": "La file dattente enregistrée dans la liste de lecture {name}",
"appended-tracks": "{count} pistes ajoutées à la file dattente",
"empty-queue": "La file dattente est vide"
},
"group-by-list": {
"today": "Aujourdhui",
"last-week": "La semaine dernière",
"last-month": "Le mois dernier"
}
}

View File

@ -1,4 +1,5 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import i18n from './i18n'
import store from './store' import store from './store'
import { router } from './router' import { router } from './router'
import VueProgressBar from '@aacassandra/vue3-progressbar' import VueProgressBar from '@aacassandra/vue3-progressbar'
@ -30,6 +31,7 @@ const app = createApp(App)
.use(mdiVue, { .use(mdiVue, {
icons: icons icons: icons
}) })
.use(i18n)
app.config.globalProperties.$filters = filters app.config.globalProperties.$filters = filters
app.mount('#app') app.mount('#app')

View File

@ -4,10 +4,7 @@
<div class="container"> <div class="container">
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-four-fifths has-text-centered-mobile"> <div class="column is-four-fifths has-text-centered-mobile">
<p class="heading"><b>OwnTone</b> - version {{ config.version }}</p> <h1 class="title is-4" v-text="config.library_name" />
<h1 class="title is-4">
{{ config.library_name }}
</h1>
</div> </div>
</div> </div>
</div> </div>
@ -21,65 +18,84 @@
<!-- Left side --> <!-- Left side -->
<div class="level-left"> <div class="level-left">
<div class="level-item"> <div class="level-item">
<h2 class="title is-5">Library</h2> <h2 class="title is-5" v-text="$t('page.about.library')" />
</div> </div>
</div> </div>
<!-- Right side --> <!-- Right side -->
<div class="level-right"> <div class="level-right">
<div v-if="library.updating"> <div v-if="library.updating">
<a class="button is-small is-loading">Update</a> <a
class="button is-small is-loading"
v-text="$t('page.about.update')"
/>
</div> </div>
<div v-else> <div v-else>
<a class="button is-small" @click="showUpdateDialog()" <a
>Update</a class="button is-small"
> @click="showUpdateDialog()"
v-text="$t('page.about.update')"
/>
</div> </div>
</div> </div>
</nav> </nav>
<table class="table"> <table class="table">
<tbody> <tbody>
<tr> <tr>
<th>Artists</th> <th class="has-text-left" v-text="$t('page.about.artists')" />
<td
class="has-text-right"
v-text="$filters.number(library.artists)"
/>
</tr>
<tr>
<th class="has-text-left" v-text="$t('page.about.albums')" />
<td
class="has-text-right"
v-text="$filters.number(library.albums)"
/>
</tr>
<tr>
<th class="has-text-left" v-text="$t('page.about.tracks')" />
<td
class="has-text-right"
v-text="$filters.number(library.songs)"
/>
</tr>
<tr>
<th class="has-text-left" v-text="$t('page.about.total-playtime')" />
<td
class="has-text-right"
v-text="
$filters.durationInDays(library.db_playtime * 1000)
"
/>
</tr>
<tr>
<th class="has-text-left" v-text="$t('page.about.updated')" />
<td class="has-text-right"> <td class="has-text-right">
{{ $filters.number(library.artists) }} <span
v-text="
$t('page.about.updated-on', {
time: $filters.timeFromNow(library.updated_at)
})
"
/>
(<span
class="has-text-grey"
v-text="$filters.datetime(library.updated_at)"
/>)
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Albums</th> <th class="has-text-left" v-text="$t('page.about.uptime')" />
<td class="has-text-right"> <td class="has-text-right">
{{ $filters.number(library.albums) }} <span
</td> v-text="$filters.timeFromNow(library.started_at, true)"
</tr> />
<tr> (<span
<th>Tracks</th> class="has-text-grey"
<td class="has-text-right"> v-text="$filters.datetime(library.started_at)"
{{ $filters.number(library.songs) }} />)
</td>
</tr>
<tr>
<th>Total playtime</th>
<td class="has-text-right">
{{ $filters.durationInDays(library.db_playtime * 1000) }}
</td>
</tr>
<tr>
<th>Library updated</th>
<td class="has-text-right">
{{ $filters.timeFromNow(library.updated_at) }} ago
<span class="has-text-grey"
>({{ $filters.datetime(library.updated_at) }})</span
>
</td>
</tr>
<tr>
<th>Uptime</th>
<td class="has-text-right">
{{ $filters.timeFromNow(library.started_at, true) }}
<span class="has-text-grey"
>({{ $filters.datetime(library.started_at) }})</span
>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -94,20 +110,16 @@
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-four-fifths"> <div class="column is-four-fifths">
<div class="content has-text-centered-mobile"> <div class="content has-text-centered-mobile">
<p class="is-size-7"> <p class="is-size-7" v-text="$t('page.about.version', { version: config.version })" />
Compiled with support for {{ config.buildoptions.join(', ') }}. <p
</p> class="is-size-7"
<p class="is-size-7"> v-text="
Web interface built with <a href="http://bulma.io">Bulma</a>, $t('page.about.compiled-with', {
<a href="https://materialdesignicons.com/" options: config.buildoptions.join(', ')
>Material Design Icons</a })
>, <a href="https://vuejs.org/">Vue.js</a>, "
<a href="https://github.com/mzabriskie/axios">axios</a> and />
<a <p class="is-size-7" v-html="$t('page.about.built-with')" />
href="https://github.com/owntone/owntone-server/network/dependencies"
>more</a
>.
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,23 +1,20 @@
<template> <template>
<content-with-hero> <content-with-hero>
<template #heading-left> <template #heading-left>
<h1 class="title is-5"> <h1 class="title is-5" v-text="album.name" />
{{ album.name }}
</h1>
<h2 class="subtitle is-6 has-text-link has-text-weight-normal"> <h2 class="subtitle is-6 has-text-link has-text-weight-normal">
<a class="has-text-link" @click="open_artist">{{ album.artist }}</a> <a class="has-text-link" @click="open_artist" v-text="album.artist" />
</h2> </h2>
<div class="buttons fd-is-centered-mobile fd-has-margin-top"> <div class="buttons fd-is-centered-mobile fd-has-margin-top">
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span> <mdicon class="icon" name="shuffle" size="16" />
<span>Shuffle</span> <span v-text="$t('page.album.shuffle')" />
</a> </a>
<a <a
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_album_details_modal = true" @click="show_album_details_modal = true"
> >
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span> <mdicon class="icon" name="dots-horizontal" size="16" />
</a> </a>
</div> </div>
</template> </template>
@ -32,9 +29,10 @@
</p> </p>
</template> </template>
<template #content> <template #content>
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top"> <p
{{ album.track_count }} tracks class="heading is-7 has-text-centered-mobile fd-has-margin-top"
</p> v-text="$t('page.album.track-count', { count: album.track_count })"
/>
<list-tracks :tracks="tracks" :uris="album.uri" /> <list-tracks :tracks="tracks" :uris="album.uri" />
<modal-dialog-album <modal-dialog-album
:show="show_album_details_modal" :show="show_album_details_modal"

View File

@ -1,14 +1,16 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #options> <template #options>
<index-button-list :index="albums.indexList" /> <index-button-list :index="albums.indexList" />
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<p class="heading" style="margin-bottom: 24px">Filter</p> <p
class="heading"
style="margin-bottom: 24px"
v-text="$t('page.albums.filter')"
/>
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input <input
@ -18,12 +20,12 @@
name="switchHideSingles" name="switchHideSingles"
class="switch" class="switch"
/> />
<label for="switchHideSingles">Hide singles</label> <label
for="switchHideSingles"
v-text="$t('page.albums.hide-singles')"
/>
</div> </div>
<p class="help"> <p class="help" v-text="$t('page.albums.hide-singles-help')" />
If active, hides singles and albums with tracks that only appear
in playlists.
</p>
</div> </div>
<div v-if="spotify_enabled" class="field"> <div v-if="spotify_enabled" class="field">
<div class="control"> <div class="control">
@ -34,26 +36,33 @@
name="switchHideSpotify" name="switchHideSpotify"
class="switch" class="switch"
/> />
<label for="switchHideSpotify">Hide albums from Spotify</label> <label
for="switchHideSpotify"
v-text="$t('page.albums.hide-spotify')"
/>
</div> </div>
<p class="help"> <p class="help" v-text="$t('page.albums.hide-spotify-help')" />
If active, hides albums that only appear in your Spotify
library.
</p>
</div> </div>
</div> </div>
<div class="column"> <div class="column">
<p class="heading" style="margin-bottom: 24px">Sort by</p> <p
class="heading"
style="margin-bottom: 24px"
v-text="$t('page.albums.sort-by.title')"
/>
<dropdown-menu <dropdown-menu
v-model="selected_groupby_option_name" v-model="selected_groupby_option_id"
:options="groupby_option_names" :options="groupby_options"
/> />
</div> </div>
</div> </div>
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4">Albums</p> <p class="title is-4" v-text="$t('page.albums.title')" />
<p class="heading">{{ albums.count }} Albums</p> <p
class="heading"
v-text="$t('page.albums.count', { count: albums.count })"
/>
</template> </template>
<template #heading-right /> <template #heading-right />
<template #content> <template #content>
@ -117,16 +126,22 @@ export default {
// List of group by/sort options for itemsGroupByList // List of group by/sort options for itemsGroupByList
groupby_options: [ groupby_options: [
{ name: 'Name', options: bySortName('name_sort') },
{ {
name: 'Recently added', id: 1,
name: this.$t('page.albums.sort-by.name'),
options: bySortName('name_sort')
},
{
id: 2,
name: this.$t('page.albums.sort-by.recently-added'),
options: byYear('time_added', { options: byYear('time_added', {
direction: 'desc', direction: 'desc',
defaultValue: '0000' defaultValue: '0000'
}) })
}, },
{ {
name: 'Recently released', id: 3,
name: this.$t('page.albums.sort-by.recently-released'),
options: byYear('date_released', { options: byYear('date_released', {
direction: 'desc', direction: 'desc',
defaultValue: '0000' defaultValue: '0000'
@ -139,7 +154,7 @@ export default {
computed: { computed: {
albums() { albums() {
const groupBy = this.groupby_options.find( const groupBy = this.groupby_options.find(
(o) => o.name === this.selected_groupby_option_name (o) => o.id === this.selected_groupby_option_id
) )
this.albums_list.group(groupBy.options, [ this.albums_list.group(groupBy.options, [
(album) => !this.hide_singles || album.track_count > 2, (album) => !this.hide_singles || album.track_count > 2,
@ -149,11 +164,7 @@ export default {
return this.albums_list return this.albums_list
}, },
groupby_option_names() { selected_groupby_option_id: {
return [...this.groupby_options].map((o) => o.name)
},
selected_groupby_option_name: {
get() { get() {
return this.$store.state.albums_sort return this.$store.state.albums_sort
}, },

View File

@ -3,18 +3,20 @@
<template #options> <template #options>
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<p class="heading" style="margin-bottom: 24px">Sort by</p> <p
class="heading"
style="margin-bottom: 24px"
v-text="$t('page.artist.sort-by.title')"
/>
<dropdown-menu <dropdown-menu
v-model="selected_groupby_option_name" v-model="selected_groupby_option_id"
:options="groupby_option_names" :options="groupby_options"
/> />
</div> </div>
</div> </div>
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4" v-text="artist.name" />
{{ artist.name }}
</p>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -22,20 +24,24 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_artist_details_modal = true" @click="show_artist_details_modal = true"
> >
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span> <mdicon class="icon" name="dots-horizontal" size="16" />
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span> <mdicon class="icon" name="shuffle" size="16" />
<span>Shuffle</span> <span v-text="$t('page.artist.shuffle')" />
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p class="heading has-text-centered-mobile">
{{ artist.album_count }} albums | <span
<a class="has-text-link" @click="open_tracks" v-text="$t('page.artist.album-count', { count: artist.album_count })"
>{{ artist.track_count }} tracks</a />
> <a
class="has-text-link"
@click="open_tracks"
v-text="$t('page.artist.track-count', { count: artist.track_count })"
/>
</p> </p>
<list-albums :albums="albums" :hide_group_title="true" /> <list-albums :albums="albums" :hide_group_title="true" />
<modal-dialog-artist <modal-dialog-artist
@ -99,9 +105,14 @@ export default {
// List of group by/sort options for itemsGroupByList // List of group by/sort options for itemsGroupByList
groupby_options: [ groupby_options: [
{ name: 'Name', options: bySortName('name_sort') },
{ {
name: 'Release date', id: 1,
name: this.$t('page.artist.sort-by.name'),
options: bySortName('name_sort')
},
{
id: 2,
name: this.$t('page.artist.sort-by.release-date'),
options: byYear('date_released', { options: byYear('date_released', {
direction: 'asc', direction: 'asc',
defaultValue: '0000' defaultValue: '0000'
@ -116,18 +127,14 @@ export default {
computed: { computed: {
albums() { albums() {
const groupBy = this.groupby_options.find( const groupBy = this.groupby_options.find(
(o) => o.name === this.selected_groupby_option_name (o) => o.name === this.selected_groupby_option_id
) )
this.albums_list.group(groupBy.options) this.albums_list.group(groupBy.options)
return this.albums_list return this.albums_list
}, },
groupby_option_names() { selected_groupby_option_id: {
return [...this.groupby_options].map((o) => o.name)
},
selected_groupby_option_name: {
get() { get() {
return this.$store.state.artist_albums_sort return this.$store.state.artist_albums_sort
}, },

View File

@ -5,9 +5,7 @@
<index-button-list :index="index_list" /> <index-button-list :index="index_list" />
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4" v-text="artist.name" />
{{ artist.name }}
</p>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -15,22 +13,26 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_artist_details_modal = true" @click="show_artist_details_modal = true"
> >
<span class="icon" <mdicon class="icon" name="dots-horizontal" size="16" />
><mdicon name="dots-horizontal" size="16"
/></span>
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span> <mdicon class="icon" name="shuffle" size="16" />
<span>Shuffle</span> <span v-text="$t('page.artist.shuffle')" />
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p class="heading has-text-centered-mobile">
<a class="has-text-link" @click="open_artist" <a
>{{ artist.album_count }} albums</a class="has-text-link"
> @click="open_artist"
| {{ artist.track_count }} tracks v-text="
$t('page.artist.track-count', {
albums: artist.album_count,
tracks: artist.track_count
})
"
/>
</p> </p>
<list-tracks :tracks="tracks.items" :uris="track_uris" /> <list-tracks :tracks="tracks.items" :uris="track_uris" />
<modal-dialog-artist <modal-dialog-artist

View File

@ -1,14 +1,16 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #options> <template #options>
<index-button-list :index="artists.indexList" /> <index-button-list :index="artists.indexList" />
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<p class="heading" style="margin-bottom: 24px">Filter</p> <p
class="heading"
style="margin-bottom: 24px"
v-text="$t('page.artists.filter')"
/>
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input <input
@ -18,12 +20,12 @@
name="switchHideSingles" name="switchHideSingles"
class="switch" class="switch"
/> />
<label for="switchHideSingles">Hide singles</label> <label
for="switchHideSingles"
v-text="$t('page.artists.hide-singles')"
/>
</div> </div>
<p class="help"> <p class="help" v-text="$t('page.artists.hide-singles-help')" />
If active, hides artists that only appear on singles or
playlists.
</p>
</div> </div>
<div v-if="spotify_enabled" class="field"> <div v-if="spotify_enabled" class="field">
<div class="control"> <div class="control">
@ -34,26 +36,33 @@
name="switchHideSpotify" name="switchHideSpotify"
class="switch" class="switch"
/> />
<label for="switchHideSpotify">Hide artists from Spotify</label> <label
for="switchHideSpotify"
v-text="$t('page.artists.hide-spotify')"
/>
</div> </div>
<p class="help"> <p class="help" v-text="$t('page.artists.hide-spotify-help')" />
If active, hides artists that only appear in your Spotify
library.
</p>
</div> </div>
</div> </div>
<div class="column"> <div class="column">
<p class="heading" style="margin-bottom: 24px">Sort by</p> <p
class="heading"
style="margin-bottom: 24px"
v-text="$t('page.artists.sort-by.title')"
/>
<dropdown-menu <dropdown-menu
v-model="selected_groupby_option_name" v-model="selected_groupby_option_id"
:options="groupby_option_names" :options="groupby_options"
/> />
</div> </div>
</div> </div>
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4">Artists</p> <p class="title is-4" v-text="$t('page.artists.title')" />
<p class="heading">{{ artists.count }} Artists</p> <p
class="heading"
v-text="$t('page.artists.count', { count: artists.count })"
/>
</template> </template>
<template #heading-right /> <template #heading-right />
<template #content> <template #content>
@ -118,9 +127,14 @@ export default {
// List of group by/sort options for itemsGroupByList // List of group by/sort options for itemsGroupByList
groupby_options: [ groupby_options: [
{ name: 'Name', options: bySortName('name_sort') },
{ {
name: 'Recently added', id: 1,
name: this.$t('page.artists.sort-by.name'),
options: bySortName('name_sort')
},
{
id: 2,
name: this.$t('page.artists.sort-by.recently-added'),
options: byYear('time_added', { options: byYear('time_added', {
direction: 'desc', direction: 'desc',
defaultValue: '0000' defaultValue: '0000'
@ -138,7 +152,7 @@ export default {
} }
const groupBy = this.groupby_options.find( const groupBy = this.groupby_options.find(
(o) => o.name === this.selected_groupby_option_name (o) => o.id === this.selected_groupby_option_id
) )
this.artists_list.group(groupBy.options, [ this.artists_list.group(groupBy.options, [
(artist) => (artist) =>
@ -149,12 +163,7 @@ export default {
return this.artists_list return this.artists_list
}, },
// List for the drop down menu selected_groupby_option_id: {
groupby_option_names() {
return [...this.groupby_options].map((o) => o.name)
},
selected_groupby_option_name: {
get() { get() {
return this.$store.state.artists_sort return this.$store.state.artists_sort
}, },

View File

@ -1,23 +1,20 @@
<template> <template>
<content-with-hero> <content-with-hero>
<template #heading-left> <template #heading-left>
<h1 class="title is-5"> <h1 class="title is-5" v-text="album.name" />
{{ album.name }}
</h1>
<h2 class="subtitle is-6 has-text-link has-text-weight-normal"> <h2 class="subtitle is-6 has-text-link has-text-weight-normal">
<a class="has-text-link" @click="open_artist">{{ album.artist }}</a> <a class="has-text-link" @click="open_artist" v-text="album.artist" />
</h2> </h2>
<div class="buttons fd-is-centered-mobile fd-has-margin-top"> <div class="buttons fd-is-centered-mobile fd-has-margin-top">
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span>Play</span> <span v-text="$t('page.audiobooks.album.play')" />
</a> </a>
<a <a
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_album_details_modal = true" @click="show_album_details_modal = true"
> >
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span> <mdicon class="icon" name="dots-horizontal" size="16" />
</a> </a>
</div> </div>
</template> </template>
@ -32,9 +29,12 @@
</p> </p>
</template> </template>
<template #content> <template #content>
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top"> <p
{{ album.track_count }} tracks class="heading is-7 has-text-centered-mobile fd-has-margin-top"
</p> v-text="
$t('page.audiobooks.album.track-count', { count: album.track_count })
"
/>
<list-tracks :tracks="tracks" :uris="album.uri" /> <list-tracks :tracks="tracks" :uris="album.uri" />
<modal-dialog-album <modal-dialog-album
:show="show_album_details_modal" :show="show_album_details_modal"

View File

@ -1,14 +1,16 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-audiobooks /> <tabs-audiobooks />
<content-with-heading> <content-with-heading>
<template #options> <template #options>
<index-button-list :index="albums.indexList" /> <index-button-list :index="albums.indexList" />
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4">Audiobooks</p> <p class="title is-4" v-text="$t('page.audiobooks.albums.title')" />
<p class="heading">{{ albums.count }} Audiobooks</p> <p
class="heading"
v-text="$t('page.audiobooks.albums.count', { count: albums.count })"
/>
</template> </template>
<template #content> <template #content>
<list-albums :albums="albums" /> <list-albums :albums="albums" />

View File

@ -1,9 +1,7 @@
<template> <template>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4" v-text="artist.name" />
{{ artist.name }}
</p>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -11,18 +9,23 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_artist_details_modal = true" @click="show_artist_details_modal = true"
> >
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span> <mdicon class="icon" name="dots-horizontal" size="16" />
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span>Shuffle</span> <span v-text="$t('page.audiobooks.artist.shuffle')" />
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p
{{ artist.album_count }} albums class="heading has-text-centered-mobile"
</p> v-text="
$t('page.audiobooks.artist.album-count', {
count: artist.album_count
})
"
/>
<list-albums :albums="albums" /> <list-albums :albums="albums" />
<modal-dialog-artist <modal-dialog-artist
:show="show_artist_details_modal" :show="show_artist_details_modal"

View File

@ -1,14 +1,16 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-audiobooks /> <tabs-audiobooks />
<content-with-heading> <content-with-heading>
<template #options> <template #options>
<index-button-list :index="artists.indexList" /> <index-button-list :index="artists.indexList" />
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4">Authors</p> <p class="title is-4" v-text="$t('page.audiobooks.artists.title')" />
<p class="heading">{{ artists.count }} Authors</p> <p
class="heading"
v-text="$t('page.audiobooks.artists.count', { count: tists.count })"
/>
</template> </template>
<template #heading-right /> <template #heading-right />
<template #content> <template #content>

View File

@ -1,12 +1,11 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-music /> <tabs-music />
<!-- Recently added --> <!-- Recently added -->
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4">Recently added</p> <p class="title is-4" v-text="$t('page.browse.recently-added.title')" />
<p class="heading">albums</p> <p class="heading" v-text="$t('page.browse.albums')" />
</template> </template>
<template #content> <template #content>
<list-albums :albums="recently_added" /> <list-albums :albums="recently_added" />
@ -17,18 +16,20 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_browse('recently_added')" @click="open_browse('recently_added')"
>Show more</a v-text="$t('page.browse.show-more')"
> />
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<!-- Recently played --> <!-- Recently played -->
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4">Recently played</p> <p
<p class="heading">tracks</p> class="title is-4"
v-text="$t('page.browse.recently-played.title')"
/>
<p class="heading" v-text="$t('page.browse.tracks')" />
</template> </template>
<template #content> <template #content>
<list-tracks :tracks="recently_played.items" /> <list-tracks :tracks="recently_played.items" />
@ -39,8 +40,8 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_browse('recently_played')" @click="open_browse('recently_played')"
>Show more</a v-text="$t('page.browse.show-more')"
> />
</p> </p>
</nav> </nav>
</template> </template>

View File

@ -1,11 +1,10 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4">Recently added</p> <p class="title is-4" v-text="$t('page.browse.recently-added.title')" />
<p class="heading">albums</p> <p class="heading" v-text="$t('page.browse.recently-added.albums')" />
</template> </template>
<template #content> <template #content>
<list-albums :albums="recently_added" /> <list-albums :albums="recently_added" />

View File

@ -1,11 +1,13 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4">Recently played</p> <p
<p class="heading">tracks</p> class="title is-4"
v-text="$t('page.browse.recently.played.title')"
/>
<p class="heading" v-text="$t('page.browse.recently.played.tracks')" />
</template> </template>
<template #content> <template #content>
<list-tracks :tracks="recently_played.items" /> <list-tracks :tracks="recently_played.items" />

View File

@ -2,9 +2,7 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4" v-text="composer.name" />
{{ composer.name }}
</p>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -12,25 +10,30 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_composer_details_modal = true" @click="show_composer_details_modal = true"
> >
<span class="icon" <mdicon class="icon" name="dots-horizontal" size="16" />
><mdicon name="dots-horizontal" size="16"
/></span>
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span> <mdicon class="icon" name="shuffle" size="16" />
<span>Shuffle</span> <span v-text="$t('page.composer.shuffle')" />
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p class="heading has-text-centered-mobile">
{{ composer.album_count }} albums | <span
<a class="has-text-link" @click="open_tracks" v-text="
>{{ composer.track_count }} tracks</a $t('page.composer.album-count', { count: composer.album_count })
> "
/>
<a
class="has-text-link"
@click="open_tracks"
v-text="
$t('page.composer.track-count', { count: composer.track_count })
"
/>
</p> </p>
<list-albums :albums="albums_list" :hide_group_title="true" /> <list-albums :albums="albums_list" :hide_group_title="true" />
<modal-dialog-composer <modal-dialog-composer
:show="show_composer_details_modal" :show="show_composer_details_modal"
:composer="composer" :composer="composer"

View File

@ -2,9 +2,7 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4" v-text="composer.name" />
{{ composer.name }}
</p>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -12,22 +10,30 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_composer_details_modal = true" @click="show_composer_details_modal = true"
> >
<span class="icon" <mdicon class="icon" name="dots-horizontal" size="16" />
><mdicon name="dots-horizontal" size="16"
/></span>
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span> <mdicon class="icon" name="shuffle" size="16" />
<span>Shuffle</span> <span v-text="$t('page.composer.tracks.shuffle')" />
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p class="heading has-text-centered-mobile">
<a class="has-text-link" @click="open_albums" <a
>{{ composer.album_count }} albums</a class="has-text-link"
> @click="open_albums"
| {{ composer.track_count }} tracks v-text="
$t('page.composer.tracks.album-count', {
count: composer.album_count
})
"
/>
<span
v-text="
$t('page.composer.track-count', { count: composer.track_count })
"
/>
</p> </p>
<list-tracks :tracks="tracks.items" :expression="play_expression" /> <list-tracks :tracks="tracks.items" :expression="play_expression" />
<modal-dialog-composer <modal-dialog-composer

View File

@ -1,14 +1,16 @@
<template> <template>
<div> <div>
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #options> <template #options>
<index-button-list :index="composers.indexList" /> <index-button-list :index="composers.indexList" />
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4">Composers</p> <p class="title is-4" v-text="$t('page.composers.title')" />
<p class="heading">{{ composers.total }} composers</p> <p
class="heading"
v-text="$t('page.composers.count', { count: composers.total })"
/>
</template> </template>
<template #content> <template #content>
<list-composers :composers="composers" /> <list-composers :composers="composers" />

View File

@ -2,10 +2,8 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4">Files</p> <p class="title is-4" v-text="$t('page.files.title')" />
<p class="title is-7 has-text-grey"> <p class="title is-7 has-text-grey" v-text="current_directory" />
{{ current_directory }}
</p>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -13,27 +11,22 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="open_directory_dialog({ path: current_directory })" @click="open_directory_dialog({ path: current_directory })"
> >
<span class="icon" <mdicon class="icon" name="dots-horizontal" size="16" />
><mdicon name="dots-horizontal" size="16"
/></span>
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span> <mdicon class="icon" name="play" size="16" />
<span>Play</span> <span v-text="$t('page.files.play')" />
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<list-directories :directories="files.directories" /> <list-directories :directories="files.directories" />
<list-playlists :playlists="files.playlists.items" /> <list-playlists :playlists="files.playlists.items" />
<list-tracks <list-tracks
:tracks="files.tracks.items" :tracks="files.tracks.items"
:expression="play_expression" :expression="play_expression"
:show_icon="true" :show_icon="true"
/> />
<modal-dialog-directory <modal-dialog-directory
:show="show_directory_details_modal" :show="show_directory_details_modal"
:directory="selected_directory" :directory="selected_directory"

View File

@ -5,9 +5,7 @@
<index-button-list :index="albums_list.indexList" /> <index-button-list :index="albums_list.indexList" />
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4" v-text="genre.name" />
{{ genre.name }}
</p>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -15,22 +13,24 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_genre_details_modal = true" @click="show_genre_details_modal = true"
> >
<span class="icon" <mdicon class="icon" name="dots-horizontal" size="16" />
><mdicon name="dots-horizontal" size="16"
/></span>
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span> <mdicon class="icon" name="shuffle" size="16" />
<span>Shuffle</span> <span v-text="$t('page.genre.shuffle')" />
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p class="heading has-text-centered-mobile">
{{ genre.album_count }} albums | <span
<a class="has-text-link" @click="open_tracks" v-text="$t('page.genre.album-count', { count: genre.album_count })"
>{{ genre.track_count }} tracks</a />
> <a
class="has-text-link"
@click="open_tracks"
v-text="$t('page.genre.track-count', { count: genre.track_count })"
/>
</p> </p>
<list-albums :albums="albums_list" /> <list-albums :albums="albums_list" />
<modal-dialog-genre <modal-dialog-genre

View File

@ -5,9 +5,7 @@
<index-button-list :index="index_list" /> <index-button-list :index="index_list" />
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4" v-text="genre.name" />
{{ genre.name }}
</p>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -15,22 +13,26 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_genre_details_modal = true" @click="show_genre_details_modal = true"
> >
<span class="icon" <mdicon class="icon" name="dots-horizontal" size="16" />
><mdicon name="dots-horizontal" size="16"
/></span>
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span> <mdicon class="icon" name="shuffle" size="16" />
<span>Shuffle</span> <span v-text="$t('page.genre.tracks.shuffle')" />
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p class="heading has-text-centered-mobile">
<a class="has-text-link" @click="open_genre" <a
>{{ genre.album_count }} albums</a class="has-text-link"
> @click="open_genre"
| {{ genre.track_count }} tracks v-text="
$t('page.genre.tracks.album-count', { count: genre.album_count })
"
/>
<span
v-text="$t('page.genre.tracks.count', { count: genre.track_count })"
/>
</p> </p>
<list-tracks :tracks="tracks.items" :expression="expression" /> <list-tracks :tracks="tracks.items" :expression="expression" />
<modal-dialog-genre <modal-dialog-genre

View File

@ -1,14 +1,16 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #options> <template #options>
<index-button-list :index="genres.indexList" /> <index-button-list :index="genres.indexList" />
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4">Genres</p> <p class="title is-4" v-text="$t('page.genres.title')" />
<p class="heading">{{ genres.total }} genres</p> <p
class="heading"
v-text="$t('page.genres.count', { count: genres.total })"
/>
</template> </template>
<template #content> <template #content>
<list-genres :genres="genres" /> <list-genres :genres="genres" />

View File

@ -26,41 +26,29 @@
@start="start_dragging" @start="start_dragging"
@end="end_dragging" @end="end_dragging"
/> />
<!--range-slider
class="seek-slider fd-has-action"
min="0"
:max="state.item_length_ms"
:value="item_progress_ms"
:disabled="state.state === 'stop'"
step="1000"
@change="seek" >
</range-slider-->
</p> </p>
<p class="content"> <p class="content">
<span <span
>{{ $filters.durationInHours(item_progress_ms) }} / v-text="
{{ $filters.durationInHours(now_playing.length_ms) }}</span [
> $filters.durationInHours(item_progress_ms),
$filters.durationInHours(now_playing.length_ms)
].join(' / ')
"
/>
</p> </p>
</div> </div>
</div> </div>
<div class="fd-has-padding-left-right"> <div class="fd-has-padding-left-right">
<div class="container has-text-centered fd-has-margin-top"> <div class="container has-text-centered fd-has-margin-top">
<h1 class="title is-5"> <h1 class="title is-5" v-text="now_playing.title" />
{{ now_playing.title }} <h2 class="title is-6" v-text="now_playing.artist" />
</h1>
<h2 class="title is-6">
{{ now_playing.artist }}
</h2>
<h2 <h2
v-if="composer" v-if="composer"
class="subtitle is-6 has-text-grey has-text-weight-bold" class="subtitle is-6 has-text-grey has-text-weight-bold"
> v-text="composer"
{{ composer }} />
</h2> <h3 class="subtitle is-6" v-text="now_playing.album" />
<h3 class="subtitle is-6">
{{ now_playing.album }}
</h3>
</div> </div>
</div> </div>
</div> </div>
@ -70,8 +58,8 @@
style="flex-direction: column" style="flex-direction: column"
> >
<div class="content has-text-centered"> <div class="content has-text-centered">
<h1 class="title is-5">Your play queue is empty</h1> <h1 class="title is-5" v-text="$('page.now-playing.title')" />
<p>Add some tracks by browsing your library</p> <p v-text="$('page.now-playing.info')" />
</div> </div>
</div> </div>
</div> </div>
@ -85,7 +73,6 @@
<script> <script>
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue' import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue'
//import RangeSlider from 'vue-range-slider'
import Slider from '@vueform/slider' import Slider from '@vueform/slider'
import CoverArtwork from '@/components/CoverArtwork.vue' import CoverArtwork from '@/components/CoverArtwork.vue'
import webapi from '@/webapi' import webapi from '@/webapi'
@ -95,7 +82,6 @@ export default {
name: 'PageNowPlaying', name: 'PageNowPlaying',
components: { components: {
ModalDialogQueueItem, ModalDialogQueueItem,
// RangeSlider,
Slider, Slider,
CoverArtwork CoverArtwork
}, },

View File

@ -1,9 +1,7 @@
<template> <template>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4"> <div class="title is-4" v-text="playlist.name" />
{{ playlist.name }}
</div>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -11,16 +9,19 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_playlist_details_modal = true" @click="show_playlist_details_modal = true"
> >
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span> <mdicon class="icon" name="dots-horizontal" size="16" />
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span> <mdicon class="icon" name="shuffle" size="16" />
<span>Shuffle</span> <span v-text="$t('page.playlist.shuffle')" />
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile">{{ tracks.length }} tracks</p> <p
class="heading has-text-centered-mobile"
v-text="$t('page.playlist.length', { length: tracks.length })"
/>
<list-tracks :tracks="tracks" :uris="uris" /> <list-tracks :tracks="tracks" :uris="uris" />
<modal-dialog-playlist <modal-dialog-playlist
:show="show_playlist_details_modal" :show="show_playlist_details_modal"

View File

@ -1,10 +1,11 @@
<template> <template>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4" v-text="playlist.name" />
{{ playlist.name }} <p
</p> class="heading"
<p class="heading">{{ playlists.count }} playlists</p> v-text="$t('page.playlists.count', { count: playlists.count })"
/>
</template> </template>
<template #content> <template #content>
<list-playlists :playlists="playlists" /> <list-playlists :playlists="playlists" />

View File

@ -1,9 +1,7 @@
<template> <template>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4"> <div class="title is-4" v-text="album.name" />
{{ album.name }}
</div>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -11,20 +9,19 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_album_details_modal = true" @click="show_album_details_modal = true"
> >
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span> <mdicon class="icon" name="dots-horizontal" size="16" />
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"> <mdicon class="icon" name="play" size="16" />
<mdicon name="play" size="16" /> <span v-text="$t('page.podcast.play')" />
</span>
<span>Play</span>
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p
{{ album.track_count }} tracks class="heading has-text-centered-mobile"
</p> v-text="$t('page.podcast.track-count', { count: album.track_count })"
/>
<list-tracks <list-tracks
:tracks="tracks" :tracks="tracks"
:show_progress="true" :show_progress="true"
@ -47,11 +44,10 @@
@delete="remove_podcast" @delete="remove_podcast"
> >
<template #modal-content> <template #modal-content>
<p>Permanently remove this podcast from your library?</p> <p v-text="$t('page.podcast.remove-info-1')" />
<p class="is-size-7"> <p class="is-size-7">
(This will also remove the RSS playlist (<span v-text="$t('page.podcast.remove-info-2')" />
<b>{{ rss_playlist_to_remove.name }}</b <b v-text="rss_playlist_to_remove.name" />)
>.)
</p> </p>
</template> </template>
</modal-dialog> </modal-dialog>
@ -131,7 +127,7 @@ export default {
const rssPlaylists = data.items.filter((pl) => pl.type === 'rss') const rssPlaylists = data.items.filter((pl) => pl.type === 'rss')
if (rssPlaylists.length !== 1) { if (rssPlaylists.length !== 1) {
this.$store.dispatch('add_notification', { this.$store.dispatch('add_notification', {
text: 'Podcast cannot be removed. Probably it was not added as an RSS playlist.', text: this.$t('page.podcast.remove-error'),
type: 'danger' type: 'danger'
}) })
return return

View File

@ -2,15 +2,13 @@
<div> <div>
<content-with-heading v-if="new_episodes.items.length > 0"> <content-with-heading v-if="new_episodes.items.length > 0">
<template #heading-left> <template #heading-left>
<p class="title is-4">New episodes</p> <p class="title is-4" v-text="$t('page.podcasts.new-episodes')" />
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
<a class="button is-small" @click="mark_all_played"> <a class="button is-small" @click="mark_all_played">
<span class="icon"> <mdicon class="icon" name="pencil" size="16" />
<mdicon name="pencil" size="16" /> <span v-text="$t('page.podcasts.mark-all-played')" />
</span>
<span>Mark All Played</span>
</a> </a>
</div> </div>
</template> </template>
@ -22,25 +20,23 @@
/> />
</template> </template>
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4">Podcasts</p> <p class="title is-4" v-text="$t('page.podcasts.title')" />
<p class="heading">{{ albums.total }} podcasts</p> <p
class="heading"
v-text="$t('page.podcasts.count', { count: albums.total })"
/>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
<a v-if="rss.tracks > 0" class="button is-small" @click="update_rss"> <a v-if="rss.tracks > 0" class="button is-small" @click="update_rss">
<span class="icon"> <mdicon class="icon" name="refresh" size="16" />
<mdicon name="refresh" size="16" /> <span v-text="$t('page.podcasts.update')" />
</span>
<span>Update</span>
</a> </a>
<a class="button is-small" @click="open_add_podcast_dialog"> <a class="button is-small" @click="open_add_podcast_dialog">
<span class="icon"> <mdicon class="icon" name="rss" size="16" />
<mdicon name="rss" size="16" /> <span v-text="$t('page.podcasts.add')" />
</span>
<span>Add Podcast</span>
</a> </a>
</div> </div>
</template> </template>

View File

@ -1,8 +1,11 @@
<template> <template>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="heading">{{ queue.count }} tracks</p> <p class="title is-4" v-text="$t('page.queue.title')" />
<p class="title is-4">Queue</p> <p
class="heading"
v-text="$t('page.queue.count', { count: queue.count })"
/>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -11,32 +14,24 @@
:class="{ 'is-info': show_only_next_items }" :class="{ 'is-info': show_only_next_items }"
@click="update_show_next_items" @click="update_show_next_items"
> >
<span class="icon"> <mdicon class="icon" name="arrow-collapse-down" size="16" />
<mdicon name="arrow-collapse-down" size="16" /> <span v-text="$t('page.queue.hide-previous')" />
</span>
<span>Hide previous</span>
</a> </a>
<a class="button is-small" @click="open_add_stream_dialog"> <a class="button is-small" @click="open_add_stream_dialog">
<span class="icon"> <mdicon class="icon" name="web" size="16" />
<mdicon name="web" size="16" /> <span v-text="$t('page.queue.add-stream')" />
</span>
<span>Add Stream</span>
</a> </a>
<a <a
class="button is-small" class="button is-small"
:class="{ 'is-info': edit_mode }" :class="{ 'is-info': edit_mode }"
@click="edit_mode = !edit_mode" @click="edit_mode = !edit_mode"
> >
<span class="icon"> <mdicon class="icon" name="pencil" size="16" />
<mdicon name="pencil" size="16" /> <span v-text="$t('page.queue.edit')" />
</span>
<span>Edit</span>
</a> </a>
<a class="button is-small" @click="queue_clear"> <a class="button is-small" @click="queue_clear">
<span class="icon"> <mdicon class="icon" name="delete-empty" size="16" />
<mdicon name="delete-empty" size="16" /> <span v-text="$t('page.queue.clear')" />
</span>
<span>Clear</span>
</a> </a>
<a <a
v-if="is_queue_save_allowed" v-if="is_queue_save_allowed"
@ -44,10 +39,8 @@
:disabled="queue_items.length === 0" :disabled="queue_items.length === 0"
@click="save_dialog" @click="save_dialog"
> >
<span class="icon"> <mdicon class="icon" name="content-save" size="16" />
<mdicon name="content-save" size="16" /> <span v-text="$t('page.queue.save')" />
</span>
<span>Save</span>
</a> </a>
</div> </div>
</template> </template>
@ -68,17 +61,17 @@
> >
<template #actions> <template #actions>
<a v-if="!edit_mode" @click.prevent.stop="open_dialog(element)"> <a v-if="!edit_mode" @click.prevent.stop="open_dialog(element)">
<span class="icon has-text-dark" <mdicon
><mdicon name="dots-vertical" size="16" class="icon has-text-dark"
/></span> name="dots-vertical"
size="16"
/>
</a> </a>
<a <a
v-if="element.id !== state.item_id && edit_mode" v-if="element.id !== state.item_id && edit_mode"
@click.prevent.stop="remove(element)" @click.prevent.stop="remove(element)"
> >
<span class="icon has-text-grey" <mdicon class="icon has-text-grey" name="delete" size="18" />
><mdicon name="delete" size="18"
/></span>
</a> </a>
</template> </template>
</list-item-queue-item> </list-item-queue-item>

View File

@ -2,12 +2,13 @@
<div> <div>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4">Radio</p> <p class="title is-4" v-text="$t('page.radio.title')" />
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p
{{ tracks.total }} tracks class="heading has-text-centered-mobile"
</p> v-text="$t('page.radio.count', { count: tracks.total })"
/>
<list-tracks :tracks="tracks.items" /> <list-tracks :tracks="tracks.items" />
</template> </template>
</content-with-heading> </content-with-heading>

View File

@ -16,18 +16,10 @@
placeholder="Search" placeholder="Search"
autocomplete="off" autocomplete="off"
/> />
<span class="icon is-left"> <mdicon class="icon is-left" name="magnify" size="16" />
<mdicon name="magnify" size="16" />
</span>
</p> </p>
<p class="help has-text-centered"> <p class="help has-text-centered">
Tip: you can search by a smart playlist query language <span v-html="$t('page.search.help')" />
<a
href="https://github.com/owntone/owntone-server/blob/master/README_SMARTPL.md"
target="_blank"
>expression</a
>
if you prefix it with <code>query:</code>.
</p> </p>
</div> </div>
</form> </form>
@ -37,20 +29,18 @@
:key="recent_search" :key="recent_search"
class="tag" class="tag"
@click="open_recent_search(recent_search)" @click="open_recent_search(recent_search)"
>{{ recent_search }}</a v-text="recent_search"
> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<tabs-search :query="search_query" /> <tabs-search :query="search_query" />
<!-- Tracks --> <!-- Tracks -->
<content-with-heading v-if="show_tracks && tracks.total"> <content-with-heading v-if="show_tracks && tracks.total">
<template #heading-left> <template #heading-left>
<p class="title is-4">Tracks</p> <p class="title is-4" v-text="$t('page.search.tracks')" />
</template> </template>
<template #content> <template #content>
<list-tracks :tracks="tracks.items" /> <list-tracks :tracks="tracks.items" />
@ -61,22 +51,25 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search_tracks" @click="open_search_tracks"
>Show all {{ tracks.total.toLocaleString() }} tracks</a v-text="
> $t('page.search.show.tracks', {
count: tracks.total.toLocaleString()
})
"
/>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show_tracks && !tracks.total" class="mt-6"> <content-text v-if="show_tracks && !tracks.total" class="mt-6">
<template #content> <template #content>
<p><i>No tracks found</i></p> <p><i v-text="$t('page.search.no-tracks')" /></p>
</template> </template>
</content-text> </content-text>
<!-- Artists --> <!-- Artists -->
<content-with-heading v-if="show_artists && artists.total"> <content-with-heading v-if="show_artists && artists.total">
<template #heading-left> <template #heading-left>
<p class="title is-4">Artists</p> <p class="title is-4" v-text="$t('page.search.artists')" />
</template> </template>
<template #content> <template #content>
<list-artists :artists="artists" :hide_group_title="true" /> <list-artists :artists="artists" :hide_group_title="true" />
@ -87,22 +80,25 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search_artists" @click="open_search_artists"
>Show all {{ artists.total.toLocaleString() }} artists</a v-text="
> $t('page.search.show.artists', {
count: artists.total.toLocaleString()
})
"
/>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show_artists && !artists.total"> <content-text v-if="show_artists && !artists.total">
<template #content> <template #content>
<p><i>No artists found</i></p> <p><i v-text="$t('page.search.no-artists')" /></p>
</template> </template>
</content-text> </content-text>
<!-- Albums --> <!-- Albums -->
<content-with-heading v-if="show_albums && albums.total"> <content-with-heading v-if="show_albums && albums.total">
<template #heading-left> <template #heading-left>
<p class="title is-4">Albums</p> <p class="title is-4" v-text="$t('page.search.albums')" />
</template> </template>
<template #content> <template #content>
<list-albums :albums="albums" :hide_group_title="true" /> <list-albums :albums="albums" :hide_group_title="true" />
@ -113,22 +109,25 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search_albums" @click="open_search_albums"
>Show all {{ albums.total.toLocaleString() }} albums</a v-text="
> $t('page.search.show-albums', {
count: albums.total.toLocaleString()
})
"
/>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show_albums && !albums.total"> <content-text v-if="show_albums && !albums.total">
<template #content> <template #content>
<p><i>No albums found</i></p> <p><i v-text="$t('page.search.no-albums')" /></p>
</template> </template>
</content-text> </content-text>
<!-- Composers --> <!-- Composers -->
<content-with-heading v-if="show_composers && composers.total"> <content-with-heading v-if="show_composers && composers.total">
<template #heading-left> <template #heading-left>
<p class="title is-4">Composers</p> <p class="title is-4" v-text="$t('page.search.composers')" />
</template> </template>
<template #content> <template #content>
<list-composers :composers="composers" /> <list-composers :composers="composers" />
@ -139,22 +138,25 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search_composers" @click="open_search_composers"
>Show all {{ composers.total.toLocaleString() }} composers</a v-text="
> $t('page.search.show.composers', {
count: composers.total.toLocaleString()
})
"
/>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show_composers && !composers.total"> <content-text v-if="show_composers && !composers.total">
<template #content> <template #content>
<p><i>No composers found</i></p> <p><i v-text="$t('page.search.no-composers')" /></p>
</template> </template>
</content-text> </content-text>
<!-- Playlists --> <!-- Playlists -->
<content-with-heading v-if="show_playlists && playlists.total"> <content-with-heading v-if="show_playlists && playlists.total">
<template #heading-left> <template #heading-left>
<p class="title is-4">Playlists</p> <p class="title is-4" v-text="$t('page.search.playlists')" />
</template> </template>
<template #content> <template #content>
<list-playlists :playlists="playlists" /> <list-playlists :playlists="playlists" />
@ -165,22 +167,25 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search_playlists" @click="open_search_playlists"
>Show all {{ playlists.total.toLocaleString() }} playlists</a v-text="
> $t('page.search.show.playlists', {
count: playlists.total.toLocaleString()
})
"
/>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show_playlists && !playlists.total"> <content-text v-if="show_playlists && !playlists.total">
<template #content> <template #content>
<p><i>No playlists found</i></p> <p><i v-text="$t('page.search.no-playlists')" /></p>
</template> </template>
</content-text> </content-text>
<!-- Podcasts --> <!-- Podcasts -->
<content-with-heading v-if="show_podcasts && podcasts.total"> <content-with-heading v-if="show_podcasts && podcasts.total">
<template #heading-left> <template #heading-left>
<p class="title is-4">Podcasts</p> <p class="title is-4" v-text="$t('page.search.podcasts')" />
</template> </template>
<template #content> <template #content>
<list-albums :albums="podcasts" /> <list-albums :albums="podcasts" />
@ -191,22 +196,26 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search_podcasts" @click="open_search_podcasts"
>Show all {{ podcasts.total.toLocaleString() }} podcasts</a v-text="
> $t('page.search.show.podcasts', {
count: podcasts.total.toLocaleString()
})
"
/>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show_podcasts && !podcasts.total"> <content-text v-if="show_podcasts && !podcasts.total">
<template #content> <template #content>
<p><i>No podcasts found</i></p> <p><i v-text="$t('page.search.no-podcasts')" /></p>
</template> </template>
</content-text> </content-text>
<!-- Audiobooks --> <!-- Audiobooks -->
<content-with-heading v-if="show_audiobooks && audiobooks.total"> <content-with-heading v-if="show_audiobooks && audiobooks.total">
<template #heading-left> <template #heading-left>
<p class="title is-4">Audiobooks</p> <p class="title is-4" v-text="$t('page.search.audiobooks')" />
</template> </template>
<template #content> <template #content>
<list-albums :albums="audiobooks" /> <list-albums :albums="audiobooks" />
@ -217,15 +226,19 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search_audiobooks" @click="open_search_audiobooks"
>Show all {{ audiobooks.total.toLocaleString() }} audiobooks</a v-text="
> $t('page.search.show.audiobooks', {
count: audiobooks.total.toLocaleString()
})
"
/>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show_audiobooks && !audiobooks.total"> <content-text v-if="show_audiobooks && !audiobooks.total">
<template #content> <template #content>
<p><i>No audiobooks found</i></p> <p><i v-text="$t('page.search.no-audiobooks')" /></p>
</template> </template>
</content-text> </content-text>
</div> </div>
@ -260,7 +273,6 @@ export default {
data() { data() {
return { return {
search_query: '', search_query: '',
tracks: { items: [], total: 0 }, tracks: { items: [], total: 0 },
artists: new GroupByList(), artists: new GroupByList(),
albums: new GroupByList(), albums: new GroupByList(),

View File

@ -1,39 +1,40 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-settings /> <tabs-settings />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4">Artwork</div> <div class="title is-4" v-text="$t('page.settings.artwork.artwork')" />
</template> </template>
<template #content> <template #content>
<div class="content"> <div
<p> class="content"
OwnTone supports PNG and JPEG artwork which is either placed as v-text="$t('page.settings.artwork.explanation-1')"
separate image files in the library, embedded in the media files or />
made available online by radio stations. <div
</p> class="content"
<p> v-text="$t('page.settings.artwork.explanation-2')"
In addition to that, you can enable fetching artwork from the />
following artwork providers:
</p>
</div>
<settings-checkbox <settings-checkbox
v-if="spotify.libspotify_logged_in" v-if="spotify.libspotify_logged_in"
category_name="artwork" category_name="artwork"
option_name="use_artwork_source_spotify" option_name="use_artwork_source_spotify"
> >
<template #label> Spotify </template> <template #label>
<span v-text="$t('page.settings.artwork.spotify')" />
<a href="https://www.spotify.com/" target="_blank">
<mdicon class="icon" name="open-in-new" size="16" />
</a>
</template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox
category_name="artwork" category_name="artwork"
option_name="use_artwork_source_discogs" option_name="use_artwork_source_discogs"
> >
<template #label> <template #label>
Discogs (<a href="https://www.discogs.com/" <span v-text="$t('page.settings.artwork.discogs')" />
>https://www.discogs.com/</a <a href="https://www.discogs.com/" target="_blank">
>) <mdicon class="icon" name="open-in-new" size="16" />
</a>
</template> </template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox
@ -41,9 +42,10 @@
option_name="use_artwork_source_coverartarchive" option_name="use_artwork_source_coverartarchive"
> >
<template #label> <template #label>
Cover Art Archive (<a href="https://coverartarchive.org/" <span v-text="$t('page.settings.artwork.coverartarchive')" />
>https://coverartarchive.org/</a <a href="https://coverartarchive.org/" target="_blank">
>) <mdicon class="icon" name="open-in-new" size="16" />
</a>
</template> </template>
</settings-checkbox> </settings-checkbox>
</template> </template>

View File

@ -1,38 +1,33 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-settings /> <tabs-settings />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4">Spotify</div> <div
class="title is-4"
v-text="$t('page.settings.services.spotify.title')"
/>
</template> </template>
<template #content> <template #content>
<div v-if="!spotify.spotify_installed" class="notification is-size-7"> <div v-if="!spotify.spotify_installed" class="notification is-size-7">
<p> <p v-text="$t('page.settings.services.spotify.no-support')" />
OwnTone was either built without support for Spotify or libspotify
is not installed.
</p>
</div> </div>
<div v-if="spotify.spotify_installed"> <div v-if="spotify.spotify_installed">
<div class="notification is-size-7"> <div class="notification is-size-7">
<b>You must have a Spotify premium account</b>. <b v-text="$t('page.settings.services.spotify.requirements')" />
<span v-if="use_libspotity" <span
>If you normally log into Spotify with your Facebook account you v-if="use_libspotity"
must first go to Spotify's web site where you can get the Spotify v-text="$t('page.settings.services.spotify.help')"
username and password that matches your account.</span />
>
</div> </div>
<div v-if="use_libspotity"> <div v-if="use_libspotity">
<p class="content"> <p class="content">
<b>libspotify</b> - Login with your Spotify username and password <b v-text="$t('page.settings.services.spotify')" />
<span v-text="$t('page.settings.services.spotify.credentials')" />
</p> </p>
<p v-if="spotify.libspotify_logged_in" class="fd-has-margin-bottom"> <p v-if="spotify.libspotify_logged_in" class="fd-has-margin-bottom">
Logged in as <span v-text="$t('page.settings.services.spotify.logged-as')" />
<b <b><code v-text="spotify.libspotify_user" /></b>
><code>{{ spotify.libspotify_user }}</code></b
>
</p> </p>
<form <form
v-if="spotify.spotify_installed && !spotify.libspotify_logged_in" v-if="spotify.spotify_installed && !spotify.libspotify_logged_in"
@ -46,9 +41,7 @@
type="text" type="text"
placeholder="Username" placeholder="Username"
/> />
<p class="help is-danger"> <p class="help is-danger" v-text="libspotify.errors.user" />
{{ libspotify.errors.user }}
</p>
</div> </div>
<div class="control is-expanded"> <div class="control is-expanded">
<input <input
@ -57,44 +50,41 @@
type="password" type="password"
placeholder="Password" placeholder="Password"
/> />
<p class="help is-danger"> <p
{{ libspotify.errors.password }} class="help is-danger"
</p> v-text="libspotify.errors.password"
/>
</div> </div>
<div class="control"> <div class="control">
<button class="button is-info">Login</button> <button
class="button is-info"
v-text="$t('page.settings.services.login')"
/>
</div> </div>
</div> </div>
</form> </form>
<p class="help is-danger"> <p class="help is-danger" v-text="libspotify.errors.error" />
{{ libspotify.errors.error }} <p
</p> class="help"
<p class="help"> v-text="$t('page.settings.services.spotify.help-1')"
libspotify enables OwnTone to play Spotify tracks. />
</p> <p
<p class="help"> class="help"
OwnTone will not store your password, but will still be able to v-text="$t('page.settings.services.spotify.help-2')"
log you in automatically afterwards, because libspotify saves a />
login token.
</p>
</div> </div>
<div class="fd-has-margin-top"> <div class="fd-has-margin-top">
<p class="content"> <p
<b>Spotify Web API</b> - Grant access to the Spotify Web API class="content"
</p> v-html="$t('page.settings.services.spotify.grant-access')"
/>
<p v-if="spotify.webapi_token_valid"> <p v-if="spotify.webapi_token_valid">
Access granted for <span v-text="$t('page.settings.services.spotify.user')" />
<b <code v-text="spotify.webapi_user" />
><code>{{ spotify.webapi_user }}</code></b
>
</p> </p>
<p v-if="spotify_missing_scope.length > 0" class="help is-danger"> <p v-if="spotify_missing_scope.length > 0" class="help is-danger">
Please reauthorize Web API access to grant OwnTone the following <span v-text="$t('page.settings.services.spotify.reauthorize')" />
additional access rights: <code v-text="spotify_missing_scope.join()" />
<b
><code>{{ spotify_missing_scope.join() }}</code></b
>
</p> </p>
<div class="field fd-has-margin-top"> <div class="field fd-has-margin-top">
<div class="control"> <div class="control">
@ -106,45 +96,52 @@
spotify_missing_scope.length > 0 spotify_missing_scope.length > 0
}" }"
:href="spotify.oauth_uri" :href="spotify.oauth_uri"
>Authorize Web API access</a v-text="$t('page.settings.services.spotify.authorize')"
> />
</div> </div>
</div> </div>
<p class="help"> <p class="help">
Access to the Spotify Web API enables scanning of your Spotify <span v-text="$t('page.settings.services.spotify.scopes')" />
library. Required scopes are <code v-text="spotify_required_scope.join()" />
<code>{{ spotify_required_scope.join() }}</code
>.
</p> </p>
<div <div
v-if="spotify.webapi_token_valid" v-if="spotify.webapi_token_valid"
class="field fd-has-margin-top" class="field fd-has-margin-top"
> >
<div class="control"> <div class="control">
<a class="button is-danger" @click="logout_spotify">Logout</a> <a
class="button is-danger"
@click="logout_spotify"
v-text="$t('page.settings.services.logout')"
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4">Last.fm</div> <div
class="title is-4"
v-text="$t('page.settings.services.lastfm.title')"
/>
</template> </template>
<template #content> <template #content>
<div v-if="!lastfm.enabled" class="notification is-size-7"> <div v-if="!lastfm.enabled" class="notification is-size-7">
<p>OwnTone was built without support for Last.fm.</p> <p v-text="$t('page.settings.services.lastfm.no-support')" />
</div> </div>
<div v-if="lastfm.enabled"> <div v-if="lastfm.enabled">
<p class="content"> <p
<b>Last.fm</b> - Login with your Last.fm username and password to class="content"
enable scrobbling v-html="$t('page.settings.services.lastfm.grant-access')"
</p> />
<div v-if="lastfm.scrobbling_enabled"> <div v-if="lastfm.scrobbling_enabled">
<a class="button" @click="logoutLastfm">Stop scrobbling</a> <a
class="button"
@click="logoutLastfm"
v-text="$t('page.settings.services.lastfm.stop-scrobbling')"
/>
</div> </div>
<div v-if="!lastfm.scrobbling_enabled"> <div v-if="!lastfm.scrobbling_enabled">
<form @submit.prevent="login_lastfm"> <form @submit.prevent="login_lastfm">
@ -156,9 +153,7 @@
type="text" type="text"
placeholder="Username" placeholder="Username"
/> />
<p class="help is-danger"> <p class="help is-danger" v-text="lastfm_login.errors.user" />
{{ lastfm_login.errors.user }}
</p>
</div> </div>
<div class="control is-expanded"> <div class="control is-expanded">
<input <input
@ -167,21 +162,24 @@
type="password" type="password"
placeholder="Password" placeholder="Password"
/> />
<p class="help is-danger"> <p
{{ lastfm_login.errors.password }} class="help is-danger"
</p> v-text="lastfm_login.errors.password"
/>
</div> </div>
<div class="control"> <div class="control">
<button class="button is-info" type="submit">Login</button> <button
class="button is-info"
type="submit"
v-text="$t('page.settings.services.login')"
/>
</div> </div>
</div> </div>
<p class="help is-danger"> <p class="help is-danger" v-text="lastfm_login.errors.error" />
{{ lastfm_login.errors.error }} <p
</p> class="help"
<p class="help"> v-text="$t('page.settings.services.lastfm.info')"
OwnTone will not store your Last.fm username/password, only the />
session key. The session key does not expire.
</p>
</form> </form>
</div> </div>
</div> </div>

View File

@ -1,18 +1,17 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-settings /> <tabs-settings />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4">Remote Pairing</div> <div class="title is-4" v-text="$t('page.settings.devices.pairing')" />
</template> </template>
<template #content> <template #content>
<!-- Paring request active --> <!-- Paring request active -->
<div v-if="pairing.active" class="notification"> <div v-if="pairing.active" class="notification">
<form @submit.prevent="kickoff_pairing"> <form @submit.prevent="kickoff_pairing">
<label class="label has-text-weight-normal"> <label class="label has-text-weight-normal">
Remote pairing request from <b>{{ pairing.remote }}</b> <span v-text="$t('page.settings.devices.pairing-request')" />
<b v-text="pairing.remote" />
</label> </label>
<div class="field is-grouped"> <div class="field is-grouped">
<div class="control"> <div class="control">
@ -24,29 +23,33 @@
/> />
</div> </div>
<div class="control"> <div class="control">
<button class="button is-info" type="submit">Send</button> <button
class="button is-info"
type="submit"
v-text="$t('page.settings.devices.send')"
/>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
<!-- No pairing requests --> <!-- No pairing requests -->
<div v-if="!pairing.active" class="content"> <div v-if="!pairing.active" class="content">
<p>No active pairing request.</p> <p v-text="$t('page.settings.devices.no-active-pairing')" />
</div> </div>
</template> </template>
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4">Speaker pairing and device verification</div> <div
class="title is-4"
v-text="$t('page.settings.devices.speaker-pairing')"
/>
</template> </template>
<template #content> <template #content>
<p class="content"> <p
If your speaker requires pairing then activate it below and enter the class="content"
PIN that it displays. v-text="$t('page.settings.devices.speaker-pairing-info')"
</p> />
<div v-for="output in outputs" :key="output.id"> <div v-for="output in outputs" :key="output.id">
<div class="field"> <div class="field">
<div class="control"> <div class="control">
@ -54,9 +57,10 @@
<input <input
v-model="output.selected" v-model="output.selected"
type="checkbox" type="checkbox"
style="margin-right: 5px"
@change="output_toggle(output.id)" @change="output_toggle(output.id)"
/> />
{{ output.name }} <span v-text="output.name" />
</label> </label>
</div> </div>
</div> </div>
@ -71,11 +75,15 @@
v-model="verification_req.pin" v-model="verification_req.pin"
class="input" class="input"
type="text" type="text"
placeholder="Enter verification code" :placeholder="$t('page.settings.devices.verification-code')"
/> />
</div> </div>
<div class="control"> <div class="control">
<button class="button is-info" type="submit">Verify</button> <button
class="button is-info"
type="submit"
v-text="$t('page.settings.devices.verify')"
/>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,92 +1,123 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-settings /> <tabs-settings />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4">Navbar items</div> <div class="title is-4" v-text="$t('page.settings.general.language')" />
</template> </template>
<template #content> <template #content>
<p class="content">Select the top navigation bar menu items</p> <dropdown-menu v-model="locale" :options="locales" />
<div class="notification is-size-7"> </template>
If you select more items than can be shown on your screen then the </content-with-heading>
burger menu will disappear. <content-with-heading>
</div> <template #heading-left>
<div
class="title is-4"
v-text="$t('page.settings.general.navigation-items')"
/>
</template>
<template #content>
<p
class="content"
v-text="$t('page.settings.general.navigation-item-selection')"
/>
<div
class="notification is-size-7"
v-text="$t('page.settings.general.navigation-item-selection-info')"
/>
<settings-checkbox <settings-checkbox
category_name="webinterface" category_name="webinterface"
option_name="show_menu_item_playlists" option_name="show_menu_item_playlists"
> >
<template #label> Playlists </template> <template #label>
<span v-text="$t('page.settings.general.playlists')" />
</template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox
category_name="webinterface" category_name="webinterface"
option_name="show_menu_item_music" option_name="show_menu_item_music"
> >
<template #label> Music </template> <template #label>
<span v-text="$t('page.settings.general.music')" />
</template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox
category_name="webinterface" category_name="webinterface"
option_name="show_menu_item_podcasts" option_name="show_menu_item_podcasts"
> >
<template #label> Podcasts </template> <template #label>
<span v-text="$t('page.settings.general.podcasts')" />
</template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox
category_name="webinterface" category_name="webinterface"
option_name="show_menu_item_audiobooks" option_name="show_menu_item_audiobooks"
> >
<template #label> Audiobooks </template> <template #label>
<span v-text="$t('page.settings.general.audiobooks')" />
</template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox
category_name="webinterface" category_name="webinterface"
option_name="show_menu_item_radio" option_name="show_menu_item_radio"
> >
<template #label> Radio </template> <template #label>
<span v-text="$t('page.settings.general.radio')" />
</template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox
category_name="webinterface" category_name="webinterface"
option_name="show_menu_item_files" option_name="show_menu_item_files"
> >
<template #label> Files </template> <template #label>
<span v-text="$t('page.settings.general.files')" />
</template>
</settings-checkbox> </settings-checkbox>
<settings-checkbox <settings-checkbox
category_name="webinterface" category_name="webinterface"
option_name="show_menu_item_search" option_name="show_menu_item_search"
> >
<template #label> Search </template> <template #label>
<span v-text="$t('page.settings.general.search')" />
</template>
</settings-checkbox> </settings-checkbox>
</template> </template>
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4">Album lists</div> <div
class="title is-4"
v-text="$t('page.settings.general.album-lists')"
/>
</template> </template>
<template #content> <template #content>
<settings-checkbox <settings-checkbox
category_name="webinterface" category_name="webinterface"
option_name="show_cover_artwork_in_album_lists" option_name="show_cover_artwork_in_album_lists"
> >
<template #label> Show cover artwork in album list </template> <template #label>
<span v-text="$t('page.settings.general.show-coverart')" />
</template>
</settings-checkbox> </settings-checkbox>
</template> </template>
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4">Now playing page</div> <div
class="title is-4"
v-text="$t('page.settings.general.now-playing-page')"
/>
</template> </template>
<template #content> <template #content>
<settings-checkbox <settings-checkbox
category_name="webinterface" category_name="webinterface"
option_name="show_composer_now_playing" option_name="show_composer_now_playing"
> >
<template #label> Show composer </template> <template #label>
<span v-text="$t('page.settings.general.show-composer')" />
</template>
<template #info> <template #info>
If enabled the composer of the current playing track is shown on the <span v-text="$t('page.settings.general.show-composer-info')" />
&quot;now playing page&quot;
</template> </template>
</settings-checkbox> </settings-checkbox>
<settings-textfield <settings-textfield
@ -95,36 +126,42 @@
:disabled="!settings_option_show_composer_now_playing" :disabled="!settings_option_show_composer_now_playing"
placeholder="Genres" placeholder="Genres"
> >
<template #label> Show composer only for listed genres </template> <template #label>
<span v-text="$t('page.settings.general.show-composer-genres')" />
</template>
<template #info> <template #info>
<p class="help"> <p
Comma separated list of genres the composer should be displayed on class="help"
the &quot;now playing page&quot;. v-text="$t('page.settings.general.show-composer-genres-info-1')"
</p> />
<p class="help">Leave empty to always show the composer.</p> <p
<p class="help"> class="help"
The genre tag of the current track is matched by checking, if one v-text="$t('page.settings.general.show-composer-genres-info-2')"
of the defined genres are included. For example setting to />
<code>classical, soundtrack</code> will show the composer for <p
tracks with a genre tag of &quot;Contemporary Classical&quot;.<br /> class="help"
</p> v-text="$t('page.settings.general.show-composer-genres-info-3')"
/>
</template> </template>
</settings-textfield> </settings-textfield>
</template> </template>
</content-with-heading> </content-with-heading>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4">Recently added page</div> <div
class="title is-4"
v-text="$t('page.settings.general.recently-added-page')"
/>
</template> </template>
<template #content> <template #content>
<settings-intfield <settings-intfield
category_name="webinterface" category_name="webinterface"
option_name="recently_added_limit" option_name="recently_added_limit"
> >
<template #label> <template #label>
Limit the number of albums shown on the "Recently Added" page <span
v-text="$t('page.settings.general.recently-added-page-info')"
/>
</template> </template>
</settings-intfield> </settings-intfield>
</template> </template>
@ -138,6 +175,7 @@ import TabsSettings from '@/components/TabsSettings.vue'
import SettingsCheckbox from '@/components/SettingsCheckbox.vue' import SettingsCheckbox from '@/components/SettingsCheckbox.vue'
import SettingsTextfield from '@/components/SettingsTextfield.vue' import SettingsTextfield from '@/components/SettingsTextfield.vue'
import SettingsIntfield from '@/components/SettingsIntfield.vue' import SettingsIntfield from '@/components/SettingsIntfield.vue'
import DropdownMenu from '@/components/DropdownMenu.vue'
export default { export default {
name: 'SettingsPageWebinterface', name: 'SettingsPageWebinterface',
@ -146,12 +184,28 @@ export default {
TabsSettings, TabsSettings,
SettingsCheckbox, SettingsCheckbox,
SettingsTextfield, SettingsTextfield,
SettingsIntfield SettingsIntfield,
DropdownMenu
}, },
computed: { computed: {
settings_option_show_composer_now_playing() { settings_option_show_composer_now_playing() {
return this.$store.getters.settings_option_show_composer_now_playing return this.$store.getters.settings_option_show_composer_now_playing
},
locale: {
get() {
return this.$i18n.locale
},
set(locale) {
this.$i18n.locale = locale
}
},
locales: {
get() {
return this.$i18n.availableLocales.map((item) => {
return { id: item, name: this.$t('language.' + item) }
})
}
} }
} }
} }

View File

@ -1,29 +1,27 @@
<template> <template>
<content-with-hero> <content-with-hero>
<template #heading-left> <template #heading-left>
<h1 class="title is-5"> <h1 class="title is-5" v-text="album.name" />
{{ album.name }}
</h1>
<h2 class="subtitle is-6 has-text-link has-text-weight-normal"> <h2 class="subtitle is-6 has-text-link has-text-weight-normal">
<a class="has-text-link" @click="open_artist">{{ <a
album.artists[0].name class="has-text-link"
}}</a> @click="open_artist"
v-text="album.artists[0].name"
/>
</h2> </h2>
<div class="buttons fd-is-centered-mobile fd-has-margin-top"> <div class="buttons fd-is-centered-mobile fd-has-margin-top">
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span> <mdicon class="icon" name="shuffle" size="16" />
<span>Shuffle</span> <span v-text="$t('page.spotify.album.shuffle')" />
</a> </a>
<a <a
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_album_details_modal = true" @click="show_album_details_modal = true"
> >
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span> <mdicon class="icon" name="dots-horizontal" size="16" />
</a> </a>
</div> </div>
</template> </template>
<template #heading-right> <template #heading-right>
<p class="image is-square fd-has-shadow fd-has-action"> <p class="image is-square fd-has-shadow fd-has-action">
<cover-artwork <cover-artwork
@ -34,11 +32,13 @@
/> />
</p> </p>
</template> </template>
<template #content> <template #content>
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top"> <p
{{ album.tracks.total }} tracks class="heading is-7 has-text-centered-mobile fd-has-margin-top"
</p> v-text="
$t('page.spotify.album.track-count', { count: album.tracks.total })
"
/>
<spotify-list-item-track <spotify-list-item-track
v-for="(track, index) in album.tracks.items" v-for="(track, index) in album.tracks.items"
:key="track.id" :key="track.id"
@ -49,9 +49,7 @@
> >
<template #actions> <template #actions>
<a @click.prevent.stop="open_track_dialog(track)"> <a @click.prevent.stop="open_track_dialog(track)">
<span class="icon has-text-dark" <mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
><mdicon name="dots-vertical" size="16"
/></span>
</a> </a>
</template> </template>
</spotify-list-item-track> </spotify-list-item-track>

View File

@ -1,9 +1,7 @@
<template> <template>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4" v-text="artist.name" />
{{ artist.name }}
</p>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -11,16 +9,19 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_artist_details_modal = true" @click="show_artist_details_modal = true"
> >
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span> <mdicon class="icon" name="dots-horizontal" size="16" />
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span> <mdicon class="icon" name="shuffle" size="16" />
<span>Shuffle</span> <span v-text="$t('page.spotify.artist.shuffle')" />
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile">{{ total }} albums</p> <p
class="heading has-text-centered-mobile"
v-text="$t('page.spotify.artist.album-count', { count: total })"
/>
<spotify-list-item-album <spotify-list-item-album
v-for="album in albums" v-for="album in albums"
:key="album.id" :key="album.id"
@ -40,9 +41,7 @@
</template> </template>
<template #actions> <template #actions>
<a @click.prevent.stop="open_dialog(album)"> <a @click.prevent.stop="open_dialog(album)">
<span class="icon has-text-dark" <mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
><mdicon name="dots-vertical" size="16"
/></span>
</a> </a>
</template> </template>
</spotify-list-item-album> </spotify-list-item-album>

View File

@ -1,11 +1,10 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-music /> <tabs-music />
<!-- New Releases --> <!-- New Releases -->
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4">New Releases</p> <p class="title is-4" v-text="$t('page.spotify.browse.new-releases')" />
</template> </template>
<template #content> <template #content>
<spotify-list-item-album <spotify-list-item-album
@ -27,9 +26,11 @@
</template> </template>
<template #actions> <template #actions>
<a @click.prevent.stop="open_album_dialog(album)"> <a @click.prevent.stop="open_album_dialog(album)">
<span class="icon has-text-dark" <mdicon
><mdicon name="dots-vertical" size="16" class="icon has-text-dark"
/></span> name="dots-vertical"
size="16"
/>
</a> </a>
</template> </template>
</spotify-list-item-album> </spotify-list-item-album>
@ -45,18 +46,19 @@
<router-link <router-link
to="/music/spotify/new-releases" to="/music/spotify/new-releases"
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
> v-text="$t('page.spotify.browse.show-more')"
Show more />
</router-link>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<!-- Featured Playlists --> <!-- Featured Playlists -->
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4">Featured Playlists</p> <p
class="title is-4"
v-text="$t('page.spotify.browse.featured-playlists')"
/>
</template> </template>
<template #content> <template #content>
<spotify-list-item-playlist <spotify-list-item-playlist
@ -66,9 +68,11 @@
> >
<template #actions> <template #actions>
<a @click.prevent.stop="open_playlist_dialog(playlist)"> <a @click.prevent.stop="open_playlist_dialog(playlist)">
<span class="icon has-text-dark" <mdicon
><mdicon name="dots-vertical" size="16" class="icon has-text-dark"
/></span> name="dots-vertical"
size="16"
/>
</a> </a>
</template> </template>
</spotify-list-item-playlist> </spotify-list-item-playlist>
@ -84,9 +88,8 @@
<router-link <router-link
to="/music/spotify/featured-playlists" to="/music/spotify/featured-playlists"
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
> v-text="$t('page.spotify.browse.show-more')"
Show more />
</router-link>
</p> </p>
</nav> </nav>
</template> </template>

View File

@ -1,7 +1,6 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4">Featured Playlists</p> <p class="title is-4">Featured Playlists</p>
@ -14,9 +13,11 @@
> >
<template #actions> <template #actions>
<a @click.prevent.stop="open_playlist_dialog(playlist)"> <a @click.prevent.stop="open_playlist_dialog(playlist)">
<span class="icon has-text-dark" <mdicon
><mdicon name="dots-vertical" size="16" class="icon has-text-dark"
/></span> name="dots-vertical"
size="16"
/>
</a> </a>
</template> </template>
</spotify-list-item-playlist> </spotify-list-item-playlist>

View File

@ -1,7 +1,6 @@
<template> <template>
<div class="fd-page-with-tabs"> <div class="fd-page-with-tabs">
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4">New Releases</p> <p class="title is-4">New Releases</p>
@ -26,9 +25,11 @@
</template> </template>
<template #actions> <template #actions>
<a @click.prevent.stop="open_album_dialog(album)"> <a @click.prevent.stop="open_album_dialog(album)">
<span class="icon has-text-dark" <mdicon
><mdicon name="dots-vertical" size="16" class="icon has-text-dark"
/></span> name="dots-vertical"
size="16"
/>
</a> </a>
</template> </template>
</spotify-list-item-album> </spotify-list-item-album>

View File

@ -1,9 +1,7 @@
<template> <template>
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<div class="title is-4"> <div class="title is-4" v-text="playlist.name" />
{{ playlist.name }}
</div>
</template> </template>
<template #heading-right> <template #heading-right>
<div class="buttons is-centered"> <div class="buttons is-centered">
@ -11,18 +9,21 @@
class="button is-small is-light is-rounded" class="button is-small is-light is-rounded"
@click="show_playlist_details_modal = true" @click="show_playlist_details_modal = true"
> >
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span> <mdicon class="icon" name="dots-horizontal" size="16" />
</a> </a>
<a class="button is-small is-dark is-rounded" @click="play"> <a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span> <mdicon class="icon" name="shuffle" size="16" />
<span>Shuffle</span> <span v-text="$t('page.spotify.playlist.shuffle')" />
</a> </a>
</div> </div>
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p
{{ playlist.tracks.total }} tracks class="heading has-text-centered-mobile"
</p> v-text="
$t('page.spotify.playlist.count', { count: playlist.tracks.total })
"
/>
<spotify-list-item-track <spotify-list-item-track
v-for="(item, index) in tracks" v-for="(item, index) in tracks"
:key="item.track.id" :key="item.track.id"
@ -33,9 +34,7 @@
> >
<template #actions> <template #actions>
<a @click.prevent.stop="open_track_dialog(item.track)"> <a @click.prevent.stop="open_track_dialog(item.track)">
<span class="icon has-text-dark" <mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
><mdicon name="dots-vertical" size="16"
/></span>
</a> </a>
</template> </template>
</spotify-list-item-track> </spotify-list-item-track>

View File

@ -16,9 +16,7 @@
placeholder="Search" placeholder="Search"
autocomplete="off" autocomplete="off"
/> />
<span class="icon is-left"> <mdicon class="icon is-left" name="magnify" size="16" />
<mdicon name="magnify" size="16" />
</span>
</p> </p>
</div> </div>
</form> </form>
@ -28,20 +26,18 @@
:key="recent_search" :key="recent_search"
class="tag" class="tag"
@click="open_recent_search(recent_search)" @click="open_recent_search(recent_search)"
>{{ recent_search }}</a v-text="recent_search"
> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<tabs-search :query="search_query" /> <tabs-search :query="search_query" />
<!-- Tracks --> <!-- Tracks -->
<content-with-heading v-if="show_tracks && tracks.total"> <content-with-heading v-if="show_tracks && tracks.total">
<template #heading-left> <template #heading-left>
<p class="title is-4">Tracks</p> <p class="title is-4" v-text="$t('page.spotify.search.tracks')" />
</template> </template>
<template #content> <template #content>
<spotify-list-item-track <spotify-list-item-track
@ -54,9 +50,11 @@
> >
<template #actions> <template #actions>
<a @click.prevent.stop="open_track_dialog(track)"> <a @click.prevent.stop="open_track_dialog(track)">
<span class="icon has-text-dark" <mdicon
><mdicon name="dots-vertical" size="16" class="icon has-text-dark"
/></span> name="dots-vertical"
size="16"
/>
</a> </a>
</template> </template>
</spotify-list-item-track> </spotify-list-item-track>
@ -79,22 +77,25 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search_tracks" @click="open_search_tracks"
>Show all {{ tracks.total.toLocaleString() }} tracks</a v-text="
> $t('page.spotify.search.show-all-tracks', {
count: tracks.total.toLocaleString()
})
"
/>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show_tracks && !tracks.total" class="mt-6"> <content-text v-if="show_tracks && !tracks.total" class="mt-6">
<template #content> <template #content>
<p><i>No tracks found</i></p> <p><i v-text="$t('page.spotify.search.no-tracks')" /></p>
</template> </template>
</content-text> </content-text>
<!-- Artists --> <!-- Artists -->
<content-with-heading v-if="show_artists && artists.total"> <content-with-heading v-if="show_artists && artists.total">
<template #heading-left> <template #heading-left>
<p class="title is-4">Artists</p> <p class="title is-4" v-text="$t('page.spotify.search.artists')" />
</template> </template>
<template #content> <template #content>
<spotify-list-item-artist <spotify-list-item-artist
@ -104,9 +105,11 @@
> >
<template #actions> <template #actions>
<a @click.prevent.stop="open_artist_dialog(artist)"> <a @click.prevent.stop="open_artist_dialog(artist)">
<span class="icon has-text-dark" <mdicon
><mdicon name="dots-vertical" size="16" class="icon has-text-dark"
/></span> name="dots-vertical"
size="16"
/>
</a> </a>
</template> </template>
</spotify-list-item-artist> </spotify-list-item-artist>
@ -128,22 +131,25 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search_artists" @click="open_search_artists"
>Show all {{ artists.total.toLocaleString() }} artists</a v-text="
> $t('page.spotify.search.show-all-artists', {
count: artists.total.toLocaleString()
})
"
/>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show_artists && !artists.total"> <content-text v-if="show_artists && !artists.total">
<template #content> <template #content>
<p><i>No artists found</i></p> <p><i v-text="$t('page.spotify.search.no-artists')" /></p>
</template> </template>
</content-text> </content-text>
<!-- Albums --> <!-- Albums -->
<content-with-heading v-if="show_albums && albums.total"> <content-with-heading v-if="show_albums && albums.total">
<template #heading-left> <template #heading-left>
<p class="title is-4">Albums</p> <p class="title is-4" v-text="$t('page.spotify.search.albums')" />
</template> </template>
<template #content> <template #content>
<spotify-list-item-album <spotify-list-item-album
@ -165,9 +171,11 @@
</template> </template>
<template #actions> <template #actions>
<a @click.prevent.stop="open_album_dialog(album)"> <a @click.prevent.stop="open_album_dialog(album)">
<span class="icon has-text-dark" <mdicon
><mdicon name="dots-vertical" size="16" class="icon has-text-dark"
/></span> name="dots-vertical"
size="16"
/>
</a> </a>
</template> </template>
</spotify-list-item-album> </spotify-list-item-album>
@ -189,22 +197,25 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search_albums" @click="open_search_albums"
>Show all {{ albums.total.toLocaleString() }} albums</a v-text="
> $t('page.spotify.search.show-all-albums', {
count: albums.total.toLocaleString()
})
"
/>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show_albums && !albums.total"> <content-text v-if="show_albums && !albums.total">
<template #content> <template #content>
<p><i>No albums found</i></p> <p><i v-text="$t('page.spotify.search.no-albums')" /></p>
</template> </template>
</content-text> </content-text>
<!-- Playlists --> <!-- Playlists -->
<content-with-heading v-if="show_playlists && playlists.total"> <content-with-heading v-if="show_playlists && playlists.total">
<template #heading-left> <template #heading-left>
<p class="title is-4">Playlists</p> <p class="title is-4" v-text="$t('page.spotify.search.playlists')" />
</template> </template>
<template #content> <template #content>
<spotify-list-item-playlist <spotify-list-item-playlist
@ -214,9 +225,11 @@
> >
<template #actions> <template #actions>
<a @click.prevent.stop="open_playlist_dialog(playlist)"> <a @click.prevent.stop="open_playlist_dialog(playlist)">
<span class="icon has-text-dark" <mdicon
><mdicon name="dots-vertical" size="16" class="icon has-text-dark"
/></span> name="dots-vertical"
size="16"
/>
</a> </a>
</template> </template>
</spotify-list-item-playlist> </spotify-list-item-playlist>
@ -238,15 +251,19 @@
<a <a
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search_playlists" @click="open_search_playlists"
>Show all {{ playlists.total.toLocaleString() }} playlists</a v-text="
> $t('page.spotify.search.show-all-playlists', {
count: playlists.total.toLocaleString()
})
"
/>
</p> </p>
</nav> </nav>
</template> </template>
</content-with-heading> </content-with-heading>
<content-text v-if="show_playlists && !playlists.total"> <content-text v-if="show_playlists && !playlists.total">
<template #content> <template #content>
<p><i>No playlists found</i></p> <p><i v-text="$t('page.spotify.search.no-playlists')" /></p>
</template> </template>
</content-text> </content-text>
</div> </div>

View File

@ -55,9 +55,9 @@ export default createStore({
hide_singles: false, hide_singles: false,
hide_spotify: false, hide_spotify: false,
artists_sort: 'Name', artists_sort: 1,
artist_albums_sort: 'Name', artist_albums_sort: 1,
albums_sort: 'Name', albums_sort: 1,
show_only_next_items: false, show_only_next_items: false,
show_burger_menu: false, show_burger_menu: false,
show_player_menu: false, show_player_menu: false,

Some files were not shown because too many files have changed in this diff Show More