[web] Improve user interaction with sliders for output volume and music progress bar #1620

The sliders for output volume and the music slider have been optimised and simplified (one library less) to ensure easier user interaction.
This commit is contained in:
Alain Nussbaumer 2023-06-21 15:14:17 +02:00
parent b7a52d1761
commit eeb4d328c8
15 changed files with 249 additions and 241 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,6 @@
"@aacassandra/vue3-progressbar": "^1.0.3", "@aacassandra/vue3-progressbar": "^1.0.3",
"@mdi/js": "^6.7.96", "@mdi/js": "^6.7.96",
"@ts-pro/vue-eternal-loading": "^1.2.0", "@ts-pro/vue-eternal-loading": "^1.2.0",
"@vueform/slider": "github:chme/slider#faff83ed8a77f2cdbcb7252505ef734301efd139",
"axios": "^1.4.0", "axios": "^1.4.0",
"bulma": "^0.9.4", "bulma": "^0.9.4",
"bulma-switch": "^2.0.4", "bulma-switch": "^2.0.4",
@ -36,7 +35,7 @@
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-vue": "^9.14.1", "eslint-plugin-vue": "^9.14.1",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"sass": "^1.62.1", "sass": "^1.63.5",
"vite": "^2.9.16" "vite": "^2.9.16"
} }
}, },
@ -50,9 +49,9 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.22.4", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.4.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz",
"integrity": "sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==", "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==",
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
}, },
@ -124,9 +123,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "8.42.0", "version": "8.43.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz",
"integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -481,16 +480,10 @@
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
}, },
"node_modules/@vueform/slider": {
"version": "2.0.9",
"resolved": "git+ssh://git@github.com/chme/slider.git#faff83ed8a77f2cdbcb7252505ef734301efd139",
"integrity": "sha512-IfGEwBS5hwptmLbcnjrhDB7olw8mhCbQFDYv24bfYK/LVJo8pueumd7qsO9qlwfyezgnBdp1/CGBm+Wrh1oYdA==",
"license": "MIT"
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.8.2", "version": "8.9.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
"dev": true, "dev": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
@ -745,9 +738,9 @@
"dev": true "dev": true
}, },
"node_modules/core-js": { "node_modules/core-js": {
"version": "3.30.2", "version": "3.31.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.31.0.tgz",
"integrity": "sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg==", "integrity": "sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ==",
"hasInstallScript": true, "hasInstallScript": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -1197,15 +1190,15 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "8.42.0", "version": "8.43.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz",
"integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.4.0", "@eslint-community/regexpp": "^4.4.0",
"@eslint/eslintrc": "^2.0.3", "@eslint/eslintrc": "^2.0.3",
"@eslint/js": "8.42.0", "@eslint/js": "8.43.0",
"@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/config-array": "^0.11.10",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",
@ -1265,9 +1258,9 @@
} }
}, },
"node_modules/eslint-plugin-vue": { "node_modules/eslint-plugin-vue": {
"version": "9.14.1", "version": "9.15.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.14.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.0.tgz",
"integrity": "sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw==", "integrity": "sha512-XYzpK6e2REli100+6iCeBA69v6Sm0D/yK2FZP+fCeNt0yH/m82qZQq+ztseyV0JsKdhFysuSEzeE1yCmSC92BA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.3.0", "@eslint-community/eslint-utils": "^4.3.0",
@ -2399,9 +2392,9 @@
} }
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.62.1", "version": "1.63.5",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.5.tgz",
"integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==", "integrity": "sha512-Q6c5gs482oezdAp+0fWF9cRisvpy7yfYb64knID0OE8AnMgtkluRPfpGMFjeD4/+M4+6QpJZCU6JRSxbjiktkg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
@ -2416,9 +2409,9 @@
} }
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "7.5.1", "version": "7.5.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
"integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
@ -2645,9 +2638,9 @@
} }
}, },
"node_modules/vue-eslint-parser": { "node_modules/vue-eslint-parser": {
"version": "9.3.0", "version": "9.3.1",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.0.tgz", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz",
"integrity": "sha512-48IxT9d0+wArT1+3wNIy0tascRoywqSUe2E1YalIC1L8jsUGe5aJQItWfRok7DVFGz3UYvzEI7n5wiTXsCMAcQ==", "integrity": "sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"debug": "^4.3.4", "debug": "^4.3.4",

View File

@ -6,7 +6,7 @@
"build": "vite build --base='./'", "build": "vite build --base='./'",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src", "lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
"dev": "vite", "dev": "vite",
"format": "prettier . --write", "format": "prettier . --write",
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"", "i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"",
"preview": "vite preview" "preview": "vite preview"
}, },
@ -14,7 +14,6 @@
"@aacassandra/vue3-progressbar": "^1.0.3", "@aacassandra/vue3-progressbar": "^1.0.3",
"@mdi/js": "^6.7.96", "@mdi/js": "^6.7.96",
"@ts-pro/vue-eternal-loading": "^1.2.0", "@ts-pro/vue-eternal-loading": "^1.2.0",
"@vueform/slider": "github:chme/slider#faff83ed8a77f2cdbcb7252505ef734301efd139",
"axios": "^1.4.0", "axios": "^1.4.0",
"bulma": "^0.9.4", "bulma": "^0.9.4",
"bulma-switch": "^2.0.4", "bulma-switch": "^2.0.4",
@ -39,7 +38,7 @@
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-vue": "^9.14.1", "eslint-plugin-vue": "^9.14.1",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"sass": "^1.62.1", "sass": "^1.63.5",
"vite": "^2.9.16" "vite": "^2.9.16"
} }
} }

View File

@ -3,7 +3,7 @@
<navbar-top /> <navbar-top />
<vue-progress-bar class="fd-progress-bar" /> <vue-progress-bar class="fd-progress-bar" />
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<component :is="Component" class="fd-page" /> <component :is="Component" />
</router-view> </router-view>
<modal-dialog-remote-pairing <modal-dialog-remote-pairing

View File

@ -115,14 +115,13 @@
<div class="level-item fd-expanded"> <div class="level-item fd-expanded">
<div class="fd-expanded"> <div class="fd-expanded">
<p class="heading" v-text="$t('navigation.volume')" /> <p class="heading" v-text="$t('navigation.volume')" />
<Slider <input
v-model="player.volume" v-model="player.volume"
:min="0" class="slider"
:max="100" max="100"
:step="1" type="range"
:tooltips="false" :style="{ '--ratio': player.volume / 100 }"
:classes="{ target: 'slider' }" @change="change_volume"
@change="set_volume"
/> />
</div> </div>
</div> </div>
@ -175,15 +174,15 @@
/></span> /></span>
</a> </a>
</p> </p>
<Slider <input
v-model="stream_volume" v-model="stream_volume"
:min="0"
:max="100"
:step="1"
:tooltips="false"
:disabled="!playing" :disabled="!playing"
:classes="{ target: 'slider' }" class="slider"
@change="set_stream_volume" :class="{ 'is-inactive': !playing }"
max="100"
type="range"
:style="{ '--ratio': stream_volume / 100 }"
@change="change_stream_volume"
/> />
</div> </div>
</div> </div>
@ -238,14 +237,13 @@
<div class="level-item fd-expanded"> <div class="level-item fd-expanded">
<div class="fd-expanded"> <div class="fd-expanded">
<p class="heading" v-text="$t('navigation.volume')" /> <p class="heading" v-text="$t('navigation.volume')" />
<Slider <input
v-model="player.volume" v-model="player.volume"
:min="0" class="slider"
:max="100" max="100"
:step="1" type="range"
:tooltips="false" :style="{ '--ratio': player.volume / 100 }"
:classes="{ target: 'slider' }" @change="change_volume"
@change="set_volume"
/> />
</div> </div>
</div> </div>
@ -260,7 +258,7 @@
/> />
<!-- Outputs: stream volume --> <!-- Outputs: stream volume -->
<hr class="fd-navbar-divider" /> <hr class="fd-navbar-divider" />
<div class="navbar-item fd-has-margin-bottom"> <div class="navbar-item mb-5">
<div class="level is-mobile"> <div class="level is-mobile">
<div class="level-left fd-expanded"> <div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0"> <div class="level-item" style="flex-grow: 0">
@ -298,15 +296,15 @@
/></span> /></span>
</a> </a>
</p> </p>
<Slider <input
v-model="stream_volume" v-model="stream_volume"
:min="0"
:max="100"
:step="1"
:tooltips="false"
:disabled="!playing" :disabled="!playing"
:classes="{ target: 'slider' }" class="slider"
@change="set_stream_volume" :class="{ 'is-inactive': !playing }"
max="100"
type="range"
:style="{ '--ratio': stream_volume / 100 }"
@change="change_stream_volume"
/> />
</div> </div>
</div> </div>
@ -331,7 +329,6 @@ import PlayerButtonConsume from '@/components/PlayerButtonConsume.vue'
import PlayerButtonRepeat from '@/components/PlayerButtonRepeat.vue' import PlayerButtonRepeat from '@/components/PlayerButtonRepeat.vue'
import PlayerButtonSeekBack from '@/components/PlayerButtonSeekBack.vue' import PlayerButtonSeekBack from '@/components/PlayerButtonSeekBack.vue'
import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward.vue' import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward.vue'
import Slider from '@vueform/slider'
import * as types from '@/store/mutation_types' import * as types from '@/store/mutation_types'
export default { export default {
@ -339,7 +336,6 @@ export default {
components: { components: {
NavbarItemLink, NavbarItemLink,
NavbarItemOutput, NavbarItemOutput,
Slider,
PlayerButtonPlayPause, PlayerButtonPlayPause,
PlayerButtonNext, PlayerButtonNext,
PlayerButtonPrevious, PlayerButtonPrevious,
@ -382,9 +378,6 @@ export default {
return '' return ''
}, },
state() {
return this.$store.state.player
},
now_playing() { now_playing() {
return this.$store.getters.now_playing return this.$store.getters.now_playing
}, },
@ -427,16 +420,13 @@ export default {
this.show_outputs_menu = false this.show_outputs_menu = false
}, },
set_volume(newVolume) { change_volume() {
webapi.player_volume(newVolume) webapi.player_volume(this.player.volume)
}, },
toggle_mute_volume() { toggle_mute_volume() {
if (this.player.volume > 0) { this.player.volume = this.player.volume > 0 ? 0 : this.old_volume
this.set_volume(0) this.change_volume()
} else {
this.set_volume(this.old_volume)
}
}, },
setupAudio() { setupAudio() {
@ -492,8 +482,8 @@ export default {
return this.playChannel() return this.playChannel()
}, },
set_stream_volume(newVolume) { change_stream_volume() {
this.stream_volume = newVolume console.log(this.stream_volume)
_audio.setVolume(this.stream_volume / 100) _audio.setVolume(this.stream_volume / 100)
} }
} }

View File

@ -19,15 +19,15 @@
:class="{ 'has-text-grey-light': !output.selected }" :class="{ 'has-text-grey-light': !output.selected }"
v-text="output.name" v-text="output.name"
/> />
<Slider <input
v-model="volume" v-model="volume"
:min="0"
:max="100"
:step="1"
:tooltips="false"
:disabled="!output.selected" :disabled="!output.selected"
:classes="{ target: 'slider' }" class="slider"
@change="set_volume" :class="{ 'is-inactive': !output.selected }"
max="100"
type="range"
:style="{ '--ratio': volume / 100 }"
@change="change_volume"
/> />
</div> </div>
</div> </div>
@ -37,17 +37,19 @@
</template> </template>
<script> <script>
import Slider from '@vueform/slider'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'NavbarItemOutput', name: 'NavbarItemOutput',
components: {
Slider
},
props: ['output'], props: ['output'],
data() {
return {
volume: this.output.selected ? this.output.volume : 0
}
},
computed: { computed: {
type_class() { type_class() {
if (this.output.type.startsWith('AirPlay')) { if (this.output.type.startsWith('AirPlay')) {
@ -59,20 +61,18 @@ export default {
} else { } else {
return 'server' return 'server'
} }
}, }
},
volume() { watch: {
return this.output.selected ? this.output.volume : 0 output() {
this.volume = this.output.volume
} }
}, },
methods: { methods: {
play_next() { change_volume() {
webapi.player_next() webapi.player_output_volume(this.output.id, this.volume)
},
set_volume(newVolume) {
webapi.player_output_volume(this.output.id, newVolume)
}, },
set_enabled() { set_enabled() {

View File

@ -12,7 +12,6 @@ import { icons } from './icons'
import App from './App.vue' import App from './App.vue'
import './mystyles.scss' import './mystyles.scss'
import '@vueform/slider/themes/default.css'
const app = createApp(App) const app = createApp(App)
.use(store) .use(store)

View File

@ -3,40 +3,6 @@
@import 'bulma/bulma.sass'; @import 'bulma/bulma.sass';
@import 'bulma-switch'; @import 'bulma-switch';
/* Volume slider */
.slider {
min-width: 250px;
width: 100%;
margin-top: 16px;
margin-bottom: 16px;
--slider-height: 4px;
--slider-connect-bg: #{$dark};
--slider-tooltip-bg: #{$dark};
--slider-handle-ring-color: #3b82f630;
--slider-handle-shadow: 0.5px 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.32);
--slider-handle-shadow-active: 0.5px 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.42);
}
/* Now playing progress bar */
.seek-slider {
min-width: 250px;
max-width: 500px;
width: 100% !important;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
margin: 0 auto 16px auto;
--slider-height: 4px;
--slider-connect-bg: #{$primary};
--slider-tooltip-bg: #{$primary};
--slider-handle-bg: #{$primary};
--slider-handle-border: 0;
--slider-handle-width: 10px;
--slider-handle-height: 10px;
--slider-handle-radius: 9999px;
--slider-handle-shadow: 0.5px 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.32);
--slider-handle-shadow-active: 0.5px 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.42);
--slider-handle-ring-width: 3px;
}
.progress-bar { .progress-bar {
background-color: $info; background-color: $info;
border-radius: 9999px; border-radius: 9999px;
@ -299,3 +265,67 @@ hr.fd-navbar-divider {
.hero-body { .hero-body {
padding: 1.5rem !important; padding: 1.5rem !important;
} }
/* Slider */
@mixin thumb {
-webkit-appearance: none;
width: var(--th);
height: var(--th);
border-radius: 50%;
background: $dark;
border: none;
}
@mixin track {
height: calc(var(--sh));
border-radius: calc(var(--sh) / 2);
background: linear-gradient(90deg, $dark var(--sx), $grey-light var(--sx));
}
input[type='range'].slider {
--sh: 0.5rem;
--th: calc(var(--sh) * 1.5);
--sx: calc(var(--th) / 2 + (var(--ratio) * (100% - var(--th))));
-webkit-appearance: none;
min-width: 250px;
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 {
&::-webkit-slider-thumb {
background-color: $grey-light;
}
&::-webkit-slider-runnable-track {
background: linear-gradient(
90deg,
$grey-light var(--sx),
$light var(--sx)
);
}
&::-moz-range-thumb {
background-color: $grey-light;
}
&::-moz-range-track {
background: linear-gradient(
90deg,
$grey-light var(--sx),
$light var(--sx)
);
}
}
}

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<section class="section"> <section class="section fd-page">
<div class="container"> <div class="container">
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-four-fifths has-text-centered-mobile"> <div class="column is-four-fifths has-text-centered-mobile">

View File

@ -1,69 +1,54 @@
<template> <template>
<section> <div class="hero is-fullheight">
<div v-if="now_playing.id > 0" class="fd-is-fullheight"> <div v-if="track.id > 0" class="hero-body">
<cover-artwork <div class="container has-text-centered" style="max-width: 500px">
:artwork_url="now_playing.artwork_url" <cover-artwork
:artist="now_playing.artist" :artwork_url="track.artwork_url"
:album="now_playing.album" :artist="track.artist"
class="is-clickable fd-has-shadow fd-is-expanded fd-cover fd-cover-big-image" :album="track.album"
@click="open_dialog(now_playing)" class="is-clickable fd-has-shadow fd-cover-big-image"
/> @click="open_dialog(track)"
<div class="fd-has-padding-left-right"> />
<div class="container has-text-centered"> <input
<p class="control has-text-centered fd-progress-now-playing"> v-model.number="item_progress_ms"
<slider :step="1000"
ref="slider" :max="is_live ? 1000 : track.length_ms"
v-model="item_progress_ms" type="range"
:min="0" class="slider mt-5"
:max="state.item_length_ms" :style="{ '--ratio': progress }"
:step="1000" @change="seek"
:tooltips="false" @touchstart="start_dragging"
:disabled="state.state === 'stop'" @touchend="end_dragging"
:classes="{ target: 'seek-slider' }" />
@change="seek" <div class="is-flex is-justify-content-space-between">
@start="start_dragging" <p
@end="end_dragging" class="subtitle is-7"
/> v-text="$filters.durationInHours(item_progress_ms)"
</p>
<p class="content">
<span
v-text="
[
$filters.durationInHours(item_progress_ms),
$filters.durationInHours(now_playing.length_ms)
].join(' / ')
"
/>
</p>
</div>
</div>
<div class="fd-has-padding-left-right">
<div class="container has-text-centered fd-has-margin-top">
<h1 class="title is-5" v-text="now_playing.title" />
<h2 class="title is-6" v-text="now_playing.artist" />
<h2
v-if="composer"
class="subtitle is-6 has-text-grey has-text-weight-bold"
v-text="composer"
/> />
<h3 class="subtitle is-6" v-text="now_playing.album" /> <p
<h3 class="subtitle is-7"
v-if="filepath" v-text="$filters.durationInHours(track.length_ms)"
class="subtitle is-6 has-text-grey"
v-text="filepath"
/> />
</div> </div>
<h1 class="title is-5" v-text="track.title" />
<h2 class="title is-6" v-text="track.artist" />
<h2
v-if="composer"
class="subtitle is-6 has-text-grey has-text-weight-bold"
v-text="composer"
/>
<h3 class="subtitle is-6" v-text="track.album" />
<h3
v-if="filepath"
class="subtitle is-6 has-text-grey"
v-text="filepath"
/>
</div> </div>
</div> </div>
<div v-else class="fd-is-fullheight"> <div v-else class="hero-body">
<div <div class="container has-text-centered">
class="fd-is-expanded fd-has-padding-left-right" <p class="title is-5" v-text="$t('page.now-playing.title')" />
style="flex-direction: column" <p class="subtitle" v-text="$t('page.now-playing.info')" />
>
<div class="content has-text-centered">
<h1 class="title is-5" v-text="$t('page.now-playing.title')" />
<p v-text="$t('page.now-playing.info')" />
</div>
</div> </div>
</div> </div>
<modal-dialog-queue-item <modal-dialog-queue-item
@ -71,12 +56,11 @@
:item="selected_item" :item="selected_item"
@close="show_details_modal = false" @close="show_details_modal = false"
/> />
</section> </div>
</template> </template>
<script> <script>
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue' import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue'
import Slider from '@vueform/slider'
import CoverArtwork from '@/components/CoverArtwork.vue' import CoverArtwork from '@/components/CoverArtwork.vue'
import webapi from '@/webapi' import webapi from '@/webapi'
import * as types from '@/store/mutation_types' import * as types from '@/store/mutation_types'
@ -85,7 +69,6 @@ export default {
name: 'PageNowPlaying', name: 'PageNowPlaying',
components: { components: {
ModalDialogQueueItem, ModalDialogQueueItem,
Slider,
CoverArtwork CoverArtwork
}, },
@ -101,11 +84,19 @@ export default {
}, },
computed: { computed: {
state() { progress() {
return this.is_live ? 2 : this.item_progress_ms / this.track.length_ms
},
is_live() {
return this.track.length_ms == 0
},
player() {
return this.$store.state.player return this.$store.state.player
}, },
now_playing() { track() {
return this.$store.getters.now_playing return this.$store.getters.now_playing
}, },
@ -121,16 +112,16 @@ export default {
if (this.settings_option_show_composer_now_playing) { if (this.settings_option_show_composer_now_playing) {
if ( if (
!this.settings_option_show_composer_for_genre || !this.settings_option_show_composer_for_genre ||
(this.now_playing.genre && (this.track.genre &&
this.settings_option_show_composer_for_genre this.settings_option_show_composer_for_genre
.toLowerCase() .toLowerCase()
.split(',') .split(',')
.findIndex( .findIndex(
(elem) => (elem) =>
this.now_playing.genre.toLowerCase().indexOf(elem.trim()) >= 0 this.track.genre.toLowerCase().indexOf(elem.trim()) >= 0
) >= 0) ) >= 0)
) { ) {
return this.now_playing.composer return this.track.composer
} }
} }
return null return null
@ -142,32 +133,30 @@ export default {
filepath() { filepath() {
if (this.settings_option_show_filepath_now_playing) { if (this.settings_option_show_filepath_now_playing) {
return this.now_playing.path return this.track.path
} }
return null return null
} }
}, },
watch: { watch: {
state() { player() {
if (this.interval_id > 0) { if (this.interval_id > 0) {
window.clearTimeout(this.interval_id) window.clearTimeout(this.interval_id)
this.interval_id = 0 this.interval_id = 0
} }
this.item_progress_ms = this.state.item_progress_ms this.item_progress_ms = this.player.item_progress_ms
if (this.state.state === 'play') { if (this.player.state === 'play') {
this.interval_id = window.setInterval(this.tick, 1000) this.interval_id = window.setInterval(this.tick, 1000)
} }
} }
}, },
mounted() {},
created() { created() {
this.item_progress_ms = this.state.item_progress_ms this.item_progress_ms = this.player.item_progress_ms
webapi.player_status().then(({ data }) => { webapi.player_status().then(({ data }) => {
this.$store.commit(types.UPDATE_PLAYER_STATUS, data) this.$store.commit(types.UPDATE_PLAYER_STATUS, data)
if (this.state.state === 'play') { if (this.player.state === 'play') {
this.interval_id = window.setInterval(this.tick, 1000) this.interval_id = window.setInterval(this.tick, 1000)
} }
}) })
@ -183,7 +172,13 @@ export default {
methods: { methods: {
tick() { tick() {
if (!this.is_dragged) { if (!this.is_dragged) {
this.item_progress_ms += 1000 if (this.is_live) {
this.item_progress_ms += 1000
} else if (this.item_progress_ms + 1000 > this.track.length_ms) {
this.item_progress_ms = this.track.length_ms
} else {
this.item_progress_ms += 1000
}
} }
}, },
@ -195,10 +190,12 @@ export default {
this.is_dragged = false this.is_dragged = false
}, },
seek(newPosition) { seek() {
webapi.player_seek_to_pos(newPosition).catch(() => { if (!this.is_live) {
this.item_progress_ms = this.state.item_progress_ms webapi.player_seek_to_pos(this.item_progress_ms).catch(() => {
}) this.item_progress_ms = this.player.item_progress_ms
})
}
}, },
open_dialog(item) { open_dialog(item) {

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<!-- Search field + recent searches --> <!-- Search field + recent searches -->
<section class="section pb-0"> <section class="section fd-page pb-0">
<div class="container"> <div class="container">
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-four-fifths"> <div class="column is-four-fifths">

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<!-- Search field + recent searches --> <!-- Search field + recent searches -->
<section class="section pb-0"> <section class="section pb-0 fd-page">
<div class="container"> <div class="container">
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-four-fifths"> <div class="column is-four-fifths">

View File

@ -1,5 +1,5 @@
<template> <template>
<section class="section fd-content"> <section class="section fd-page fd-content">
<div class="container"> <div class="container">
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-four-fifths"> <div class="column is-four-fifths">

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<section class="hero is-light is-bold fd-content"> <section class="hero is-light is-bold fd-page fd-content">
<div class="hero-body"> <div class="hero-body">
<div class="container"> <div class="container">
<div class="columns is-centered"> <div class="columns is-centered">