koel/resources/assets/js/components/ui/equalizer/EqualizerBand.vue
2024-10-14 00:37:01 +07:00

149 lines
3 KiB
Vue

<template>
<article class="flex flex-col items-center min-w-[24px]">
<span ref="sliderEl" class="slider h-[100px]" />
<label class="mt-2 mb-0 text-left text-sm text-k-text-primary">
<slot />
</label>
</article>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import noUiSlider from 'nouislider'
const props = withDefaults(defineProps<{
type?: 'preamp' | 'gain'
modelValue?: number
}>(), {
type: 'gain',
modelValue: 0,
})
const emit = defineEmits<{
(e: 'update:modelValue', value: number): void
(e: 'commit')
}>()
const sliderEl = ref<EqualizerBandElement>()
const value = computed({
get: () => props.modelValue,
set: value => emit('update:modelValue', value),
})
/**
* Since watching the value and updating the slider UI proves to be not performant,
* we defined an explicit method to update the UI and expose it so that the
* parent component (Equalizer) can call it when resetting the preset.
*/
const updateSliderValue = (val: number) => {
sliderEl.value?.noUiSlider.set(val)
value.value = val
}
onMounted(() => {
noUiSlider.create(sliderEl.value!, {
connect: [false, true],
// the first element is the preamp. The rest are gains.
start: value.value,
range: { min: -20, max: 20 },
orientation: 'vertical',
direction: 'rtl',
})
sliderEl.value!.noUiSlider.on('slide', (values, handle) => {
emit('update:modelValue', Number.parseFloat(values[handle]))
})
sliderEl.value!.noUiSlider.on('change', () => emit('commit'))
})
defineExpose({
updateSliderValue,
})
</script>
<style lang="postcss">
/* overriding the global noUi import, don't scope */
/* also, don't use Tailwind here as it will mess things up */
/* and wrap the styles in a class to ensure cascading for built assets */
article {
.noUi {
&-connect {
background: none;
box-shadow: none;
&::after {
content: ' ';
position: absolute;
width: 2px;
height: 100%;
top: 0;
left: 7px;
}
}
&-touch-area {
cursor: ns-resize;
}
&-target {
background: transparent;
border-radius: 0;
border: 0;
box-shadow: none;
width: 16px;
&::after {
content: ' ';
position: absolute;
width: 2px;
height: 100%;
background: linear-gradient(
to bottom,
var(--color-highlight) 0%,
var(--color-highlight) 36%,
var(--color-success) 100%
);
background-size: 2px;
top: 0;
left: 7px;
}
}
&-handle {
border: 0;
border-radius: 0;
box-shadow: none;
cursor: pointer;
&::before,
&::after {
display: none;
}
}
&-vertical {
.noUi-handle {
width: 16px;
height: 6px;
left: -16px;
border-radius: 9999px;
top: 0;
}
}
}
.noUi-handle {
border: 0;
border-radius: 0;
box-shadow: none;
cursor: pointer;
&::before,
&::after {
display: none;
}
}
}
</style>