summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
commit3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch)
tree57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx
parent729d10592f4e802f91c555c309ff3ba4b119f843 (diff)
downloadopencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx')
-rw-r--r--apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx141
1 files changed, 0 insertions, 141 deletions
diff --git a/apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx b/apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx
deleted file mode 100644
index 7b69e820..00000000
--- a/apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { type JSX, useLayoutEffect, useRef } from "react";
-import {
- BASE_TIMELINE_PIXELS_PER_SECOND,
-} from "@/lib/timeline/scale";
-import { mediaTimeToSeconds } from "opencut-wasm";
-import { TICKS_PER_SECOND } from "@/lib/wasm";
-import { TIMELINE_RULER_HEIGHT_PX } from "./layout";
-import { DEFAULT_FPS } from "@/lib/fps/defaults";
-import { useEditor } from "@/hooks/use-editor";
-import { getRulerConfig, shouldShowLabel } from "@/lib/timeline/ruler-utils";
-import { useScrollPosition } from "@/hooks/timeline/use-scroll-position";
-import { TimelineTick } from "./timeline-tick";
-
-interface TimelineRulerProps {
- zoomLevel: number;
- dynamicTimelineWidth: number;
- rulerRef: React.Ref<HTMLDivElement>;
- tracksScrollRef: React.RefObject<HTMLElement | null>;
- handleWheel: (e: React.WheelEvent) => void;
- handleTimelineContentClick: (e: React.MouseEvent) => void;
- handleRulerTrackingMouseDown: (e: React.MouseEvent) => void;
- handleRulerMouseDown: (e: React.MouseEvent) => void;
-}
-
-export function TimelineRuler({
- zoomLevel,
- dynamicTimelineWidth,
- rulerRef,
- tracksScrollRef,
- handleWheel,
- handleTimelineContentClick,
- handleRulerTrackingMouseDown,
- handleRulerMouseDown,
-}: TimelineRulerProps) {
- const durationTicks = useEditor((e) => e.timeline.getTotalDuration());
- const durationSeconds = mediaTimeToSeconds({ time: durationTicks });
- const pixelsPerSecond = BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel;
- const visibleDurationSeconds = dynamicTimelineWidth / pixelsPerSecond;
- const effectiveDurationSeconds = Math.max(durationSeconds, visibleDurationSeconds);
- const fps =
- useEditor((e) => e.project.getActiveOrNull()?.settings.fps) ?? DEFAULT_FPS;
- const { labelIntervalSeconds, tickIntervalSeconds } = getRulerConfig({
- zoomLevel,
- fps,
- });
- const tickCount = Math.ceil(effectiveDurationSeconds / tickIntervalSeconds) + 1;
-
- const { scrollLeft, viewportWidth } = useScrollPosition({
- scrollRef: tracksScrollRef,
- });
-
- /**
- * widens the virtualization buffer during zoom transitions.
- * useScrollPosition lags one frame behind the scroll adjustment
- * that useLayoutEffect applies after a zoom change.
- */
- const prevZoomRef = useRef(zoomLevel);
- const isZoomTransition = zoomLevel !== prevZoomRef.current;
- const bufferPx = isZoomTransition
- ? Math.max(200, (scrollLeft + viewportWidth) * 0.15)
- : 200;
-
- useLayoutEffect(() => {
- prevZoomRef.current = zoomLevel;
- }, [zoomLevel]);
-
- const visibleStartTimeSeconds = Math.max(
- 0,
- (scrollLeft - bufferPx) / pixelsPerSecond,
- );
- const visibleEndTimeSeconds =
- (scrollLeft + viewportWidth + bufferPx) / pixelsPerSecond;
-
- const startTickIndex = Math.max(
- 0,
- Math.floor(visibleStartTimeSeconds / tickIntervalSeconds),
- );
- const endTickIndex = Math.min(
- tickCount - 1,
- Math.ceil(visibleEndTimeSeconds / tickIntervalSeconds),
- );
-
- const timelineTicks: Array<JSX.Element> = [];
- for (
- let tickIndex = startTickIndex;
- tickIndex <= endTickIndex;
- tickIndex += 1
- ) {
- const timeSeconds = tickIndex * tickIntervalSeconds;
- if (timeSeconds > effectiveDurationSeconds) break;
-
- const timeTicks = Math.round(timeSeconds * TICKS_PER_SECOND);
- const showLabel = shouldShowLabel({ time: timeSeconds, labelIntervalSeconds });
- timelineTicks.push(
- <TimelineTick
- key={tickIndex}
- time={timeTicks}
- timeInSeconds={timeSeconds}
- zoomLevel={zoomLevel}
- fps={fps}
- showLabel={showLabel}
- />,
- );
- }
-
- return (
- <div
- role="slider"
- tabIndex={0}
- aria-label="Timeline ruler"
- aria-valuemin={0}
- aria-valuemax={effectiveDurationSeconds}
- aria-valuenow={0}
- className="relative flex-1 overflow-x-visible"
- style={{ height: TIMELINE_RULER_HEIGHT_PX }}
- onWheel={handleWheel}
- onClick={(event) => {
- // Ruler seek already happens on mousedown via playhead scrubbing.
- // Forwarding the follow-up click re-enters the selection-clearing path.
- if (event.target === event.currentTarget) {
- handleTimelineContentClick(event);
- }
- }}
- onMouseDown={handleRulerTrackingMouseDown}
- onKeyDown={() => {}}
- >
- <div
- role="none"
- ref={rulerRef}
- className="relative cursor-default select-none"
- style={{
- height: TIMELINE_RULER_HEIGHT_PX,
- width: `${dynamicTimelineWidth}px`,
- }}
- onMouseDown={handleRulerMouseDown}
- >
- {timelineTicks}
- </div>
- </div>
- );
-}