mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-23 12:43:18 -05:00
Revert "Merge branch 'master' of github.com:owntone/owntone-server"
This reverts commit bb2a778b46afca0fcd56bda6f02857fad78b14d8, reversing changes made to f8e2298b677476f46eb67cc4331c18124f92a791.
This commit is contained in:
parent
614bcaa630
commit
8a177ed48d
19
web-src/package-lock.json
generated
19
web-src/package-lock.json
generated
@ -1,18 +1,19 @@
|
||||
{
|
||||
"name": "owntone-web",
|
||||
"version": "3.0.0",
|
||||
"version": "2.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "owntone-web",
|
||||
"version": "3.0.0",
|
||||
"version": "2.0.1",
|
||||
"dependencies": {
|
||||
"@aacassandra/vue3-progressbar": "^1.0.3",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@ts-pro/vue-eternal-loading": "^1.3.1",
|
||||
"axios": "^1.7.4",
|
||||
"bulma": "^1.0.2",
|
||||
"bulma": "^0.9.4",
|
||||
"bulma-switch": "^2.0.4",
|
||||
"luxon": "^3.4.4",
|
||||
"mdi-vue": "^3.0.13",
|
||||
"pinia": "^2.1.7",
|
||||
@ -1701,9 +1702,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/bulma": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-1.0.3.tgz",
|
||||
"integrity": "sha512-9eVXBrXwlU337XUXBjIIq7i88A+tRbJYAjXQjT/21lwam+5tpvKF0R7dCesre9N+HV9c6pzCNEPKrtgvBBes2g==",
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.4.tgz",
|
||||
"integrity": "sha512-86FlT5+1GrsgKbPLRRY7cGDg8fsJiP/jzTqXXVqiUZZ2aZT8uemEOHlU1CDU+TxklPEZ11HZNNWclRBBecP4CQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bulma-switch": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/bulma-switch/-/bulma-switch-2.0.4.tgz",
|
||||
"integrity": "sha512-kMu4H0Pr0VjvfsnT6viRDCgptUq0Rvy7y7PX6q+IHg1xUynsjszPjhAdal5ysAlCG5HNO+5YXxeiu92qYGQolw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "owntone-web",
|
||||
"version": "3.0.0",
|
||||
"version": "2.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"serve": "vite --port 3000 --host",
|
||||
@ -16,7 +16,8 @@
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@ts-pro/vue-eternal-loading": "^1.3.1",
|
||||
"axios": "^1.7.4",
|
||||
"bulma": "^1.0.2",
|
||||
"bulma": "^0.9.4",
|
||||
"bulma-switch": "^2.0.4",
|
||||
"luxon": "^3.4.4",
|
||||
"mdi-vue": "^3.0.13",
|
||||
"pinia": "^2.1.7",
|
||||
|
@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<navbar-top />
|
||||
<vue-progress-bar class="has-background-info" />
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
|
||||
<modal-dialog-remote-pairing
|
||||
:show="pairing_active"
|
||||
@close="pairing_active = false"
|
||||
@ -16,9 +18,10 @@
|
||||
<navbar-bottom />
|
||||
<div
|
||||
v-show="show_burger_menu || show_player_menu"
|
||||
class="overlay-fullscreen"
|
||||
class="fd-overlay-fullscreen"
|
||||
@click="show_burger_menu = show_player_menu = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -1,54 +0,0 @@
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
<footer class="card-footer is-clipped">
|
||||
<slot name="footer" />
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseModal',
|
||||
props: {
|
||||
show: Boolean
|
||||
},
|
||||
emits: ['close'],
|
||||
watch: {
|
||||
show(value) {
|
||||
const { classList } = document.querySelector('html')
|
||||
if (value) {
|
||||
classList.add('is-clipped')
|
||||
} else {
|
||||
classList.remove('is-clipped')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.fade-enter-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-enter-to,
|
||||
.fade-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
@ -64,3 +64,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,62 +0,0 @@
|
||||
<template>
|
||||
<div class="media is-align-items-center mb-0">
|
||||
<div class="media-left">
|
||||
<a class="button is-small" @click="toggle">
|
||||
<mdicon class="icon" :name="icon" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div class="is-size-7 is-uppercase" v-text="$t('navigation.volume')" />
|
||||
<control-slider
|
||||
v-model:value="player.volume"
|
||||
:cursor="cursor"
|
||||
:max="100"
|
||||
@change="changeVolume"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ControlSlider from '@/components/ControlSlider.vue'
|
||||
import { mdiCancel } from '@mdi/js'
|
||||
import { usePlayerStore } from '@/stores/player'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ControlVolume',
|
||||
components: { ControlSlider },
|
||||
setup() {
|
||||
return {
|
||||
player: usePlayerStore()
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cursor: mdiCancel,
|
||||
old_volume: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
icon() {
|
||||
return this.player.volume > 0 ? 'volume-high' : 'volume-off'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'player.volume'() {
|
||||
if (this.player.volume > 0) {
|
||||
this.old_volume = this.player.volume
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeVolume(value) {
|
||||
webapi.player_volume(this.player.volume)
|
||||
},
|
||||
toggle() {
|
||||
this.player.volume = this.player.volume > 0 ? 0 : this.old_volume
|
||||
this.changeVolume()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,79 +0,0 @@
|
||||
<template>
|
||||
<div class="media is-align-items-center mb-0">
|
||||
<div class="media-left">
|
||||
<a
|
||||
class="button is-small"
|
||||
:class="{ 'has-text-grey-light': !output.selected }"
|
||||
@click="toggle"
|
||||
>
|
||||
<mdicon class="icon" :name="icon" :title="output.type" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
:class="{ 'has-text-grey-light': !output.selected }"
|
||||
v-text="output.name"
|
||||
/>
|
||||
<control-slider
|
||||
v-model:value="volume"
|
||||
:disabled="!output.selected"
|
||||
:max="100"
|
||||
:cursor="cursor"
|
||||
@change="changeVolume"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ControlSlider from '@/components/ControlSlider.vue'
|
||||
import { mdiCancel } from '@mdi/js'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ControlOutputVolume',
|
||||
components: {
|
||||
ControlSlider
|
||||
},
|
||||
props: { output: { required: true, type: Object } },
|
||||
|
||||
data() {
|
||||
return {
|
||||
cursor: mdiCancel,
|
||||
volume: this.output.selected ? this.output.volume : 0
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
icon() {
|
||||
if (this.output.type.startsWith('AirPlay')) {
|
||||
return 'cast-variant'
|
||||
} else if (this.output.type === 'Chromecast') {
|
||||
return 'cast'
|
||||
} else if (this.output.type === 'fifo') {
|
||||
return 'pipe'
|
||||
}
|
||||
return 'server'
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
output() {
|
||||
this.volume = this.output.volume
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeVolume() {
|
||||
webapi.player_output_volume(this.output.id, this.volume)
|
||||
},
|
||||
toggle() {
|
||||
const values = {
|
||||
selected: !this.output.selected
|
||||
}
|
||||
webapi.output_update(this.output.id, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -32,113 +32,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use 'bulma/bulma';
|
||||
@use 'bulma/sass/utilities/mixins';
|
||||
|
||||
@mixin thumb {
|
||||
-webkit-appearance: none;
|
||||
width: var(--th);
|
||||
height: var(--th);
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
background: var(--bulma-light);
|
||||
border: 1px solid var(--bulma-grey-lighter);
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background: var(--bulma-grey-lighter);
|
||||
border: 1px solid var(--bulma-grey-dark);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin thumb-inactive {
|
||||
box-sizing: border-box;
|
||||
background-color: var(--bulma-light);
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: var(--bulma-grey-dark);
|
||||
border: 1px solid var(--bulma-grey-darker);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin track {
|
||||
height: calc(var(--sh));
|
||||
border-radius: calc(var(--sh) / 2);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bulma-dark) var(--sx),
|
||||
var(--bulma-grey-lighter) var(--sx)
|
||||
);
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bulma-grey-lighter) var(--sx),
|
||||
var(--bulma-grey-dark) var(--sx)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin track-inactive {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bulma-grey-light) var(--sx),
|
||||
var(--bulma-light) var(--sx)
|
||||
);
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bulma-grey-dark) var(--sx),
|
||||
var(--bulma-black-ter) var(--sx)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
input[type='range'].slider {
|
||||
--sh: 0.25rem;
|
||||
--th: calc(var(--sh) * 4);
|
||||
background-color: transparent;
|
||||
@include mixins.mobile {
|
||||
--th: calc(var(--sh) * 5);
|
||||
}
|
||||
& {
|
||||
--sx: calc(var(--th) / 2 + (var(--ratio) * (100% - var(--th))));
|
||||
-webkit-appearance: none;
|
||||
min-width: 250px;
|
||||
height: calc(var(--sh) * 5);
|
||||
width: 100% !important;
|
||||
cursor: grab;
|
||||
}
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
&::-webkit-slider-thumb {
|
||||
@include thumb;
|
||||
& {
|
||||
margin-top: calc((var(--th) - var(--sh)) / -2);
|
||||
}
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
@include thumb;
|
||||
}
|
||||
&::-webkit-slider-runnable-track {
|
||||
@include track;
|
||||
}
|
||||
&::-moz-range-track {
|
||||
@include track;
|
||||
}
|
||||
&.is-inactive {
|
||||
cursor: var(--cursor, not-allowed);
|
||||
&::-webkit-slider-thumb {
|
||||
@include thumb-inactive;
|
||||
}
|
||||
&::-webkit-slider-runnable-track {
|
||||
@include track-inactive;
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
@include thumb-inactive;
|
||||
}
|
||||
&::-moz-range-track {
|
||||
@include track-inactive;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,107 +0,0 @@
|
||||
<template>
|
||||
<div class="media is-align-items-center mb-0">
|
||||
<div class="media-left">
|
||||
<a
|
||||
class="button is-small"
|
||||
:class="{
|
||||
'has-text-grey-light': !playing && !loading,
|
||||
'is-loading': loading
|
||||
}"
|
||||
@click="togglePlay"
|
||||
>
|
||||
<mdicon class="icon" name="broadcast" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="media-content is-align-items-center">
|
||||
<div class="is-flex" :class="{ 'has-text-grey-light': !playing }">
|
||||
<div class="is-size-7 is-uppercase" v-text="$t('navigation.stream')" />
|
||||
<a href="stream.mp3" class="ml-2" target="_blank">
|
||||
<mdicon class="icon is-small" name="open-in-new" />
|
||||
</a>
|
||||
</div>
|
||||
<control-slider
|
||||
v-model:value="volume"
|
||||
:cursor="cursor"
|
||||
:disabled="!playing"
|
||||
:max="100"
|
||||
@change="changeVolume"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ControlSlider from '@/components/ControlSlider.vue'
|
||||
import audio from '@/lib/Audio'
|
||||
import { mdiCancel } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'ControlStreamVolume',
|
||||
components: { ControlSlider },
|
||||
emits: ['change', 'mute'],
|
||||
data() {
|
||||
return {
|
||||
cursor: mdiCancel,
|
||||
loading: false,
|
||||
playing: false,
|
||||
volume: 10
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setupAudio()
|
||||
},
|
||||
unmounted() {
|
||||
this.closeAudio()
|
||||
},
|
||||
methods: {
|
||||
changeVolume() {
|
||||
audio.setVolume(this.volume / 100)
|
||||
},
|
||||
closeAudio() {
|
||||
audio.stop()
|
||||
this.playing = false
|
||||
},
|
||||
playChannel() {
|
||||
if (this.playing) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
audio.play('/stream.mp3')
|
||||
audio.setVolume(this.volume / 100)
|
||||
},
|
||||
setupAudio() {
|
||||
const a = audio.setup()
|
||||
a.addEventListener('waiting', () => {
|
||||
this.playing = false
|
||||
this.loading = true
|
||||
})
|
||||
a.addEventListener('playing', () => {
|
||||
this.playing = true
|
||||
this.loading = false
|
||||
})
|
||||
a.addEventListener('ended', () => {
|
||||
this.playing = false
|
||||
this.loading = false
|
||||
})
|
||||
a.addEventListener('error', () => {
|
||||
this.closeAudio()
|
||||
this.notificationsStore.add({
|
||||
text: this.$t('navigation.stream-error'),
|
||||
type: 'danger'
|
||||
})
|
||||
this.playing = false
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
togglePlay() {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
if (this.playing) {
|
||||
this.closeAudio()
|
||||
}
|
||||
this.playChannel()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,79 +0,0 @@
|
||||
<template>
|
||||
<div class="field">
|
||||
<label class="toggle">
|
||||
<div class="control is-flex is-align-content-center">
|
||||
<input
|
||||
:checked="modelValue"
|
||||
type="checkbox"
|
||||
class="toggle-checkbox"
|
||||
@change="$emit('update:modelValue', !modelValue)"
|
||||
/>
|
||||
<div class="toggle-switch" />
|
||||
<slot name="label" />
|
||||
<slot name="info" />
|
||||
</div>
|
||||
</label>
|
||||
<p v-if="$slots['help']" class="help notification">
|
||||
<slot name="help" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ControlSwitch',
|
||||
props: {
|
||||
modelValue: Boolean
|
||||
},
|
||||
emits: ['update:modelValue']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.toggle {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
display: inline-block;
|
||||
background: var(--bulma-grey-lighter);
|
||||
border-radius: 1rem;
|
||||
min-width: 2.5rem;
|
||||
width: 2.5rem;
|
||||
height: 1.25rem;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
transition: background 0.25s;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.toggle-switch:before,
|
||||
.toggle-switch:after {
|
||||
content: '';
|
||||
}
|
||||
.toggle-switch:before {
|
||||
display: block;
|
||||
background: var(--bulma-white);
|
||||
border-radius: 50%;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
position: absolute;
|
||||
top: 0.125rem;
|
||||
left: 0.125rem;
|
||||
transition: left 0.25s;
|
||||
}
|
||||
.toggle:hover .toggle-switch:before {
|
||||
background: var(--bulma-white);
|
||||
}
|
||||
.toggle-checkbox:checked + .toggle-switch {
|
||||
background: var(--bulma-dark);
|
||||
}
|
||||
.toggle-checkbox:checked + .toggle-switch:before {
|
||||
left: 1.375rem;
|
||||
}
|
||||
|
||||
.toggle-checkbox {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<section>
|
||||
<nav class="buttons is-centered mb-4">
|
||||
<nav class="buttons is-centered mb-4 fd-is-square">
|
||||
<router-link
|
||||
v-for="index in indices"
|
||||
:key="index"
|
||||
class="button is-small is-square"
|
||||
class="button is-small"
|
||||
:to="{ hash: `#index_${index}`, query: $route.query }"
|
||||
>
|
||||
{{ index }}
|
||||
@ -20,11 +20,4 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.is-square {
|
||||
height: 1.75rem;
|
||||
min-width: 1.75rem;
|
||||
padding-left: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
<style></style>
|
||||
|
@ -1,36 +1,38 @@
|
||||
<template>
|
||||
<template v-for="item in items" :key="item.itemId">
|
||||
<div v-if="!item.isItem" class="py-5">
|
||||
<div v-if="!item.isItem" class="mt-6 mb-5 py-2">
|
||||
<span
|
||||
:id="`index_${item.index}`"
|
||||
class="tag is-small has-text-weight-bold"
|
||||
class="tag is-info is-light is-small has-text-weight-bold"
|
||||
v-text="item.index"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="media is-align-items-center" @click="open(item.item)">
|
||||
<div
|
||||
v-else
|
||||
class="media is-align-items-center is-clickable mb-0"
|
||||
@click="open(item.item)"
|
||||
v-if="settingsStore.show_cover_artwork_in_album_lists"
|
||||
class="media-left"
|
||||
>
|
||||
<cover-artwork
|
||||
v-if="settingsStore.show_cover_artwork_in_album_lists"
|
||||
:url="item.item.artwork_url"
|
||||
:artist="item.item.artist"
|
||||
:album="item.item.name"
|
||||
class="media-left fd-has-shadow fd-cover fd-cover-small-image"
|
||||
class="is-clickable fd-has-shadow fd-cover fd-cover-small-image"
|
||||
/>
|
||||
<div class="media-content">
|
||||
<div class="is-size-6 has-text-weight-bold" v-text="item.item.name" />
|
||||
<div
|
||||
class="is-size-7 has-text-grey has-text-weight-bold"
|
||||
</div>
|
||||
<div class="media-content is-clickable is-clipped">
|
||||
<div>
|
||||
<h1 class="title is-6" v-text="item.item.name" />
|
||||
<h2
|
||||
class="subtitle is-7 has-text-grey has-text-weight-bold"
|
||||
v-text="item.item.artist"
|
||||
/>
|
||||
<div
|
||||
<h2
|
||||
v-if="item.item.date_released && item.item.media_kind === 'music'"
|
||||
class="is-size-7 has-text-grey"
|
||||
class="subtitle is-7 has-text-grey"
|
||||
v-text="$filters.date(item.item.date_released)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<a @click.prevent.stop="open_dialog(item.item)">
|
||||
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
|
||||
@ -146,3 +148,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,29 +1,28 @@
|
||||
<template>
|
||||
<template v-for="item in items" :key="item.id">
|
||||
<div
|
||||
class="media is-align-items-center is-clickable mb-0"
|
||||
@click="open(item)"
|
||||
>
|
||||
<div class="media is-align-items-center" @click="open(item)">
|
||||
<div
|
||||
v-if="settingsStore.show_cover_artwork_in_album_lists"
|
||||
class="media-left"
|
||||
class="media-left is-clickable"
|
||||
>
|
||||
<cover-artwork
|
||||
:url="artwork_url(item)"
|
||||
:artist="item.artist"
|
||||
:album="item.name"
|
||||
class="fd-has-shadow fd-cover fd-cover-small-image"
|
||||
class="is-clickable fd-has-shadow fd-cover fd-cover-small-image"
|
||||
/>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div class="is-size-6 has-text-weight-bold" v-text="item.name" />
|
||||
<div
|
||||
class="is-size-7 has-text-weight-bold has-text-grey"
|
||||
<div class="media-content is-clickable is-clipped">
|
||||
<h1 class="title is-6" v-text="item.name" />
|
||||
<h2
|
||||
class="subtitle is-7 has-text-grey has-text-weight-bold"
|
||||
v-text="item.artists[0]?.name"
|
||||
/>
|
||||
<div
|
||||
class="is-size-7 has-text-grey"
|
||||
v-text="$filters.date(item.release_date)"
|
||||
<h2
|
||||
class="subtitle is-7 has-text-grey"
|
||||
v-text="
|
||||
[item.album_type, $filters.date(item.release_date)].join(', ')
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
@ -77,3 +76,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,21 +1,17 @@
|
||||
<template>
|
||||
<template v-for="item in items" :key="item.itemId">
|
||||
<div v-if="!item.isItem" class="py-5">
|
||||
<div class="media-content">
|
||||
<div v-if="!item.isItem" class="mt-6 mb-5 py-2">
|
||||
<div class="media-content is-clipped">
|
||||
<span
|
||||
:id="`index_${item.index}`"
|
||||
class="tag is-small has-text-weight-bold"
|
||||
class="tag is-info is-light is-small has-text-weight-bold"
|
||||
v-text="item.index"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="media is-align-items-center is-clickable mb-0"
|
||||
@click="open(item.item)"
|
||||
>
|
||||
<div class="media-content">
|
||||
<p class="title is-6" v-text="item.item.name" />
|
||||
<div v-else class="media is-align-items-center" @click="open(item.item)">
|
||||
<div class="media-content is-clickable is-clipped">
|
||||
<h1 class="title is-6" v-text="item.item.name" />
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<a @click.prevent.stop="open_dialog(item.item)">
|
||||
@ -64,3 +60,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<template v-for="item in items" :key="item.id">
|
||||
<div class="media is-align-items-center mb-0">
|
||||
<div class="media-content is-clickable" @click="open(item)">
|
||||
<p class="title is-6" v-text="item.name" />
|
||||
<div class="media is-align-items-center">
|
||||
<div class="media-content is-clickable is-clipped" @click="open(item)">
|
||||
<h1 class="title is-6" v-text="item.name" />
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<a @click.prevent.stop="open_dialog(item)">
|
||||
@ -45,3 +45,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,21 +1,17 @@
|
||||
<template>
|
||||
<template v-for="item in items" :key="item.itemId">
|
||||
<div v-if="!item.isItem" class="py-5">
|
||||
<div class="media-content">
|
||||
<div v-if="!item.isItem" class="mt-6 mb-5 py-2">
|
||||
<div class="media-content is-clipped">
|
||||
<span
|
||||
:id="`index_${item.index}`"
|
||||
class="tag is-small has-text-weight-bold"
|
||||
class="tag is-info is-light is-small has-text-weight-bold"
|
||||
v-text="item.index"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="media is-align-items-center is-clickable mb-0"
|
||||
@click="open(item.item)"
|
||||
>
|
||||
<div class="media-content">
|
||||
<p class="title is-6" v-text="item.item.name" />
|
||||
<div v-else class="media is-align-items-center" @click="open(item.item)">
|
||||
<div class="media-content is-clickable is-clipped">
|
||||
<h1 class="title is-6" v-text="item.item.name" />
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<a @click.prevent.stop="open_dialog(item.item)">
|
||||
@ -66,3 +62,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,11 +1,9 @@
|
||||
<template>
|
||||
<div v-if="$route.query.directory" class="media is-align-items-center mb-0">
|
||||
<mdicon
|
||||
class="icon media-left is-clickable"
|
||||
name="chevron-left"
|
||||
@click="open_parent"
|
||||
/>
|
||||
<div class="media-content">
|
||||
<div v-if="$route.query.directory" class="media is-align-items-center">
|
||||
<figure class="media-left is-clickable" @click="open_parent">
|
||||
<mdicon class="icon" name="chevron-left" size="16" />
|
||||
</figure>
|
||||
<div class="media-content is-clipped">
|
||||
<nav class="breadcrumb">
|
||||
<ul>
|
||||
<li v-for="directory in directories" :key="directory.index">
|
||||
@ -21,13 +19,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="item in items" :key="item.path">
|
||||
<div
|
||||
class="media is-align-items-center is-clickable mb-0"
|
||||
@click="open(item)"
|
||||
>
|
||||
<mdicon class="media-left icon" name="folder" />
|
||||
<div class="media-content">
|
||||
<p class="title is-6" v-text="item.name" />
|
||||
<div class="media is-align-items-center" @click="open(item)">
|
||||
<figure class="media-left is-clickable">
|
||||
<mdicon class="icon" name="folder" size="16" />
|
||||
</figure>
|
||||
<div class="media-content is-clickable is-clipped">
|
||||
<h1 class="title is-6" v-text="item.name" />
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<a @click.prevent.stop="open_dialog(item)">
|
||||
@ -93,3 +90,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,21 +1,17 @@
|
||||
<template>
|
||||
<template v-for="item in items" :key="item.itemId">
|
||||
<div v-if="!item.isItem" class="py-5">
|
||||
<div class="media-content">
|
||||
<div v-if="!item.isItem" class="mt-6 mb-5 py-2">
|
||||
<div class="media-content is-clipped">
|
||||
<span
|
||||
:id="`index_${item.index}`"
|
||||
class="tag is-small has-text-weight-bold"
|
||||
class="tag is-info is-light is-small has-text-weight-bold"
|
||||
v-text="item.index"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="media is-align-items-center is-clickable mb-0"
|
||||
@click="open(item.item)"
|
||||
>
|
||||
<div class="media-content">
|
||||
<p class="title is-6" v-text="item.item.name" />
|
||||
<div v-else class="media is-align-items-center" @click="open(item.item)">
|
||||
<div class="media-content is-clickable is-clipped">
|
||||
<h1 class="title is-6" v-text="item.item.name" />
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<a @click.prevent.stop="open_dialog(item.item)">
|
||||
@ -67,3 +63,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,27 +1,26 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="is_next || !show_only_next_items"
|
||||
class="media is-align-items-center is-clickable mb-0"
|
||||
@click="play"
|
||||
class="media is-align-items-center"
|
||||
>
|
||||
<div v-if="edit_mode" class="media-left">
|
||||
<mdicon
|
||||
class="icon has-text-grey is-movable"
|
||||
class="icon has-text-grey fd-is-movable handle"
|
||||
name="drag-horizontal"
|
||||
size="18"
|
||||
size="16"
|
||||
/>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div
|
||||
class="is-size-6 has-text-weight-bold"
|
||||
<div class="media-content is-clickable is-clipped" @click="play">
|
||||
<h1
|
||||
class="title is-6"
|
||||
:class="{
|
||||
'has-text-primary': item.id === player.item_id,
|
||||
'has-text-grey-light': !is_next
|
||||
}"
|
||||
v-text="item.title"
|
||||
/>
|
||||
<div
|
||||
class="is-size-7 has-text-weight-bold"
|
||||
<h2
|
||||
class="subtitle is-7 has-text-weight-bold"
|
||||
:class="{
|
||||
'has-text-primary': item.id === player.item_id,
|
||||
'has-text-grey-light': !is_next,
|
||||
@ -29,8 +28,8 @@
|
||||
}"
|
||||
v-text="item.artist"
|
||||
/>
|
||||
<div
|
||||
class="is-size-7"
|
||||
<h2
|
||||
class="subtitle is-7"
|
||||
:class="{
|
||||
'has-text-primary': item.id === player.item_id,
|
||||
'has-text-grey-light': !is_next,
|
||||
@ -82,8 +81,4 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.is-movable {
|
||||
cursor: move;
|
||||
}
|
||||
</style>
|
||||
<style></style>
|
||||
|
@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<template v-for="item in items" :key="item.itemId">
|
||||
<div
|
||||
class="media is-align-items-center is-clickable mb-0"
|
||||
@click="open(item.item)"
|
||||
>
|
||||
<mdicon class="media-left icon" :name="icon(item.item)" />
|
||||
<div class="media-content">
|
||||
<p class="title is-6" v-text="item.item.name" />
|
||||
<div class="media is-align-items-center" @click="open(item.item)">
|
||||
<figure class="media-left is-clickable">
|
||||
<mdicon class="icon" :name="icon_name(item.item)" size="16" />
|
||||
</figure>
|
||||
<div class="media-content is-clickable is-clipped">
|
||||
<h1 class="title is-6" v-text="item.item.name" />
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<a @click.prevent.stop="open_dialog(item.item)">
|
||||
@ -40,7 +39,7 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
icon(item) {
|
||||
icon_name(item) {
|
||||
if (item.type === 'folder') {
|
||||
return 'folder'
|
||||
} else if (item.type === 'rss') {
|
||||
@ -62,3 +61,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,15 +1,9 @@
|
||||
<template>
|
||||
<template v-for="item in items" :key="item.id">
|
||||
<div
|
||||
class="media is-align-items-center is-clickable mb-0"
|
||||
@click="open(item)"
|
||||
>
|
||||
<div class="media-content">
|
||||
<div class="is-size-6 has-text-weight-bold" v-text="item.name" />
|
||||
<div
|
||||
class="is-size-7 has-text-weight-bold has-text-grey"
|
||||
v-text="item.owner.display_name"
|
||||
/>
|
||||
<div class="media is-align-items-center">
|
||||
<div class="media-content is-clickable is-clipped" @click="open(item)">
|
||||
<h1 class="title is-6" v-text="item.name" />
|
||||
<h2 class="subtitle is-7" v-text="item.owner.display_name" />
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<a @click.prevent.stop="open_dialog(item)">
|
||||
@ -52,3 +46,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,33 +1,35 @@
|
||||
<template>
|
||||
<template v-for="item in items" :key="item.itemId">
|
||||
<div v-if="!item.isItem" class="py-5">
|
||||
<div v-if="!item.isItem" class="mt-6 mb-5 py-2">
|
||||
<span
|
||||
:id="`index_${item.index}`"
|
||||
class="tag is-small has-text-weight-bold"
|
||||
class="tag is-info is-light is-small has-text-weight-bold"
|
||||
v-text="item.index"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="media is-align-items-center is-clickable mb-0"
|
||||
class="media is-align-items-center"
|
||||
:class="{ 'with-progress': show_progress }"
|
||||
@click="play(item.item)"
|
||||
>
|
||||
<mdicon v-if="show_icon" class="media-left icon" name="file-outline" />
|
||||
<div class="media-content">
|
||||
<div
|
||||
class="is-size-6 has-text-weight-bold"
|
||||
<figure v-if="show_icon" class="media-left is-clickable">
|
||||
<mdicon class="icon" name="file-outline" size="16" />
|
||||
</figure>
|
||||
<div class="media-content is-clickable is-clipped">
|
||||
<h1
|
||||
class="title is-6"
|
||||
:class="{
|
||||
'has-text-grey':
|
||||
item.item.media_kind === 'podcast' && item.item.play_count > 0
|
||||
}"
|
||||
v-text="item.item.title"
|
||||
/>
|
||||
<div
|
||||
class="is-size-7 has-text-weight-bold has-text-grey"
|
||||
<h2
|
||||
class="subtitle is-7 has-text-grey has-text-weight-bold"
|
||||
v-text="item.item.artist"
|
||||
/>
|
||||
<div class="is-size-7 has-text-grey" v-text="item.item.album" />
|
||||
<h2 class="subtitle is-7 has-text-grey" v-text="item.item.album" />
|
||||
<progress
|
||||
v-if="show_progress && item.item.seek_ms > 0"
|
||||
class="progress is-info"
|
||||
@ -101,8 +103,4 @@ export default {
|
||||
.progress {
|
||||
height: 0.25rem;
|
||||
}
|
||||
|
||||
.media.with-progress {
|
||||
margin-top: 0.375rem;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,29 +1,29 @@
|
||||
<template>
|
||||
<template v-for="item in items" :key="item.id">
|
||||
<div class="media is-align-items-center mb-0">
|
||||
<div class="media is-align-items-center">
|
||||
<div
|
||||
class="media-content"
|
||||
class="media-content is-clipped"
|
||||
:class="{
|
||||
'is-clickable': item.is_playable,
|
||||
'is-not-allowed': !item.is_playable
|
||||
'fd-is-not-allowed': !item.is_playable
|
||||
}"
|
||||
@click="play(item)"
|
||||
>
|
||||
<div
|
||||
class="is-size-6 has-text-weight-bold"
|
||||
<h1
|
||||
class="title is-6"
|
||||
:class="{ 'has-text-grey-light': !item.is_playable }"
|
||||
v-text="item.name"
|
||||
/>
|
||||
<div
|
||||
class="is-size-7 has-text-weight-bold"
|
||||
<h2
|
||||
class="subtitle is-7 has-text-weight-bold"
|
||||
:class="{
|
||||
'has-text-grey': item.is_playable,
|
||||
'has-text-grey-light': !item.is_playable
|
||||
}"
|
||||
v-text="item.artists[0].name"
|
||||
/>
|
||||
<div class="is-size-7 has-text-grey" v-text="item.album.name" />
|
||||
<div v-if="!item.is_playable" class="is-size-7 has-text-grey">
|
||||
<h2 class="subtitle is-7 has-text-grey" v-text="item.album.name" />
|
||||
<h2 v-if="!item.is_playable" class="subtitle is-7">
|
||||
(<span v-text="$t('list.spotify.not-playable-track')" />
|
||||
<span
|
||||
v-if="item.restrictions?.reason"
|
||||
@ -33,7 +33,7 @@
|
||||
})
|
||||
"
|
||||
/>)
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<a @click.prevent.stop="open_dialog(item)">
|
||||
@ -83,8 +83,4 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.is-not-allowed {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
<style></style>
|
||||
|
@ -223,14 +223,4 @@ export default {
|
||||
.lyrics div:last-child {
|
||||
padding-bottom: calc(25vh - 3rem);
|
||||
}
|
||||
|
||||
/* Lyrics animation */
|
||||
@keyframes pop-color {
|
||||
0% {
|
||||
color: var(--bulma-black);
|
||||
}
|
||||
100% {
|
||||
color: var(--bulma-success);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,36 +1,49 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p v-if="title" class="title is-4" v-text="title" />
|
||||
<slot name="modal-content" />
|
||||
</template>
|
||||
<template #footer>
|
||||
</div>
|
||||
<footer class="card-footer is-clipped">
|
||||
<a class="card-footer-item has-text-dark" @click="$emit('close')">
|
||||
<mdicon class="icon" name="cancel" size="16" />
|
||||
<span class="is-size-7" v-text="close_action" />
|
||||
</a>
|
||||
<a
|
||||
v-if="delete_action"
|
||||
class="card-footer-item has-background-danger"
|
||||
class="card-footer-item has-background-danger has-text-white has-text-weight-bold"
|
||||
@click="$emit('delete')"
|
||||
>
|
||||
<mdicon class="icon" name="delete" size="16" />
|
||||
<span class="is-size-7" v-text="delete_action" />
|
||||
</a>
|
||||
<a v-if="ok_action" class="card-footer-item" @click="$emit('ok')">
|
||||
<a
|
||||
v-if="ok_action"
|
||||
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
|
||||
@click="$emit('ok')"
|
||||
>
|
||||
<mdicon class="icon" name="check" size="16" />
|
||||
<span class="is-size-7" v-text="ok_action" />
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialog',
|
||||
components: { BaseModal },
|
||||
props: {
|
||||
close_action: { default: '', type: String },
|
||||
delete_action: { default: '', type: String },
|
||||
@ -38,16 +51,8 @@ export default {
|
||||
show: Boolean,
|
||||
title: { required: true, type: String }
|
||||
},
|
||||
emits: ['delete', 'close', 'ok'],
|
||||
watch: {
|
||||
show() {
|
||||
const { classList } = document.querySelector('html')
|
||||
if (this.show) {
|
||||
classList.add('is-clipped')
|
||||
} else {
|
||||
classList.remove('is-clipped')
|
||||
}
|
||||
}
|
||||
}
|
||||
emits: ['delete', 'close', 'ok']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,13 +1,17 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<form class="card" @submit.prevent="add_stream">
|
||||
<div class="card-content">
|
||||
<p class="title is-4" v-text="$t('dialog.add.rss.title')" />
|
||||
<div class="field">
|
||||
<p class="control has-icons-left">
|
||||
<input
|
||||
ref="url_field"
|
||||
v-model="url"
|
||||
class="input"
|
||||
class="input is-shadowless"
|
||||
type="url"
|
||||
pattern="http[s]?://.+"
|
||||
required
|
||||
@ -19,37 +23,46 @@
|
||||
</p>
|
||||
<p class="help" v-text="$t('dialog.add.rss.help')" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="loading" #footer>
|
||||
</div>
|
||||
<footer v-if="loading" class="card-footer">
|
||||
<a class="card-footer-item has-text-dark">
|
||||
<mdicon class="icon" name="web" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.add.rss.processing')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.add.rss.processing')"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else #footer>
|
||||
</footer>
|
||||
<footer v-else class="card-footer is-clipped">
|
||||
<a class="card-footer-item has-text-dark" @click="$emit('close')">
|
||||
<mdicon class="icon" name="cancel" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.add.rss.cancel')" />
|
||||
</a>
|
||||
<a
|
||||
:class="{ 'is-disabled': disabled }"
|
||||
class="card-footer-item"
|
||||
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
|
||||
@click="add_stream"
|
||||
>
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.add.rss.add')" />
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogAddRss',
|
||||
components: { BaseModal },
|
||||
props: { show: Boolean },
|
||||
emits: ['close', 'podcast-added'],
|
||||
|
||||
@ -94,3 +107,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,14 +1,17 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<form @submit.prevent="play">
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<form class="card" @submit.prevent="play">
|
||||
<div class="card-content">
|
||||
<p class="title is-4" v-text="$t('dialog.add.stream.title')" />
|
||||
<div class="field">
|
||||
<p class="control has-icons-left">
|
||||
<input
|
||||
ref="url_field"
|
||||
v-model="url"
|
||||
class="input"
|
||||
class="input is-shadowless"
|
||||
type="url"
|
||||
pattern="http[s]?://.+"
|
||||
required
|
||||
@ -19,15 +22,17 @@
|
||||
<mdicon class="icon is-left" name="web" size="16" />
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<template v-if="loading" #footer>
|
||||
</div>
|
||||
<footer v-if="loading" class="card-footer">
|
||||
<a class="card-footer-item has-text-dark">
|
||||
<mdicon class="icon" name="web" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.add.stream.loading')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.add.stream.loading')"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else #footer>
|
||||
</footer>
|
||||
<footer v-else class="card-footer is-clipped">
|
||||
<a class="card-footer-item has-text-dark" @click="$emit('close')">
|
||||
<mdicon class="icon" name="cancel" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.add.stream.cancel')" />
|
||||
@ -42,23 +47,29 @@
|
||||
</a>
|
||||
<a
|
||||
:class="{ 'is-disabled': disabled }"
|
||||
class="card-footer-item has-text-dark"
|
||||
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
|
||||
@click="play"
|
||||
>
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.add.stream.play')" />
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogAddUrlStream',
|
||||
components: { BaseModal },
|
||||
props: { show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -114,3 +125,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,15 +1,19 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<div class="title is-4">
|
||||
<a @click="open" v-text="item.name" />
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<cover-artwork
|
||||
:url="item.artwork_url"
|
||||
:artist="item.artist"
|
||||
:album="item.name"
|
||||
class="fd-has-shadow fd-cover fd-cover-normal-image mb-3"
|
||||
class="fd-has-shadow fd-cover fd-cover-normal-image mb-5"
|
||||
/>
|
||||
<p class="title is-4">
|
||||
<a class="has-text-link" @click="open" v-text="item.name" />
|
||||
</p>
|
||||
<div v-if="media_kind_resolved === 'podcast'" class="buttons">
|
||||
<a
|
||||
class="button is-small"
|
||||
@ -23,61 +27,59 @@
|
||||
v-text="$t('dialog.album.remove-podcast')"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="item.artist" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.album.artist')"
|
||||
<div class="content is-small">
|
||||
<p v-if="item.artist">
|
||||
<span class="heading" v-text="$t('dialog.album.artist')" />
|
||||
<a
|
||||
class="title is-6 has-text-link"
|
||||
@click="open_artist"
|
||||
v-text="item.artist"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
<a @click="open_artist" v-text="item.artist" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.date_released" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
</p>
|
||||
<p v-if="item.date_released">
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.album.release-date')"
|
||||
/>
|
||||
<div class="title is-6" v-text="$filters.date(item.date_released)" />
|
||||
</div>
|
||||
<div v-else-if="item.year" class="mb-3">
|
||||
<div class="is-size-7 is-uppercase" v-text="$t('dialog.album.year')" />
|
||||
<div class="title is-6" v-text="item.year" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.album.tracks')"
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.date(item.date_released)"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.track_count" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.album.duration')"
|
||||
/>
|
||||
<div
|
||||
</p>
|
||||
<p v-else-if="item.year">
|
||||
<span class="heading" v-text="$t('dialog.album.year')" />
|
||||
<span class="title is-6" v-text="item.year" />
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.album.tracks')" />
|
||||
<span class="title is-6" v-text="item.track_count" />
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.album.duration')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.durationInHours(item.length_ms)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="is-size-7 is-uppercase" v-text="$t('dialog.album.type')" />
|
||||
<div
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.album.type')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="
|
||||
`${$t(`media.kind.${item.media_kind}`)} - ${$t(`data.kind.${item.data_kind}`)}`
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.album.added-on')"
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.album.added-on')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.datetime(item.time_added)"
|
||||
/>
|
||||
<div class="title is-6" v-text="$filters.datetime(item.time_added)" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add">
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.album.add')" />
|
||||
@ -90,18 +92,25 @@
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.album.play')" />
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import CoverArtwork from '@/components/CoverArtwork.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogAlbum',
|
||||
components: { BaseModal, CoverArtwork },
|
||||
components: { CoverArtwork },
|
||||
props: {
|
||||
item: { required: true, type: Object },
|
||||
media_kind: { default: '', type: String },
|
||||
@ -175,3 +184,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,66 +1,90 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<div class="title is-4">
|
||||
<a @click="open" v-text="item.name" />
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<cover-artwork
|
||||
:url="artwork_url(item)"
|
||||
:artist="item.artist"
|
||||
:album="item.name"
|
||||
class="fd-has-shadow fd-cover fd-cover-normal-image mb-3"
|
||||
class="fd-has-shadow fd-cover fd-cover-normal-image mb-5"
|
||||
@load="artwork_loaded"
|
||||
@error="artwork_error"
|
||||
/>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<p class="title is-4">
|
||||
<a class="has-text-link" @click="open" v-text="item.name" />
|
||||
</p>
|
||||
<div class="content is-small">
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.album.album-artist')"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
<a @click="open_artist" v-text="item.artists[0].name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<a
|
||||
class="title is-6 has-text-link"
|
||||
@click="open_artist"
|
||||
v-text="item.artists[0].name"
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.album.release-date')"
|
||||
/>
|
||||
<div class="title is-6" v-text="$filters.date(item.release_date)" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.date(item.release_date)"
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.album.type')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.album_type" />
|
||||
<span class="title is-6" v-text="item.album_type" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add">
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.spotify.album.add')" />
|
||||
</a>
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add_next">
|
||||
<mdicon class="icon" name="playlist-play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.spotify.album.add-next')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.spotify.album.add-next')"
|
||||
/>
|
||||
</a>
|
||||
<a class="card-footer-item has-text-dark" @click="play">
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.spotify.album.play')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.spotify.album.play')"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import CoverArtwork from '@/components/CoverArtwork.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogAlbumSpotify',
|
||||
components: { BaseModal, CoverArtwork },
|
||||
components: { CoverArtwork },
|
||||
props: { item: { required: true, type: Object }, show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -109,3 +133,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,36 +1,39 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<div class="title is-4">
|
||||
<a @click="open" v-text="item.name" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.artist.albums')"
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="title is-4">
|
||||
<a class="has-text-link" @click="open" v-text="item.name" />
|
||||
</p>
|
||||
<div class="content is-small">
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.artist.albums')" />
|
||||
<span class="title is-6" v-text="item.album_count" />
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.artist.tracks')" />
|
||||
<span class="title is-6" v-text="item.track_count" />
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.artist.type')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$t(`data.kind.${item.data_kind}`)"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.album_count" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.artist.tracks')"
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.artist.added-on')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.datetime(item.time_added)"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.track_count" />
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="is-size-7 is-uppercase" v-text="$t('dialog.artist.type')" />
|
||||
<div class="title is-6" v-text="$t(`data.kind.${item.data_kind}`)" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.artist.added-on')"
|
||||
/>
|
||||
<div class="title is-6" v-text="$filters.datetime(item.time_added)" />
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add">
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.artist.add')" />
|
||||
@ -43,17 +46,23 @@
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.artist.play')" />
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogArtist',
|
||||
components: { BaseModal },
|
||||
props: { item: { required: true, type: Object }, show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -80,3 +89,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,51 +1,72 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<div class="title is-4">
|
||||
<a @click="open" v-text="item.name" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="title is-4">
|
||||
<a class="has-text-link" @click="open" v-text="item.name" />
|
||||
</p>
|
||||
<div class="content is-small">
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.artist.popularity')"
|
||||
/>
|
||||
<div
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="[item.popularity, item.followers.total].join(' / ')"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.artist.genres')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.genres.join(', ')" />
|
||||
<span class="title is-6" v-text="item.genres.join(', ')" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add">
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.spotify.artist.add')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.spotify.artist.add')"
|
||||
/>
|
||||
</a>
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add_next">
|
||||
<mdicon class="icon" name="playlist-play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.spotify.artist.add-next')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.spotify.artist.add-next')"
|
||||
/>
|
||||
</a>
|
||||
<a class="card-footer-item has-text-dark" @click="play">
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.spotify.artist.play')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.spotify.artist.play')"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogArtistSpotify',
|
||||
components: { BaseModal },
|
||||
props: { item: { required: true, type: Object }, show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -72,3 +93,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,39 +1,42 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<div class="title is-4">
|
||||
<a @click="open_albums" v-text="item.name" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.composer.albums')"
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="title is-4">
|
||||
<a
|
||||
class="has-text-link"
|
||||
@click="open_albums"
|
||||
v-text="item.name"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
<a @click="open_albums" v-text="item.album_count" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.composer.tracks')"
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.composer.albums')" />
|
||||
<a
|
||||
class="has-text-link is-6"
|
||||
@click="open_albums"
|
||||
v-text="item.album_count"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
<a @click="open_tracks" v-text="item.track_count" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.composer.duration')"
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.composer.tracks')" />
|
||||
<a
|
||||
class="has-text-link is-6"
|
||||
@click="open_tracks"
|
||||
v-text="item.track_count"
|
||||
/>
|
||||
<div
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.composer.duration')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.durationInHours(item.length_ms)"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add">
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.composer.add')" />
|
||||
@ -46,17 +49,23 @@
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.composer.play')" />
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogComposer',
|
||||
components: { BaseModal },
|
||||
props: { item: { required: true, type: Object }, show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -96,3 +105,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,32 +1,45 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="title is-4" v-text="item" />
|
||||
</template>
|
||||
<template #footer>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add">
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.directory.add')" />
|
||||
</a>
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add_next">
|
||||
<mdicon class="icon" name="playlist-play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.directory.add-next')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.directory.add-next')"
|
||||
/>
|
||||
</a>
|
||||
<a class="card-footer-item has-text-dark" @click="play">
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.directory.play')" />
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogDirectory',
|
||||
components: { BaseModal },
|
||||
props: { item: { required: true, type: String }, show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -53,3 +66,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,35 +1,32 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<div class="title is-4">
|
||||
<a @click="open" v-text="item.name" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.genre.albums')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.album_count" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.genre.tracks')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.track_count" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.genre.duration')"
|
||||
/>
|
||||
<div
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="title is-4">
|
||||
<a class="has-text-link" @click="open" v-text="item.name" />
|
||||
</p>
|
||||
<div class="content is-small">
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.genre.albums')" />
|
||||
<span class="title is-6" v-text="item.album_count" />
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.genre.tracks')" />
|
||||
<span class="title is-6" v-text="item.track_count" />
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.genre.duration')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.durationInHours(item.length_ms)"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add">
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.genre.add')" />
|
||||
@ -42,17 +39,23 @@
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.genre.play')" />
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogGenre',
|
||||
components: { BaseModal },
|
||||
props: {
|
||||
item: { required: true, type: Object },
|
||||
media_kind: { required: true, type: String },
|
||||
@ -89,3 +92,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,32 +1,32 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<div class="title is-4">
|
||||
<a @click="open" v-text="item.name" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.playlist.path')"
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="title is-4">
|
||||
<a class="has-text-link" @click="open" v-text="item.name" />
|
||||
</p>
|
||||
<div class="content is-small">
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.playlist.path')" />
|
||||
<span class="title is-6" v-text="item.path" />
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.playlist.type')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$t(`playlist.type.${item.type}`)"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.path" />
|
||||
</p>
|
||||
<p v-if="!item.folder">
|
||||
<span class="heading" v-text="$t('dialog.playlist.tracks')" />
|
||||
<span class="title is-6" v-text="item.item_count" />
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.playlist.type')"
|
||||
/>
|
||||
<div class="title is-6" v-text="$t(`playlist.type.${item.type}`)" />
|
||||
</div>
|
||||
<div v-if="!item.folder" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.playlist.tracks')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.item_count" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!item.folder" #footer>
|
||||
<footer v-if="!item.folder" class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add">
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.playlist.add')" />
|
||||
@ -39,17 +39,23 @@
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.playlist.play')" />
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogPlaylist',
|
||||
components: { BaseModal },
|
||||
props: {
|
||||
item: { required: true, type: Object },
|
||||
show: Boolean,
|
||||
@ -80,3 +86,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,14 +1,17 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<form @submit.prevent="save">
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<form class="card" @submit.prevent="save">
|
||||
<div class="card-content">
|
||||
<p class="title is-4" v-text="$t('dialog.playlist.save.title')" />
|
||||
<div class="field">
|
||||
<p class="control has-icons-left">
|
||||
<input
|
||||
ref="playlist_name_field"
|
||||
v-model="playlist_name"
|
||||
class="input"
|
||||
class="input is-shadowless"
|
||||
type="text"
|
||||
pattern=".+"
|
||||
required
|
||||
@ -19,38 +22,52 @@
|
||||
<mdicon class="icon is-left" name="file-music" size="16" />
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<template v-if="loading" #footer>
|
||||
</div>
|
||||
<footer v-if="loading" class="card-footer">
|
||||
<a class="card-footer-item has-text-dark">
|
||||
<mdicon class="icon" name="web" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.playlist.save.saving')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.playlist.save.saving')"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else #footer>
|
||||
</footer>
|
||||
<footer v-else class="card-footer is-clipped">
|
||||
<a class="card-footer-item has-text-danger" @click="$emit('close')">
|
||||
<mdicon class="icon" name="cancel" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.playlist.save.cancel')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.playlist.save.cancel')"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
:class="{ 'is-disabled': disabled }"
|
||||
class="card-footer-item has-text-weight-bold"
|
||||
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
|
||||
@click="save"
|
||||
>
|
||||
<mdicon class="icon" name="content-save" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.playlist.save.save')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.playlist.save.save')"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogPlaylistSave',
|
||||
components: { BaseModal },
|
||||
props: { show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -94,3 +111,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,35 +1,44 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<div class="title is-4">
|
||||
<a @click="open" v-text="item.name" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="title is-4">
|
||||
<a class="has-text-link" @click="open" v-text="item.name" />
|
||||
</p>
|
||||
<div class="content is-small">
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.playlist.owner')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.owner.display_name" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<span class="title is-6" v-text="item.owner.display_name" />
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.playlist.tracks')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.tracks.total" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<span class="title is-6" v-text="item.tracks.total" />
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.playlist.path')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.uri" />
|
||||
<span class="title is-6" v-text="item.uri" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add">
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.spotify.playlist.add')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.spotify.playlist.add')"
|
||||
/>
|
||||
</a>
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add_next">
|
||||
<mdicon class="icon" name="playlist-play" size="16" />
|
||||
@ -40,19 +49,28 @@
|
||||
</a>
|
||||
<a class="card-footer-item has-text-dark" @click="play">
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.spotify.playlist.play')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.spotify.playlist.play')"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogPlaylistSpotify',
|
||||
components: { BaseModal },
|
||||
props: { item: { required: true, type: Object }, show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -79,3 +97,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,95 +1,91 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<div class="title is-4" v-text="item.title" />
|
||||
<div class="subtitle" v-text="item.artist" />
|
||||
<div v-if="item.album" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.queue-item.album')"
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="title is-4" v-text="item.title" />
|
||||
<p class="subtitle" v-text="item.artist" />
|
||||
<div class="content is-small">
|
||||
<p v-if="item.album">
|
||||
<span class="heading" v-text="$t('dialog.queue-item.album')" />
|
||||
<a
|
||||
class="title is-6 has-text-link"
|
||||
@click="open_album"
|
||||
v-text="item.album"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
<a @click="open_album" v-text="item.album" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.album_artist" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
</p>
|
||||
<p v-if="item.album_artist">
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.queue-item.album-artist')"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
<a @click="open_album_artist" v-text="item.album_artist" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.composer" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<a
|
||||
class="title is-6 has-text-link"
|
||||
@click="open_album_artist"
|
||||
v-text="item.album_artist"
|
||||
/>
|
||||
</p>
|
||||
<p v-if="item.composer">
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.queue-item.composer')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.composer" />
|
||||
</div>
|
||||
<div v-if="item.year" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.queue-item.year')"
|
||||
<span class="title is-6" v-text="item.composer" />
|
||||
</p>
|
||||
<p v-if="item.year">
|
||||
<span class="heading" v-text="$t('dialog.queue-item.year')" />
|
||||
<span class="title is-6" v-text="item.year" />
|
||||
</p>
|
||||
<p v-if="item.genre">
|
||||
<span class="heading" v-text="$t('dialog.queue-item.genre')" />
|
||||
<a
|
||||
class="title is-6 has-text-link"
|
||||
@click="open_genre"
|
||||
v-text="item.genre"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.year" />
|
||||
</div>
|
||||
<div v-if="item.genre" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.queue-item.genre')"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
<a @click="open_genre" v-text="item.genre" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.disc_number" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
</p>
|
||||
<p v-if="item.disc_number">
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.queue-item.position')"
|
||||
/>
|
||||
<div
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="[item.disc_number, item.track_number].join(' / ')"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="item.length_ms" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
</p>
|
||||
<p v-if="item.length_ms">
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.queue-item.duration')"
|
||||
/>
|
||||
<div
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.durationInHours(item.length_ms)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.queue-item.path')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.path" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.queue-item.type')"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.queue-item.path')" />
|
||||
<span class="title is-6" v-text="item.path" />
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.queue-item.type')" />
|
||||
<span class="title is-6">
|
||||
<span
|
||||
v-text="
|
||||
`${$t(`media.kind.${item.media_kind}`)} - ${$t(`data.kind.${item.data_kind}`)}`
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.samplerate" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
</span>
|
||||
</p>
|
||||
<p v-if="item.samplerate">
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.queue-item.quality')"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
<span class="title is-6">
|
||||
<span v-text="item.type" />
|
||||
<span
|
||||
v-if="item.samplerate"
|
||||
@ -109,12 +105,15 @@
|
||||
/>
|
||||
<span
|
||||
v-if="item.bitrate"
|
||||
v-text="$t('dialog.queue-item.bitrate', { rate: item.bitrate })"
|
||||
v-text="
|
||||
$t('dialog.queue-item.bitrate', { rate: item.bitrate })
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="remove">
|
||||
<mdicon class="icon" name="delete" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.queue-item.remove')" />
|
||||
@ -123,19 +122,25 @@
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.queue-item.play')" />
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import SpotifyWebApi from 'spotify-web-api-js'
|
||||
import { useServicesStore } from '@/stores/services'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogQueueItem',
|
||||
components: { BaseModal },
|
||||
props: { item: { required: true, type: Object }, show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -228,3 +233,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="title is-4" v-text="$t('dialog.remote-pairing.title')" />
|
||||
<form @submit.prevent="kickoff_pairing">
|
||||
<label class="label" v-text="pairing.remote" />
|
||||
@ -17,28 +21,43 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<template #footer>
|
||||
</div>
|
||||
<footer class="card-footer is-clipped">
|
||||
<a class="card-footer-item has-text-danger" @click="$emit('close')">
|
||||
<mdicon class="icon" name="cancel" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.remote-pairing.cancel')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.remote-pairing.cancel')"
|
||||
/>
|
||||
</a>
|
||||
<a class="card-footer-item" @click="kickoff_pairing">
|
||||
<a
|
||||
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
|
||||
@click="kickoff_pairing"
|
||||
>
|
||||
<mdicon class="icon" name="cellphone" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.remote-pairing.pair')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.remote-pairing.pair')"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import { useRemotesStore } from '@/stores/remotes'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogRemotePairing',
|
||||
components: { BaseModal },
|
||||
props: { show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -79,3 +98,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="title is-4" v-text="item.title" />
|
||||
<p class="subtitle" v-text="item.artist" />
|
||||
<div v-if="item.media_kind === 'podcast'" class="buttons">
|
||||
@ -17,87 +21,83 @@
|
||||
v-text="$t('dialog.track.mark-as-played')"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="item.album" class="mb-3">
|
||||
<div class="is-size-7 is-uppercase" v-text="$t('dialog.track.album')" />
|
||||
<div class="title is-6">
|
||||
<a @click="open_album" v-text="item.album" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.album_artist && item.media_kind !== 'audiobook'"
|
||||
class="mb-3"
|
||||
>
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<div class="content is-small">
|
||||
<p v-if="item.album">
|
||||
<span class="heading" v-text="$t('dialog.track.album')" />
|
||||
<a
|
||||
class="title is-6 has-text-link"
|
||||
@click="open_album"
|
||||
v-text="item.album"
|
||||
/>
|
||||
</p>
|
||||
<p v-if="item.album_artist && item.media_kind !== 'audiobook'">
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.track.album-artist')"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
<a @click="open_album_artist" v-text="item.album_artist" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.composer" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.track.composer')"
|
||||
<a
|
||||
class="title is-6 has-text-link"
|
||||
@click="open_album_artist"
|
||||
v-text="item.album_artist"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.composer" />
|
||||
</div>
|
||||
<div v-if="item.date_released" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
</p>
|
||||
<p v-if="item.composer">
|
||||
<span class="heading" v-text="$t('dialog.track.composer')" />
|
||||
<span class="title is-6" v-text="item.composer" />
|
||||
</p>
|
||||
<p v-if="item.date_released">
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.track.release-date')"
|
||||
/>
|
||||
<div class="title is-6" v-text="$filters.date(item.date_released)" />
|
||||
</div>
|
||||
<div v-else-if="item.year" class="mb-3">
|
||||
<div class="is-size-7 is-uppercase" v-text="$t('dialog.track.year')" />
|
||||
<div class="title is-6" v-text="item.year" />
|
||||
</div>
|
||||
<div v-if="item.genre" class="mb-3">
|
||||
<div class="is-size-7 is-uppercase" v-text="$t('dialog.track.genre')" />
|
||||
<div class="title is-6">
|
||||
<a @click="open_genre" v-text="item.genre" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.disc_number" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.track.position')"
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.date(item.date_released)"
|
||||
/>
|
||||
<div
|
||||
</p>
|
||||
<p v-else-if="item.year">
|
||||
<span class="heading" v-text="$t('dialog.track.year')" />
|
||||
<span class="title is-6" v-text="item.year" />
|
||||
</p>
|
||||
<p v-if="item.genre">
|
||||
<span class="heading" v-text="$t('dialog.track.genre')" />
|
||||
<a
|
||||
class="title is-6 has-text-link"
|
||||
@click="open_genre"
|
||||
v-text="item.genre"
|
||||
/>
|
||||
</p>
|
||||
<p v-if="item.disc_number">
|
||||
<span class="heading" v-text="$t('dialog.track.position')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="[item.disc_number, item.track_number].join(' / ')"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="item.length_ms" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.track.duration')"
|
||||
/>
|
||||
<div
|
||||
</p>
|
||||
<p v-if="item.length_ms">
|
||||
<span class="heading" v-text="$t('dialog.track.duration')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.durationInHours(item.length_ms)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="is-size-7 is-uppercase" v-text="$t('dialog.track.path')" />
|
||||
<div class="title is-6" v-text="item.path" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="is-size-7 is-uppercase" v-text="$t('dialog.track.type')" />
|
||||
<div
|
||||
class="title is-6"
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.track.path')" />
|
||||
<span class="title is-6" v-text="item.path" />
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.track.type')" />
|
||||
<span class="title is-6">
|
||||
<span
|
||||
v-text="
|
||||
`${$t(`media.kind.${item.media_kind}`)} - ${$t(`data.kind.${item.data_kind}`)}`
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="item.samplerate" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.track.quality')"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
</span>
|
||||
</p>
|
||||
<p v-if="item.samplerate">
|
||||
<span class="heading" v-text="$t('dialog.track.quality')" />
|
||||
<span class="title is-6">
|
||||
<span v-text="item.type" />
|
||||
<span
|
||||
v-if="item.samplerate"
|
||||
@ -119,21 +119,18 @@
|
||||
v-if="item.bitrate"
|
||||
v-text="$t('dialog.track.bitrate', { rate: item.bitrate })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.track.added-on')"
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.track.added-on')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.datetime(item.time_added)"
|
||||
/>
|
||||
<div class="title is-6" v-text="$filters.datetime(item.time_added)" />
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.track.rating')"
|
||||
/>
|
||||
<div
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading" v-text="$t('dialog.track.rating')" />
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="
|
||||
$t('dialog.track.rating-value', {
|
||||
@ -141,16 +138,14 @@
|
||||
})
|
||||
"
|
||||
/>
|
||||
</p>
|
||||
<p v-if="item.comment">
|
||||
<span class="heading" v-text="$t('dialog.track.comment')" />
|
||||
<span class="title is-6" v-text="item.comment" />
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="item.comment" class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('dialog.track.comment')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.comment" />
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add">
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.track.add')" />
|
||||
@ -163,19 +158,25 @@
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.track.play')" />
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import SpotifyWebApi from 'spotify-web-api-js'
|
||||
import { useServicesStore } from '@/stores/services'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogTrack',
|
||||
components: { BaseModal },
|
||||
props: { item: { required: true, type: Object }, show: Boolean },
|
||||
emits: ['close', 'play-count-changed'],
|
||||
|
||||
@ -295,3 +296,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,88 +1,110 @@
|
||||
<template>
|
||||
<base-modal :show="show" @close="$emit('close')">
|
||||
<template #content>
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="modal is-active">
|
||||
<div class="modal-background" @click="$emit('close')" />
|
||||
<div class="modal-content">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="title is-4" v-text="item.name" />
|
||||
<p class="subtitle" v-text="item.artists[0].name" />
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<div class="content is-small">
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.track.album')"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
<a @click="open_album" v-text="item.album.name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<a
|
||||
class="title is-6 has-text-link"
|
||||
@click="open_album"
|
||||
v-text="item.album.name"
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.track.album-artist')"
|
||||
/>
|
||||
<div class="title is-6">
|
||||
<a @click="open_artist" v-text="item.artists[0].name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<a
|
||||
class="title is-6 has-text-link"
|
||||
@click="open_artist"
|
||||
v-text="item.artists[0].name"
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.track.release-date')"
|
||||
/>
|
||||
<div
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.date(item.album.release_date)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.track.position')"
|
||||
/>
|
||||
<div
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="[item.disc_number, item.track_number].join(' / ')"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.track.duration')"
|
||||
/>
|
||||
<div
|
||||
<span
|
||||
class="title is-6"
|
||||
v-text="$filters.durationInHours(item.duration_ms)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
class="heading"
|
||||
v-text="$t('dialog.spotify.track.path')"
|
||||
/>
|
||||
<div class="title is-6" v-text="item.uri" />
|
||||
<span class="title is-6" v-text="item.uri" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add">
|
||||
<mdicon class="icon" name="playlist-plus" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.spotify.track.add')" />
|
||||
</a>
|
||||
<a class="card-footer-item has-text-dark" @click="queue_add_next">
|
||||
<mdicon class="icon" name="playlist-play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.spotify.track.add-next')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.spotify.track.add-next')"
|
||||
/>
|
||||
</a>
|
||||
<a class="card-footer-item has-text-dark" @click="play">
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span class="is-size-7" v-text="$t('dialog.spotify.track.play')" />
|
||||
<span
|
||||
class="is-size-7"
|
||||
v-text="$t('dialog.spotify.track.play')"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
</base-modal>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large"
|
||||
aria-label="close"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogTrackSpotify',
|
||||
components: { BaseModal },
|
||||
props: { item: { required: true, type: Object }, show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -116,3 +138,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -2,18 +2,18 @@
|
||||
<modal-dialog
|
||||
:show="show"
|
||||
:title="$t('dialog.update.title')"
|
||||
:ok_action="libraryStore.updating ? '' : $t('dialog.update.rescan')"
|
||||
:ok_action="library.updating ? '' : $t('dialog.update.rescan')"
|
||||
:close_action="$t('dialog.update.cancel')"
|
||||
@ok="update_library"
|
||||
@close="close()"
|
||||
>
|
||||
<template #modal-content>
|
||||
<div v-if="!libraryStore.updating">
|
||||
<div v-if="!library.updating">
|
||||
<p class="mb-3" v-text="$t('dialog.update.info')" />
|
||||
<div v-if="spotify_enabled || rss.tracks > 0" class="field">
|
||||
<label class="label" v-text="$t('dialog.update.info')" />
|
||||
<div class="control">
|
||||
<div class="select is-small">
|
||||
<select v-model="libraryStore.update_dialog_scan_kind">
|
||||
<select v-model="update_dialog_scan_kind">
|
||||
<option value="" v-text="$t('dialog.update.all')" />
|
||||
<option value="files" v-text="$t('dialog.update.local')" />
|
||||
<option
|
||||
@ -30,11 +30,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<control-switch v-model="rescan_metadata">
|
||||
<template #label>
|
||||
<span v-text="$t('dialog.update.rescan-metadata')" />
|
||||
</template>
|
||||
</control-switch>
|
||||
<div class="field">
|
||||
<input
|
||||
id="rescan"
|
||||
v-model="rescan_metadata"
|
||||
type="checkbox"
|
||||
class="switch is-rounded is-small"
|
||||
/>
|
||||
<label for="rescan" v-text="$t('dialog.update.rescan-metadata')" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p class="mb-3" v-text="$t('dialog.update.progress')" />
|
||||
@ -44,7 +48,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ControlSwitch from '@/components/ControlSwitch.vue'
|
||||
import ModalDialog from '@/components/ModalDialog.vue'
|
||||
import { useLibraryStore } from '@/stores/library'
|
||||
import { useServicesStore } from '@/stores/services'
|
||||
@ -52,7 +55,7 @@ import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ModalDialogUpdate',
|
||||
components: { ControlSwitch, ModalDialog },
|
||||
components: { ModalDialog },
|
||||
props: { show: Boolean },
|
||||
emits: ['close'],
|
||||
|
||||
@ -70,26 +73,42 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
library() {
|
||||
return this.libraryStore.$state
|
||||
},
|
||||
|
||||
rss() {
|
||||
return this.libraryStore.rss
|
||||
},
|
||||
|
||||
spotify_enabled() {
|
||||
return this.servicesStore.spotify.webapi_token_valid
|
||||
},
|
||||
|
||||
update_dialog_scan_kind: {
|
||||
get() {
|
||||
return this.library.update_dialog_scan_kind
|
||||
},
|
||||
set(value) {
|
||||
this.library.update_dialog_scan_kind = value
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.libraryStore.update_dialog_scan_kind = ''
|
||||
this.update_dialog_scan_kind = ''
|
||||
this.$emit('close')
|
||||
},
|
||||
update_library() {
|
||||
if (this.rescan_metadata) {
|
||||
webapi.library_rescan(this.libraryStore.update_dialog_scan_kind)
|
||||
webapi.library_rescan(this.update_dialog_scan_kind)
|
||||
} else {
|
||||
webapi.library_update(this.libraryStore.update_dialog_scan_kind)
|
||||
webapi.library_update(this.update_dialog_scan_kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,26 +1,118 @@
|
||||
<template>
|
||||
<nav
|
||||
class="navbar is-fixed-bottom"
|
||||
:class="{ 'is-dark': !is_now_playing_page }"
|
||||
class="navbar is-block is-white is-fixed-bottom fd-bottom-navbar"
|
||||
:class="{
|
||||
'is-transparent': is_now_playing_page,
|
||||
'is-dark': !is_now_playing_page
|
||||
}"
|
||||
role="navigation"
|
||||
aria-label="player controls"
|
||||
>
|
||||
<!-- Player menu for desktop -->
|
||||
<div
|
||||
class="navbar-item has-dropdown has-dropdown-up is-hidden-touch"
|
||||
:class="{ 'is-active': show_player_menu }"
|
||||
>
|
||||
<div class="navbar-dropdown is-right fd-width-auto">
|
||||
<div class="navbar-item">
|
||||
<!-- Outputs: master volume -->
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left is-flex-grow-1">
|
||||
<div class="level-item is-flex-grow-0">
|
||||
<a class="button is-white is-small" @click="toggle_mute_volume">
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="player.volume > 0 ? 'volume-high' : 'volume-off'"
|
||||
size="18"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<div>
|
||||
<p class="heading" v-text="$t('navigation.volume')" />
|
||||
<control-slider
|
||||
v-model:value="player.volume"
|
||||
:max="100"
|
||||
@change="change_volume"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Outputs: master volume -->
|
||||
<hr class="my-3" />
|
||||
<navbar-item-output
|
||||
v-for="output in outputs"
|
||||
:key="output.id"
|
||||
:output="output"
|
||||
/>
|
||||
<!-- Outputs: stream volume -->
|
||||
<hr class="my-3" />
|
||||
<div class="navbar-item">
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left is-flex-grow-1">
|
||||
<div class="level-item is-flex-grow-0">
|
||||
<a
|
||||
class="button is-white is-small"
|
||||
:class="{
|
||||
'has-text-grey-light': !playing && !loading,
|
||||
'is-loading': loading
|
||||
}"
|
||||
@click="togglePlay"
|
||||
>
|
||||
<mdicon class="icon" name="broadcast" size="18" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<div class="is-flex-grow-1">
|
||||
<div
|
||||
class="is-flex is-align-content-center"
|
||||
:class="{ 'has-text-grey-light': !playing }"
|
||||
>
|
||||
<p class="heading" v-text="$t('navigation.stream')" />
|
||||
<a href="stream.mp3" class="heading ml-2" target="_blank">
|
||||
<mdicon
|
||||
class="icon is-small"
|
||||
name="open-in-new"
|
||||
size="16"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<control-slider
|
||||
v-model:value="stream_volume"
|
||||
:disabled="!playing"
|
||||
:max="100"
|
||||
:cursor="cursor"
|
||||
@change="change_stream_volume"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-3" />
|
||||
<div class="navbar-item is-justify-content-center">
|
||||
<div class="buttons has-addons">
|
||||
<player-button-repeat class="button" />
|
||||
<player-button-shuffle class="button" />
|
||||
<player-button-consume class="button" />
|
||||
<player-button-lyrics class="button" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-brand is-flex-grow-1">
|
||||
<control-link class="navbar-item" :to="{ name: 'queue' }">
|
||||
<mdicon class="icon" name="playlist-play" />
|
||||
</control-link>
|
||||
<template v-if="is_now_playing_page">
|
||||
<control-player-previous class="navbar-item ml-auto" />
|
||||
<control-player-back class="navbar-item" :offset="10000" />
|
||||
<control-player-play class="navbar-item" show_disabled_message />
|
||||
<control-player-forward class="navbar-item" :offset="30000" />
|
||||
<control-player-next class="navbar-item mr-auto" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<control-link
|
||||
<navbar-item-link :to="{ name: 'queue' }" class="mr-auto">
|
||||
<mdicon class="icon" name="playlist-play" size="24" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
v-if="!is_now_playing_page"
|
||||
:to="{ name: 'now-playing' }"
|
||||
exact
|
||||
class="navbar-item is-justify-content-flex-start is-expanded is-clipped is-size-7"
|
||||
class="is-expanded is-clipped is-size-7"
|
||||
>
|
||||
<div class="is-text-clipped">
|
||||
<div class="fd-is-text-clipped">
|
||||
<strong v-text="current.title" />
|
||||
<br />
|
||||
<span v-text="current.artist" />
|
||||
@ -29,40 +121,130 @@
|
||||
v-text="$t('navigation.now-playing', { album: current.album })"
|
||||
/>
|
||||
</div>
|
||||
</control-link>
|
||||
<control-player-play class="navbar-item" show_disabled_message />
|
||||
</template>
|
||||
</navbar-item-link>
|
||||
<player-button-previous
|
||||
v-if="is_now_playing_page"
|
||||
class="navbar-item px-2"
|
||||
:icon_size="24"
|
||||
/>
|
||||
<player-button-seek-back
|
||||
v-if="is_now_playing_page"
|
||||
:seek_ms="10000"
|
||||
class="navbar-item px-2"
|
||||
:icon_size="24"
|
||||
/>
|
||||
<player-button-play-pause
|
||||
class="navbar-item px-2"
|
||||
:icon_size="36"
|
||||
show_disabled_message
|
||||
/>
|
||||
<player-button-seek-forward
|
||||
v-if="is_now_playing_page"
|
||||
:seek_ms="30000"
|
||||
class="navbar-item px-2"
|
||||
:icon_size="24"
|
||||
/>
|
||||
<player-button-next
|
||||
v-if="is_now_playing_page"
|
||||
class="navbar-item px-2"
|
||||
:icon_size="24"
|
||||
/>
|
||||
<a
|
||||
class="navbar-item"
|
||||
@click="uiStore.show_player_menu = !uiStore.show_player_menu"
|
||||
class="navbar-item ml-auto"
|
||||
@click="show_player_menu = !show_player_menu"
|
||||
>
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="uiStore.show_player_menu ? 'chevron-down' : 'chevron-up'"
|
||||
:name="show_player_menu ? 'chevron-down' : 'chevron-up'"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Player menu for mobile and tablet -->
|
||||
<div
|
||||
class="dropdown is-up is-right"
|
||||
:class="{ 'is-active': uiStore.show_player_menu }"
|
||||
class="navbar-menu is-hidden-desktop"
|
||||
:class="{ 'is-active': show_player_menu }"
|
||||
>
|
||||
<div class="dropdown-menu">
|
||||
<div class="dropdown-content">
|
||||
<div class="dropdown-item pt-0">
|
||||
<control-main-volume />
|
||||
<control-output-volume
|
||||
v-for="output in outputsStore.outputs"
|
||||
<div class="navbar-item">
|
||||
<div class="buttons has-addons is-centered">
|
||||
<player-button-repeat class="button" />
|
||||
<player-button-shuffle class="button" />
|
||||
<player-button-consume class="button" />
|
||||
<player-button-lyrics class="button" />
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-3" />
|
||||
<!-- Outputs: master volume -->
|
||||
<div class="navbar-item">
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left is-flex-grow-1">
|
||||
<div class="level-item is-flex-grow-0">
|
||||
<a class="button is-white is-small" @click="toggle_mute_volume">
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="player.volume > 0 ? 'volume-high' : 'volume-off'"
|
||||
size="18"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<div class="is-flex-grow-1">
|
||||
<p class="heading" v-text="$t('navigation.volume')" />
|
||||
<control-slider
|
||||
v-model:value="player.volume"
|
||||
:max="100"
|
||||
@change="change_volume"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-3" />
|
||||
<!-- Outputs: speaker volumes -->
|
||||
<navbar-item-output
|
||||
v-for="output in outputs"
|
||||
:key="output.id"
|
||||
:output="output"
|
||||
/>
|
||||
<control-stream-volume />
|
||||
<!-- Outputs: stream volume -->
|
||||
<hr class="my-3" />
|
||||
<div class="navbar-item mb-5">
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left is-flex-grow-1">
|
||||
<div class="level-item is-flex-grow-0">
|
||||
<a
|
||||
class="button is-white is-small"
|
||||
:class="{
|
||||
'has-text-grey-light': !playing && !loading,
|
||||
'is-loading': loading
|
||||
}"
|
||||
@click="togglePlay"
|
||||
>
|
||||
<mdicon class="icon" name="radio-tower" size="16" />
|
||||
</a>
|
||||
</div>
|
||||
<hr class="dropdown-divider" />
|
||||
<div class="dropdown-item is-flex is-justify-content-center">
|
||||
<div class="buttons has-addons">
|
||||
<control-player-repeat class="button" />
|
||||
<control-player-shuffle class="button" />
|
||||
<control-player-consume class="button" />
|
||||
<control-player-lyrics class="button" />
|
||||
<div class="level-item">
|
||||
<div class="is-flex-grow-1">
|
||||
<div
|
||||
class="is-flex is-align-content-center"
|
||||
:class="{ 'has-text-grey-light': !playing }"
|
||||
>
|
||||
<p class="heading" v-text="$t('navigation.stream')" />
|
||||
<a href="stream.mp3" class="heading ml-2" target="_blank">
|
||||
<mdicon
|
||||
class="icon is-small"
|
||||
name="open-in-new"
|
||||
size="16"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<control-slider
|
||||
v-model:value="stream_volume"
|
||||
:disabled="!playing"
|
||||
:max="100"
|
||||
:cursor="cursor"
|
||||
@change="change_stream_volume"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -73,66 +255,168 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ControlLink from '@/components/ControlLink.vue'
|
||||
import ControlMainVolume from '@/components/ControlMainVolume.vue'
|
||||
import ControlOutputVolume from '@/components/ControlOutputVolume.vue'
|
||||
import ControlPlayerBack from '@/components/ControlPlayerBack.vue'
|
||||
import ControlPlayerConsume from '@/components/ControlPlayerConsume.vue'
|
||||
import ControlPlayerForward from '@/components/ControlPlayerForward.vue'
|
||||
import ControlPlayerLyrics from '@/components/ControlPlayerLyrics.vue'
|
||||
import ControlPlayerNext from '@/components/ControlPlayerNext.vue'
|
||||
import ControlPlayerPlay from '@/components/ControlPlayerPlay.vue'
|
||||
import ControlPlayerPrevious from '@/components/ControlPlayerPrevious.vue'
|
||||
import ControlPlayerRepeat from '@/components/ControlPlayerRepeat.vue'
|
||||
import ControlPlayerShuffle from '@/components/ControlPlayerShuffle.vue'
|
||||
import ControlStreamVolume from '@/components/ControlStreamVolume.vue'
|
||||
import ControlSlider from '@/components/ControlSlider.vue'
|
||||
import NavbarItemLink from '@/components/NavbarItemLink.vue'
|
||||
import NavbarItemOutput from '@/components/NavbarItemOutput.vue'
|
||||
import PlayerButtonConsume from '@/components/PlayerButtonConsume.vue'
|
||||
import PlayerButtonLyrics from '@/components/PlayerButtonLyrics.vue'
|
||||
import PlayerButtonNext from '@/components/PlayerButtonNext.vue'
|
||||
import PlayerButtonPlayPause from '@/components/PlayerButtonPlayPause.vue'
|
||||
import PlayerButtonPrevious from '@/components/PlayerButtonPrevious.vue'
|
||||
import PlayerButtonRepeat from '@/components/PlayerButtonRepeat.vue'
|
||||
import PlayerButtonSeekBack from '@/components/PlayerButtonSeekBack.vue'
|
||||
import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward.vue'
|
||||
import PlayerButtonShuffle from '@/components/PlayerButtonShuffle.vue'
|
||||
import audio from '@/lib/Audio'
|
||||
import { mdiCancel } from '@mdi/js'
|
||||
import { useNotificationsStore } from '@/stores/notifications'
|
||||
import { useOutputsStore } from '@/stores/outputs'
|
||||
import { usePlayerStore } from '@/stores/player'
|
||||
import { useQueueStore } from '@/stores/queue'
|
||||
import { useUIStore } from '@/stores/ui'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'NavbarBottom',
|
||||
components: {
|
||||
ControlLink,
|
||||
ControlOutputVolume,
|
||||
ControlMainVolume,
|
||||
ControlPlayerBack,
|
||||
ControlPlayerConsume,
|
||||
ControlPlayerForward,
|
||||
ControlPlayerLyrics,
|
||||
ControlPlayerNext,
|
||||
ControlPlayerPlay,
|
||||
ControlPlayerPrevious,
|
||||
ControlPlayerRepeat,
|
||||
ControlPlayerShuffle,
|
||||
ControlStreamVolume
|
||||
ControlSlider,
|
||||
NavbarItemLink,
|
||||
NavbarItemOutput,
|
||||
PlayerButtonConsume,
|
||||
PlayerButtonLyrics,
|
||||
PlayerButtonNext,
|
||||
PlayerButtonPlayPause,
|
||||
PlayerButtonPrevious,
|
||||
PlayerButtonRepeat,
|
||||
PlayerButtonSeekBack,
|
||||
PlayerButtonSeekForward,
|
||||
PlayerButtonShuffle
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
notificationsStore: useNotificationsStore(),
|
||||
outputsStore: useOutputsStore(),
|
||||
playerStore: usePlayerStore(),
|
||||
queueStore: useQueueStore(),
|
||||
uiStore: useUIStore()
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
cursor: mdiCancel,
|
||||
loading: false,
|
||||
old_volume: 0,
|
||||
playing: false,
|
||||
show_desktop_outputs_menu: false,
|
||||
show_outputs_menu: false,
|
||||
stream_volume: 10
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
is_now_playing_page() {
|
||||
return this.$route.name === 'now-playing'
|
||||
},
|
||||
current() {
|
||||
return this.queueStore.current
|
||||
},
|
||||
outputs() {
|
||||
return this.outputsStore.outputs
|
||||
},
|
||||
player() {
|
||||
return this.playerStore
|
||||
},
|
||||
show_player_menu: {
|
||||
get() {
|
||||
return this.uiStore.show_player_menu
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.show_player_menu = value
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'playerStore.volume'() {
|
||||
if (this.player.volume > 0) {
|
||||
this.old_volume = this.player.volume
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// On app mounted
|
||||
mounted() {
|
||||
this.setupAudio()
|
||||
},
|
||||
|
||||
// On app destroyed
|
||||
unmounted() {
|
||||
this.closeAudio()
|
||||
},
|
||||
|
||||
methods: {
|
||||
change_stream_volume() {
|
||||
audio.setVolume(this.stream_volume / 100)
|
||||
},
|
||||
change_volume() {
|
||||
webapi.player_volume(this.player.volume)
|
||||
},
|
||||
closeAudio() {
|
||||
audio.stop()
|
||||
this.playing = false
|
||||
},
|
||||
on_click_outside_outputs() {
|
||||
this.show_outputs_menu = false
|
||||
},
|
||||
playChannel() {
|
||||
if (this.playing) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
audio.play('/stream.mp3')
|
||||
audio.setVolume(this.stream_volume / 100)
|
||||
},
|
||||
setupAudio() {
|
||||
const a = audio.setup()
|
||||
a.addEventListener('waiting', () => {
|
||||
this.playing = false
|
||||
this.loading = true
|
||||
})
|
||||
a.addEventListener('playing', () => {
|
||||
this.playing = true
|
||||
this.loading = false
|
||||
})
|
||||
a.addEventListener('ended', () => {
|
||||
this.playing = false
|
||||
this.loading = false
|
||||
})
|
||||
a.addEventListener('error', () => {
|
||||
this.closeAudio()
|
||||
this.notificationsStore.add({
|
||||
text: this.$t('navigation.stream-error'),
|
||||
type: 'danger'
|
||||
})
|
||||
this.playing = false
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
togglePlay() {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
if (this.playing) {
|
||||
this.closeAudio()
|
||||
}
|
||||
this.playChannel()
|
||||
},
|
||||
toggle_mute_volume() {
|
||||
this.player.volume = this.player.volume > 0 ? 0 : this.old_volume
|
||||
this.change_volume()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.is-text-clipped {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
<style></style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a :href="href" @click.stop.prevent="open">
|
||||
<a class="navbar-item" :href="href" @click.stop.prevent="open">
|
||||
<slot />
|
||||
</a>
|
||||
</template>
|
||||
@ -8,7 +8,7 @@
|
||||
import { useUIStore } from '@/stores/ui'
|
||||
|
||||
export default {
|
||||
name: 'ControlLink',
|
||||
name: 'NavbarItemLink',
|
||||
props: {
|
||||
to: { required: true, type: Object }
|
||||
},
|
93
web-src/src/components/NavbarItemOutput.vue
Normal file
93
web-src/src/components/NavbarItemOutput.vue
Normal file
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="navbar-item">
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left is-flex-grow-1">
|
||||
<div class="level-item is-flex-grow-0">
|
||||
<a
|
||||
class="button is-white is-small"
|
||||
:class="{ 'has-text-grey-light': !output.selected }"
|
||||
@click="set_enabled"
|
||||
>
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="type_class"
|
||||
size="18"
|
||||
:title="output.type"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<div class="is-flex-grow-1">
|
||||
<p
|
||||
class="heading"
|
||||
:class="{ 'has-text-grey-light': !output.selected }"
|
||||
v-text="output.name"
|
||||
/>
|
||||
<control-slider
|
||||
v-model:value="volume"
|
||||
:disabled="!output.selected"
|
||||
:max="100"
|
||||
:cursor="cursor"
|
||||
@change="change_volume"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ControlSlider from '@/components/ControlSlider.vue'
|
||||
import { mdiCancel } from '@mdi/js'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'NavbarItemOutput',
|
||||
components: {
|
||||
ControlSlider
|
||||
},
|
||||
props: { output: { required: true, type: Object } },
|
||||
|
||||
data() {
|
||||
return {
|
||||
cursor: mdiCancel,
|
||||
volume: this.output.selected ? this.output.volume : 0
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
type_class() {
|
||||
if (this.output.type.startsWith('AirPlay')) {
|
||||
return 'cast-variant'
|
||||
} else if (this.output.type === 'Chromecast') {
|
||||
return 'cast'
|
||||
} else if (this.output.type === 'fifo') {
|
||||
return 'pipe'
|
||||
}
|
||||
return 'server'
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
output() {
|
||||
this.volume = this.output.volume
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
change_volume() {
|
||||
webapi.player_output_volume(this.output.id, this.volume)
|
||||
},
|
||||
|
||||
set_enabled() {
|
||||
const values = {
|
||||
selected: !this.output.selected
|
||||
}
|
||||
webapi.output_update(this.output.id, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,151 +1,135 @@
|
||||
<template>
|
||||
<nav class="navbar is-light is-fixed-top" :style="zindex">
|
||||
<div class="navbar-brand is-flex-grow-1">
|
||||
<control-link
|
||||
<nav
|
||||
class="navbar is-light is-fixed-top"
|
||||
:style="zindex"
|
||||
role="navigation"
|
||||
aria-label="main navigation"
|
||||
>
|
||||
<div class="navbar-brand">
|
||||
<navbar-item-link
|
||||
v-if="settingsStore.show_menu_item_playlists"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'playlists' }"
|
||||
>
|
||||
<mdicon class="icon" name="music-box-multiple" size="16" />
|
||||
</control-link>
|
||||
<control-link
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
v-if="settingsStore.show_menu_item_music"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'music' }"
|
||||
>
|
||||
<mdicon class="icon" name="music" size="16" />
|
||||
</control-link>
|
||||
<control-link
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
v-if="settingsStore.show_menu_item_podcasts"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'podcasts' }"
|
||||
>
|
||||
<mdicon class="icon" name="microphone" size="16" />
|
||||
</control-link>
|
||||
<control-link
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
v-if="settingsStore.show_menu_item_audiobooks"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'audiobooks' }"
|
||||
>
|
||||
<mdicon class="icon" name="book-open-variant" size="16" />
|
||||
</control-link>
|
||||
<control-link
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
v-if="settingsStore.show_menu_item_radio"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'radio' }"
|
||||
>
|
||||
<mdicon class="icon" name="radio" size="16" />
|
||||
</control-link>
|
||||
<control-link
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
v-if="settingsStore.show_menu_item_files"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'files' }"
|
||||
>
|
||||
<mdicon class="icon" name="folder-open" size="16" />
|
||||
</control-link>
|
||||
<control-link
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
v-if="settingsStore.show_menu_item_search"
|
||||
class="navbar-item"
|
||||
:to="{ name: searchStore.search_source }"
|
||||
>
|
||||
<mdicon class="icon" name="magnify" size="16" />
|
||||
</control-link>
|
||||
<a
|
||||
class="navbar-item ml-auto"
|
||||
@click="uiStore.show_burger_menu = !uiStore.show_burger_menu"
|
||||
>
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="uiStore.show_burger_menu ? 'close' : 'menu'"
|
||||
/>
|
||||
</a>
|
||||
</navbar-item-link>
|
||||
<div
|
||||
class="dropdown is-right"
|
||||
:class="{ 'is-active': uiStore.show_burger_menu }"
|
||||
class="navbar-burger"
|
||||
:class="{ 'is-active': show_burger_menu }"
|
||||
@click="show_burger_menu = !show_burger_menu"
|
||||
>
|
||||
<div class="dropdown-menu">
|
||||
<div class="dropdown-content">
|
||||
<control-link class="dropdown-item" :to="{ name: 'playlists' }">
|
||||
<span class="icon-text">
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-menu" :class="{ 'is-active': show_burger_menu }">
|
||||
<div class="navbar-start" />
|
||||
<div class="navbar-end">
|
||||
<!-- Burger menu entries -->
|
||||
<div
|
||||
class="navbar-item has-dropdown is-hoverable"
|
||||
:class="{ 'is-active': show_settings_menu }"
|
||||
@click="on_click_outside_settings"
|
||||
>
|
||||
<a class="navbar-item is-arrowless is-hidden-touch">
|
||||
<mdicon class="icon" name="menu" size="24" />
|
||||
</a>
|
||||
<div class="navbar-dropdown is-right">
|
||||
<navbar-item-link :to="{ name: 'playlists' }">
|
||||
<mdicon class="icon" name="music-box-multiple" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.playlists')" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'music' }">
|
||||
<span class="icon-text">
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'music' }">
|
||||
<mdicon class="icon" name="music" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.music')" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'music-artists' }">
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'music-artists' }">
|
||||
<span class="pl-5" v-text="$t('navigation.artists')" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'music-albums' }">
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'music-albums' }">
|
||||
<span class="pl-5" v-text="$t('navigation.albums')" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'music-genres' }">
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'music-genres' }">
|
||||
<span class="pl-5" v-text="$t('navigation.genres')" />
|
||||
</control-link>
|
||||
<control-link
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
v-if="spotify_enabled"
|
||||
class="dropdown-item"
|
||||
:to="{ name: 'music-spotify' }"
|
||||
>
|
||||
<span class="pl-5" v-text="$t('navigation.spotify')" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'podcasts' }">
|
||||
<span class="icon-text">
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'podcasts' }">
|
||||
<mdicon class="icon" name="microphone" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.podcasts')" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'audiobooks' }">
|
||||
<span class="icon-text">
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'audiobooks' }">
|
||||
<mdicon class="icon" name="book-open-variant" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.audiobooks')" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'radio' }">
|
||||
<span class="icon-text">
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'radio' }">
|
||||
<mdicon class="icon" name="radio" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.radio')" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'files' }">
|
||||
<span class="icon-text">
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'files' }">
|
||||
<mdicon class="icon" name="folder-open" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.files')" />
|
||||
</control-link>
|
||||
<control-link
|
||||
class="dropdown-item"
|
||||
:to="{ name: searchStore.search_source }"
|
||||
>
|
||||
<span class="icon-text">
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: searchStore.search_source }">
|
||||
<mdicon class="icon" name="magnify" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.search')" />
|
||||
</control-link>
|
||||
</navbar-item-link>
|
||||
<hr class="my-3" />
|
||||
<control-link
|
||||
class="dropdown-item"
|
||||
:to="{ name: 'settings-webinterface' }"
|
||||
>
|
||||
<navbar-item-link :to="{ name: 'settings-webinterface' }">
|
||||
{{ $t('navigation.settings') }}
|
||||
</control-link>
|
||||
</navbar-item-link>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
class="navbar-item"
|
||||
@click.stop.prevent="open_update_dialog()"
|
||||
v-text="$t('navigation.update-library')"
|
||||
/>
|
||||
<control-link class="dropdown-item" :to="{ name: 'about' }">
|
||||
<navbar-item-link :to="{ name: 'about' }">
|
||||
{{ $t('navigation.about') }}
|
||||
</control-link>
|
||||
</navbar-item-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-show="show_settings_menu"
|
||||
class="is-overlay"
|
||||
@ -155,7 +139,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ControlLink from '@/components/ControlLink.vue'
|
||||
import NavbarItemLink from '@/components/NavbarItemLink.vue'
|
||||
import { useSearchStore } from '@/stores/search'
|
||||
import { useServicesStore } from '@/stores/services'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
@ -163,7 +147,7 @@ import { useUIStore } from '@/stores/ui'
|
||||
|
||||
export default {
|
||||
name: 'NavbarTop',
|
||||
components: { ControlLink },
|
||||
components: { NavbarItemLink },
|
||||
|
||||
setup() {
|
||||
return {
|
||||
@ -181,6 +165,22 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
show_burger_menu: {
|
||||
get() {
|
||||
return this.uiStore.show_burger_menu
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.show_burger_menu = value
|
||||
}
|
||||
},
|
||||
show_update_dialog: {
|
||||
get() {
|
||||
return this.uiStore.show_update_dialog
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.show_update_dialog = value
|
||||
}
|
||||
},
|
||||
spotify_enabled() {
|
||||
return this.servicesStore.spotify.webapi_token_valid
|
||||
},
|
||||
@ -203,10 +203,12 @@ export default {
|
||||
this.show_settings_menu = !this.show_settings_menu
|
||||
},
|
||||
open_update_dialog() {
|
||||
this.uiStore.show_update_dialog = true
|
||||
this.show_update_dialog = true
|
||||
this.show_settings_menu = false
|
||||
this.uiStore.show_burger_menu = false
|
||||
this.show_burger_menu = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<a :class="{ 'is-info': is_consume }" @click="toggle">
|
||||
<a :class="{ 'is-info': is_consume }" @click="toggle_consume_mode">
|
||||
<mdicon
|
||||
class="icon"
|
||||
name="fire"
|
||||
size="16"
|
||||
:size="icon_size"
|
||||
:title="$t('player.button.consume')"
|
||||
/>
|
||||
</a>
|
||||
@ -14,7 +14,10 @@ import { usePlayerStore } from '@/stores/player'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ControlPlayerConsume',
|
||||
name: 'PlayerButtonConsume',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
@ -29,9 +32,11 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle() {
|
||||
toggle_consume_mode() {
|
||||
webapi.player_consume(!this.is_consume)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<a :class="{ 'is-info': is_active }" @click="toggle">
|
||||
<a :class="{ 'is-info': is_active }" @click="toggle_lyrics">
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="icon"
|
||||
:size="16"
|
||||
:name="icon_name"
|
||||
:size="icon_size"
|
||||
:title="$t('player.button.toggle-lyrics')"
|
||||
/>
|
||||
</a>
|
||||
@ -13,7 +13,10 @@
|
||||
import { useLyricsStore } from '@/stores/lyrics'
|
||||
|
||||
export default {
|
||||
name: 'ControlPlayerLyrics',
|
||||
name: 'PlayerButtonLyrics',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
@ -22,7 +25,7 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
icon() {
|
||||
icon_name() {
|
||||
return this.is_active ? 'script-text-play' : 'script-text-outline'
|
||||
},
|
||||
is_active() {
|
||||
@ -31,9 +34,11 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle() {
|
||||
toggle_lyrics() {
|
||||
this.lyricsStore.pane = !this.lyricsStore.pane
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<a :disabled="disabled" @click="play_next">
|
||||
<mdicon
|
||||
class="icon"
|
||||
name="skip-forward"
|
||||
:size="icon_size"
|
||||
:title="$t('player.button.skip-forward')"
|
||||
/>
|
||||
</a>
|
||||
@ -13,7 +13,10 @@ import { useQueueStore } from '@/stores/queue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ControlPlayerNext',
|
||||
name: 'PlayerButtonNext',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
|
||||
computed: {
|
||||
disabled() {
|
||||
@ -26,8 +29,11 @@ export default {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
webapi.player_next()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<a :disabled="disabled" @click="toggle">
|
||||
<mdicon class="icon" :name="icon" :title="$t(`player.button.${icon}`)" />
|
||||
<a :disabled="disabled" @click="toggle_play_pause">
|
||||
<mdicon
|
||||
:name="icon_name"
|
||||
:size="icon_size"
|
||||
:title="$t(`player.button.${icon_name}`)"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
@ -11,8 +15,9 @@ import { useQueueStore } from '@/stores/queue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ControlPlayerPlay',
|
||||
name: 'PlayerButtonPlayPause',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number },
|
||||
show_disabled_message: Boolean
|
||||
},
|
||||
|
||||
@ -28,7 +33,7 @@ export default {
|
||||
disabled() {
|
||||
return this.queueStore?.count <= 0
|
||||
},
|
||||
icon() {
|
||||
icon_name() {
|
||||
if (!this.is_playing) {
|
||||
return 'play'
|
||||
} else if (this.is_pause_allowed) {
|
||||
@ -46,7 +51,7 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle() {
|
||||
toggle_play_pause() {
|
||||
if (this.disabled) {
|
||||
if (this.show_disabled_message) {
|
||||
this.notificationsStore.add({
|
||||
@ -69,3 +74,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<a :disabled="disabled" @click="play_previous">
|
||||
<mdicon
|
||||
class="icon"
|
||||
name="skip-backward"
|
||||
:size="icon_size"
|
||||
:title="$t('player.button.skip-backward')"
|
||||
/>
|
||||
</a>
|
||||
@ -13,7 +13,10 @@ import { useQueueStore } from '@/stores/queue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ControlPlayerPrevious',
|
||||
name: 'PlayerButtonPrevious',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
@ -37,3 +40,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<a :class="{ 'is-info': !is_repeat_off }" @click="toggle">
|
||||
<a :class="{ 'is-info': !is_repeat_off }" @click="toggle_repeat_mode">
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="icon"
|
||||
:size="16"
|
||||
:title="$t(`player.button.${icon}`)"
|
||||
:name="icon_name"
|
||||
:size="icon_size"
|
||||
:title="$t(`player.button.${icon_name}`)"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
@ -14,14 +14,19 @@ import { usePlayerStore } from '@/stores/player'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ControlPlayerRepeat',
|
||||
name: 'PlayerButtonRepeat',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
playerStore: usePlayerStore()
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
icon() {
|
||||
icon_name() {
|
||||
if (this.is_repeat_all) {
|
||||
return 'repeat'
|
||||
} else if (this.is_repeat_single) {
|
||||
@ -41,7 +46,7 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle() {
|
||||
toggle_repeat_mode() {
|
||||
if (this.is_repeat_all) {
|
||||
webapi.player_repeat('single')
|
||||
} else if (this.is_repeat_single) {
|
||||
@ -53,3 +58,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<a v-if="visible" :disabled="disabled" @click="seek">
|
||||
<mdicon
|
||||
class="icon"
|
||||
name="rewind-10"
|
||||
:size="icon_size"
|
||||
:title="$t('player.button.seek-backward')"
|
||||
/>
|
||||
</a>
|
||||
@ -14,9 +14,10 @@ import { useQueueStore } from '@/stores/queue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ControlPlayerBack',
|
||||
name: 'PlayerButtonSeekBack',
|
||||
props: {
|
||||
offset: { required: true, type: Number }
|
||||
icon_size: { default: 16, type: Number },
|
||||
seek_ms: { required: true, type: Number }
|
||||
},
|
||||
|
||||
setup() {
|
||||
@ -51,7 +52,7 @@ export default {
|
||||
methods: {
|
||||
seek() {
|
||||
if (!this.disabled) {
|
||||
webapi.player_seek(this.offset * -1)
|
||||
webapi.player_seek(this.seek_ms * -1)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<a v-if="visible" :disabled="disabled" @click="seek">
|
||||
<mdicon
|
||||
class="icon"
|
||||
name="fast-forward-30"
|
||||
:size="icon_size"
|
||||
:title="$t('player.button.seek-forward')"
|
||||
/>
|
||||
</a>
|
||||
@ -14,9 +14,10 @@ import { useQueueStore } from '@/stores/queue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ControlPlayerForward',
|
||||
name: 'PlayerButtonSeekForward',
|
||||
props: {
|
||||
offset: { required: true, type: Number }
|
||||
icon_size: { default: 16, type: Number },
|
||||
seek_ms: { required: true, type: Number }
|
||||
},
|
||||
|
||||
setup() {
|
||||
@ -51,7 +52,7 @@ export default {
|
||||
methods: {
|
||||
seek() {
|
||||
if (!this.disabled) {
|
||||
webapi.player_seek(this.offset)
|
||||
webapi.player_seek(this.seek_ms)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<a :class="{ 'is-info': is_shuffle }" @click="toggle">
|
||||
<a :class="{ 'is-info': is_shuffle }" @click="toggle_shuffle_mode">
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="icon"
|
||||
:size="16"
|
||||
:title="$t(`player.button.${icon}`)"
|
||||
:name="icon_name"
|
||||
:size="icon_size"
|
||||
:title="$t(`player.button.${icon_name}`)"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
@ -14,14 +14,20 @@ import { usePlayerStore } from '@/stores/player'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'ControlPlayerShuffle',
|
||||
name: 'PlayerButtonShuffle',
|
||||
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
playerStore: usePlayerStore()
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
icon() {
|
||||
icon_name() {
|
||||
if (this.is_shuffle) {
|
||||
return 'shuffle'
|
||||
}
|
||||
@ -31,10 +37,13 @@ export default {
|
||||
return this.playerStore.shuffle
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle() {
|
||||
toggle_shuffle_mode() {
|
||||
webapi.player_shuffle(!this.is_shuffle)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,33 +1,32 @@
|
||||
<template>
|
||||
<control-switch v-model="setting.value" @update:model-value="update">
|
||||
<template #label>
|
||||
<div class="field">
|
||||
<input
|
||||
:id="setting.name"
|
||||
v-model="setting.value"
|
||||
type="checkbox"
|
||||
class="switch is-rounded mr-2"
|
||||
@change="update_setting"
|
||||
/>
|
||||
<label class="pt-0" :for="setting.name">
|
||||
<slot name="label" />
|
||||
</template>
|
||||
<template #info>
|
||||
<mdicon
|
||||
v-if="isSuccess"
|
||||
class="icon has-text-info"
|
||||
name="check"
|
||||
size="16"
|
||||
</label>
|
||||
<i
|
||||
class="is-size-7"
|
||||
:class="{ 'has-text-info': is_success, 'has-text-danger': is_error }"
|
||||
v-text="info"
|
||||
/>
|
||||
<mdicon
|
||||
v-if="isError"
|
||||
class="icon has-text-danger"
|
||||
name="close"
|
||||
size="16"
|
||||
/>
|
||||
</template>
|
||||
</control-switch>
|
||||
<p v-if="$slots['info']" class="help">
|
||||
<slot name="info" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ControlSwitch from '@/components/ControlSwitch.vue'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'SettingsCheckbox',
|
||||
components: { ControlSwitch },
|
||||
props: {
|
||||
category: { required: true, type: String },
|
||||
name: { required: true, type: String }
|
||||
@ -48,10 +47,18 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
isError() {
|
||||
info() {
|
||||
if (this.is_success) {
|
||||
return this.$t('setting.saved')
|
||||
} else if (this.is_error) {
|
||||
return this.$t('setting.not-saved')
|
||||
}
|
||||
return ''
|
||||
},
|
||||
is_error() {
|
||||
return this.statusUpdate === 'error'
|
||||
},
|
||||
isSuccess() {
|
||||
is_success() {
|
||||
return this.statusUpdate === 'success'
|
||||
},
|
||||
setting() {
|
||||
@ -68,13 +75,13 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
clearStatus() {
|
||||
clear_status() {
|
||||
if (this.is_error) {
|
||||
this.setting.value = !this.setting.value
|
||||
}
|
||||
this.statusUpdate = ''
|
||||
},
|
||||
update() {
|
||||
update_setting() {
|
||||
this.timerId = -1
|
||||
const setting = {
|
||||
category: this.category,
|
||||
@ -91,9 +98,11 @@ export default {
|
||||
this.statusUpdate = 'error'
|
||||
})
|
||||
.finally(() => {
|
||||
this.timerId = window.setTimeout(this.clearStatus, this.timerDelay)
|
||||
this.timerId = window.setTimeout(this.clear_status, this.timerDelay)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -116,3 +116,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -117,3 +117,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -67,3 +67,5 @@ export default {
|
||||
name: 'TabsAudiobooks'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -129,3 +129,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -53,3 +53,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -64,3 +64,5 @@ export default {
|
||||
name: 'TabsSettings'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -32,7 +32,7 @@ export const filters = {
|
||||
.toLocaleString(DateTime.DATETIME_MED)
|
||||
},
|
||||
durationInDays(value) {
|
||||
const minutes = Math.floor(value / 60)
|
||||
const minutes = Math.floor(value / 60000)
|
||||
if (minutes > 1440) {
|
||||
return Duration.fromObject({ minutes })
|
||||
.shiftTo('days', 'hours', 'minutes')
|
||||
@ -53,6 +53,6 @@ export const filters = {
|
||||
},
|
||||
timeFromNow(value) {
|
||||
const diff = DateTime.now().diff(DateTime.fromISO(value))
|
||||
return this.durationInDays(diff.as('seconds'))
|
||||
return this.durationInDays(diff.as('milliseconds'))
|
||||
}
|
||||
}
|
||||
|
@ -241,7 +241,6 @@
|
||||
"compiled-with": "Compiliert mit Unterstützung von {options}.",
|
||||
"library": "Bibliothek",
|
||||
"more": "mehr",
|
||||
"name": "Name",
|
||||
"total-playtime": "Gesamte Spielzeit",
|
||||
"tracks": "Tracks",
|
||||
"update": "Neu einlesen",
|
||||
@ -408,8 +407,8 @@
|
||||
"count": "{count} Track|{count} Track|{count} Tracks",
|
||||
"edit": "Bearbeiten",
|
||||
"hide-previous": "Vorherige verbergen",
|
||||
"save": "Speichern",
|
||||
"title": "Warteschlange"
|
||||
"title": "Warteschlange",
|
||||
"save": "Speichern"
|
||||
},
|
||||
"radio": {
|
||||
"count": "{count} Station|{count} Station|{count} Stationen",
|
||||
@ -496,7 +495,8 @@
|
||||
"spotify": {
|
||||
"no-support": "OwnTone wurde entweder ohne Unterstützung für Spotify erstellt oder libspotify ist nicht installiert.",
|
||||
"logged-as": "Angemeldet als ",
|
||||
"requirements": "Spotify Premium Abo erforderlich. Zugriff auf die Spotify Web-Api ermöglicht scannen der Spotify-Blibliothek. Erforderliche scopes sind: ",
|
||||
"requirements": "Spotify Premium Abo erforderlich.",
|
||||
"scopes": "Zugriff auf die Spotify Web-Api ermöglicht scannen der Spotify-Blibliothek. Erforderliche scopes sind: ",
|
||||
"user": "Zugriff gestattet für ",
|
||||
"authorize": "Authorisiere Web-API-Zugriff",
|
||||
"grant-access": "Zugriff auf die Spotify Web-API gestatten",
|
||||
@ -572,6 +572,10 @@
|
||||
"toggle-lyrics": "Liedtexte anzeigen/verbergen"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"not-saved": " (Fehler beim Speichern der Einstellungen)",
|
||||
"saved": " (Einstellungen gesichert)"
|
||||
},
|
||||
"server": {
|
||||
"connection-failed": "Fehler bei Verbindung zum OwnTone-Server",
|
||||
"request-failed": "Anfrage gescheitert (Status: {status} {cause} {url})",
|
||||
|
@ -241,7 +241,6 @@
|
||||
"compiled-with": "Compiled with support for {options}.",
|
||||
"library": "Library",
|
||||
"more": "more",
|
||||
"name": "Name",
|
||||
"total-playtime": "Total playtime",
|
||||
"tracks": "Tracks",
|
||||
"update": "Update",
|
||||
@ -408,8 +407,8 @@
|
||||
"count": "{count} track|{count} track|{count} tracks",
|
||||
"edit": "Edit",
|
||||
"hide-previous": "Hide previous",
|
||||
"save": "Save",
|
||||
"title": "Queue"
|
||||
"title": "Queue",
|
||||
"save": "Save"
|
||||
},
|
||||
"radio": {
|
||||
"count": "{count} station|{count} station|{count} stations",
|
||||
@ -495,7 +494,8 @@
|
||||
"spotify": {
|
||||
"no-support": "OwnTone was either built without support for Spotify or libspotify is not installed.",
|
||||
"logged-as": "Logged in as ",
|
||||
"requirements": "You must have a Spotify premium account. Access to the Spotify Web API enables scanning of your Spotify library. Required scopes are: ",
|
||||
"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",
|
||||
"grant-access": "Grant access to the Spotify Web API",
|
||||
@ -571,6 +571,10 @@
|
||||
"toggle-lyrics": "Toggle lyrics"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"not-saved": " (error saving setting)",
|
||||
"saved": " (setting saved)"
|
||||
},
|
||||
"server": {
|
||||
"connection-failed": "Failed to connect to OwnTone server",
|
||||
"request-failed": "Request failed (status: {status} {cause} {url})",
|
||||
|
@ -241,7 +241,6 @@
|
||||
"compiled-with": "Compilé avec les options {options}.",
|
||||
"library": "Bibliothèque",
|
||||
"more": "plus",
|
||||
"name": "Nom",
|
||||
"total-playtime": "Durée totale de lecture",
|
||||
"tracks": "Pistes",
|
||||
"update": "Actualiser",
|
||||
@ -408,8 +407,8 @@
|
||||
"count": "{count} piste|{count} piste|{count} pistes",
|
||||
"edit": "Éditer",
|
||||
"hide-previous": "Masquer l’historique",
|
||||
"save": "Enregistrer",
|
||||
"title": "File d’attente"
|
||||
"queue": "File d’attente",
|
||||
"save": "Enregistrer"
|
||||
},
|
||||
"radio": {
|
||||
"count": "{count} station|{count} station|{count} stations",
|
||||
@ -495,7 +494,8 @@
|
||||
"spotify": {
|
||||
"no-support": "L’option Spotify n’est pas présente.",
|
||||
"logged-as": "Connecté en tant que ",
|
||||
"requirements": "Vous devez posséder un compte Spotify Premium. L’accès à l’API de Spotify permet l’analyse de votre bibliothèque Spotify. Les champs d’application requis sont les suivants :",
|
||||
"requirements": "Vous devez posséder un compte Spotify Premium.",
|
||||
"scopes": "L’accès à l’API de Spotify permet l’analyse de votre bibliothèque Spotify. Les champs d’application requis sont les suivants :",
|
||||
"user": "Accès autorisé pour ",
|
||||
"authorize": "Autoriser l’accès à l’API",
|
||||
"grant-access": "Accordez l’accès à l’API de Spotify",
|
||||
@ -571,6 +571,10 @@
|
||||
"toggle-lyrics": "Voir/Cacher les paroles"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"not-saved": " (erreur à l’enregistrement du réglage)",
|
||||
"saved": " (réglage enregistré)"
|
||||
},
|
||||
"server": {
|
||||
"connection-failed": "Échec de connexion au serveur",
|
||||
"request-failed": "La requête a échoué (status: {status} {cause} {url})",
|
||||
|
@ -241,7 +241,6 @@
|
||||
"compiled-with": "编译支持来自于 {options}",
|
||||
"library": "资料库",
|
||||
"more": "更多",
|
||||
"name": "名称",
|
||||
"total-playtime": "总播放时长",
|
||||
"tracks": "曲目总数",
|
||||
"update": "更新",
|
||||
@ -408,8 +407,8 @@
|
||||
"count": "{count} 只曲目|{count} 只曲目",
|
||||
"edit": "编辑",
|
||||
"hide-previous": "隐藏历史",
|
||||
"save": "保存",
|
||||
"title": "清单"
|
||||
"title": "清单",
|
||||
"save": "保存"
|
||||
},
|
||||
"radio": {
|
||||
"count": "{count} 个电台|{count} 个电台",
|
||||
@ -495,7 +494,8 @@
|
||||
"spotify": {
|
||||
"no-support": "OwnTone的构建没有来自 Spotify 官方的支持,也未安装 libspotify",
|
||||
"logged-as": "登录为 ",
|
||||
"requirements": "您必须拥有 Spotify付费帐户。访问 Spotify Web API 可以扫描您的 Spotify库。所需范围是:",
|
||||
"requirements": "您必须拥有 Spotify付费帐户",
|
||||
"scopes": "访问 Spotify Web API 可以扫描您的 Spotify库。所需范围是:",
|
||||
"user": "授予访问权限",
|
||||
"authorize": "授权 Web API 访问",
|
||||
"grant-access": "授予对 Spotify Web API 的访问权限",
|
||||
@ -571,6 +571,10 @@
|
||||
"toggle-lyrics": "显示/隐藏歌词"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"not-saved": " (设置保存错误)",
|
||||
"saved": " (设置已保存)"
|
||||
},
|
||||
"server": {
|
||||
"connection-failed": "无法连接到 OwnTone 服务器",
|
||||
"request-failed": "请求失败 (状态:{status} {cause} {url})",
|
||||
|
@ -241,7 +241,6 @@
|
||||
"compiled-with": "編譯支持來自於 {options}",
|
||||
"library": "資料庫",
|
||||
"more": "更多",
|
||||
"name": "名稱",
|
||||
"total-playtime": "總播放時長",
|
||||
"tracks": "曲目總數",
|
||||
"update": "更新",
|
||||
@ -408,8 +407,8 @@
|
||||
"count": "{count} 首曲目|{count} 首曲目",
|
||||
"edit": "編輯",
|
||||
"hide-previous": "隱藏歷史",
|
||||
"save": "儲存",
|
||||
"title": "清單"
|
||||
"title": "清單",
|
||||
"save": "儲存"
|
||||
},
|
||||
"radio": {
|
||||
"count": "{count} 個電台|{count} 個電台",
|
||||
@ -495,7 +494,8 @@
|
||||
"spotify": {
|
||||
"no-support": "OwnTone並無 Spotify 官方的支持,也未安裝 libspotify",
|
||||
"logged-as": "登入為 ",
|
||||
"requirements": "您必須擁有 Spotify付費帳戶。訪問 Spotify Web API 可以掃描您的 Spotify庫。所需範圍是:",
|
||||
"requirements": "您必須擁有 Spotify付費帳戶",
|
||||
"scopes": "訪問 Spotify Web API 可以掃描您的 Spotify庫。所需範圍是:",
|
||||
"user": "授予訪問權限",
|
||||
"authorize": "授權 Web API 訪問",
|
||||
"grant-access": "授予對 Spotify Web API 的訪問權限",
|
||||
@ -571,6 +571,10 @@
|
||||
"toggle-lyrics": "顯示/隱藏歌詞"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"not-saved": " (設定儲存錯誤)",
|
||||
"saved": " (設定已儲存)"
|
||||
},
|
||||
"server": {
|
||||
"connection-failed": "無法連接到 OwnTone 伺服器",
|
||||
"request-failed": "請求失敗 (狀態:{status} {cause} {url})",
|
||||
|
@ -14,7 +14,6 @@ import {
|
||||
mdiChevronDown,
|
||||
mdiChevronLeft,
|
||||
mdiChevronUp,
|
||||
mdiClose,
|
||||
mdiContentSave,
|
||||
mdiDelete,
|
||||
mdiDeleteEmpty,
|
||||
@ -80,7 +79,6 @@ export const icons = {
|
||||
mdiChevronDown,
|
||||
mdiChevronLeft,
|
||||
mdiChevronUp,
|
||||
mdiClose,
|
||||
mdiContentSave,
|
||||
mdiDelete,
|
||||
mdiDeleteEmpty,
|
||||
|
@ -1,12 +1,203 @@
|
||||
@charset "utf-8";
|
||||
|
||||
@use 'bulma/bulma';
|
||||
@use 'bulma/sass/utilities/mixins';
|
||||
@import 'bulma/bulma.sass';
|
||||
@import 'bulma-switch';
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body,
|
||||
html,
|
||||
.content table th,
|
||||
td,
|
||||
.fd-tabs-section {
|
||||
background-color: $black-ter !important;
|
||||
color: $grey-light;
|
||||
}
|
||||
.tabs ul {
|
||||
border-bottom-color: $grey-dark;
|
||||
}
|
||||
.tabs a:hover {
|
||||
border-bottom-color: $grey-lighter;
|
||||
color: $grey-lighter !important;
|
||||
}
|
||||
a:hover,
|
||||
a.has-text-dark:hover,
|
||||
a.has-text-dark:focus {
|
||||
color: $grey-lighter !important;
|
||||
}
|
||||
.media + .media {
|
||||
border-top-color: $grey-dark !important;
|
||||
}
|
||||
.tabs a {
|
||||
border-bottom-color: $grey-dark;
|
||||
}
|
||||
.tabs a,
|
||||
.hero.is-light .title,
|
||||
.title,
|
||||
.subtitle,
|
||||
.navbar.is-light .navbar-brand > .navbar-item,
|
||||
.navbar-item,
|
||||
.navbar.is-white .navbar-brand a.navbar-item,
|
||||
.navbar.is-dark .navbar-brand .navbar-item,
|
||||
.navbar.is-light .navbar-burger {
|
||||
color: $grey-light;
|
||||
}
|
||||
.navbar-item.has-dropdown-up .navbar-dropdown {
|
||||
border-bottom-color: $grey-dark;
|
||||
}
|
||||
.navbar-dropdown {
|
||||
background-color: $grey-darker;
|
||||
border-top-color: $grey-dark;
|
||||
}
|
||||
a.tag:hover,
|
||||
a.tag.is-delete:hover,
|
||||
a.dropdown-item:hover,
|
||||
a.dropdown-item:focus,
|
||||
a.navbar-item:hover,
|
||||
a.navbar-item:focus,
|
||||
a.navbar-item:active,
|
||||
.button:hover,
|
||||
.button.is-white:focus,
|
||||
.button.is-white:hover,
|
||||
.button.is-dark:hover,
|
||||
.button.is-light:hover,
|
||||
hr,
|
||||
.navbar-burger:hover,
|
||||
.navbar.is-white .navbar-brand > a.navbar-item:hover,
|
||||
.navbar.is-light .navbar-brand > a.navbar-item:focus,
|
||||
.navbar.is-light .navbar-brand > a.navbar-item:hover,
|
||||
.navbar.is-dark .navbar-brand > a.navbar-item:focus,
|
||||
.navbar.is-dark .navbar-brand > a.navbar-item:hover,
|
||||
.navbar-dropdown a.navbar-item:hover,
|
||||
.navbar-dropdown a.navbar-item:focus,
|
||||
.modal-content .input,
|
||||
.modal-content select,
|
||||
.tabs.is-toggle a:hover {
|
||||
background-color: $grey-dark;
|
||||
color: $grey-lighter;
|
||||
}
|
||||
.card-footer .has-text-dark,
|
||||
.media .has-text-dark {
|
||||
color: $grey-light !important;
|
||||
}
|
||||
.navbar-menu,
|
||||
.navbar-brand,
|
||||
.notification,
|
||||
.card {
|
||||
background-color: $grey-darker;
|
||||
color: $grey-light;
|
||||
}
|
||||
a.tag,
|
||||
.button,
|
||||
.button:active,
|
||||
.button:focus,
|
||||
.dropdown-content,
|
||||
.dropdown-item,
|
||||
.input,
|
||||
.input .switch {
|
||||
background-color: $grey-darker;
|
||||
border-width: 0;
|
||||
color: $grey-lighter;
|
||||
}
|
||||
.input::placeholder,
|
||||
.control.has-icons-left .icon {
|
||||
color: $grey;
|
||||
}
|
||||
.label,
|
||||
.tabs a:hover,
|
||||
.control.has-icons-left .input:focus ~ .icon {
|
||||
color: $grey-lighter;
|
||||
}
|
||||
.tabs.is-toggle a:hover,
|
||||
.navbar-item .buttons .button,
|
||||
.modal-content select,
|
||||
.modal-content select:hover {
|
||||
border-color: $grey-dark;
|
||||
border-width: 1px;
|
||||
}
|
||||
.tabs.is-toggle a {
|
||||
background-color: $grey-darker;
|
||||
border-color: $grey-darker;
|
||||
}
|
||||
.button.is-light,
|
||||
.button.is-dark,
|
||||
.button.is-white,
|
||||
.button[disabled],
|
||||
.button[disabled]:hover {
|
||||
background-color: $grey-darker;
|
||||
color: $grey-light;
|
||||
}
|
||||
.has-text-grey-light,
|
||||
a.has-text-grey-light:hover {
|
||||
color: $grey !important;
|
||||
}
|
||||
.table,
|
||||
code {
|
||||
background: transparent;
|
||||
}
|
||||
.card-footer {
|
||||
border-top-color: $grey-dark;
|
||||
}
|
||||
.card-footer-item:not(:last-child) {
|
||||
border-right-color: $grey-dark;
|
||||
}
|
||||
.hero.is-light.is-bold {
|
||||
background-image: linear-gradient(
|
||||
141deg,
|
||||
$black-ter 0%,
|
||||
$grey-darker 71%,
|
||||
$grey-dark
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Lyrics animation */
|
||||
@keyframes pop-color {
|
||||
0% {
|
||||
color: $black;
|
||||
}
|
||||
100% {
|
||||
color: $success;
|
||||
}
|
||||
}
|
||||
|
||||
.media.with-progress h2:last-of-type {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.media.with-progress {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
a.navbar-item {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.fd-is-not-allowed {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.fd-is-movable {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.fd-is-square .button {
|
||||
height: 27px;
|
||||
min-width: 27px;
|
||||
padding-left: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.fd-is-text-clipped {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fd-tabs-section {
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
background: var(--bulma-body-background-color);
|
||||
background: $white;
|
||||
top: $navbar-height;
|
||||
z-index: 20;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
@ -14,12 +205,16 @@
|
||||
|
||||
.fd-has-shadow img {
|
||||
box-shadow:
|
||||
0 0.25rem 0.5rem 0 var(--bulma-background-active),
|
||||
0 0.375rem 1.25rem 0 var(--bulma-background-active);
|
||||
0 4px 8px 0 rgba(0, 0, 0, 0.2),
|
||||
0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
|
||||
.fd-page-with-tabs {
|
||||
margin-top: $navbar-height !important;
|
||||
}
|
||||
|
||||
.is-full-height {
|
||||
min-height: calc(100vh - calc(2 * var(--bulma-navbar-height)));
|
||||
min-height: calc(100vh - calc(2 * $navbar-height));
|
||||
}
|
||||
|
||||
.is-disabled {
|
||||
@ -30,11 +225,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
width: var(--bulma-navbar-height);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.fd-cover {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@ -43,28 +233,28 @@
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
img {
|
||||
border-radius: var(--bulma-radius-small);
|
||||
border-radius: $radius-small;
|
||||
max-width: 4rem;
|
||||
max-height: 4rem;
|
||||
}
|
||||
}
|
||||
&-medium-image {
|
||||
@include mixins.tablet {
|
||||
@include from($tablet) {
|
||||
justify-content: right;
|
||||
}
|
||||
img {
|
||||
border-radius: var(--bulma-radius);
|
||||
border-radius: $radius;
|
||||
max-height: calc(150px - 1.5rem);
|
||||
}
|
||||
}
|
||||
&-normal-image {
|
||||
img {
|
||||
border-radius: var(--bulma-radius-large);
|
||||
border-radius: $radius-large;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
&-big-image {
|
||||
@include mixins.mobile {
|
||||
@include mobile {
|
||||
@media screen and (orientation: landscape) {
|
||||
img {
|
||||
display: none;
|
||||
@ -72,7 +262,7 @@
|
||||
}
|
||||
}
|
||||
img {
|
||||
border-radius: var(--bulma-radius-large);
|
||||
border-radius: $radius-large;
|
||||
max-height: calc(100vh - 26rem);
|
||||
}
|
||||
&.is-masked {
|
||||
@ -81,22 +271,69 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sortable-chosen .media-right {
|
||||
visibility: hidden;
|
||||
}
|
||||
.sortable-ghost h1,
|
||||
.sortable-ghost h2 {
|
||||
color: $danger;
|
||||
}
|
||||
|
||||
.media:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
/* Transition effect */
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.fade-enter-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-enter-to,
|
||||
.fade-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Add a little bit of spacing between title and subtitle */
|
||||
.title:not(.is-spaced) + .subtitle + .subtitle {
|
||||
margin-top: -1.3rem !important;
|
||||
}
|
||||
|
||||
/* Only scroll content if modal contains a card component */
|
||||
.modal-content .card-content {
|
||||
max-height: calc(100vh - calc(4 * var(--bulma-navbar-height)));
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.fd-width-auto {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
/* Show scrollbar for navbar menu in desktop mode if content exceeds the screen size */
|
||||
@include desktop {
|
||||
.navbar-dropdown {
|
||||
max-height: calc(100vh - calc(2 * $navbar-height) - 2rem);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Limit the size of the bottom navbar menu to not be displayed behind the Safari browser menu on iOS */
|
||||
.fd-bottom-navbar .navbar-menu {
|
||||
max-height: calc(100vh - calc(2 * $navbar-height) - 1rem);
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
@include mixins.mobile {
|
||||
&.is-centered-mobile {
|
||||
@include mobile {
|
||||
&.fd-is-centered-mobile {
|
||||
justify-content: center;
|
||||
&:not(.has-addons) {
|
||||
.button {
|
||||
.button:not(.is-fullwidth) {
|
||||
margin-left: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
@ -107,16 +344,16 @@
|
||||
|
||||
.column {
|
||||
&.fd-has-cover {
|
||||
@include mixins.mobile {
|
||||
@include mobile {
|
||||
margin: auto;
|
||||
}
|
||||
@include mixins.tablet {
|
||||
@include from($tablet) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overlay-fullscreen {
|
||||
.fd-overlay-fullscreen {
|
||||
@extend .is-overlay;
|
||||
z-index: 25;
|
||||
background-color: rgba(10, 10, 10, 0.2);
|
||||
@ -127,13 +364,100 @@
|
||||
padding: 1.5rem !important;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
@include mixins.mobile {
|
||||
width: 100vw;
|
||||
/* Slider */
|
||||
@mixin thumb {
|
||||
-webkit-appearance: none;
|
||||
width: var(--th);
|
||||
height: var(--th);
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
background: $light;
|
||||
border: 1px solid $grey-light;
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background: $grey-light;
|
||||
border: 1px solid $grey-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
max-height: calc(100vh - calc(2 * var(--bulma-navbar-height)));
|
||||
overflow: auto;
|
||||
@mixin thumb-inactive {
|
||||
box-sizing: border-box;
|
||||
background-color: $light;
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: $grey-dark;
|
||||
border: 1px solid $grey-darker;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin track {
|
||||
height: calc(var(--sh));
|
||||
border-radius: calc(var(--sh) / 2);
|
||||
background: linear-gradient(90deg, $dark var(--sx), $grey-light var(--sx));
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
$grey-light var(--sx),
|
||||
$grey-dark var(--sx)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin track-inactive {
|
||||
background: linear-gradient(90deg, $grey-light var(--sx), $light var(--sx));
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
$grey-dark var(--sx),
|
||||
$black-ter var(--sx)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
input[type='range'].slider {
|
||||
--sh: 0.25rem;
|
||||
--th: calc(var(--sh) * 4);
|
||||
background-color: transparent;
|
||||
@include mobile {
|
||||
--th: calc(var(--sh) * 5);
|
||||
}
|
||||
& {
|
||||
--sx: calc(var(--th) / 2 + (var(--ratio) * (100% - var(--th))));
|
||||
-webkit-appearance: none;
|
||||
min-width: 250px;
|
||||
height: calc(var(--sh) * 5);
|
||||
width: 100% !important;
|
||||
cursor: grab;
|
||||
}
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
&::-webkit-slider-thumb {
|
||||
@include thumb;
|
||||
& {
|
||||
margin-top: calc((var(--th) - var(--sh)) / -2);
|
||||
}
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
@include thumb;
|
||||
}
|
||||
&::-webkit-slider-runnable-track {
|
||||
@include track;
|
||||
}
|
||||
&::-moz-range-track {
|
||||
@include track;
|
||||
}
|
||||
&.is-inactive {
|
||||
cursor: var(--cursor, not-allowed);
|
||||
&::-webkit-slider-thumb {
|
||||
@include thumb-inactive;
|
||||
}
|
||||
&::-webkit-slider-runnable-track {
|
||||
@include track-inactive;
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
@include thumb-inactive;
|
||||
}
|
||||
&::-moz-range-track {
|
||||
@include track-inactive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,27 @@
|
||||
<template>
|
||||
<div>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-four-fifths has-text-centered-mobile">
|
||||
<h1 class="title is-4" v-text="configuration.library_name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-four-fifths">
|
||||
<div class="content">
|
||||
<nav class="level">
|
||||
<nav class="level is-mobile">
|
||||
<!-- Left side -->
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<p class="title is-4" v-text="$t('page.about.library')" />
|
||||
<p class="title is-5" v-text="$t('page.about.library')" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right side -->
|
||||
<div class="level-right">
|
||||
<div v-if="library.updating">
|
||||
<a
|
||||
@ -26,59 +38,56 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="media">
|
||||
<div
|
||||
class="media-content has-text-weight-bold"
|
||||
v-text="$t('page.about.name')"
|
||||
/>
|
||||
<div class="media-right" v-text="configuration.library_name" />
|
||||
</div>
|
||||
<div class="media">
|
||||
<div
|
||||
class="media-content has-text-weight-bold"
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th
|
||||
class="has-text-left"
|
||||
v-text="$t('page.about.artists')"
|
||||
/>
|
||||
<div
|
||||
class="media-right"
|
||||
<td
|
||||
class="has-text-right"
|
||||
v-text="$filters.number(library.artists)"
|
||||
/>
|
||||
</div>
|
||||
<div class="media">
|
||||
<div
|
||||
class="media-content has-text-weight-bold"
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
class="has-text-left"
|
||||
v-text="$t('page.about.albums')"
|
||||
/>
|
||||
<div
|
||||
media="media-right"
|
||||
<td
|
||||
class="has-text-right"
|
||||
v-text="$filters.number(library.albums)"
|
||||
/>
|
||||
</div>
|
||||
<div class="media">
|
||||
<div
|
||||
class="media-content has-text-weight-bold"
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
class="has-text-left"
|
||||
v-text="$t('page.about.tracks')"
|
||||
/>
|
||||
<div
|
||||
class="media-right"
|
||||
<td
|
||||
class="has-text-right"
|
||||
v-text="$filters.number(library.songs)"
|
||||
/>
|
||||
</div>
|
||||
<div class="media">
|
||||
<div
|
||||
class="media-content has-text-weight-bold"
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
class="has-text-left"
|
||||
v-text="$t('page.about.total-playtime')"
|
||||
/>
|
||||
<div
|
||||
class="media-right"
|
||||
v-text="$filters.durationInDays(library.db_playtime)"
|
||||
<td
|
||||
class="has-text-right"
|
||||
v-text="
|
||||
$filters.durationInDays(library.db_playtime * 1000)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="media">
|
||||
<div
|
||||
class="media-content has-text-weight-bold"
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
class="has-text-left"
|
||||
v-text="$t('page.about.updated')"
|
||||
/>
|
||||
<div class="media-right">
|
||||
<td class="has-text-right">
|
||||
<span
|
||||
v-text="
|
||||
$t('page.about.updated-on', {
|
||||
@ -90,21 +99,25 @@
|
||||
class="has-text-grey"
|
||||
v-text="$filters.datetime(library.updated_at)"
|
||||
/>)
|
||||
</div>
|
||||
</div>
|
||||
<div class="media">
|
||||
<div
|
||||
class="media-content has-text-weight-bold"
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
class="has-text-left"
|
||||
v-text="$t('page.about.uptime')"
|
||||
/>
|
||||
<div class="media-right">
|
||||
<span v-text="$filters.timeFromNow(library.started_at, true)" />
|
||||
<td class="has-text-right">
|
||||
<span
|
||||
v-text="$filters.timeFromNow(library.started_at, true)"
|
||||
/>
|
||||
(<span
|
||||
class="has-text-grey"
|
||||
v-text="$filters.datetime(library.started_at)"
|
||||
/>)
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -161,6 +174,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -195,3 +209,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -2,20 +2,17 @@
|
||||
<div>
|
||||
<content-with-hero>
|
||||
<template #heading-left>
|
||||
<div class="title is-5" v-text="album.name" />
|
||||
<div class="subtitle is-6">
|
||||
<a @click="open_artist" v-text="album.artist" />
|
||||
</div>
|
||||
<div class="buttons is-centered-mobile mt-5">
|
||||
<a
|
||||
class="button has-background-light is-small is-rounded"
|
||||
@click="play"
|
||||
>
|
||||
<h1 class="title is-5" v-text="album.name" />
|
||||
<h2 class="subtitle is-6 has-text-link">
|
||||
<a class="has-text-link" @click="open_artist" v-text="album.artist" />
|
||||
</h2>
|
||||
<div class="buttons fd-is-centered-mobile mt-5">
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<mdicon class="icon" name="shuffle" size="16" />
|
||||
<span v-text="$t('page.album.shuffle')" />
|
||||
</a>
|
||||
<a
|
||||
class="button is-small has-background-light is-rounded"
|
||||
class="button is-small is-light is-rounded"
|
||||
@click="show_details_modal = true"
|
||||
>
|
||||
<mdicon class="icon" name="dots-horizontal" size="16" />
|
||||
@ -32,8 +29,8 @@
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<div
|
||||
class="is-size-7 is-uppercase has-text-centered-mobile my-5"
|
||||
<p
|
||||
class="heading has-text-centered-mobile mt-5"
|
||||
v-text="$t('page.album.track-count', { count: album.track_count })"
|
||||
/>
|
||||
<list-tracks :items="tracks" :uris="album.uri" />
|
||||
@ -107,3 +104,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -2,11 +2,15 @@
|
||||
<div>
|
||||
<content-with-hero>
|
||||
<template #heading-left>
|
||||
<div class="title is-5" v-text="album.name" />
|
||||
<div class="subtitle is-6">
|
||||
<a @click="open_artist" v-text="album.artists[0].name" />
|
||||
</div>
|
||||
<div class="buttons is-centered-mobile mt-5">
|
||||
<h1 class="title is-5" v-text="album.name" />
|
||||
<h2 class="subtitle is-6 has-text-link">
|
||||
<a
|
||||
class="has-text-link"
|
||||
@click="open_artist"
|
||||
v-text="album.artists[0].name"
|
||||
/>
|
||||
</h2>
|
||||
<div class="buttons fd-is-centered-mobile mt-5">
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<mdicon class="icon" name="shuffle" size="16" />
|
||||
<span v-text="$t('page.spotify.album.shuffle')" />
|
||||
@ -29,8 +33,8 @@
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<div
|
||||
class="is-size-7 is-uppercase has-text-centered-mobile mt-5"
|
||||
<p
|
||||
class="heading has-text-centered-mobile mt-5"
|
||||
v-text="
|
||||
$t('page.spotify.album.track-count', { count: album.tracks.total })
|
||||
"
|
||||
@ -123,3 +127,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,54 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music />
|
||||
<content-with-heading>
|
||||
<template #options>
|
||||
<index-button-list :indices="albums.indices" />
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.albums.filter')"
|
||||
<p class="heading mb-5" v-text="$t('page.albums.filter')" />
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input
|
||||
id="switchHideSingles"
|
||||
v-model="hide_singles"
|
||||
type="checkbox"
|
||||
class="switch is-rounded"
|
||||
/>
|
||||
<control-switch v-model="uiStore.hide_singles">
|
||||
<template #label>
|
||||
<span v-text="$t('page.albums.hide-singles')" />
|
||||
</template>
|
||||
<template #help>
|
||||
<span v-text="$t('page.albums.hide-singles-help')" />
|
||||
</template>
|
||||
</control-switch>
|
||||
<control-switch
|
||||
v-if="spotify_enabled"
|
||||
v-model="uiStore.hide_spotify"
|
||||
>
|
||||
<template #label>
|
||||
<span v-text="$t('page.albums.hide-spotify')" />
|
||||
</template>
|
||||
<template #help>
|
||||
<span v-text="$t('page.albums.hide-spotify-help')" />
|
||||
</template>
|
||||
</control-switch>
|
||||
<label
|
||||
for="switchHideSingles"
|
||||
v-text="$t('page.albums.hide-singles')"
|
||||
/>
|
||||
</div>
|
||||
<p class="help" v-text="$t('page.albums.hide-singles-help')" />
|
||||
</div>
|
||||
<div v-if="spotify_enabled" class="field">
|
||||
<div class="control">
|
||||
<input
|
||||
id="switchHideSpotify"
|
||||
v-model="hide_spotify"
|
||||
type="checkbox"
|
||||
class="switch is-rounded"
|
||||
/>
|
||||
<label
|
||||
for="switchHideSpotify"
|
||||
v-text="$t('page.albums.hide-spotify')"
|
||||
/>
|
||||
</div>
|
||||
<p class="help" v-text="$t('page.albums.hide-spotify-help')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.albums.sort.title')"
|
||||
/>
|
||||
<p class="heading mb-5" v-text="$t('page.albums.sort.title')" />
|
||||
<control-dropdown
|
||||
v-model:value="uiStore.albums_sort"
|
||||
v-model:value="selected_grouping_id"
|
||||
:options="groupings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="$t('page.albums.title')" />
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<p class="title is-4" v-text="$t('page.albums.title')" />
|
||||
<p
|
||||
class="heading"
|
||||
v-text="$t('page.albums.count', { count: albums.count })"
|
||||
/>
|
||||
</template>
|
||||
<template #heading-right />
|
||||
<template #content>
|
||||
<list-albums :items="albums" />
|
||||
</template>
|
||||
@ -59,7 +65,6 @@
|
||||
<script>
|
||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||
import ControlDropdown from '@/components/ControlDropdown.vue'
|
||||
import ControlSwitch from '@/components/ControlSwitch.vue'
|
||||
import { GroupedList } from '@/lib/GroupedList'
|
||||
import IndexButtonList from '@/components/IndexButtonList.vue'
|
||||
import ListAlbums from '@/components/ListAlbums.vue'
|
||||
@ -83,7 +88,6 @@ export default {
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ControlDropdown,
|
||||
ControlSwitch,
|
||||
IndexButtonList,
|
||||
ListAlbums,
|
||||
TabsMusic
|
||||
@ -153,17 +157,43 @@ export default {
|
||||
computed: {
|
||||
albums() {
|
||||
const { options } = this.groupings.find(
|
||||
(grouping) => grouping.id === this.uiStore.albums_sort
|
||||
(grouping) => grouping.id === this.selected_grouping_id
|
||||
)
|
||||
options.filters = [
|
||||
(album) => !this.uiStore.hide_singles || album.track_count > 2,
|
||||
(album) => !this.uiStore.hide_spotify || album.data_kind !== 'spotify'
|
||||
(album) => !this.hide_singles || album.track_count > 2,
|
||||
(album) => !this.hide_spotify || album.data_kind !== 'spotify'
|
||||
]
|
||||
return this.albums_list.group(options)
|
||||
},
|
||||
hide_singles: {
|
||||
get() {
|
||||
return this.uiStore.hide_singles
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.hide_singles = value
|
||||
}
|
||||
},
|
||||
hide_spotify: {
|
||||
get() {
|
||||
return this.uiStore.hide_spotify
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.hide_spotify = value
|
||||
}
|
||||
},
|
||||
selected_grouping_id: {
|
||||
get() {
|
||||
return this.uiStore.albums_sort
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.albums_sort = value
|
||||
}
|
||||
},
|
||||
spotify_enabled() {
|
||||
return this.servicesStore.spotify.webapi_token_valid
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -4,46 +4,34 @@
|
||||
<template #options>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.artist.filter')"
|
||||
<p class="heading mb-5" v-text="$t('page.artist.filter')" />
|
||||
<div v-if="spotify_enabled" class="field">
|
||||
<div class="control">
|
||||
<input
|
||||
id="switchHideSpotify"
|
||||
v-model="hide_spotify"
|
||||
type="checkbox"
|
||||
class="switch is-rounded"
|
||||
/>
|
||||
<control-switch
|
||||
v-if="spotify_enabled"
|
||||
v-model="uiStore.hide_spotify"
|
||||
>
|
||||
<template #label>
|
||||
<span v-text="$t('page.artist.hide-spotify')" />
|
||||
</template>
|
||||
<template #help>
|
||||
<span v-text="$t('page.artist.hide-spotify-help')" />
|
||||
</template>
|
||||
</control-switch>
|
||||
<label
|
||||
for="switchHideSpotify"
|
||||
v-text="$t('page.artist.hide-spotify')"
|
||||
/>
|
||||
</div>
|
||||
<p class="help" v-text="$t('page.artist.hide-spotify-help')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.artist.sort.title')"
|
||||
/>
|
||||
<p class="heading mb-5" v-text="$t('page.artist.sort.title')" />
|
||||
<control-dropdown
|
||||
v-model:value="uiStore.artist_albums_sort"
|
||||
v-model:value="selected_grouping_id"
|
||||
:options="groupings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="artist.name" />
|
||||
<div class="is-size-7 is-uppercase">
|
||||
<span
|
||||
v-text="$t('page.artist.album-count', { count: albums.count })"
|
||||
/>
|
||||
<span> | </span>
|
||||
<a
|
||||
@click="open_tracks"
|
||||
v-text="$t('page.artist.track-count', { count: track_count })"
|
||||
/>
|
||||
</div>
|
||||
<p class="title is-4" v-text="artist.name" />
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
@ -60,6 +48,17 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
<span
|
||||
v-text="$t('page.artist.album-count', { count: albums.count })"
|
||||
/>
|
||||
<span> | </span>
|
||||
<a
|
||||
class="has-text-link"
|
||||
@click="open_tracks"
|
||||
v-text="$t('page.artist.track-count', { count: track_count })"
|
||||
/>
|
||||
</p>
|
||||
<list-albums :items="albums" />
|
||||
<modal-dialog-artist
|
||||
:item="artist"
|
||||
@ -74,7 +73,6 @@
|
||||
<script>
|
||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||
import ControlDropdown from '@/components/ControlDropdown.vue'
|
||||
import ControlSwitch from '@/components/ControlSwitch.vue'
|
||||
import { GroupedList } from '@/lib/GroupedList'
|
||||
import ListAlbums from '@/components/ListAlbums.vue'
|
||||
import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
|
||||
@ -101,7 +99,6 @@ export default {
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ControlDropdown,
|
||||
ControlSwitch,
|
||||
ListAlbums,
|
||||
ModalDialogArtist
|
||||
},
|
||||
@ -139,13 +136,29 @@ export default {
|
||||
computed: {
|
||||
albums() {
|
||||
const { options } = this.groupings.find(
|
||||
(grouping) => grouping.id === this.uiStore.artist_albums_sort
|
||||
(grouping) => grouping.id === this.selected_grouping_id
|
||||
)
|
||||
options.filters = [
|
||||
(album) => !this.uiStore.hide_spotify || album.data_kind !== 'spotify'
|
||||
(album) => !this.hide_spotify || album.data_kind !== 'spotify'
|
||||
]
|
||||
return this.albums_list.group(options)
|
||||
},
|
||||
hide_spotify: {
|
||||
get() {
|
||||
return this.uiStore.hide_spotify
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.hide_spotify = value
|
||||
}
|
||||
},
|
||||
selected_grouping_id: {
|
||||
get() {
|
||||
return this.uiStore.artist_albums_sort
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.artist_albums_sort = value
|
||||
}
|
||||
},
|
||||
spotify_enabled() {
|
||||
return this.servicesStore.spotify.webapi_token_valid
|
||||
},
|
||||
@ -174,3 +187,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -2,11 +2,7 @@
|
||||
<div>
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="artist.name" />
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.spotify.artist.album-count', { count: total })"
|
||||
/>
|
||||
<p class="title is-4" v-text="artist.name" />
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
@ -23,6 +19,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<p
|
||||
class="heading has-text-centered-mobile"
|
||||
v-text="$t('page.spotify.artist.album-count', { count: total })"
|
||||
/>
|
||||
<list-albums-spotify :items="albums" />
|
||||
<VueEternalLoading v-if="offset < total" :load="load_next">
|
||||
<template #loading>
|
||||
@ -140,3 +140,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -5,29 +5,27 @@
|
||||
<index-button-list :indices="tracks.indices" />
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<p
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.artist.filter')"
|
||||
<p class="heading mb-5" v-text="$t('page.artist.filter')" />
|
||||
<div v-if="spotify_enabled" class="field">
|
||||
<div class="control">
|
||||
<input
|
||||
id="switchHideSpotify"
|
||||
v-model="hide_spotify"
|
||||
type="checkbox"
|
||||
class="switch is-rounded"
|
||||
/>
|
||||
<control-switch
|
||||
v-if="spotify_enabled"
|
||||
v-model="uiStore.hide_spotify"
|
||||
>
|
||||
<template #label>
|
||||
<span v-text="$t('page.artist.hide-spotify')" />
|
||||
</template>
|
||||
<template #help>
|
||||
<span v-text="$t('page.artist.hide-spotify-help')" />
|
||||
</template>
|
||||
</control-switch>
|
||||
<label
|
||||
for="switchHideSpotify"
|
||||
v-text="$t('page.artist.hide-spotify')"
|
||||
/>
|
||||
</div>
|
||||
<p class="help" v-text="$t('page.artist.hide-spotify-help')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<p
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.artist.sort.title')"
|
||||
/>
|
||||
<p class="heading mb-5" v-text="$t('page.artist.sort.title')" />
|
||||
<control-dropdown
|
||||
v-model:value="uiStore.artist_tracks_sort"
|
||||
v-model:value="selected_grouping_id"
|
||||
:options="groupings"
|
||||
/>
|
||||
</div>
|
||||
@ -35,16 +33,6 @@
|
||||
</template>
|
||||
<template #heading-left>
|
||||
<p class="title is-4" v-text="artist.name" />
|
||||
<div class="is-size-7 is-uppercase">
|
||||
<a
|
||||
@click="open_artist"
|
||||
v-text="$t('page.artist.album-count', { count: album_count })"
|
||||
/>
|
||||
<span> | </span>
|
||||
<span
|
||||
v-text="$t('page.artist.track-count', { count: tracks.count })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
@ -61,6 +49,17 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
<a
|
||||
class="has-text-link"
|
||||
@click="open_artist"
|
||||
v-text="$t('page.artist.album-count', { count: album_count })"
|
||||
/>
|
||||
<span> | </span>
|
||||
<span
|
||||
v-text="$t('page.artist.track-count', { count: tracks.count })"
|
||||
/>
|
||||
</p>
|
||||
<list-tracks :items="tracks" :uris="track_uris" />
|
||||
<modal-dialog-artist
|
||||
:item="artist"
|
||||
@ -75,7 +74,6 @@
|
||||
<script>
|
||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||
import ControlDropdown from '@/components/ControlDropdown.vue'
|
||||
import ControlSwitch from '@/components/ControlSwitch.vue'
|
||||
import { GroupedList } from '@/lib/GroupedList'
|
||||
import IndexButtonList from '@/components/IndexButtonList.vue'
|
||||
import ListTracks from '@/components/ListTracks.vue'
|
||||
@ -103,7 +101,6 @@ export default {
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ControlDropdown,
|
||||
ControlSwitch,
|
||||
IndexButtonList,
|
||||
ListTracks,
|
||||
ModalDialogArtist
|
||||
@ -150,6 +147,22 @@ export default {
|
||||
.map((track) => track.item.album_id)
|
||||
).size
|
||||
},
|
||||
hide_spotify: {
|
||||
get() {
|
||||
return this.uiStore.hide_spotify
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.hide_spotify = value
|
||||
}
|
||||
},
|
||||
selected_grouping_id: {
|
||||
get() {
|
||||
return this.uiStore.artist_tracks_sort
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.artist_tracks_sort = value
|
||||
}
|
||||
},
|
||||
spotify_enabled() {
|
||||
return this.servicesStore.spotify.webapi_token_valid
|
||||
},
|
||||
@ -158,10 +171,10 @@ export default {
|
||||
},
|
||||
tracks() {
|
||||
const { options } = this.groupings.find(
|
||||
(grouping) => grouping.id === this.uiStore.artist_tracks_sort
|
||||
(grouping) => grouping.id === this.selected_grouping_id
|
||||
)
|
||||
options.filters = [
|
||||
(track) => !this.uiStore.hide_spotify || track.data_kind !== 'spotify'
|
||||
(track) => !this.hide_spotify || track.data_kind !== 'spotify'
|
||||
]
|
||||
return this.tracks_list.group(options)
|
||||
}
|
||||
@ -184,3 +197,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,53 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music />
|
||||
<content-with-heading>
|
||||
<template #options>
|
||||
<index-button-list :indices="artists.indices" />
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.artists.filter')"
|
||||
<p class="heading mb-5" v-text="$t('page.artists.filter')" />
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input
|
||||
id="switchHideSingles"
|
||||
v-model="hide_singles"
|
||||
type="checkbox"
|
||||
class="switch is-rounded"
|
||||
/>
|
||||
<control-switch v-model="uiStore.hide_singles">
|
||||
<template #label>
|
||||
<span v-text="$t('page.artists.hide-singles')" />
|
||||
</template>
|
||||
<template #help>
|
||||
<span v-text="$t('page.artists.hide-singles-help')" />
|
||||
</template>
|
||||
</control-switch>
|
||||
<label
|
||||
for="switchHideSingles"
|
||||
v-text="$t('page.artists.hide-singles')"
|
||||
/>
|
||||
</div>
|
||||
<p class="help" v-text="$t('page.artists.hide-singles-help')" />
|
||||
</div>
|
||||
<div v-if="spotify_enabled" class="field">
|
||||
<control-switch v-model="uiStore.hide_spotify">
|
||||
<template #label>
|
||||
<span v-text="$t('page.artists.hide-spotify')" />
|
||||
</template>
|
||||
<template #help>
|
||||
<span v-text="$t('page.artists.hide-spotify-help')" />
|
||||
</template>
|
||||
</control-switch>
|
||||
<div class="control">
|
||||
<input
|
||||
id="switchHideSpotify"
|
||||
v-model="hide_spotify"
|
||||
type="checkbox"
|
||||
class="switch is-rounded"
|
||||
/>
|
||||
<label
|
||||
for="switchHideSpotify"
|
||||
v-text="$t('page.artists.hide-spotify')"
|
||||
/>
|
||||
</div>
|
||||
<p class="help" v-text="$t('page.artists.hide-spotify-help')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.artists.sort.title')"
|
||||
/>
|
||||
<p class="heading mb-5" v-text="$t('page.artists.sort.title')" />
|
||||
<control-dropdown
|
||||
v-model:value="uiStore.artists_sort"
|
||||
v-model:value="selected_grouping_id"
|
||||
:options="groupings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="$t('page.artists.title')" />
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<p class="title is-4" v-text="$t('page.artists.title')" />
|
||||
<p
|
||||
class="heading"
|
||||
v-text="$t('page.artists.count', { count: artists.count })"
|
||||
/>
|
||||
</template>
|
||||
<template #heading-right />
|
||||
<template #content>
|
||||
<list-artists :items="artists" />
|
||||
</template>
|
||||
@ -58,7 +65,6 @@
|
||||
<script>
|
||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||
import ControlDropdown from '@/components/ControlDropdown.vue'
|
||||
import ControlSwitch from '@/components/ControlSwitch.vue'
|
||||
import { GroupedList } from '@/lib/GroupedList'
|
||||
import IndexButtonList from '@/components/IndexButtonList.vue'
|
||||
import ListArtists from '@/components/ListArtists.vue'
|
||||
@ -82,7 +88,6 @@ export default {
|
||||
components: {
|
||||
ContentWithHeading,
|
||||
ControlDropdown,
|
||||
ControlSwitch,
|
||||
IndexButtonList,
|
||||
ListArtists,
|
||||
TabsMusic
|
||||
@ -120,21 +125,47 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
// Wraps GroupedList and updates it if filter or sort changes
|
||||
artists() {
|
||||
const { options } = this.groupings.find(
|
||||
(grouping) => grouping.id === this.uiStore.artists_sort
|
||||
(grouping) => grouping.id === this.selected_grouping_id
|
||||
)
|
||||
options.filters = [
|
||||
(artist) =>
|
||||
!this.uiStore.hide_singles ||
|
||||
artist.track_count > artist.album_count * 2,
|
||||
(artist) => !this.uiStore.hide_spotify || artist.data_kind !== 'spotify'
|
||||
!this.hide_singles || artist.track_count > artist.album_count * 2,
|
||||
(artist) => !this.hide_spotify || artist.data_kind !== 'spotify'
|
||||
]
|
||||
return this.artists_list.group(options)
|
||||
},
|
||||
hide_singles: {
|
||||
get() {
|
||||
return this.uiStore.hide_singles
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.hide_singles = value
|
||||
}
|
||||
},
|
||||
hide_spotify: {
|
||||
get() {
|
||||
return this.uiStore.hide_spotify
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.hide_spotify = value
|
||||
}
|
||||
},
|
||||
selected_grouping_id: {
|
||||
get() {
|
||||
return this.uiStore.artists_sort
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.artists_sort = value
|
||||
}
|
||||
},
|
||||
spotify_enabled() {
|
||||
return this.servicesStore.spotify.webapi_token_valid
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -2,11 +2,11 @@
|
||||
<div>
|
||||
<content-with-hero>
|
||||
<template #heading-left>
|
||||
<div class="title is-5" v-text="album.name" />
|
||||
<div class="subtitle is-6">
|
||||
<a @click="open_artist" v-text="album.artist" />
|
||||
</div>
|
||||
<div class="buttons is-centered-mobile mt-5">
|
||||
<h1 class="title is-5" v-text="album.name" />
|
||||
<h2 class="subtitle is-6 has-text-link">
|
||||
<a class="has-text-link" @click="open_artist" v-text="album.artist" />
|
||||
</h2>
|
||||
<div class="buttons fd-is-centered-mobile mt-5">
|
||||
<a class="button is-small is-dark is-rounded" @click="play">
|
||||
<mdicon class="icon" name="play" size="16" />
|
||||
<span v-text="$t('page.audiobooks.album.play')" />
|
||||
@ -29,8 +29,8 @@
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<div
|
||||
class="is-size-7 is-uppercase has-text-centered-mobile mt-5"
|
||||
<p
|
||||
class="heading has-text-centered-mobile mt-5"
|
||||
v-text="
|
||||
$t('page.audiobooks.album.track-count', {
|
||||
count: album.track_count
|
||||
@ -103,3 +103,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-audiobooks />
|
||||
<content-with-heading>
|
||||
<template #options>
|
||||
@ -8,7 +8,7 @@
|
||||
<template #heading-left>
|
||||
<p class="title is-4" v-text="$t('page.audiobooks.albums.title')" />
|
||||
<p
|
||||
class="is-size-7 is-uppercase"
|
||||
class="heading"
|
||||
v-text="$t('page.audiobooks.albums.count', { count: albums.count })"
|
||||
/>
|
||||
</template>
|
||||
@ -61,3 +61,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -2,15 +2,7 @@
|
||||
<div>
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="artist.name" />
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="
|
||||
$t('page.audiobooks.artist.album-count', {
|
||||
count: artist.album_count
|
||||
})
|
||||
"
|
||||
/>
|
||||
<p class="title is-4" v-text="artist.name" />
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
@ -27,6 +19,14 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<p
|
||||
class="heading has-text-centered-mobile"
|
||||
v-text="
|
||||
$t('page.audiobooks.artist.album-count', {
|
||||
count: artist.album_count
|
||||
})
|
||||
"
|
||||
/>
|
||||
<list-albums :items="albums" />
|
||||
<modal-dialog-artist
|
||||
:item="artist"
|
||||
@ -87,3 +87,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,17 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-audiobooks />
|
||||
<content-with-heading>
|
||||
<template #options>
|
||||
<index-button-list :indices="artists.indices" />
|
||||
</template>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="$t('page.audiobooks.artists.title')" />
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<p class="title is-4" v-text="$t('page.audiobooks.artists.title')" />
|
||||
<p
|
||||
class="heading"
|
||||
v-text="$t('page.audiobooks.artists.count', { count: artists.count })"
|
||||
/>
|
||||
</template>
|
||||
<template #heading-right />
|
||||
<template #content>
|
||||
<list-artists :items="artists" />
|
||||
</template>
|
||||
@ -61,3 +62,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-audiobooks />
|
||||
<content-with-heading>
|
||||
<template #options>
|
||||
<index-button-list :indices="genres.indices" />
|
||||
</template>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="$t('page.genres.title')" />
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<p class="title is-4" v-text="$t('page.genres.title')" />
|
||||
<p
|
||||
class="heading"
|
||||
v-text="$t('page.genres.count', { count: genres.total })"
|
||||
/>
|
||||
</template>
|
||||
@ -61,3 +61,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -2,21 +2,7 @@
|
||||
<div>
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="composer.name" />
|
||||
<div class="is-size-7 is-uppercase">
|
||||
<span
|
||||
v-text="
|
||||
$t('page.composer.album-count', { count: composer.album_count })
|
||||
"
|
||||
/>
|
||||
<span> | </span>
|
||||
<a
|
||||
@click="open_tracks"
|
||||
v-text="
|
||||
$t('page.composer.track-count', { count: composer.track_count })
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<p class="title is-4" v-text="composer.name" />
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
@ -33,6 +19,21 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
<span
|
||||
v-text="
|
||||
$t('page.composer.album-count', { count: composer.album_count })
|
||||
"
|
||||
/>
|
||||
<span> | </span>
|
||||
<a
|
||||
class="has-text-link"
|
||||
@click="open_tracks"
|
||||
v-text="
|
||||
$t('page.composer.track-count', { count: composer.track_count })
|
||||
"
|
||||
/>
|
||||
</p>
|
||||
<list-albums :items="albums" />
|
||||
<modal-dialog-composer
|
||||
:item="composer"
|
||||
@ -103,3 +104,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -5,35 +5,16 @@
|
||||
<index-button-list :indices="tracks.indices" />
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.artist.sort.title')"
|
||||
/>
|
||||
<p class="heading mb-5" v-text="$t('page.artist.sort.title')" />
|
||||
<control-dropdown
|
||||
v-model:value="uiStore.composer_tracks_sort"
|
||||
v-model:value="selected_grouping_id"
|
||||
:options="groupings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="composer.name" />
|
||||
<div class="is-size-7 is-uppercase">
|
||||
<a
|
||||
@click="open_albums"
|
||||
v-text="
|
||||
$t('page.composer.album-count', {
|
||||
count: composer.album_count
|
||||
})
|
||||
"
|
||||
/>
|
||||
<span> | </span>
|
||||
<span
|
||||
v-text="
|
||||
$t('page.composer.track-count', { count: composer.track_count })
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<p class="title is-4" v-text="composer.name" />
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
@ -50,6 +31,23 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
<a
|
||||
class="has-text-link"
|
||||
@click="open_albums"
|
||||
v-text="
|
||||
$t('page.composer.album-count', {
|
||||
count: composer.album_count
|
||||
})
|
||||
"
|
||||
/>
|
||||
<span> | </span>
|
||||
<span
|
||||
v-text="
|
||||
$t('page.composer.track-count', { count: composer.track_count })
|
||||
"
|
||||
/>
|
||||
</p>
|
||||
<list-tracks :items="tracks" :expression="expression" />
|
||||
<modal-dialog-composer
|
||||
:item="composer"
|
||||
@ -132,9 +130,17 @@ export default {
|
||||
expression() {
|
||||
return `composer is "${this.composer.name}" and media_kind is music`
|
||||
},
|
||||
selected_grouping_id: {
|
||||
get() {
|
||||
return this.uiStore.composer_tracks_sort
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.composer_tracks_sort = value
|
||||
}
|
||||
},
|
||||
tracks() {
|
||||
const { options } = this.groupings.find(
|
||||
(grouping) => grouping.id === this.uiStore.composer_tracks_sort
|
||||
(grouping) => grouping.id === this.selected_grouping_id
|
||||
)
|
||||
return this.tracks_list.group(options)
|
||||
}
|
||||
@ -154,3 +160,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music />
|
||||
<content-with-heading>
|
||||
<template #options>
|
||||
<index-button-list :indices="composers.indices" />
|
||||
</template>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="$t('page.composers.title')" />
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<p class="title is-4" v-text="$t('page.composers.title')" />
|
||||
<p
|
||||
class="heading"
|
||||
v-text="$t('page.composers.count', { count: composers.total })"
|
||||
/>
|
||||
</template>
|
||||
@ -56,3 +56,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -137,3 +137,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -5,17 +5,7 @@
|
||||
<index-button-list :indices="albums.indices" />
|
||||
</template>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="genre.name" />
|
||||
<div class="is-size-7 is-uppercase">
|
||||
<span
|
||||
v-text="$t('page.genre.album-count', { count: genre.album_count })"
|
||||
/>
|
||||
<span> | </span>
|
||||
<a
|
||||
@click="open_tracks"
|
||||
v-text="$t('page.genre.track-count', { count: genre.track_count })"
|
||||
/>
|
||||
</div>
|
||||
<p class="title is-4" v-text="genre.name" />
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
@ -32,6 +22,17 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
<span
|
||||
v-text="$t('page.genre.album-count', { count: genre.album_count })"
|
||||
/>
|
||||
<span> | </span>
|
||||
<a
|
||||
class="has-text-link"
|
||||
@click="open_tracks"
|
||||
v-text="$t('page.genre.track-count', { count: genre.track_count })"
|
||||
/>
|
||||
</p>
|
||||
<list-albums :items="albums" />
|
||||
<modal-dialog-genre
|
||||
:item="genre"
|
||||
@ -108,3 +109,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -5,29 +5,16 @@
|
||||
<index-button-list :indices="tracks.indices" />
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.genre.sort.title')"
|
||||
/>
|
||||
<p class="heading mb-5" v-text="$t('page.genre.sort.title')" />
|
||||
<control-dropdown
|
||||
v-model:value="uiStore.genre_tracks_sort"
|
||||
v-model:value="selected_grouping_id"
|
||||
:options="groupings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="genre.name" />
|
||||
<div class="is-size-7 is-uppercase">
|
||||
<a
|
||||
@click="open_genre"
|
||||
v-text="$t('page.genre.album-count', { count: genre.album_count })"
|
||||
/>
|
||||
<span> | </span>
|
||||
<span
|
||||
v-text="$t('page.genre.track-count', { count: genre.track_count })"
|
||||
/>
|
||||
</div>
|
||||
<p class="title is-4" v-text="genre.name" />
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
@ -44,6 +31,17 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<p class="heading has-text-centered-mobile">
|
||||
<a
|
||||
class="has-text-link"
|
||||
@click="open_genre"
|
||||
v-text="$t('page.genre.album-count', { count: genre.album_count })"
|
||||
/>
|
||||
<span> | </span>
|
||||
<span
|
||||
v-text="$t('page.genre.track-count', { count: genre.track_count })"
|
||||
/>
|
||||
</p>
|
||||
<list-tracks :items="tracks" :expression="expression" />
|
||||
<modal-dialog-genre
|
||||
:item="genre"
|
||||
@ -128,9 +126,17 @@ export default {
|
||||
expression() {
|
||||
return `genre is "${this.genre.name}" and media_kind is ${this.media_kind}`
|
||||
},
|
||||
selected_grouping_id: {
|
||||
get() {
|
||||
return this.uiStore.genre_tracks_sort
|
||||
},
|
||||
set(value) {
|
||||
this.uiStore.genre_tracks_sort = value
|
||||
}
|
||||
},
|
||||
tracks() {
|
||||
const { options } = this.groupings.find(
|
||||
(grouping) => grouping.id === this.uiStore.genre_tracks_sort
|
||||
(grouping) => grouping.id === this.selected_grouping_id
|
||||
)
|
||||
return this.tracks_list.group(options)
|
||||
}
|
||||
@ -151,3 +157,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music />
|
||||
<content-with-heading>
|
||||
<template #options>
|
||||
<index-button-list :indices="genres.indices" />
|
||||
</template>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="$t('page.genres.title')" />
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<p class="title is-4" v-text="$t('page.genres.title')" />
|
||||
<p
|
||||
class="heading"
|
||||
v-text="$t('page.genres.count', { count: genres.total })"
|
||||
/>
|
||||
</template>
|
||||
@ -61,3 +61,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music />
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
@ -12,7 +12,7 @@
|
||||
<nav class="level">
|
||||
<p class="level-item">
|
||||
<router-link
|
||||
class="button is-small is-rounded"
|
||||
class="button is-light is-small is-rounded"
|
||||
:to="{ name: 'music-recently-added' }"
|
||||
>
|
||||
{{ $t('page.music.show-more') }}
|
||||
@ -32,7 +32,7 @@
|
||||
<nav class="level">
|
||||
<p class="level-item">
|
||||
<router-link
|
||||
class="button is-small is-rounded"
|
||||
class="button is-light is-small is-rounded"
|
||||
:to="{ name: 'music-recently-played' }"
|
||||
>
|
||||
{{ $t('page.music.show-more') }}
|
||||
@ -95,3 +95,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music />
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
@ -62,3 +62,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music />
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
@ -51,3 +51,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music />
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
@ -13,7 +13,7 @@
|
||||
<p class="level-item">
|
||||
<router-link
|
||||
:to="{ name: 'music-spotify-new-releases' }"
|
||||
class="button is-small is-rounded"
|
||||
class="button is-light is-small is-rounded"
|
||||
>
|
||||
{{ $t('page.spotify.music.show-more') }}
|
||||
</router-link>
|
||||
@ -36,7 +36,7 @@
|
||||
<p class="level-item">
|
||||
<router-link
|
||||
:to="{ name: 'music-spotify-featured-playlists' }"
|
||||
class="button is-small is-rounded"
|
||||
class="button is-light is-small is-rounded"
|
||||
>
|
||||
{{ $t('page.spotify.music.show-more') }}
|
||||
</router-link>
|
||||
@ -102,3 +102,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music />
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
@ -60,3 +60,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="fd-page-with-tabs">
|
||||
<tabs-music />
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
@ -57,3 +57,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<div class="hero is-full-height">
|
||||
<div class="hero-body is-flex is-align-items-center">
|
||||
<div class="container has-text-centered">
|
||||
<div v-if="track.id" class="mx-auto" style="max-width: 32rem">
|
||||
<div v-if="track.id > 0" class="hero-body is-flex is-align-items-center">
|
||||
<div class="container has-text-centered" style="max-width: 500px">
|
||||
<cover-artwork
|
||||
:url="track.artwork_url"
|
||||
:artist="track.artist"
|
||||
@ -40,7 +39,9 @@
|
||||
v-text="track.path"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
</div>
|
||||
<div v-else class="hero-body is-flex is-align-items-center">
|
||||
<div class="container has-text-centered">
|
||||
<p class="title is-5" v-text="$t('page.now-playing.title')" />
|
||||
<p class="subtitle" v-text="$t('page.now-playing.info')" />
|
||||
</div>
|
||||
@ -51,7 +52,6 @@
|
||||
@close="show_details_modal = false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -196,3 +196,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -2,14 +2,14 @@
|
||||
<div>
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
<div
|
||||
<p
|
||||
class="title is-4"
|
||||
v-text="
|
||||
playlist.id === 0 ? $t('page.playlists.title') : playlist.name
|
||||
"
|
||||
/>
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
<p
|
||||
class="heading"
|
||||
v-text="$t('page.playlists.count', { count: playlists.count })"
|
||||
/>
|
||||
</template>
|
||||
@ -89,3 +89,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -3,10 +3,6 @@
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="playlist.name" />
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="$t('page.playlist.track-count', { count: tracks.count })"
|
||||
/>
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
@ -23,6 +19,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<p
|
||||
class="heading has-text-centered-mobile"
|
||||
v-text="$t('page.playlist.track-count', { count: tracks.count })"
|
||||
/>
|
||||
<list-tracks :items="tracks" :uris="uris" />
|
||||
<modal-dialog-playlist
|
||||
:item="playlist"
|
||||
@ -90,3 +90,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -3,12 +3,6 @@
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="playlist.name" />
|
||||
<div
|
||||
class="is-size-7 is-uppercase"
|
||||
v-text="
|
||||
$t('page.spotify.playlist.count', { count: playlist.tracks.total })
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #heading-right>
|
||||
<div class="buttons is-centered">
|
||||
@ -25,6 +19,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<p
|
||||
class="heading has-text-centered-mobile"
|
||||
v-text="
|
||||
$t('page.spotify.playlist.count', { count: playlist.tracks.total })
|
||||
"
|
||||
/>
|
||||
<list-tracks-spotify :items="tracks" :context_uri="playlist.uri" />
|
||||
<VueEternalLoading v-if="offset < total" :load="load_next">
|
||||
<template #loading>
|
||||
@ -151,3 +151,5 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user