Let queued and player extensions use the full duration slider.
Stop clamping extensions to 3s or 5s so each segment can use the same length control as generate.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="text-sm">
|
||||
<span v-if="label" class="mb-1 block text-zinc-400">{{ label }}</span>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="draft"
|
||||
type="text"
|
||||
inputmode="decimal"
|
||||
class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 pr-8 cursor-ns-resize outline-none ring-amber-300/40 placeholder:text-zinc-600 focus:cursor-text focus:ring-2"
|
||||
:class="{ 'select-none caret-transparent': scrubbing }"
|
||||
placeholder="seconds"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
@blur="commit"
|
||||
@keydown.enter.prevent="commit"
|
||||
@pointerdown="onPointerDown"
|
||||
>
|
||||
<span class="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs text-zinc-500">s</span>
|
||||
</div>
|
||||
<input
|
||||
class="aigen-slider mt-3 w-full"
|
||||
type="range"
|
||||
:min="SLIDER_MIN"
|
||||
:max="SLIDER_MAX"
|
||||
:step="0.1"
|
||||
:value="slider"
|
||||
:style="{ background: `linear-gradient(to right, #fbbf24 ${sliderPct}%, #27272a ${sliderPct}%)` }"
|
||||
:aria-label="label || 'Duration in seconds'"
|
||||
@input="onSlider"
|
||||
>
|
||||
<p v-if="hint" class="mt-1 text-[11px] text-zinc-500">{{ hint }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const MIN = 0.5
|
||||
const MAX = 120
|
||||
const SLIDER_MIN = 1
|
||||
const SLIDER_MAX = 30
|
||||
|
||||
withDefaults(defineProps<{
|
||||
label?: string
|
||||
hint?: string
|
||||
}>(), {
|
||||
label: '',
|
||||
hint: ''
|
||||
})
|
||||
|
||||
const model = defineModel<number>({ required: true })
|
||||
const draft = ref(formatDuration(model.value))
|
||||
const scrubbing = ref(false)
|
||||
|
||||
const slider = computed(() => Math.min(SLIDER_MAX, Math.max(SLIDER_MIN, model.value)))
|
||||
const sliderPct = computed(() => ((slider.value - SLIDER_MIN) / (SLIDER_MAX - SLIDER_MIN)) * 100)
|
||||
|
||||
watch(model, (value) => {
|
||||
if (!scrubbing.value) draft.value = formatDuration(value)
|
||||
})
|
||||
|
||||
function clampDuration(value: number) {
|
||||
if (!Number.isFinite(value)) return 5
|
||||
return Math.min(MAX, Math.max(MIN, Math.round(value * 10) / 10))
|
||||
}
|
||||
|
||||
function formatDuration(value: number) {
|
||||
return Number.isInteger(value) ? String(value) : value.toFixed(1).replace(/\.0$/, '')
|
||||
}
|
||||
|
||||
function commit() {
|
||||
const parsed = Number.parseFloat(draft.value.replace(',', '.').trim())
|
||||
model.value = clampDuration(parsed)
|
||||
draft.value = formatDuration(model.value)
|
||||
}
|
||||
|
||||
function onSlider(event: Event) {
|
||||
model.value = clampDuration(Number((event.target as HTMLInputElement).value))
|
||||
draft.value = formatDuration(model.value)
|
||||
}
|
||||
|
||||
function onPointerDown(event: PointerEvent) {
|
||||
if (event.button !== 0) return
|
||||
const input = event.currentTarget as HTMLInputElement
|
||||
const startY = event.clientY
|
||||
const startValue = model.value
|
||||
let moved = false
|
||||
const onMove = (move: PointerEvent) => {
|
||||
const delta = startY - move.clientY
|
||||
if (!moved && Math.abs(delta) < 5) return
|
||||
if (!moved) {
|
||||
moved = true
|
||||
scrubbing.value = true
|
||||
input.setPointerCapture(move.pointerId)
|
||||
input.blur()
|
||||
}
|
||||
move.preventDefault()
|
||||
const next = clampDuration(startValue + delta / 18)
|
||||
model.value = next
|
||||
draft.value = formatDuration(next)
|
||||
}
|
||||
const onUp = () => {
|
||||
input.removeEventListener('pointermove', onMove)
|
||||
input.removeEventListener('pointerup', onUp)
|
||||
input.removeEventListener('pointercancel', onUp)
|
||||
scrubbing.value = false
|
||||
}
|
||||
input.addEventListener('pointermove', onMove)
|
||||
input.addEventListener('pointerup', onUp)
|
||||
input.addEventListener('pointercancel', onUp)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user