UNPKG

@remotion/studio

Version:

APIs for interacting with the Remotion Studio

241 lines (240 loc) 9.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TimelinePinchZoom = void 0; const react_1 = require("react"); const remotion_1 = require("remotion"); const is_current_selected_still_1 = require("../../helpers/is-current-selected-still"); const editor_zoom_gestures_1 = require("../../state/editor-zoom-gestures"); const timeline_zoom_1 = require("../../state/timeline-zoom"); const timeline_refs_1 = require("./timeline-refs"); const timeline_scroll_logic_1 = require("./timeline-scroll-logic"); /** * Maps wheel deltaY to zoom delta. Must be large enough that typical ctrl+wheel * pinch steps change `TimelineZoomCtx` zoom by at least one 0.1 step after * `Math.round(z * 10) / 10` in `timeline-zoom.tsx` (0.005 was too small). */ const ZOOM_WHEEL_DELTA = 0.06; const TimelinePinchZoom = () => { const isVideoComposition = (0, is_current_selected_still_1.useIsVideoComposition)(); const videoConfig = remotion_1.Internals.useUnsafeVideoConfig(); const { canvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager); const { zoom, setZoom } = (0, react_1.useContext)(timeline_zoom_1.TimelineZoomCtx); const { editorZoomGestures } = (0, react_1.useContext)(editor_zoom_gestures_1.EditorZoomGesturesContext); const zoomRef = (0, react_1.useRef)(zoom); zoomRef.current = zoom; const pinchBaseZoomRef = (0, react_1.useRef)(null); const suppressWheelFromWebKitPinchRef = (0, react_1.useRef)(false); const touchPinchRef = (0, react_1.useRef)(null); const onWheel = (0, react_1.useCallback)((e) => { if (!editorZoomGestures || !isVideoComposition) { return; } const { ctrlKey, metaKey, clientX, deltaY, deltaMode } = e; const wantsToZoom = ctrlKey || metaKey; if (!wantsToZoom) { return; } if (suppressWheelFromWebKitPinchRef.current && wantsToZoom) { e.preventDefault(); return; } if (!videoConfig || videoConfig.durationInFrames < 2) { return; } if (!canvasContent || canvasContent.type !== 'composition') { return; } const scrollEl = timeline_refs_1.scrollableRef.current; if (!scrollEl) { return; } e.preventDefault(); const anchorContentX = (0, timeline_scroll_logic_1.viewportClientXToScrollContentX)({ clientX, scrollEl, }); let scaledDeltaY = deltaY; if (deltaMode === WheelEvent.DOM_DELTA_LINE) { scaledDeltaY *= 16; } else if (deltaMode === WheelEvent.DOM_DELTA_PAGE) { scaledDeltaY *= scrollEl.clientHeight; } setZoom(canvasContent.compositionId, (z) => z - scaledDeltaY * ZOOM_WHEEL_DELTA, { anchorFrame: null, anchorContentX }); }, [ editorZoomGestures, isVideoComposition, videoConfig, canvasContent, setZoom, ]); const supportsWebKitPinch = typeof window !== 'undefined' && 'GestureEvent' in window; (0, react_1.useEffect)(() => { const el = timeline_refs_1.timelineVerticalScroll.current; if (!el) { return; } el.addEventListener('wheel', onWheel, { passive: false }); return () => { el.removeEventListener('wheel', onWheel); }; }, [onWheel]); (0, react_1.useEffect)(() => { const el = timeline_refs_1.timelineVerticalScroll.current; if (!el || !editorZoomGestures || !supportsWebKitPinch) { return; } const endWebKitPinch = () => { pinchBaseZoomRef.current = null; suppressWheelFromWebKitPinchRef.current = false; }; const onGestureStart = (event) => { var _a; const e = event; if (!isVideoComposition) { return; } if (!videoConfig || videoConfig.durationInFrames < 2) { return; } if (!canvasContent || canvasContent.type !== 'composition') { return; } const scrollEl = timeline_refs_1.scrollableRef.current; if (!scrollEl) { return; } e.preventDefault(); suppressWheelFromWebKitPinchRef.current = true; pinchBaseZoomRef.current = (_a = zoomRef.current[canvasContent.compositionId]) !== null && _a !== void 0 ? _a : timeline_zoom_1.TIMELINE_MIN_ZOOM; }; const onGestureChange = (event) => { const e = event; const base = pinchBaseZoomRef.current; if (base === null || !isVideoComposition || !videoConfig || videoConfig.durationInFrames < 2) { return; } if (!canvasContent || canvasContent.type !== 'composition') { return; } const scrollEl = timeline_refs_1.scrollableRef.current; if (!scrollEl) { return; } e.preventDefault(); const anchorContentX = (0, timeline_scroll_logic_1.viewportClientXToScrollContentX)({ clientX: e.clientX, scrollEl, }); setZoom(canvasContent.compositionId, () => base * e.scale, { anchorFrame: null, anchorContentX, }); }; const onGestureEnd = () => { endWebKitPinch(); }; el.addEventListener('gesturestart', onGestureStart, { passive: false }); el.addEventListener('gesturechange', onGestureChange, { passive: false }); el.addEventListener('gestureend', onGestureEnd); el.addEventListener('gesturecancel', onGestureEnd); return () => { el.removeEventListener('gesturestart', onGestureStart); el.removeEventListener('gesturechange', onGestureChange); el.removeEventListener('gestureend', onGestureEnd); el.removeEventListener('gesturecancel', onGestureEnd); }; }, [ editorZoomGestures, supportsWebKitPinch, isVideoComposition, videoConfig, canvasContent, setZoom, ]); (0, react_1.useEffect)(() => { const el = timeline_refs_1.timelineVerticalScroll.current; if (!el || !editorZoomGestures) { return; } const onTouchStart = (event) => { var _a; if (event.touches.length !== 2) { touchPinchRef.current = null; return; } if (!isVideoComposition || !videoConfig || videoConfig.durationInFrames < 2) { return; } if (!canvasContent || canvasContent.type !== 'composition') { return; } const [t0, t1] = [event.touches[0], event.touches[1]]; const initialDistance = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY); if (initialDistance < 1e-6) { return; } touchPinchRef.current = { initialDistance, initialZoom: (_a = zoomRef.current[canvasContent.compositionId]) !== null && _a !== void 0 ? _a : timeline_zoom_1.TIMELINE_MIN_ZOOM, }; }; const onTouchMove = (event) => { const pinch = touchPinchRef.current; if (pinch === null || event.touches.length !== 2 || !videoConfig || videoConfig.durationInFrames < 2) { return; } if (!canvasContent || canvasContent.type !== 'composition') { return; } const scrollEl = timeline_refs_1.scrollableRef.current; if (!scrollEl) { return; } event.preventDefault(); const [t0, t1] = [event.touches[0], event.touches[1]]; const dist = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY); const ratio = dist / pinch.initialDistance; const clientX = (t0.clientX + t1.clientX) / 2; const anchorContentX = (0, timeline_scroll_logic_1.viewportClientXToScrollContentX)({ clientX, scrollEl, }); setZoom(canvasContent.compositionId, () => pinch.initialZoom * ratio, { anchorFrame: null, anchorContentX, }); }; const onTouchEnd = (event) => { if (event.touches.length < 2) { touchPinchRef.current = null; } }; el.addEventListener('touchstart', onTouchStart, { passive: true }); el.addEventListener('touchmove', onTouchMove, { passive: false }); el.addEventListener('touchend', onTouchEnd); el.addEventListener('touchcancel', onTouchEnd); return () => { el.removeEventListener('touchstart', onTouchStart); el.removeEventListener('touchmove', onTouchMove); el.removeEventListener('touchend', onTouchEnd); el.removeEventListener('touchcancel', onTouchEnd); }; }, [ editorZoomGestures, isVideoComposition, videoConfig, canvasContent, setZoom, ]); return null; }; exports.TimelinePinchZoom = TimelinePinchZoom;