summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/panels/timeline/audio-volume-line.tsx
diff options
context:
space:
mode:
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.tsx275
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>
- );
-}