forked from nikita/muzika-gromche
94 lines
2.7 KiB
Vue
94 lines
2.7 KiB
Vue
<script setup lang="ts">
|
|
import VolumeDown from '@material-design-icons/svg/outlined/volume_down.svg'
|
|
import VolumeMute from '@material-design-icons/svg/outlined/volume_mute.svg'
|
|
import VolumeOff from '@material-design-icons/svg/outlined/volume_off.svg'
|
|
import VolumeUp from '@material-design-icons/svg/outlined/volume_up.svg'
|
|
import { computed, useId } from 'vue'
|
|
import Slider from '@/components/library/Slider.vue'
|
|
import classes from './ToolBar.module.css'
|
|
|
|
const {
|
|
defaultVolume = 1,
|
|
enableBoost = true,
|
|
} = defineProps<{
|
|
defaultVolume?: number
|
|
// Boost increases volume range from 0..1 up to 0..1.5
|
|
enableBoost?: boolean
|
|
}>()
|
|
|
|
/** Muted flag. When muted, volume slider shows zero value, but remains interactive. */
|
|
const muted = defineModel<boolean>('muted', { required: true })
|
|
|
|
/** Volume in range 0..1 or 0..1.5 if boost is enabled. */
|
|
const volume = defineModel<number>('volume', { required: true })
|
|
|
|
const mutedId = useId()
|
|
const mutedTitle = computed(() => muted.value ? 'Unmute' : 'Mute')
|
|
|
|
function toggleMuted() {
|
|
muted.value = !muted.value
|
|
}
|
|
|
|
const volumeMax = computed(() => enableBoost ? 1.5 : 1.0)
|
|
const sliderSteps = computed(() => enableBoost ? 24 : 16)
|
|
|
|
function toSteps(volume: number): number {
|
|
return volume / volumeMax.value * sliderSteps.value
|
|
}
|
|
|
|
function fromSteps(steps: number): number {
|
|
return steps / sliderSteps.value * volumeMax.value
|
|
}
|
|
|
|
const volumeDisplay = computed<number>({
|
|
get() {
|
|
// displays zero volume when muted, despite actual unmuted volume is remembered
|
|
return muted.value ? 0 : toSteps(volume.value)
|
|
},
|
|
set(value: number) {
|
|
volume.value = fromSteps(value)
|
|
},
|
|
})
|
|
|
|
function reset() {
|
|
volume.value = defaultVolume
|
|
}
|
|
|
|
const defaultValue = computed(() => toSteps(defaultVolume))
|
|
</script>
|
|
|
|
<template>
|
|
<div class="tw:flex tw:flex-row tw:gap-2 tw:items-center">
|
|
<input
|
|
:id="mutedId"
|
|
type="checkbox"
|
|
class="tw:hidden"
|
|
:title="mutedTitle"
|
|
@click="toggleMuted"
|
|
>
|
|
<label
|
|
:for="mutedId"
|
|
:class="[classes.toolbarControl, classes.toolButton]"
|
|
:title="mutedTitle"
|
|
tabindex="0"
|
|
>
|
|
<VolumeOff v-if="muted" class="tw:text-[#e64b3d]" />
|
|
<!-- transforms are needed because icons are centered rather than aligned with each other -->
|
|
<VolumeMute v-else-if="volume < 0.33" style="transform: translateX(-8px);" />
|
|
<VolumeDown v-else-if="volume < 0.66" style="transform: translateX(-4px);" />
|
|
<VolumeUp v-else :class="{ 'tw:text-[#e8ba3d]': volume > 1.01 }" />
|
|
</label>
|
|
<Slider
|
|
v-model.number="volumeDisplay"
|
|
:min="0"
|
|
:max="sliderSteps"
|
|
:step="1"
|
|
:reset="reset"
|
|
:default-value
|
|
title="Volume"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped></style>
|