replace bootstrap-vue progress bar with vue-slider-component
better hit area, drag support
This commit is contained in:
parent
e1dc32060b
commit
73b2b493a4
@ -2,10 +2,11 @@
|
|||||||
<div :class="{'visible': visible}" class="player elevated d-flex">
|
<div :class="{'visible': visible}" class="player elevated d-flex">
|
||||||
<div class="flex-fill">
|
<div class="flex-fill">
|
||||||
<!-- Progress --->
|
<!-- Progress --->
|
||||||
<div class="progress2" @click="seek">
|
<ProgressBar
|
||||||
<b-progress :value="progress" :max="100" height="4px" />
|
style="margin-bottom: -5px; margin-top: -9px"
|
||||||
</div>
|
:value="progress" @input="seek"
|
||||||
<div class="row align-items-center m-0">
|
/>
|
||||||
|
<div class="row align-items-center m-0" style="padding-top: -10px">
|
||||||
<!-- Track info --->
|
<!-- Track info --->
|
||||||
<div class="col p-0 d-flex flex-nowrap align-items-center justify-content-start" style="width: 0; min-width: 0">
|
<div class="col p-0 d-flex flex-nowrap align-items-center justify-content-start" style="width: 0; min-width: 0">
|
||||||
<template v-if="track">
|
<template v-if="track">
|
||||||
@ -96,9 +97,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.progress2 {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.player {
|
.player {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -120,8 +118,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import { mapState, mapGetters, mapActions } from 'vuex'
|
import { mapState, mapGetters, mapActions } from 'vuex'
|
||||||
|
import ProgressBar from '@/player/ProgressBar.vue'
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
ProgressBar,
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState('player', {
|
...mapState('player', {
|
||||||
isPlaying: (state: any) => state.isPlaying,
|
isPlaying: (state: any) => state.isPlaying,
|
||||||
@ -154,14 +156,8 @@
|
|||||||
'toggleRepeat',
|
'toggleRepeat',
|
||||||
'toggleShuffle',
|
'toggleShuffle',
|
||||||
'toggleMute',
|
'toggleMute',
|
||||||
|
'seek',
|
||||||
]),
|
]),
|
||||||
seek(event: any) {
|
|
||||||
if (event.target) {
|
|
||||||
const width = event.currentTarget.clientWidth
|
|
||||||
const value = event.offsetX / width
|
|
||||||
return this.$store.dispatch('player/seek', value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setVolume(volume: any) {
|
setVolume(volume: any) {
|
||||||
return this.$store.dispatch('player/setVolume', parseFloat(volume))
|
return this.$store.dispatch('player/setVolume', parseFloat(volume))
|
||||||
},
|
},
|
||||||
|
62
src/player/ProgressBar.vue
Normal file
62
src/player/ProgressBar.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<VueSlider
|
||||||
|
v-bind="$attrs"
|
||||||
|
:value="value"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:interval="0.001"
|
||||||
|
:lazy="true"
|
||||||
|
:contained="true"
|
||||||
|
:dot-options="{tooltip: 'none'}"
|
||||||
|
@change="onInput"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '/src/style/variables';
|
||||||
|
@import '~vue-slider-component/theme/material.css';
|
||||||
|
|
||||||
|
.vue-slider {
|
||||||
|
height: 4px !important;
|
||||||
|
padding: 5px 0 !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
::v-deep .vue-slider-rail {
|
||||||
|
background-color: $secondary;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
::v-deep .vue-slider-process {
|
||||||
|
background-color: $primary;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
::v-deep .vue-slider-dot-handle {
|
||||||
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
::v-deep .vue-slider-dot-handle::after {
|
||||||
|
background-color: rgba($primary, 0.32);
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
}
|
||||||
|
.vue-slider:not(:hover) ::v-deep .vue-slider-dot-handle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.vue-slider:hover ::v-deep .vue-slider-dot-handle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue'
|
||||||
|
import VueSlider from 'vue-slider-component'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
VueSlider,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: { type: Number, required: true },
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput(value: number) {
|
||||||
|
this.$emit('input', value)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
@ -187,7 +187,7 @@ export const playerModule: Module<State, any> = {
|
|||||||
},
|
},
|
||||||
seek({ state }, value) {
|
seek({ state }, value) {
|
||||||
if (isFinite(state.duration)) {
|
if (isFinite(state.duration)) {
|
||||||
audio.seek(state.duration * value)
|
audio.seek(state.duration * (value / 100.0))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async resetQueue({ commit, getters }) {
|
async resetQueue({ commit, getters }) {
|
||||||
|
@ -9,20 +9,38 @@
|
|||||||
@change="onInput"
|
@change="onInput"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<style>
|
<style lang="scss" scoped>
|
||||||
@import '~vue-slider-component/theme/default.css';
|
@import '/src/style/_variables';
|
||||||
.vue-slider-rail {
|
@import '~vue-slider-component/theme/material.css';
|
||||||
background-color: var(--secondary) !important;
|
|
||||||
|
.vue-slider {
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.vue-slider-process {
|
::v-deep .vue-slider-rail {
|
||||||
background-color: var(--primary) !important;
|
background-color: $secondary;
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
.vue-slider-dot-tooltip-inner {
|
::v-deep .vue-slider-process {
|
||||||
background-color: var(--primary);
|
background-color: $primary;
|
||||||
border-color: var(--primary);
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
.vue-slider-dot-handle {
|
::v-deep .vue-slider-dot-handle {
|
||||||
background-color: var(--text-body);
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
::v-deep .vue-slider-dot-handle::after {
|
||||||
|
background-color: rgba($primary, 0.32);
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
}
|
||||||
|
::v-deep .vue-slider-dot-handle:hover .vue-slider-dot-tooltip {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
::v-deep .vue-slider-dot-tooltip-inner {
|
||||||
|
background-color: $primary;
|
||||||
|
border-color: $primary;
|
||||||
|
}
|
||||||
|
::v-deep .vue-slider-dot-tooltip-text {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
BModal,
|
BModal,
|
||||||
BOverlay,
|
BOverlay,
|
||||||
BPopover,
|
BPopover,
|
||||||
BProgress,
|
|
||||||
BSidebar,
|
BSidebar,
|
||||||
DropdownPlugin,
|
DropdownPlugin,
|
||||||
} from 'bootstrap-vue'
|
} from 'bootstrap-vue'
|
||||||
@ -33,7 +32,6 @@ Vue.component('BFormCheckbox', BFormCheckbox)
|
|||||||
Vue.component('BFormTextarea', BFormTextarea)
|
Vue.component('BFormTextarea', BFormTextarea)
|
||||||
Vue.component('BButton', BButton)
|
Vue.component('BButton', BButton)
|
||||||
Vue.component('BPopover', BPopover)
|
Vue.component('BPopover', BPopover)
|
||||||
Vue.component('BProgress', BProgress)
|
|
||||||
Vue.component('BOverlay', BOverlay)
|
Vue.component('BOverlay', BOverlay)
|
||||||
Vue.use(DropdownPlugin)
|
Vue.use(DropdownPlugin)
|
||||||
|
|
||||||
|
15
src/style/_variables.scss
Normal file
15
src/style/_variables.scss
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
$theme-elevation-0: hsl(0, 0%, 0%);
|
||||||
|
$theme-elevation-1: hsl(0, 0%, 10%);
|
||||||
|
$theme-elevation-2: hsl(0, 0%, 20%);
|
||||||
|
$theme-text: #ccc;
|
||||||
|
$theme-text-muted: #999;
|
||||||
|
|
||||||
|
$primary: #09f;
|
||||||
|
$danger: #ff4141;
|
||||||
|
$secondary: $theme-elevation-2;
|
||||||
|
|
||||||
|
$body-bg: $theme-elevation-0;
|
||||||
|
$body-color: $theme-text;
|
||||||
|
$link-color: $theme-text;
|
||||||
|
$text-muted: $theme-text-muted;
|
||||||
|
$border-color: $theme-elevation-2;
|
@ -1,18 +1,4 @@
|
|||||||
$theme-elevation-0: hsl(0, 0%, 0%);
|
@import "./variables";
|
||||||
$theme-elevation-1: hsl(0, 0%, 10%);
|
|
||||||
$theme-elevation-2: hsl(0, 0%, 20%);
|
|
||||||
$theme-text: #ccc;
|
|
||||||
$theme-text-muted: #999;
|
|
||||||
|
|
||||||
$primary: #09f;
|
|
||||||
$danger: #ff4141;
|
|
||||||
$secondary: $theme-elevation-2;
|
|
||||||
|
|
||||||
$body-bg: $theme-elevation-0;
|
|
||||||
$body-color: $theme-text;
|
|
||||||
$link-color: $theme-text;
|
|
||||||
$text-muted: $theme-text-muted;
|
|
||||||
$border-color: $theme-elevation-2;
|
|
||||||
|
|
||||||
// Card
|
// Card
|
||||||
$card-bg: $theme-elevation-1;
|
$card-bg: $theme-elevation-1;
|
||||||
@ -42,9 +28,6 @@ $custom-range-track-height: 0.1rem;
|
|||||||
$custom-range-thumb-bg: $theme-text;
|
$custom-range-thumb-bg: $theme-text;
|
||||||
$custom-range-track-bg: $theme-text-muted;
|
$custom-range-track-bg: $theme-text-muted;
|
||||||
|
|
||||||
// Other
|
|
||||||
$progress-bg: rgb(35, 35, 35);
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--text-body: #{$theme-text};
|
--text-body: #{$theme-text};
|
||||||
--text-muted: #{$theme-text-muted};
|
--text-muted: #{$theme-text-muted};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user