mirror of
https://github.com/owntone/owntone-server.git
synced 2025-11-07 04:42:58 -05:00
[web] Change navigation bars
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a class="navbar-item" :href="href" @click.stop.prevent="open">
|
||||
<a :href="href" @click.stop.prevent="open">
|
||||
<slot />
|
||||
</a>
|
||||
</template>
|
||||
@@ -8,7 +8,7 @@
|
||||
import { useUIStore } from '@/stores/ui'
|
||||
|
||||
export default {
|
||||
name: 'NavbarItemLink',
|
||||
name: 'ControlLink',
|
||||
props: {
|
||||
to: { required: true, type: Object }
|
||||
},
|
||||
62
web-src/src/components/ControlMainVolume.vue
Normal file
62
web-src/src/components/ControlMainVolume.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="media is-align-items-center pt-0">
|
||||
<div class="media-left">
|
||||
<a class="button is-white is-small" @click="toggle">
|
||||
<mdicon class="icon" :name="icon" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="heading" 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>
|
||||
79
web-src/src/components/ControlOutputVolume.vue
Normal file
79
web-src/src/components/ControlOutputVolume.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="media is-align-items-center pt-0">
|
||||
<div class="media-left">
|
||||
<a
|
||||
class="button is-white 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">
|
||||
<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="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>
|
||||
@@ -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,10 +14,9 @@ import { useQueueStore } from '@/stores/queue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'PlayerButtonSeekBack',
|
||||
name: 'ControlPlayerBack',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number },
|
||||
seek_ms: { required: true, type: Number }
|
||||
offset: { required: true, type: Number }
|
||||
},
|
||||
|
||||
setup() {
|
||||
@@ -52,7 +51,7 @@ export default {
|
||||
methods: {
|
||||
seek() {
|
||||
if (!this.disabled) {
|
||||
webapi.player_seek(this.seek_ms * -1)
|
||||
webapi.player_seek(this.offset * -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<a :class="{ 'is-info': is_consume }" @click="toggle_consume_mode">
|
||||
<a :class="{ 'is-info': is_consume }" @click="toggle">
|
||||
<mdicon
|
||||
class="icon"
|
||||
name="fire"
|
||||
:size="icon_size"
|
||||
size="16"
|
||||
:title="$t('player.button.consume')"
|
||||
/>
|
||||
</a>
|
||||
@@ -14,10 +14,7 @@ import { usePlayerStore } from '@/stores/player'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'PlayerButtonConsume',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
name: 'ControlPlayerConsume',
|
||||
|
||||
setup() {
|
||||
return {
|
||||
@@ -32,11 +29,9 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle_consume_mode() {
|
||||
toggle() {
|
||||
webapi.player_consume(!this.is_consume)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -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,10 +14,9 @@ import { useQueueStore } from '@/stores/queue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'PlayerButtonSeekForward',
|
||||
name: 'ControlPlayerForward',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number },
|
||||
seek_ms: { required: true, type: Number }
|
||||
offset: { required: true, type: Number }
|
||||
},
|
||||
|
||||
setup() {
|
||||
@@ -52,7 +51,7 @@ export default {
|
||||
methods: {
|
||||
seek() {
|
||||
if (!this.disabled) {
|
||||
webapi.player_seek(this.seek_ms)
|
||||
webapi.player_seek(this.offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<a :class="{ 'is-info': is_active }" @click="toggle_lyrics">
|
||||
<a :class="{ 'is-info': is_active }" @click="toggle">
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="icon_name"
|
||||
:size="icon_size"
|
||||
:name="icon"
|
||||
:size="16"
|
||||
:title="$t('player.button.toggle-lyrics')"
|
||||
/>
|
||||
</a>
|
||||
@@ -13,10 +13,7 @@
|
||||
import { useLyricsStore } from '@/stores/lyrics'
|
||||
|
||||
export default {
|
||||
name: 'PlayerButtonLyrics',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
name: 'ControlPlayerLyrics',
|
||||
|
||||
setup() {
|
||||
return {
|
||||
@@ -25,7 +22,7 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
icon_name() {
|
||||
icon() {
|
||||
return this.is_active ? 'script-text-play' : 'script-text-outline'
|
||||
},
|
||||
is_active() {
|
||||
@@ -34,11 +31,9 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle_lyrics() {
|
||||
toggle() {
|
||||
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,10 +13,7 @@ import { useQueueStore } from '@/stores/queue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'PlayerButtonNext',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
name: 'ControlPlayerNext',
|
||||
|
||||
computed: {
|
||||
disabled() {
|
||||
@@ -29,11 +26,8 @@ export default {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
webapi.player_next()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<a :disabled="disabled" @click="toggle_play_pause">
|
||||
<mdicon
|
||||
:name="icon_name"
|
||||
:size="icon_size"
|
||||
:title="$t(`player.button.${icon_name}`)"
|
||||
/>
|
||||
<a :disabled="disabled" @click="toggle">
|
||||
<mdicon class="icon" :name="icon" :title="$t(`player.button.${icon}`)" />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
@@ -15,9 +11,8 @@ import { useQueueStore } from '@/stores/queue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'PlayerButtonPlayPause',
|
||||
name: 'ControlPlayerPlay',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number },
|
||||
show_disabled_message: Boolean
|
||||
},
|
||||
|
||||
@@ -33,7 +28,7 @@ export default {
|
||||
disabled() {
|
||||
return this.queueStore?.count <= 0
|
||||
},
|
||||
icon_name() {
|
||||
icon() {
|
||||
if (!this.is_playing) {
|
||||
return 'play'
|
||||
} else if (this.is_pause_allowed) {
|
||||
@@ -51,7 +46,7 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle_play_pause() {
|
||||
toggle() {
|
||||
if (this.disabled) {
|
||||
if (this.show_disabled_message) {
|
||||
this.notificationsStore.add({
|
||||
@@ -74,5 +69,3 @@ 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,10 +13,7 @@ import { useQueueStore } from '@/stores/queue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'PlayerButtonPrevious',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
name: 'ControlPlayerPrevious',
|
||||
|
||||
setup() {
|
||||
return {
|
||||
@@ -40,5 +37,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<a :class="{ 'is-info': !is_repeat_off }" @click="toggle_repeat_mode">
|
||||
<a :class="{ 'is-info': !is_repeat_off }" @click="toggle">
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="icon_name"
|
||||
:size="icon_size"
|
||||
:title="$t(`player.button.${icon_name}`)"
|
||||
:name="icon"
|
||||
:size="16"
|
||||
:title="$t(`player.button.${icon}`)"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
@@ -14,19 +14,14 @@ import { usePlayerStore } from '@/stores/player'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'PlayerButtonRepeat',
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
|
||||
name: 'ControlPlayerRepeat',
|
||||
setup() {
|
||||
return {
|
||||
playerStore: usePlayerStore()
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
icon_name() {
|
||||
icon() {
|
||||
if (this.is_repeat_all) {
|
||||
return 'repeat'
|
||||
} else if (this.is_repeat_single) {
|
||||
@@ -46,7 +41,7 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle_repeat_mode() {
|
||||
toggle() {
|
||||
if (this.is_repeat_all) {
|
||||
webapi.player_repeat('single')
|
||||
} else if (this.is_repeat_single) {
|
||||
@@ -58,5 +53,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<a :class="{ 'is-info': is_shuffle }" @click="toggle_shuffle_mode">
|
||||
<a :class="{ 'is-info': is_shuffle }" @click="toggle">
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="icon_name"
|
||||
:size="icon_size"
|
||||
:title="$t(`player.button.${icon_name}`)"
|
||||
:name="icon"
|
||||
:size="16"
|
||||
:title="$t(`player.button.${icon}`)"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
@@ -14,20 +14,14 @@ import { usePlayerStore } from '@/stores/player'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'PlayerButtonShuffle',
|
||||
|
||||
props: {
|
||||
icon_size: { default: 16, type: Number }
|
||||
},
|
||||
|
||||
name: 'ControlPlayerShuffle',
|
||||
setup() {
|
||||
return {
|
||||
playerStore: usePlayerStore()
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
icon_name() {
|
||||
icon() {
|
||||
if (this.is_shuffle) {
|
||||
return 'shuffle'
|
||||
}
|
||||
@@ -37,13 +31,10 @@ export default {
|
||||
return this.playerStore.shuffle
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle_shuffle_mode() {
|
||||
toggle() {
|
||||
webapi.player_shuffle(!this.is_shuffle)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
110
web-src/src/components/ControlStreamVolume.vue
Normal file
110
web-src/src/components/ControlStreamVolume.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="media is-align-items-center pt-0">
|
||||
<div class="media-left">
|
||||
<a
|
||||
class="button is-white 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">
|
||||
<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" />
|
||||
</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>
|
||||
@@ -223,4 +223,14 @@ 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,250 +1,74 @@
|
||||
<template>
|
||||
<nav
|
||||
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"
|
||||
class="navbar is-fixed-bottom"
|
||||
:class="{ 'is-dark': !is_now_playing_page }"
|
||||
>
|
||||
<!-- 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">
|
||||
<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="is-expanded is-clipped is-size-7"
|
||||
>
|
||||
<div class="fd-is-text-clipped">
|
||||
<strong v-text="current.title" />
|
||||
<br />
|
||||
<span v-text="current.artist" />
|
||||
<span
|
||||
v-if="current.album"
|
||||
v-text="$t('navigation.now-playing', { album: current.album })"
|
||||
/>
|
||||
</div>
|
||||
</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"
|
||||
/>
|
||||
<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
|
||||
:to="{ name: 'now-playing' }"
|
||||
exact
|
||||
class="navbar-item is-expanded is-clipped is-size-7"
|
||||
>
|
||||
<div class="fd-is-text-clipped">
|
||||
<strong v-text="current.title" />
|
||||
<br />
|
||||
<span v-text="current.artist" />
|
||||
<span
|
||||
v-if="current.album"
|
||||
v-text="$t('navigation.now-playing', { album: current.album })"
|
||||
/>
|
||||
</div>
|
||||
</control-link>
|
||||
<control-player-play class="navbar-item" show_disabled_message />
|
||||
</template>
|
||||
<a
|
||||
class="navbar-item ml-auto"
|
||||
@click="show_player_menu = !show_player_menu"
|
||||
class="navbar-item"
|
||||
@click="uiStore.show_player_menu = !uiStore.show_player_menu"
|
||||
>
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="show_player_menu ? 'chevron-down' : 'chevron-up'"
|
||||
:name="uiStore.show_player_menu ? 'chevron-down' : 'chevron-up'"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Player menu for mobile and tablet -->
|
||||
<div
|
||||
class="navbar-menu is-hidden-desktop"
|
||||
:class="{ 'is-active': show_player_menu }"
|
||||
>
|
||||
<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
|
||||
class="dropdown is-up is-right"
|
||||
:class="{ 'is-active': uiStore.show_player_menu }"
|
||||
>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<div class="dropdown-item">
|
||||
<control-main-volume />
|
||||
</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>
|
||||
<hr class="dropdown-divider" />
|
||||
<div class="dropdown-item">
|
||||
<control-output-volume
|
||||
v-for="output in outputsStore.outputs"
|
||||
:key="output.id"
|
||||
:output="output"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-3" />
|
||||
<!-- Outputs: speaker volumes -->
|
||||
<navbar-item-output
|
||||
v-for="output in outputs"
|
||||
:key="output.id"
|
||||
:output="output"
|
||||
/>
|
||||
<!-- 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>
|
||||
<hr class="dropdown-divider" />
|
||||
<div class="dropdown-item">
|
||||
<control-stream-volume />
|
||||
</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"
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -255,168 +79,58 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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 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 { 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: {
|
||||
ControlSlider,
|
||||
NavbarItemLink,
|
||||
NavbarItemOutput,
|
||||
PlayerButtonConsume,
|
||||
PlayerButtonLyrics,
|
||||
PlayerButtonNext,
|
||||
PlayerButtonPlayPause,
|
||||
PlayerButtonPrevious,
|
||||
PlayerButtonRepeat,
|
||||
PlayerButtonSeekBack,
|
||||
PlayerButtonSeekForward,
|
||||
PlayerButtonShuffle
|
||||
ControlLink,
|
||||
ControlOutputVolume,
|
||||
ControlMainVolume,
|
||||
ControlPlayerBack,
|
||||
ControlPlayerConsume,
|
||||
ControlPlayerForward,
|
||||
ControlPlayerLyrics,
|
||||
ControlPlayerNext,
|
||||
ControlPlayerPlay,
|
||||
ControlPlayerPrevious,
|
||||
ControlPlayerRepeat,
|
||||
ControlPlayerShuffle,
|
||||
ControlStreamVolume
|
||||
},
|
||||
|
||||
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></style>
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
<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>
|
||||
@@ -6,126 +6,148 @@
|
||||
aria-label="main navigation"
|
||||
>
|
||||
<div class="navbar-brand">
|
||||
<navbar-item-link
|
||||
<control-link
|
||||
v-if="settingsStore.show_menu_item_playlists"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'playlists' }"
|
||||
>
|
||||
<mdicon class="icon" name="music-box-multiple" size="16" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
</control-link>
|
||||
<control-link
|
||||
v-if="settingsStore.show_menu_item_music"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'music' }"
|
||||
>
|
||||
<mdicon class="icon" name="music" size="16" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
</control-link>
|
||||
<control-link
|
||||
v-if="settingsStore.show_menu_item_podcasts"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'podcasts' }"
|
||||
>
|
||||
<mdicon class="icon" name="microphone" size="16" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
</control-link>
|
||||
<control-link
|
||||
v-if="settingsStore.show_menu_item_audiobooks"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'audiobooks' }"
|
||||
>
|
||||
<mdicon class="icon" name="book-open-variant" size="16" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
</control-link>
|
||||
<control-link
|
||||
v-if="settingsStore.show_menu_item_radio"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'radio' }"
|
||||
>
|
||||
<mdicon class="icon" name="radio" size="16" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
</control-link>
|
||||
<control-link
|
||||
v-if="settingsStore.show_menu_item_files"
|
||||
class="navbar-item"
|
||||
:to="{ name: 'files' }"
|
||||
>
|
||||
<mdicon class="icon" name="folder-open" size="16" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
</control-link>
|
||||
<control-link
|
||||
v-if="settingsStore.show_menu_item_search"
|
||||
class="navbar-item"
|
||||
:to="{ name: searchStore.search_source }"
|
||||
>
|
||||
<mdicon class="icon" name="magnify" size="16" />
|
||||
</navbar-item-link>
|
||||
<div
|
||||
class="navbar-burger"
|
||||
:class="{ 'is-active': show_burger_menu }"
|
||||
@click="show_burger_menu = !show_burger_menu"
|
||||
>
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</div>
|
||||
</control-link>
|
||||
</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" />
|
||||
<div class="navbar-end">
|
||||
<a
|
||||
class="navbar-item"
|
||||
@click="uiStore.show_burger_menu = !uiStore.show_burger_menu"
|
||||
>
|
||||
<mdicon
|
||||
class="icon"
|
||||
:name="uiStore.show_burger_menu ? 'close' : 'menu'"
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
class="dropdown is-right"
|
||||
:class="{ 'is-active': uiStore.show_burger_menu }"
|
||||
>
|
||||
<div class="dropdown-menu">
|
||||
<div class="dropdown-content">
|
||||
<control-link class="dropdown-item" :to="{ name: 'playlists' }">
|
||||
<span class="icon-text">
|
||||
<mdicon class="icon" name="music-box-multiple" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.playlists')" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'music' }">
|
||||
<mdicon class="icon" name="music" size="16" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'music' }">
|
||||
<span class="icon-text">
|
||||
<mdicon class="icon" name="music" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.music')" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'music-artists' }">
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'music-artists' }">
|
||||
<span class="pl-5" v-text="$t('navigation.artists')" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'music-albums' }">
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'music-albums' }">
|
||||
<span class="pl-5" v-text="$t('navigation.albums')" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'music-genres' }">
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'music-genres' }">
|
||||
<span class="pl-5" v-text="$t('navigation.genres')" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link
|
||||
</control-link>
|
||||
<control-link
|
||||
class="dropdown-item"
|
||||
v-if="spotify_enabled"
|
||||
:to="{ name: 'music-spotify' }"
|
||||
>
|
||||
<span class="pl-5" v-text="$t('navigation.spotify')" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'podcasts' }">
|
||||
<mdicon class="icon" name="microphone" size="16" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'podcasts' }">
|
||||
<span class="icon-text">
|
||||
<mdicon class="icon" name="microphone" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.podcasts')" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'audiobooks' }">
|
||||
<mdicon class="icon" name="book-open-variant" size="16" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'audiobooks' }">
|
||||
<span class="icon-text">
|
||||
<mdicon class="icon" name="book-open-variant" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.audiobooks')" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'radio' }">
|
||||
<mdicon class="icon" name="radio" size="16" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'radio' }">
|
||||
<span class="icon-text">
|
||||
<mdicon class="icon" name="radio" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.radio')" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: 'files' }">
|
||||
<mdicon class="icon" name="folder-open" size="16" />
|
||||
</control-link>
|
||||
<control-link class="dropdown-item" :to="{ name: 'files' }">
|
||||
<span class="icon-text">
|
||||
<mdicon class="icon" name="folder-open" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.files')" />
|
||||
</navbar-item-link>
|
||||
<navbar-item-link :to="{ name: searchStore.search_source }">
|
||||
<mdicon class="icon" name="magnify" size="16" />
|
||||
</control-link>
|
||||
<control-link
|
||||
class="dropdown-item"
|
||||
:to="{ name: searchStore.search_source }"
|
||||
>
|
||||
<span class="icon-text">
|
||||
<mdicon class="icon" name="magnify" size="16" />
|
||||
</span>
|
||||
<b v-text="$t('navigation.search')" />
|
||||
</navbar-item-link>
|
||||
</control-link>
|
||||
<hr class="my-3" />
|
||||
<navbar-item-link :to="{ name: 'settings-webinterface' }">
|
||||
<control-link
|
||||
class="dropdown-item"
|
||||
:to="{ name: 'settings-webinterface' }"
|
||||
>
|
||||
{{ $t('navigation.settings') }}
|
||||
</navbar-item-link>
|
||||
</control-link>
|
||||
<a
|
||||
class="navbar-item"
|
||||
class="dropdown-item"
|
||||
@click.stop.prevent="open_update_dialog()"
|
||||
v-text="$t('navigation.update-library')"
|
||||
/>
|
||||
<navbar-item-link :to="{ name: 'about' }">
|
||||
<control-link class="dropdown-item" :to="{ name: 'about' }">
|
||||
{{ $t('navigation.about') }}
|
||||
</navbar-item-link>
|
||||
</control-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,7 +161,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavbarItemLink from '@/components/NavbarItemLink.vue'
|
||||
import ControlLink from '@/components/ControlLink.vue'
|
||||
import { useSearchStore } from '@/stores/search'
|
||||
import { useServicesStore } from '@/stores/services'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
@@ -147,7 +169,7 @@ import { useUIStore } from '@/stores/ui'
|
||||
|
||||
export default {
|
||||
name: 'NavbarTop',
|
||||
components: { NavbarItemLink },
|
||||
components: { ControlLink },
|
||||
|
||||
setup() {
|
||||
return {
|
||||
@@ -165,22 +187,6 @@ 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,12 +209,10 @@ export default {
|
||||
this.show_settings_menu = !this.show_settings_menu
|
||||
},
|
||||
open_update_dialog() {
|
||||
this.show_update_dialog = true
|
||||
this.uiStore.show_update_dialog = true
|
||||
this.show_settings_menu = false
|
||||
this.show_burger_menu = false
|
||||
this.uiStore.show_burger_menu = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
||||
Reference in New Issue
Block a user