diff options
Diffstat (limited to 'apps/web/src/components/editor/panels/timeline/audio-volume-line.tsx')
| -rw-r--r-- | apps/web/src/components/editor/panels/timeline/audio-volume-line.tsx | 275 |
1 files changed, 0 insertions, 275 deletions
diff --git a/apps/web/src/components/editor/panels/timeline/audio-volume-line.tsx b/apps/web/src/components/editor/panels/timeline/audio-volume-line.tsx deleted file mode 100644 index 208f3beb..00000000 --- a/apps/web/src/components/editor/panels/timeline/audio-volume-line.tsx +++ /dev/null @@ -1,275 +0,0 @@ -"use client"; - -import { useCallback, useRef, useState } from "react"; -import { createPortal } from "react-dom"; -import { useEditor } from "@/hooks/use-editor"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import { - getDbFromLinePos, - getLinePosFromDb, -} from "@/lib/timeline/audio-display"; -import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/lib/timeline/audio-constants"; -import { hasAnimatedVolume } from "@/lib/timeline/audio-state"; -import type { AudioElement } from "@/lib/timeline/types"; -import { - clamp, - formatNumberForDisplay, - getFractionDigitsForStep, - isNearlyEqual, - snapToStep, -} from "@/utils/math"; -import { cn } from "@/utils/ui"; - -const HIT_AREA_HEIGHT_PX = 14; -const TOOLTIP_OFFSET_PX = 10; -const VOLUME_STEP = 0.1; -const VOLUME_FRACTION_DIGITS = getFractionDigitsForStep({ step: VOLUME_STEP }); - -function clampVolume({ value }: { value: number }): number { - return clamp({ - value: snapToStep({ value, step: VOLUME_STEP }), - min: VOLUME_DB_MIN, - max: VOLUME_DB_MAX, - }); -} - -function getVolumeFromPointer({ - clientY, - rect, -}: { - clientY: number; - rect: DOMRect; -}): number { - const clampedOffset = clamp({ - value: clientY - rect.top, - min: 0, - max: rect.height, - }); - const progressPercent = - rect.height <= 0 ? 0 : (clampedOffset / rect.height) * 100; - return clampVolume({ value: getDbFromLinePos({ percent: progressPercent }) }); -} - -export function AudioVolumeLine({ - element, - trackId, -}: { - element: AudioElement; - trackId: string; -}) { - const editor = useEditor(); - const surfaceRef = useRef<HTMLDivElement>(null); - const activePointerIdRef = useRef<number | null>(null); - const startVolumeRef = useRef(element.volume ?? DEFAULTS.element.volume); - const lastPreviewVolumeRef = useRef( - element.volume ?? DEFAULTS.element.volume, - ); - const hasChangedRef = useRef(false); - const [isDragging, setIsDragging] = useState(false); - const [tooltipClientPos, setTooltipClientPos] = useState<{ - x: number; - y: number; - } | null>(null); - - const hasAnimatedEnvelope = hasAnimatedVolume({ element }); - const currentVolume = element.volume ?? DEFAULTS.element.volume; - const lineTop = `${getLinePosFromDb({ db: currentVolume })}%`; - - const volumeLabel = `${formatNumberForDisplay({ - value: currentVolume, - fractionDigits: VOLUME_FRACTION_DIGITS, - })} dB`; - - const previewVolume = useCallback( - (nextVolume: number) => { - if ( - isNearlyEqual({ - leftValue: nextVolume, - rightValue: lastPreviewVolumeRef.current, - }) - ) { - return; - } - - editor.timeline.previewElements({ - updates: [ - { - trackId, - elementId: element.id, - updates: { volume: nextVolume }, - }, - ], - }); - lastPreviewVolumeRef.current = nextVolume; - hasChangedRef.current = !isNearlyEqual({ - leftValue: startVolumeRef.current, - rightValue: nextVolume, - }); - }, - [editor, element.id, trackId], - ); - - const finishDrag = useCallback( - ({ shouldCommit }: { shouldCommit: boolean }) => { - activePointerIdRef.current = null; - setIsDragging(false); - - if (shouldCommit && hasChangedRef.current) { - editor.timeline.commitPreview(); - } else { - editor.timeline.discardPreview(); - } - - hasChangedRef.current = false; - lastPreviewVolumeRef.current = startVolumeRef.current; - setTooltipClientPos(null); - }, - [editor], - ); - - const updateFromPointer = useCallback( - ({ clientX, clientY }: { clientX: number; clientY: number }) => { - const rect = surfaceRef.current?.getBoundingClientRect(); - if (!rect) { - return; - } - - setTooltipClientPos({ - x: clientX + TOOLTIP_OFFSET_PX, - y: clientY - TOOLTIP_OFFSET_PX, - }); - previewVolume(getVolumeFromPointer({ clientY, rect })); - }, - [previewVolume], - ); - - const handleClick = useCallback((event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - }, []); - - const handleMouseDown = useCallback((event: React.MouseEvent) => { - event.stopPropagation(); - }, []); - - const handlePointerDown = useCallback( - (event: React.PointerEvent<HTMLDivElement>) => { - if (event.button !== 0) { - return; - } - - event.preventDefault(); - event.stopPropagation(); - editor.selection.setSelectedElements({ - elements: [{ trackId, elementId: element.id }], - }); - activePointerIdRef.current = event.pointerId; - startVolumeRef.current = currentVolume; - lastPreviewVolumeRef.current = currentVolume; - hasChangedRef.current = false; - setIsDragging(true); - event.currentTarget.setPointerCapture(event.pointerId); - updateFromPointer({ - clientX: event.clientX, - clientY: event.clientY, - }); - }, - [currentVolume, editor.selection, element.id, trackId, updateFromPointer], - ); - - const handlePointerMove = useCallback( - (event: React.PointerEvent) => { - if (activePointerIdRef.current !== event.pointerId) { - return; - } - - event.preventDefault(); - updateFromPointer({ - clientX: event.clientX, - clientY: event.clientY, - }); - }, - [updateFromPointer], - ); - - const handlePointerUp = useCallback( - (event: React.PointerEvent) => { - if (activePointerIdRef.current !== event.pointerId) { - return; - } - - event.preventDefault(); - event.stopPropagation(); - finishDrag({ shouldCommit: true }); - }, - [finishDrag], - ); - - const handlePointerCancel = useCallback( - (event: React.PointerEvent) => { - if (activePointerIdRef.current !== event.pointerId) { - return; - } - - event.preventDefault(); - event.stopPropagation(); - finishDrag({ shouldCommit: false }); - }, - [finishDrag], - ); - - const handleLostPointerCapture = useCallback(() => { - if (activePointerIdRef.current === null) { - return; - } - - finishDrag({ shouldCommit: hasChangedRef.current }); - }, [finishDrag]); - - if (hasAnimatedEnvelope) { - return null; - } - - return ( - <div className="pointer-events-none absolute inset-0"> - <div ref={surfaceRef} className="absolute inset-0"> - <div - className={cn( - "pointer-events-none absolute inset-x-0 -translate-y-1/2 border-t transition-colors", - isDragging - ? "border-white" - : "border-white/50 group-hover/audio:border-white/80", - )} - style={{ top: lineTop }} - /> - {/* biome-ignore lint/a11y/noStaticElementInteractions: timeline volume line is a pointer-only editing surface */} - {/* biome-ignore lint/a11y/useKeyWithClickEvents: timeline volume line is a pointer-only editing surface */} - <div - className="absolute inset-x-0 -translate-y-1/2 touch-none cursor-ns-resize pointer-events-auto" - style={{ top: lineTop, height: `${HIT_AREA_HEIGHT_PX}px` }} - onClick={handleClick} - onMouseDown={handleMouseDown} - onPointerDown={handlePointerDown} - onPointerMove={handlePointerMove} - onPointerUp={handlePointerUp} - onPointerCancel={handlePointerCancel} - onLostPointerCapture={handleLostPointerCapture} - title="Drag to adjust clip volume" - /> - {isDragging && - tooltipClientPos && - createPortal( - <div - className="pointer-events-none fixed left-0 top-0 z-50 -translate-y-full rounded bg-black/75 px-1.5 py-0.5 text-[10px] font-medium text-white whitespace-nowrap" - style={{ - transform: `translate(${tooltipClientPos.x}px, ${tooltipClientPos.y}px)`, - }} - > - {volumeLabel} - </div>, - document.body, - )} - </div> - </div> - ); -} |
