1
0
Fork 0
muzika-gromche/Frontend/src/components/library/VolumeSlider.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>