@remotion/studio
Version:
APIs for interacting with the Remotion Studio
241 lines (240 loc) • 9.54 kB
JavaScript
"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;