forked from nikita/muzika-gromche
82 lines
3.0 KiB
Vue
82 lines
3.0 KiB
Vue
<script setup lang="ts">
|
|
import Slider from '@/components/library/Slider.vue';
|
|
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 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);
|
|
},
|
|
});
|
|
|
|
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" v-on: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 min="0" :max="sliderSteps" step="1" v-model.number="volumeDisplay" :defaultValue title="Volume"
|
|
list="markers" />
|
|
<!-- TODO: markers are not rendered because of overridden style, and they affect snapping essentially overriding steps -->
|
|
<!-- list="markers" -->
|
|
<datalist id="markers">
|
|
<option :value="defaultValue"></option>
|
|
<!--
|
|
<option value="0"></option>
|
|
<option value="4"></option>
|
|
<option value="8"></option>
|
|
<option value="12"></option>
|
|
<option value="16"></option>
|
|
-->
|
|
</datalist>
|
|
</div>
|
|
</template>
|
|
<style scoped></style>
|