@remotion/studio
Version:
APIs for interacting with the Remotion Studio
266 lines (265 loc) • 11.6 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Canvas = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const remotion_1 = require("remotion");
const colors_1 = require("../helpers/colors");
const get_asset_metadata_1 = require("../helpers/get-asset-metadata");
const get_effective_translation_1 = require("../helpers/get-effective-translation");
const smooth_zoom_1 = require("../helpers/smooth-zoom");
const use_keybinding_1 = require("../helpers/use-keybinding");
const canvas_ref_1 = require("../state/canvas-ref");
const editor_guides_1 = require("../state/editor-guides");
const editor_zoom_gestures_1 = require("../state/editor-zoom-gestures");
const EditorGuides_1 = __importDefault(require("./EditorGuides"));
const EditorRuler_1 = require("./EditorRuler");
const use_is_ruler_visible_1 = require("./EditorRuler/use-is-ruler-visible");
const Preview_1 = require("./Preview");
const ResetZoomButton_1 = require("./ResetZoomButton");
const layout_1 = require("./layout");
const container = {
flex: 1,
display: 'flex',
overflow: 'hidden',
position: 'relative',
backgroundColor: colors_1.BACKGROUND,
};
const resetZoom = {
position: 'absolute',
top: layout_1.SPACING_UNIT * 2,
right: layout_1.SPACING_UNIT * 2,
};
const ZOOM_PX_FACTOR = 0.003;
const Canvas = ({ canvasContent, size }) => {
const { setSize, size: previewSize } = (0, react_1.useContext)(remotion_1.Internals.PreviewSizeContext);
const { editorZoomGestures } = (0, react_1.useContext)(editor_zoom_gestures_1.EditorZoomGesturesContext);
const keybindings = (0, use_keybinding_1.useKeybinding)();
const config = remotion_1.Internals.useUnsafeVideoConfig();
const areRulersVisible = (0, use_is_ruler_visible_1.useIsRulerVisible)();
const { editorShowGuides } = (0, react_1.useContext)(editor_guides_1.EditorShowGuidesContext);
const [assetResolution, setAssetResolution] = (0, react_1.useState)(null);
const contentDimensions = (0, react_1.useMemo)(() => {
if ((canvasContent.type === 'asset' || canvasContent.type === 'output') &&
assetResolution &&
assetResolution.type === 'found') {
return assetResolution.dimensions;
}
if (config) {
return { width: config.width, height: config.height };
}
return null;
}, [assetResolution, config, canvasContent]);
const isFit = previewSize.size === 'auto';
const onWheel = (0, react_1.useCallback)((e) => {
if (!editorZoomGestures) {
return;
}
if (!size) {
return;
}
if (!contentDimensions || contentDimensions === 'none') {
return;
}
const wantsToZoom = e.ctrlKey || e.metaKey;
if (!wantsToZoom && isFit) {
return;
}
e.preventDefault();
setSize((prevSize) => {
const scale = remotion_1.Internals.calculateScale({
canvasSize: size,
compositionHeight: contentDimensions.height,
compositionWidth: contentDimensions.width,
previewSize: prevSize.size,
});
// Zoom in/out
if (wantsToZoom) {
const oldSize = prevSize.size === 'auto' ? scale : prevSize.size;
const smoothened = (0, smooth_zoom_1.smoothenZoom)(oldSize);
const added = smoothened + e.deltaY * ZOOM_PX_FACTOR;
const unsmoothened = (0, smooth_zoom_1.unsmoothenZoom)(added);
const { centerX, centerY } = (0, get_effective_translation_1.getCenterPointWhileScrolling)({
size,
clientX: e.clientX,
clientY: e.clientY,
compositionWidth: contentDimensions.width,
compositionHeight: contentDimensions.height,
scale,
translation: prevSize.translation,
});
const zoomDifference = unsmoothened - oldSize;
const uvCoordinatesX = centerX / contentDimensions.width;
const uvCoordinatesY = centerY / contentDimensions.height;
const correctionLeft = -uvCoordinatesX * (zoomDifference * contentDimensions.width) +
(1 - uvCoordinatesX) * zoomDifference * contentDimensions.width;
const correctionTop = -uvCoordinatesY * (zoomDifference * contentDimensions.height) +
(1 - uvCoordinatesY) * zoomDifference * contentDimensions.height;
return {
translation: (0, get_effective_translation_1.getEffectiveTranslation)({
translation: {
x: prevSize.translation.x - correctionLeft / 2,
y: prevSize.translation.y - correctionTop / 2,
},
canvasSize: size,
compositionHeight: contentDimensions.height,
compositionWidth: contentDimensions.width,
scale,
}),
size: unsmoothened,
};
}
const effectiveTranslation = (0, get_effective_translation_1.getEffectiveTranslation)({
translation: prevSize.translation,
canvasSize: size,
compositionHeight: contentDimensions.height,
compositionWidth: contentDimensions.width,
scale,
});
// Pan
return {
...prevSize,
translation: (0, get_effective_translation_1.getEffectiveTranslation)({
translation: {
x: effectiveTranslation.x + e.deltaX,
y: effectiveTranslation.y + e.deltaY,
},
canvasSize: size,
compositionHeight: contentDimensions.height,
compositionWidth: contentDimensions.width,
scale,
}),
};
});
}, [editorZoomGestures, contentDimensions, isFit, setSize, size]);
(0, react_1.useEffect)(() => {
const { current } = canvas_ref_1.canvasRef;
if (!current) {
return;
}
current.addEventListener('wheel', onWheel, { passive: false });
return () =>
// @ts-expect-error
current.removeEventListener('wheel', onWheel, {
passive: false,
});
}, [onWheel]);
const onReset = (0, react_1.useCallback)(() => {
setSize(() => {
return {
translation: {
x: 0,
y: 0,
},
size: 'auto',
};
});
}, [setSize]);
const onZoomIn = (0, react_1.useCallback)(() => {
if (!contentDimensions || contentDimensions === 'none') {
return;
}
if (!size) {
return;
}
setSize((prevSize) => {
const scale = remotion_1.Internals.calculateScale({
canvasSize: size,
compositionHeight: contentDimensions.height,
compositionWidth: contentDimensions.width,
previewSize: prevSize.size,
});
return {
translation: {
x: 0,
y: 0,
},
size: Math.min(smooth_zoom_1.MAX_ZOOM, scale * 2),
};
});
}, [contentDimensions, setSize, size]);
const onZoomOut = (0, react_1.useCallback)(() => {
if (!contentDimensions || contentDimensions === 'none') {
return;
}
if (!size) {
return;
}
setSize((prevSize) => {
const scale = remotion_1.Internals.calculateScale({
canvasSize: size,
compositionHeight: contentDimensions.height,
compositionWidth: contentDimensions.width,
previewSize: prevSize.size,
});
return {
translation: {
x: 0,
y: 0,
},
size: Math.max(smooth_zoom_1.MIN_ZOOM, scale / 2),
};
});
}, [contentDimensions, setSize, size]);
(0, react_1.useEffect)(() => {
const resetBinding = keybindings.registerKeybinding({
event: 'keydown',
key: '0',
commandCtrlKey: false,
callback: onReset,
preventDefault: true,
triggerIfInputFieldFocused: false,
keepRegisteredWhenNotHighestContext: false,
});
const zoomIn = keybindings.registerKeybinding({
event: 'keydown',
key: '+',
commandCtrlKey: false,
callback: onZoomIn,
preventDefault: true,
triggerIfInputFieldFocused: false,
keepRegisteredWhenNotHighestContext: false,
});
const zoomOut = keybindings.registerKeybinding({
event: 'keydown',
key: '-',
commandCtrlKey: false,
callback: onZoomOut,
preventDefault: true,
triggerIfInputFieldFocused: false,
keepRegisteredWhenNotHighestContext: false,
});
return () => {
resetBinding.unregister();
zoomIn.unregister();
zoomOut.unregister();
};
}, [keybindings, onReset, onZoomIn, onZoomOut]);
const fetchMetadata = (0, react_1.useCallback)(async () => {
setAssetResolution(null);
if (canvasContent.type === 'composition') {
return;
}
const metadata = await (0, get_asset_metadata_1.getAssetMetadata)(canvasContent, canvasContent.type === 'asset');
setAssetResolution(metadata);
}, [canvasContent]);
(0, react_1.useEffect)(() => {
if (canvasContent.type !== 'asset') {
return;
}
const file = (0, remotion_1.watchStaticFile)(canvasContent.asset, () => {
fetchMetadata();
});
return () => {
file.cancel();
};
}, [canvasContent, fetchMetadata]);
(0, react_1.useEffect)(() => {
fetchMetadata();
}, [fetchMetadata]);
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { ref: canvas_ref_1.canvasRef, style: container, children: [size ? ((0, jsx_runtime_1.jsx)(Preview_1.VideoPreview, { canvasContent: canvasContent, contentDimensions: contentDimensions, canvasSize: size, assetMetadata: assetResolution })) : null, isFit ? null : ((0, jsx_runtime_1.jsx)("div", { style: resetZoom, className: "css-reset", children: (0, jsx_runtime_1.jsx)(ResetZoomButton_1.ResetZoomButton, { onClick: onReset }) })), editorShowGuides && canvasContent.type === 'composition' && ((0, jsx_runtime_1.jsx)(EditorGuides_1.default, { canvasSize: size, contentDimensions: contentDimensions, assetMetadata: assetResolution }))] }), areRulersVisible && ((0, jsx_runtime_1.jsx)(EditorRuler_1.EditorRulers, { contentDimensions: contentDimensions, canvasSize: size, assetMetadata: assetResolution, containerRef: canvas_ref_1.canvasRef }))] }));
};
exports.Canvas = Canvas;