import { type WheelEvent as ReactWheelEvent, type RefObject, useEffect, useLayoutEffect, useReducer, useRef, } from "react"; import { useEditor } from "@/editor/use-editor"; import { TIMELINE_ZOOM_MIN } from "@/timeline/scale"; import { ZoomController, type ZoomConfig, } from "@/timeline/controllers/zoom-controller"; interface UseTimelineZoomProps { containerRef: RefObject; minZoom?: number; initialZoom?: number; initialScrollLeft?: number; initialPlayheadTime?: number; tracksScrollRef: RefObject; rulerScrollRef: RefObject; } interface UseTimelineZoomReturn { zoomLevel: number; setZoomLevel: (zoomLevel: number | ((prev: number) => number)) => void; handleWheel: (event: ReactWheelEvent) => void; saveScrollPosition: () => void; } export function useTimelineZoom({ containerRef, minZoom = TIMELINE_ZOOM_MIN, initialZoom, initialScrollLeft, initialPlayheadTime, tracksScrollRef, rulerScrollRef, }: UseTimelineZoomProps): UseTimelineZoomReturn { const editor = useEditor(); const config: ZoomConfig = { minZoom, getContainerEl: () => containerRef.current, getTracksScrollEl: () => tracksScrollRef.current, getRulerScrollEl: () => rulerScrollRef.current, getCurrentPlayheadTime: () => editor.playback.getCurrentTime(), seek: (time) => editor.playback.seek({ time }), setTimelineViewState: (viewState) => editor.project.setTimelineViewState({ viewState }), }; const configRef = useRef(config); configRef.current = config; const controllerRef = useRef(null); if (!controllerRef.current) { controllerRef.current = new ZoomController({ configRef, initialZoom }); } const controller = controllerRef.current; const zoomLevel = controller.zoomLevel; const [, rerender] = useReducer((n: number) => n + 1, 0); useEffect(() => controller.subscribe(rerender), [controller]); useEffect(() => { controller.reconcileInitialAndMinZoom(minZoom, initialZoom); }, [controller, minZoom, initialZoom]); useLayoutEffect(() => { controller.applyZoomLayout(zoomLevel); }, [controller, zoomLevel]); useEffect(() => { return controller.restoreInitialScrollIfNeeded(initialScrollLeft); }, [controller, initialScrollLeft]); useEffect(() => { controller.restoreInitialPlayheadIfNeeded(initialPlayheadTime); }, [controller, initialPlayheadTime]); useEffect(() => controller.bindPreventBrowserZoom(), [controller]); useEffect(() => () => controller.destroy(), [controller]); return { zoomLevel, setZoomLevel: controller.setZoomLevel, handleWheel: controller.handleWheel, saveScrollPosition: controller.saveScrollPosition, }; }