@remotion/studio
Version:
APIs for interacting with the Remotion Studio
473 lines (472 loc) • 19.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineDragHandler = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const player_1 = require("@remotion/player");
const react_1 = require("react");
const remotion_1 = require("remotion");
const get_left_of_timeline_slider_1 = require("../../helpers/get-left-of-timeline-slider");
const timeline_layout_1 = require("../../helpers/timeline-layout");
const in_out_1 = require("../../state/in-out");
const timeline_zoom_1 = require("../../state/timeline-zoom");
const z_index_1 = require("../../state/z-index");
const ContextMenu_1 = require("../ContextMenu");
const ForceSpecificCursor_1 = require("../ForceSpecificCursor");
const is_menu_item_1 = require("../Menu/is-menu-item");
const TimelineInOutToggle_1 = require("../TimelineInOutToggle");
const timeline_refs_1 = require("./timeline-refs");
const timeline_scroll_logic_1 = require("./timeline-scroll-logic");
const TimelineInOutPointer_1 = require("./TimelineInOutPointer");
const TimelineInOutPointerHandle_1 = require("./TimelineInOutPointerHandle");
const TimelineSlider_1 = require("./TimelineSlider");
const TimelineWidthProvider_1 = require("./TimelineWidthProvider");
const inner = {
overflowY: 'auto',
overflowX: 'hidden',
};
const container = {
userSelect: 'none',
WebkitUserSelect: 'none',
position: 'absolute',
height: '100%',
top: 0,
};
const style = {
width: '100%',
height: '100%',
userSelect: 'none',
WebkitUserSelect: 'none',
};
const getClientXWithScroll = (x) => {
var _a;
return x + ((_a = timeline_refs_1.scrollableRef.current) === null || _a === void 0 ? void 0 : _a.scrollLeft);
};
const TimelineDragHandler = () => {
const video = remotion_1.Internals.useUnsafeVideoConfig();
const { zoom: zoomMap } = (0, react_1.useContext)(timeline_zoom_1.TimelineZoomCtx);
const { canvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager);
const containerStyle = (0, react_1.useMemo)(() => {
var _a;
if (!canvasContent || canvasContent.type !== 'composition') {
return {};
}
const zoom = (_a = zoomMap[canvasContent.compositionId]) !== null && _a !== void 0 ? _a : timeline_zoom_1.TIMELINE_MIN_ZOOM;
return {
...container,
width: 100 * zoom + '%',
};
}, [canvasContent, zoomMap]);
if (!canvasContent || canvasContent.type !== 'composition') {
return null;
}
return (jsx_runtime_1.jsx("div", { ref: timeline_refs_1.sliderAreaRef, style: containerStyle, children: video ? jsx_runtime_1.jsx(Inner, {}) : null }));
};
exports.TimelineDragHandler = TimelineDragHandler;
const Inner = () => {
var _a;
var _b, _c;
const videoConfig = (0, remotion_1.useVideoConfig)();
const size = player_1.PlayerInternals.useElementSize(timeline_refs_1.scrollableRef, {
triggerOnWindowResize: true,
shouldApplyCssTransforms: true,
});
const { isHighestContext } = (0, z_index_1.useZIndex)();
const setFrame = remotion_1.Internals.useTimelineSetFrame();
const [inOutDragging, setInOutDragging] = (0, react_1.useState)({
dragging: false,
});
const timelineWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
const get = (0, react_1.useCallback)((frame) => {
if (timelineWidth === null) {
throw new Error('timeline width is not yet determined');
}
return (0, get_left_of_timeline_slider_1.getXPositionOfItemInTimelineImperatively)(frame, videoConfig.durationInFrames, timelineWidth);
}, [timelineWidth, videoConfig.durationInFrames]);
const width = (_b = (_a = timeline_refs_1.scrollableRef.current) === null || _a === void 0 ? void 0 : _a.scrollWidth) !== null && _b !== void 0 ? _b : 0;
const left = (_c = size === null || size === void 0 ? void 0 : size.left) !== null && _c !== void 0 ? _c : 0;
const { inFrame, outFrame } = (0, in_out_1.useTimelineInOutFramePosition)();
const { setInAndOutFrames } = (0, in_out_1.useTimelineSetInOutFramePosition)();
const [dragging, setDragging] = (0, react_1.useState)({
dragging: false,
});
const { playing, play, pause, seek } = player_1.PlayerInternals.usePlayer();
const scroller = (0, react_1.useRef)(null);
const stopInterval = () => {
if (scroller.current) {
clearInterval(scroller.current);
scroller.current = null;
}
};
const onPointerDown = (0, react_1.useCallback)((e) => {
if (e.button !== 0) {
return;
}
if (!isHighestContext) {
return;
}
stopInterval();
if (!videoConfig) {
return;
}
document.body.style.userSelect = 'none';
document.body.style.webkitUserSelect = 'none';
if (e.target === TimelineInOutPointerHandle_1.inPointerHandle.current) {
if (inFrame === null) {
throw new Error('expected outframe');
}
const inMarker = get(inFrame);
const outMarker = outFrame === null ? Infinity : get(outFrame - 1);
(0, ForceSpecificCursor_1.forceSpecificCursor)('ew-resize');
setInOutDragging({
dragging: 'in',
initialOffset: getClientXWithScroll(e.clientX),
boundaries: [-Infinity, outMarker - inMarker],
});
return;
}
if (e.target === TimelineInOutPointerHandle_1.outPointerHandle.current) {
if (outFrame === null) {
throw new Error('expected outframe');
}
const outMarker = get(outFrame);
const inMarker = inFrame === null ? -Infinity : get(inFrame + 1);
(0, ForceSpecificCursor_1.forceSpecificCursor)('ew-resize');
setInOutDragging({
dragging: 'out',
initialOffset: getClientXWithScroll(e.clientX),
boundaries: [inMarker - outMarker, Infinity],
});
return;
}
if (e.button !== 0) {
return;
}
const frame = (0, timeline_scroll_logic_1.getFrameFromX)({
clientX: getClientXWithScroll(e.clientX) - left,
durationInFrames: videoConfig.durationInFrames,
width,
extrapolate: 'clamp',
});
seek(frame);
setDragging({
dragging: true,
wasPlaying: playing,
});
pause();
}, [
isHighestContext,
videoConfig,
left,
width,
seek,
playing,
pause,
inFrame,
get,
outFrame,
]);
const onPointerMoveScrubbing = (0, react_1.useCallback)((e) => {
var _a;
if (!videoConfig) {
return;
}
if (!dragging.dragging) {
return;
}
const isRightOfArea = e.clientX >=
((_a = timeline_refs_1.scrollableRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth) +
left -
timeline_layout_1.TIMELINE_PADDING;
const isLeftOfArea = e.clientX <= left;
const frame = (0, timeline_scroll_logic_1.getFrameFromX)({
clientX: getClientXWithScroll(e.clientX) - left,
durationInFrames: videoConfig.durationInFrames,
width,
extrapolate: 'clamp',
});
if (isLeftOfArea && (0, timeline_scroll_logic_1.canScrollTimelineIntoDirection)().canScrollLeft) {
if (scroller.current) {
return;
}
const scrollEvery = () => {
var _a;
if (!(0, timeline_scroll_logic_1.canScrollTimelineIntoDirection)().canScrollLeft) {
stopInterval();
return;
}
const nextFrame = (0, timeline_scroll_logic_1.getFrameWhileScrollingLeft)({
durationInFrames: videoConfig.durationInFrames,
width,
});
const scrollPos = (0, timeline_scroll_logic_1.getScrollPositionForCursorOnLeftEdge)({
nextFrame,
durationInFrames: videoConfig.durationInFrames,
});
(_a = TimelineSlider_1.redrawTimelineSliderFast.current) === null || _a === void 0 ? void 0 : _a.draw(nextFrame);
seek(nextFrame);
(0, timeline_scroll_logic_1.scrollToTimelineXOffset)(scrollPos);
};
scrollEvery();
scroller.current = setInterval(() => {
scrollEvery();
}, 100);
}
else if (isRightOfArea &&
(0, timeline_scroll_logic_1.canScrollTimelineIntoDirection)().canScrollRight) {
if (scroller.current) {
return;
}
const scrollEvery = () => {
var _a;
if (!(0, timeline_scroll_logic_1.canScrollTimelineIntoDirection)().canScrollRight) {
stopInterval();
return;
}
const nextFrame = (0, timeline_scroll_logic_1.getFrameWhileScrollingRight)({
durationInFrames: videoConfig.durationInFrames,
width,
});
const scrollPos = (0, timeline_scroll_logic_1.getScrollPositionForCursorOnRightEdge)({
nextFrame,
durationInFrames: videoConfig.durationInFrames,
});
(_a = TimelineSlider_1.redrawTimelineSliderFast.current) === null || _a === void 0 ? void 0 : _a.draw(nextFrame);
seek(nextFrame);
(0, timeline_scroll_logic_1.scrollToTimelineXOffset)(scrollPos);
};
scrollEvery();
scroller.current = setInterval(() => {
scrollEvery();
}, 100);
}
else {
stopInterval();
seek(frame);
}
}, [videoConfig, dragging.dragging, left, width, seek]);
const onPointerMoveInOut = (0, react_1.useCallback)((e) => {
if (!videoConfig) {
return;
}
if (!inOutDragging.dragging) {
return;
}
const offset = Math.max(inOutDragging.boundaries[0], Math.min(inOutDragging.boundaries[1], getClientXWithScroll(e.clientX) - inOutDragging.initialOffset));
if (inOutDragging.dragging === 'in') {
if (!TimelineInOutPointerHandle_1.inPointerHandle.current) {
throw new Error('in pointer handle');
}
if (!TimelineInOutPointer_1.inMarkerAreaRef.current) {
throw new Error('expected inMarkerAreaRef');
}
if (!inFrame) {
throw new Error('expected inframes');
}
TimelineInOutPointerHandle_1.inPointerHandle.current.style.transform = `translateX(${get(inFrame) + offset}px)`;
TimelineInOutPointer_1.inMarkerAreaRef.current.style.width =
String(get(inFrame) + offset) + 'px';
}
if (inOutDragging.dragging === 'out') {
if (!TimelineInOutPointerHandle_1.outPointerHandle.current) {
throw new Error('in pointer handle');
}
if (!TimelineInOutPointer_1.outMarkerAreaRef.current) {
throw new Error('in outMarkerAreaRef');
}
if (!outFrame) {
throw new Error('expected outframes');
}
TimelineInOutPointerHandle_1.outPointerHandle.current.style.transform = `translateX(${get(outFrame) + offset}px)`;
TimelineInOutPointer_1.outMarkerAreaRef.current.style.left =
String(get(outFrame) + offset) + 'px';
TimelineInOutPointer_1.outMarkerAreaRef.current.style.width =
String(width - get(outFrame) - offset) + 'px';
}
}, [get, inFrame, inOutDragging, outFrame, videoConfig, width]);
const onPointerUpScrubbing = (0, react_1.useCallback)((e) => {
stopInterval();
document.body.style.userSelect = '';
document.body.style.webkitUserSelect = '';
if (!videoConfig) {
return;
}
if (!dragging.dragging) {
return;
}
setDragging({
dragging: false,
});
const frame = (0, timeline_scroll_logic_1.getFrameFromX)({
clientX: getClientXWithScroll(e.clientX) - left,
durationInFrames: videoConfig.durationInFrames,
width,
extrapolate: 'clamp',
});
setFrame((c) => {
const newObj = { ...c, [videoConfig.id]: frame };
remotion_1.Internals.persistCurrentFrame(newObj);
return newObj;
});
if (dragging.wasPlaying) {
play();
}
}, [dragging, left, play, videoConfig, setFrame, width]);
const onPointerUpInOut = (0, react_1.useCallback)((e) => {
document.body.style.userSelect = '';
document.body.style.webkitUserSelect = '';
(0, ForceSpecificCursor_1.stopForcingSpecificCursor)();
if (!videoConfig) {
return;
}
if (!inOutDragging.dragging) {
return;
}
setInOutDragging({
dragging: false,
});
const frame = (0, timeline_scroll_logic_1.getFrameFromX)({
clientX: getClientXWithScroll(e.clientX) - left,
durationInFrames: videoConfig.durationInFrames,
width,
extrapolate: 'extend',
});
if (inOutDragging.dragging === 'in') {
if (frame < 1) {
return setInAndOutFrames((prev) => {
var _a;
return ({
...prev,
[videoConfig.id]: {
...((_a = prev[videoConfig.id]) !== null && _a !== void 0 ? _a : TimelineInOutToggle_1.defaultInOutValue),
inFrame: null,
},
});
});
}
const maxFrame = outFrame === null ? Infinity : outFrame - 1;
setInAndOutFrames((prev) => {
var _a;
return ({
...prev,
[videoConfig.id]: {
...((_a = prev[videoConfig.id]) !== null && _a !== void 0 ? _a : TimelineInOutToggle_1.defaultInOutValue),
inFrame: Math.min(maxFrame, frame),
},
});
});
}
else {
if (frame > videoConfig.durationInFrames - 2) {
return setInAndOutFrames((prev) => {
var _a;
return ({
...prev,
[videoConfig.id]: {
...((_a = prev[videoConfig.id]) !== null && _a !== void 0 ? _a : TimelineInOutToggle_1.defaultInOutValue),
outFrame: null,
},
});
});
}
const minFrame = inFrame === null ? -Infinity : inFrame + 1;
setInAndOutFrames((prev) => {
var _a;
return ({
...prev,
[videoConfig.id]: {
...((_a = prev[videoConfig.id]) !== null && _a !== void 0 ? _a : TimelineInOutToggle_1.defaultInOutValue),
outFrame: Math.max(minFrame, frame),
},
});
});
}
}, [
inFrame,
inOutDragging.dragging,
left,
outFrame,
setInAndOutFrames,
videoConfig,
width,
]);
(0, react_1.useEffect)(() => {
if (!dragging.dragging) {
return;
}
window.addEventListener('pointermove', onPointerMoveScrubbing);
window.addEventListener('pointerup', onPointerUpScrubbing);
return () => {
window.removeEventListener('pointermove', onPointerMoveScrubbing);
window.removeEventListener('pointerup', onPointerUpScrubbing);
};
}, [dragging.dragging, onPointerMoveScrubbing, onPointerUpScrubbing]);
(0, react_1.useEffect)(() => {
if (inOutDragging.dragging === false) {
return;
}
window.addEventListener('pointermove', onPointerMoveInOut);
window.addEventListener('pointerup', onPointerUpInOut);
return () => {
window.removeEventListener('pointermove', onPointerMoveInOut);
window.removeEventListener('pointerup', onPointerUpInOut);
};
}, [inOutDragging.dragging, onPointerMoveInOut, onPointerUpInOut]);
const inContextMenu = (0, react_1.useMemo)(() => {
return [
{
id: 'hide-in',
keyHint: null,
label: 'Clear In marker',
leftItem: null,
onClick: (_, e) => {
e === null || e === void 0 ? void 0 : e.stopPropagation();
e === null || e === void 0 ? void 0 : e.preventDefault();
setInAndOutFrames((prev) => {
var _a;
return ({
...prev,
[videoConfig.id]: {
...((_a = prev[videoConfig.id]) !== null && _a !== void 0 ? _a : TimelineInOutToggle_1.defaultInOutValue),
inFrame: null,
},
});
});
},
quickSwitcherLabel: null,
subMenu: null,
type: 'item',
value: 'hide-in',
},
];
}, [setInAndOutFrames, videoConfig.id]);
const outContextMenu = (0, react_1.useMemo)(() => {
return [
{
id: 'hide-out',
keyHint: null,
label: 'Clear Out marker',
leftItem: null,
onClick: (_, e) => {
e === null || e === void 0 ? void 0 : e.stopPropagation();
e === null || e === void 0 ? void 0 : e.preventDefault();
setInAndOutFrames((prev) => {
var _a;
return ({
...prev,
[videoConfig.id]: {
...((_a = prev[videoConfig.id]) !== null && _a !== void 0 ? _a : TimelineInOutToggle_1.defaultInOutValue),
outFrame: null,
},
});
});
},
quickSwitcherLabel: null,
subMenu: null,
type: 'item',
value: 'hide-out',
},
];
}, [setInAndOutFrames, videoConfig.id]);
return (jsx_runtime_1.jsxs("div", { style: style, onPointerDown: onPointerDown, children: [
jsx_runtime_1.jsx("div", { style: inner, className: is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME }), inFrame !== null && (jsx_runtime_1.jsx(ContextMenu_1.ContextMenu, { values: inContextMenu, children: jsx_runtime_1.jsx(TimelineInOutPointerHandle_1.TimelineInOutPointerHandle, { type: "in", atFrame: inFrame, dragging: inOutDragging.dragging === 'in' }) })), outFrame !== null && (jsx_runtime_1.jsx(ContextMenu_1.ContextMenu, { values: outContextMenu, children: jsx_runtime_1.jsx(TimelineInOutPointerHandle_1.TimelineInOutPointerHandle, { type: "out", dragging: inOutDragging.dragging === 'out', atFrame: outFrame }) }))] }));
};