From fca99d6126c31fbb18ed9f1034cee6f940b040e8 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 10 Feb 2026 16:28:21 +0100 Subject: refactor --- .../editor/panels/timeline/timeline-ruler.tsx | 126 +++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx (limited to 'apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx') diff --git a/apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx b/apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx new file mode 100644 index 00000000..b0261aa2 --- /dev/null +++ b/apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx @@ -0,0 +1,126 @@ +import { type JSX, useLayoutEffect, useRef } from "react"; +import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import { DEFAULT_FPS } from "@/constants/project-constants"; +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; + tracksScrollRef: React.RefObject; + 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 editor = useEditor(); + const duration = editor.timeline.getTotalDuration(); + const pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; + const visibleDuration = dynamicTimelineWidth / pixelsPerSecond; + const effectiveDuration = Math.max(duration, visibleDuration); + const project = editor.project.getActive(); + const fps = project?.settings.fps ?? DEFAULT_FPS; + const { labelIntervalSeconds, tickIntervalSeconds } = getRulerConfig({ + zoomLevel, + fps, + }); + const tickCount = Math.ceil(effectiveDuration / 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 visibleStartTime = Math.max( + 0, + (scrollLeft - bufferPx) / pixelsPerSecond, + ); + const visibleEndTime = + (scrollLeft + viewportWidth + bufferPx) / pixelsPerSecond; + + const startTickIndex = Math.max( + 0, + Math.floor(visibleStartTime / tickIntervalSeconds), + ); + const endTickIndex = Math.min( + tickCount - 1, + Math.ceil(visibleEndTime / tickIntervalSeconds), + ); + + const timelineTicks: Array = []; + for ( + let tickIndex = startTickIndex; + tickIndex <= endTickIndex; + tickIndex += 1 + ) { + const time = tickIndex * tickIntervalSeconds; + if (time > effectiveDuration) break; + + const showLabel = shouldShowLabel({ time, labelIntervalSeconds }); + timelineTicks.push( + , + ); + } + + return ( +
{}} + > +
+ {timelineTicks} +
+
+ ); +} -- cgit v1.3.1