init commit

This commit is contained in:
Carlos
2026-02-21 10:33:18 +01:00
parent c863a943ed
commit 9d955bf338
9512 changed files with 2015317 additions and 1305 deletions

View File

@@ -0,0 +1,2 @@
import React from 'react';
export declare const LoopedIndicator: React.FC;

View File

@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoopedIndicator = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const remotion_1 = require("remotion");
const colors_1 = require("../../helpers/colors");
const width = {
width: 0,
flexDirection: 'row',
display: 'flex',
position: 'relative',
};
const icon = {
height: 12,
};
const Icon = () => ((0, jsx_runtime_1.jsx)("svg", { viewBox: "0 0 512 512", style: icon, children: (0, jsx_runtime_1.jsx)("path", { fill: colors_1.LIGHT_COLOR, d: "M512 256c0 88.224-71.775 160-160 160H170.067l34.512 32.419c9.875 9.276 10.119 24.883.539 34.464l-10.775 10.775c-9.373 9.372-24.568 9.372-33.941 0l-92.686-92.686c-9.373-9.373-9.373-24.568 0-33.941l92.686-92.686c9.373-9.373 24.568-9.373 33.941 0l10.775 10.775c9.581 9.581 9.337 25.187-.539 34.464L170.067 352H352c52.935 0 96-43.065 96-96 0-13.958-2.996-27.228-8.376-39.204-4.061-9.039-2.284-19.626 4.723-26.633l12.183-12.183c11.499-11.499 30.965-8.526 38.312 5.982C505.814 205.624 512 230.103 512 256zM72.376 295.204C66.996 283.228 64 269.958 64 256c0-52.935 43.065-96 96-96h181.933l-34.512 32.419c-9.875 9.276-10.119 24.883-.539 34.464l10.775 10.775c9.373 9.372 24.568 9.372 33.941 0l92.686-92.686c9.373-9.373 9.373-24.568 0-33.941l-92.686-92.686c-9.373-9.373-24.568-9.373-33.941 0L306.882 29.12c-9.581 9.581-9.337 25.187.539 34.464L341.933 96H160C71.775 96 0 167.776 0 256c0 25.897 6.186 50.376 17.157 72.039 7.347 14.508 26.813 17.481 38.312 5.982l12.183-12.183c7.008-7.008 8.786-17.595 4.724-26.634z" }) }));
const topLine = {
top: 0,
height: 2,
width: 1,
background: colors_1.LIGHT_COLOR,
};
const bottomLine = {
top: 0,
height: 2,
width: 1,
background: colors_1.LIGHT_COLOR,
};
const topContainer = {
justifyContent: 'flex-start',
alignItems: 'center',
};
const centerContainer = {
justifyContent: 'center',
alignItems: 'center',
};
const bottomContainer = {
justifyContent: 'flex-end',
alignItems: 'center',
};
const LoopedIndicator = () => {
return ((0, jsx_runtime_1.jsxs)("div", { style: width, children: [(0, jsx_runtime_1.jsx)(remotion_1.AbsoluteFill, { style: topContainer, children: (0, jsx_runtime_1.jsx)("div", { style: topLine }) }), (0, jsx_runtime_1.jsx)(remotion_1.AbsoluteFill, { style: bottomContainer, children: (0, jsx_runtime_1.jsx)("div", { style: bottomLine }) }), (0, jsx_runtime_1.jsx)(remotion_1.AbsoluteFill, { style: centerContainer, children: (0, jsx_runtime_1.jsx)(Icon, {}) })] }));
};
exports.LoopedIndicator = LoopedIndicator;

View File

@@ -0,0 +1,4 @@
import React from 'react';
export declare const LoopedTimelineIndicator: React.FC<{
readonly loops: number;
}>;

View File

@@ -0,0 +1,23 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoopedTimelineIndicator = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = __importDefault(require("react"));
const remotion_1 = require("remotion");
const layout_1 = require("../layout");
const LoopedIndicator_1 = require("./LoopedIndicator");
const row = {
flexDirection: 'row',
};
const LoopedTimelineIndicator = ({ loops }) => {
const leftOver = loops % 1;
return ((0, jsx_runtime_1.jsxs)(remotion_1.AbsoluteFill, { style: row, children: [new Array(Math.floor(loops)).fill(true).map((_l, i) => {
return ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment
// eslint-disable-next-line
, { children: [(0, jsx_runtime_1.jsx)(layout_1.Flex, {}), i === loops - 1 ? null : (0, jsx_runtime_1.jsx)(LoopedIndicator_1.LoopedIndicator, {})] }, i));
}), leftOver > 0 ? (0, jsx_runtime_1.jsx)("div", { style: { flex: leftOver } }) : null] }));
};
exports.LoopedTimelineIndicator = LoopedTimelineIndicator;

View File

@@ -0,0 +1,4 @@
import React from 'react';
export declare const MAX_TIMELINE_TRACKS: number;
export declare const MAX_TIMELINE_TRACKS_NOTICE_HEIGHT = 24;
export declare const MaxTimelineTracksReached: React.FC;

View File

@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MaxTimelineTracksReached = exports.MAX_TIMELINE_TRACKS_NOTICE_HEIGHT = exports.MAX_TIMELINE_TRACKS = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const studio_shared_1 = require("@remotion/studio-shared");
const timeline_layout_1 = require("../../helpers/timeline-layout");
exports.MAX_TIMELINE_TRACKS = typeof process.env.MAX_TIMELINE_TRACKS === 'undefined' ||
process.env.MAX_TIMELINE_TRACKS === null
? studio_shared_1.DEFAULT_TIMELINE_TRACKS
: Number(process.env.MAX_TIMELINE_TRACKS);
exports.MAX_TIMELINE_TRACKS_NOTICE_HEIGHT = 24;
const container = {
height: exports.MAX_TIMELINE_TRACKS_NOTICE_HEIGHT,
display: 'flex',
alignItems: 'center',
color: 'rgba(255, 255, 255, 0.6)',
fontFamily: 'sans-serif',
fontSize: 12,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
paddingLeft: timeline_layout_1.TIMELINE_PADDING + 5,
};
const MaxTimelineTracksReached = () => {
return ((0, jsx_runtime_1.jsxs)("div", { style: container, children: ["Limited display to ", exports.MAX_TIMELINE_TRACKS, " tracks to sustain performance.", '', "You can change this by setting Config.setMaxTimelineTracks() in your remotion.config.ts file."] }));
};
exports.MaxTimelineTracksReached = MaxTimelineTracksReached;

View File

@@ -0,0 +1,2 @@
import React from 'react';
export declare const Timeline: React.FC;

View File

@@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Timeline = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const remotion_1 = require("remotion");
const calculate_timeline_1 = require("../../helpers/calculate-timeline");
const colors_1 = require("../../helpers/colors");
const timeline_layout_1 = require("../../helpers/timeline-layout");
const is_menu_item_1 = require("../Menu/is-menu-item");
const SplitterContainer_1 = require("../Splitter/SplitterContainer");
const SplitterElement_1 = require("../Splitter/SplitterElement");
const SplitterHandle_1 = require("../Splitter/SplitterHandle");
const MaxTimelineTracks_1 = require("./MaxTimelineTracks");
const TimelineDragHandler_1 = require("./TimelineDragHandler");
const TimelineInOutPointer_1 = require("./TimelineInOutPointer");
const TimelineList_1 = require("./TimelineList");
const TimelinePlayCursorSyncer_1 = require("./TimelinePlayCursorSyncer");
const TimelineScrollable_1 = require("./TimelineScrollable");
const TimelineSlider_1 = require("./TimelineSlider");
const TimelineTimeIndicators_1 = require("./TimelineTimeIndicators");
const TimelineTracks_1 = require("./TimelineTracks");
const TimelineWidthProvider_1 = require("./TimelineWidthProvider");
const is_collapsed_1 = require("./is-collapsed");
const timeline_refs_1 = require("./timeline-refs");
const container = {
minHeight: '100%',
flex: 1,
display: 'flex',
height: 0,
overflowY: 'auto',
backgroundColor: colors_1.BACKGROUND,
};
const noop = () => undefined;
const Timeline = () => {
var _a;
const { sequences } = (0, react_1.useContext)(remotion_1.Internals.SequenceManager);
const videoConfig = remotion_1.Internals.useUnsafeVideoConfig();
const timeline = (0, react_1.useMemo)(() => {
if (!videoConfig) {
return [];
}
return (0, calculate_timeline_1.calculateTimeline)({
sequences,
sequenceDuration: videoConfig.durationInFrames,
});
}, [sequences, videoConfig]);
const durationInFrames = (_a = videoConfig === null || videoConfig === void 0 ? void 0 : videoConfig.durationInFrames) !== null && _a !== void 0 ? _a : 0;
const filtered = (0, react_1.useMemo)(() => {
const withoutHidden = timeline.filter((t) => !(0, is_collapsed_1.isTrackHidden)(t));
const withoutAfter = withoutHidden.filter((t) => {
return t.sequence.from <= durationInFrames && t.sequence.duration > 0;
});
return withoutAfter.filter((t) => t.sequence.showInTimeline);
}, [durationInFrames, timeline]);
const shown = filtered.slice(0, MaxTimelineTracks_1.MAX_TIMELINE_TRACKS);
const hasBeenCut = filtered.length > shown.length;
const inner = (0, react_1.useMemo)(() => {
return {
height: shown.reduce((acc, track) => {
return (acc +
(0, timeline_layout_1.getTimelineLayerHeight)(track.sequence.type === 'video' ? 'video' : 'other') +
Number(timeline_layout_1.TIMELINE_ITEM_BORDER_BOTTOM));
}, 0) +
timeline_layout_1.TIMELINE_ITEM_BORDER_BOTTOM +
(hasBeenCut ? MaxTimelineTracks_1.MAX_TIMELINE_TRACKS_NOTICE_HEIGHT : 0) +
TimelineTimeIndicators_1.TIMELINE_TIME_INDICATOR_HEIGHT,
display: 'flex',
flex: 1,
minHeight: '100%',
overflowX: 'hidden',
};
}, [hasBeenCut, shown]);
return ((0, jsx_runtime_1.jsx)("div", { ref: timeline_refs_1.timelineVerticalScroll, style: container, className: 'css-reset ' + is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME, children: (0, jsx_runtime_1.jsx)(TimelineWidthProvider_1.TimelineWidthProvider, { children: (0, jsx_runtime_1.jsx)("div", { style: inner, children: (0, jsx_runtime_1.jsxs)(SplitterContainer_1.SplitterContainer, { orientation: "vertical", defaultFlex: 0.2, id: "names-to-timeline", maxFlex: 0.5, minFlex: 0.15, children: [(0, jsx_runtime_1.jsx)(SplitterElement_1.SplitterElement, { type: "flexer", sticky: (0, jsx_runtime_1.jsx)(TimelineTimeIndicators_1.TimelineTimePlaceholders, {}), children: (0, jsx_runtime_1.jsx)(TimelineList_1.TimelineList, { timeline: shown }) }), (0, jsx_runtime_1.jsx)(SplitterHandle_1.SplitterHandle, { onCollapse: noop, allowToCollapse: "none" }), (0, jsx_runtime_1.jsx)(SplitterElement_1.SplitterElement, { type: "anti-flexer", sticky: null, children: (0, jsx_runtime_1.jsxs)(TimelineScrollable_1.TimelineScrollable, { children: [(0, jsx_runtime_1.jsx)(TimelineTracks_1.TimelineTracks, { timeline: shown, hasBeenCut: hasBeenCut }), (0, jsx_runtime_1.jsx)(TimelineInOutPointer_1.TimelineInOutPointer, {}), (0, jsx_runtime_1.jsx)(TimelinePlayCursorSyncer_1.TimelinePlayCursorSyncer, {}), (0, jsx_runtime_1.jsx)(TimelineDragHandler_1.TimelineDragHandler, {}), (0, jsx_runtime_1.jsx)(TimelineTimeIndicators_1.TimelineTimeIndicators, {}), (0, jsx_runtime_1.jsx)(TimelineSlider_1.TimelineSlider, {})] }) })] }) }) }) }));
};
exports.Timeline = Timeline;

View File

@@ -0,0 +1,5 @@
import React from 'react';
export declare const TimelineCollapseToggle: React.FC<{
readonly collapsed: boolean;
readonly color: string;
}>;

View File

@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineCollapseToggle = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const container = {
height: 10,
width: 10,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
};
const Icon = ({ color, ...props }) => {
return ((0, jsx_runtime_1.jsx)("svg", { viewBox: "0 0 8 10", ...props, style: { height: 10, width: 8 }, children: (0, jsx_runtime_1.jsx)("path", { d: "M 0 0 L 8 5 L 0 10 z", fill: color }) }));
};
const TimelineCollapseToggle = ({ collapsed, color }) => {
return ((0, jsx_runtime_1.jsx)("div", { style: collapsed ? container : { ...container, transform: 'rotate(90deg)' }, children: (0, jsx_runtime_1.jsx)(Icon, { color: color }) }));
};
exports.TimelineCollapseToggle = TimelineCollapseToggle;

View File

@@ -0,0 +1,2 @@
import React from 'react';
export declare const TimelineDragHandler: React.FC;

View File

@@ -0,0 +1,460 @@
"use strict";
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 is_menu_item_1 = require("../Menu/is-menu-item");
const TimelineInOutToggle_1 = require("../TimelineInOutToggle");
const TimelineInOutPointer_1 = require("./TimelineInOutPointer");
const TimelineInOutPointerHandle_1 = require("./TimelineInOutPointerHandle");
const TimelineSlider_1 = require("./TimelineSlider");
const TimelineWidthProvider_1 = require("./TimelineWidthProvider");
const timeline_refs_1 = require("./timeline-refs");
const timeline_scroll_logic_1 = require("./timeline-scroll-logic");
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 ((0, jsx_runtime_1.jsx)("div", { ref: timeline_refs_1.sliderAreaRef, style: containerStyle, children: video ? (0, jsx_runtime_1.jsx)(Inner, {}) : null }));
};
exports.TimelineDragHandler = TimelineDragHandler;
const Inner = () => {
var _a, _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;
}
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);
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);
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();
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) => {
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 ((0, jsx_runtime_1.jsxs)("div", { style: style, onPointerDown: onPointerDown, children: [(0, jsx_runtime_1.jsx)("div", { style: inner, className: is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME }), inFrame !== null && ((0, jsx_runtime_1.jsx)(ContextMenu_1.ContextMenu, { values: inContextMenu, children: (0, jsx_runtime_1.jsx)(TimelineInOutPointerHandle_1.TimelineInOutPointerHandle, { type: "in", atFrame: inFrame, dragging: inOutDragging.dragging === 'in' }) })), outFrame !== null && ((0, jsx_runtime_1.jsx)(ContextMenu_1.ContextMenu, { values: outContextMenu, children: (0, jsx_runtime_1.jsx)(TimelineInOutPointerHandle_1.TimelineInOutPointerHandle, { type: "out", dragging: inOutDragging.dragging === 'out', atFrame: outFrame }) }))] }));
};

View File

@@ -0,0 +1,4 @@
import React from 'react';
export declare const inMarkerAreaRef: React.RefObject<HTMLDivElement | null>;
export declare const outMarkerAreaRef: React.RefObject<HTMLDivElement | null>;
export declare const TimelineInOutPointer: React.FC;

View File

@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineInOutPointer = exports.outMarkerAreaRef = exports.inMarkerAreaRef = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
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 in_out_1 = require("../../state/in-out");
const TimelineWidthProvider_1 = require("./TimelineWidthProvider");
const areaHighlight = {
position: 'absolute',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
height: '100%',
bottom: 0,
top: 0,
};
exports.inMarkerAreaRef = (0, react_1.createRef)();
exports.outMarkerAreaRef = (0, react_1.createRef)();
const TimelineInOutPointer = () => {
const { inFrame, outFrame } = (0, in_out_1.useTimelineInOutFramePosition)();
const videoConfig = remotion_1.Internals.useUnsafeVideoConfig();
const timelineWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
if (!videoConfig || timelineWidth === null) {
return null;
}
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [inFrame !== null && ((0, jsx_runtime_1.jsx)("div", { ref: exports.inMarkerAreaRef, style: {
...areaHighlight,
left: 0,
width: (0, get_left_of_timeline_slider_1.getXPositionOfItemInTimelineImperatively)(inFrame, videoConfig.durationInFrames, timelineWidth),
} })), outFrame !== null && ((0, jsx_runtime_1.jsx)("div", { ref: exports.outMarkerAreaRef, style: {
...areaHighlight,
left: (0, get_left_of_timeline_slider_1.getXPositionOfItemInTimelineImperatively)(outFrame, videoConfig.durationInFrames, timelineWidth),
width: timelineWidth -
(0, get_left_of_timeline_slider_1.getXPositionOfItemInTimelineImperatively)(outFrame, videoConfig.durationInFrames, timelineWidth),
} }))] }));
};
exports.TimelineInOutPointer = TimelineInOutPointer;

View File

@@ -0,0 +1,10 @@
import React from 'react';
export declare const inPointerHandle: React.RefObject<HTMLDivElement | null>;
export declare const outPointerHandle: React.RefObject<HTMLDivElement | null>;
type Props = {
readonly dragging: boolean;
readonly type: 'in' | 'out';
readonly atFrame: number;
};
export declare const TimelineInOutPointerHandle: React.FC<Props>;
export {};

View File

@@ -0,0 +1,43 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineInOutPointerHandle = exports.outPointerHandle = exports.inPointerHandle = 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_left_of_timeline_slider_1 = require("../../helpers/get-left-of-timeline-slider");
const TimelineWidthProvider_1 = require("./TimelineWidthProvider");
const line = {
height: '100%',
width: 1,
position: 'absolute',
backgroundColor: 'rgba(255, 255, 255, 0.1)',
cursor: 'ew-resize',
paddingLeft: 1,
paddingRight: 1,
};
exports.inPointerHandle = (0, react_1.createRef)();
exports.outPointerHandle = (0, react_1.createRef)();
const InnerTimelineInOutPointerHandle = ({ atFrame, dragging, timelineWidth, type }) => {
const videoConfig = (0, remotion_1.useVideoConfig)();
const style = (0, react_1.useMemo)(() => {
return {
...line,
backgroundColor: dragging
? colors_1.LIGHT_TRANSPARENT
: 'rgba(255, 255, 255, 0.1)',
transform: `translateX(${(0, get_left_of_timeline_slider_1.getXPositionOfItemInTimelineImperatively)(atFrame, videoConfig.durationInFrames, timelineWidth)}px)`,
};
}, [atFrame, dragging, timelineWidth, videoConfig.durationInFrames]);
return ((0, jsx_runtime_1.jsx)("div", { ref: type === 'in' ? exports.inPointerHandle : exports.outPointerHandle, style: style }));
};
const TimelineInOutPointerHandle = ({ dragging, type, atFrame, }) => {
const timelineWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
// When switching from a content which has no timeline (still or asset preview)
// the timeline first needs to mount, so we need to wait for the timeline width
if (timelineWidth === null) {
return null;
}
return ((0, jsx_runtime_1.jsx)(InnerTimelineInOutPointerHandle, { atFrame: atFrame, dragging: dragging, timelineWidth: timelineWidth, type: type }));
};
exports.TimelineInOutPointerHandle = TimelineInOutPointerHandle;

View File

@@ -0,0 +1,5 @@
export declare const TimelineLayerEye: React.FC<{
onInvoked: (type: 'enable' | 'disable') => void;
hidden: boolean;
type: 'eye' | 'speaker';
}>;

View File

@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineLayerEye = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const colors_1 = require("../../helpers/colors");
const eyeIcon = {
width: 12,
color: 'currentColor',
pointerEvents: 'none',
};
const speakerIcon = {
...eyeIcon,
height: 10,
marginLeft: -1,
};
const container = {
height: 16,
width: 16,
borderRadius: 2,
backgroundColor: 'rgba(0, 0, 0, 0.4)',
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
marginRight: 6,
flexShrink: 0,
};
let layerPointedDown = null;
const TimelineLayerEye = ({ onInvoked, hidden, type }) => {
const renderAction = (0, react_1.useCallback)((color) => {
if (hidden) {
return null;
}
if (type === 'speaker') {
return ((0, jsx_runtime_1.jsx)("svg", { viewBox: "0 0 10 14", fill: "none", style: speakerIcon, children: (0, jsx_runtime_1.jsx)("path", { d: "M9.40938 0.0869018C9.76875 0.249402 10 0.605652 10 0.999402V12.9994C10 13.3932 9.76875 13.7494 9.40938 13.9119C9.05 14.0744 8.62813 14.0088 8.33438 13.7463L4.11875 9.9994H2C0.896875 9.9994 0 9.10253 0 7.9994V5.9994C0 4.89628 0.896875 3.9994 2 3.9994H4.11875L8.33438 0.252527C8.62813 -0.0099732 9.05 -0.0724732 9.40938 0.0869018Z", fill: color }) }));
}
return ((0, jsx_runtime_1.jsx)("svg", { style: eyeIcon, viewBox: "0 0 24 16", fill: "none", children: (0, jsx_runtime_1.jsx)("path", { d: "M24 7.551C24 7.551 19.748 16 12.015 16C4.835 16 0 7.551 0 7.551C0 7.551 4.446 0 12.015 0C19.709 0 24 7.551 24 7.551ZM17 8C17 5.243 14.757 3 12 3C9.243 3 7 5.243 7 8C7 10.757 9.243 13 12 13C14.757 13 17 10.757 17 8Z", fill: color }) }));
}, [hidden, type]);
const onPointerDown = (0, react_1.useCallback)((e) => {
if (e.button !== 0) {
return;
}
layerPointedDown = hidden ? 'enable' : 'disable';
onInvoked(layerPointedDown);
window.addEventListener('pointerup', () => {
layerPointedDown = null;
}, { once: true });
}, [hidden, onInvoked]);
const onPointerEnter = (0, react_1.useCallback)(() => {
if (layerPointedDown) {
onInvoked(layerPointedDown);
}
}, [onInvoked]);
return ((0, jsx_runtime_1.jsx)("div", { style: container, onPointerEnter: onPointerEnter, onPointerDown: onPointerDown, children: renderAction(colors_1.LIGHT_COLOR) }));
};
exports.TimelineLayerEye = TimelineLayerEye;

View File

@@ -0,0 +1,5 @@
import React from 'react';
import type { TrackWithHash } from '../../helpers/get-timeline-sequence-sort-key';
export declare const TimelineList: React.FC<{
readonly timeline: TrackWithHash[];
}>;

View File

@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineList = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const player_1 = require("@remotion/player");
const react_1 = require("react");
const colors_1 = require("../../helpers/colors");
const TimelineListItem_1 = require("./TimelineListItem");
const TimelineTimeIndicators_1 = require("./TimelineTimeIndicators");
const container = {
flex: 1,
background: colors_1.BACKGROUND,
};
const TimelineList = ({ timeline }) => {
const ref = (0, react_1.useRef)(null);
const size = player_1.PlayerInternals.useElementSize(ref, {
shouldApplyCssTransforms: false,
triggerOnWindowResize: false,
});
const isCompact = size ? size.width < 250 : false;
return ((0, jsx_runtime_1.jsxs)("div", { ref: ref, style: container, children: [(0, jsx_runtime_1.jsx)(TimelineTimeIndicators_1.TimelineTimePadding, {}), timeline.map((track) => {
return ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(TimelineListItem_1.TimelineListItem, { nestedDepth: track.depth, sequence: track.sequence, isCompact: isCompact }, track.sequence.id) }, track.sequence.id));
})] }));
};
exports.TimelineList = TimelineList;

View File

@@ -0,0 +1,7 @@
import React from 'react';
import type { TSequence } from 'remotion';
export declare const TimelineListItem: React.FC<{
readonly sequence: TSequence;
readonly nestedDepth: number;
readonly isCompact: boolean;
}>;

View File

@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineListItem = 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 timeline_layout_1 = require("../../helpers/timeline-layout");
const TimelineLayerEye_1 = require("./TimelineLayerEye");
const TimelineStack_1 = require("./TimelineStack");
const SPACING = 5;
const space = {
width: SPACING,
flexShrink: 0,
};
const TimelineListItem = ({ nestedDepth, sequence, isCompact }) => {
const { hidden, setHidden } = (0, react_1.useContext)(remotion_1.Internals.SequenceVisibilityToggleContext);
const padder = (0, react_1.useMemo)(() => {
return {
width: Number(SPACING * 1.5) * nestedDepth,
flexShrink: 0,
};
}, [nestedDepth]);
const isItemHidden = (0, react_1.useMemo)(() => {
var _a;
return (_a = hidden[sequence.id]) !== null && _a !== void 0 ? _a : false;
}, [hidden, sequence.id]);
const onToggleVisibility = (0, react_1.useCallback)((type) => {
setHidden((prev) => {
return {
...prev,
[sequence.id]: type !== 'enable',
};
});
}, [sequence.id, setHidden]);
const outer = (0, react_1.useMemo)(() => {
return {
height: (0, timeline_layout_1.getTimelineLayerHeight)(sequence.type === 'video' ? 'video' : 'other') +
timeline_layout_1.TIMELINE_ITEM_BORDER_BOTTOM,
color: 'white',
fontFamily: 'Arial, Helvetica, sans-serif',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
wordBreak: 'break-all',
textAlign: 'left',
paddingLeft: SPACING,
borderBottom: `1px solid ${colors_1.TIMELINE_TRACK_SEPARATOR}`,
};
}, [sequence.type]);
return ((0, jsx_runtime_1.jsxs)("div", { style: outer, children: [(0, jsx_runtime_1.jsx)(TimelineLayerEye_1.TimelineLayerEye, { type: sequence.type === 'audio' ? 'speaker' : 'eye', hidden: isItemHidden, onInvoked: onToggleVisibility }), (0, jsx_runtime_1.jsx)("div", { style: padder }), sequence.parent && nestedDepth > 0 ? (0, jsx_runtime_1.jsx)("div", { style: space }) : null, (0, jsx_runtime_1.jsx)(TimelineStack_1.TimelineStack, { sequence: sequence, isCompact: isCompact })] }));
};
exports.TimelineListItem = TimelineListItem;

View File

@@ -0,0 +1,2 @@
import type React from 'react';
export declare const TimelinePlayCursorSyncer: React.FC;

View File

@@ -0,0 +1,86 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelinePlayCursorSyncer = void 0;
const react_1 = require("react");
const remotion_1 = require("remotion");
const timeline_zoom_1 = require("../../state/timeline-zoom");
const imperative_state_1 = require("./imperative-state");
const timeline_refs_1 = require("./timeline-refs");
const timeline_scroll_logic_1 = require("./timeline-scroll-logic");
let lastTimelinePositionWhileScrolling = null;
const TimelinePlayCursorSyncer = () => {
var _a, _b;
const video = remotion_1.Internals.useVideo();
const timelineContext = (0, react_1.useContext)(remotion_1.Internals.TimelineContext);
const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
const { canvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager);
const { zoom: zoomMap } = (0, react_1.useContext)(timeline_zoom_1.TimelineZoomCtx);
const compositionId = canvasContent && canvasContent.type === 'composition'
? canvasContent.compositionId
: null;
const zoom = compositionId
? ((_a = zoomMap[compositionId]) !== null && _a !== void 0 ? _a : timeline_zoom_1.TIMELINE_MIN_ZOOM)
: null;
if (zoom && video) {
(0, imperative_state_1.setCurrentFrame)(timelinePosition);
(0, imperative_state_1.setCurrentZoom)(zoom);
(0, imperative_state_1.setCurrentDuration)(video.durationInFrames);
(0, imperative_state_1.setCurrentFps)(video.fps);
}
const playing = (_b = timelineContext.playing) !== null && _b !== void 0 ? _b : false;
/**
* While playing (forwards or backwards), jump one viewport width to the left or right when the cursor goes out of the viewport.
*/
(0, react_1.useEffect)(() => {
if (!video) {
return;
}
if (!playing) {
return;
}
(0, timeline_scroll_logic_1.ensureFrameIsInViewport)({
direction: timelineContext.playbackRate > 0 ? 'page-right' : 'page-left',
durationInFrames: video.durationInFrames,
frame: timelinePosition,
});
}, [playing, timelineContext, timelinePosition, video]);
/**
* Restore state if `enter` is being pressed
*/
(0, react_1.useEffect)(() => {
const { current } = timeline_refs_1.scrollableRef;
if (!current) {
return;
}
if (playing) {
lastTimelinePositionWhileScrolling = {
scrollLeft: current.scrollLeft,
frame: (0, imperative_state_1.getCurrentFrame)(),
zoomLevel: (0, imperative_state_1.getCurrentZoom)(),
durationInFrames: (0, imperative_state_1.getCurrentDuration)(),
};
}
else if (lastTimelinePositionWhileScrolling !== null) {
if ((0, timeline_scroll_logic_1.isCursorInViewport)({
frame: (0, imperative_state_1.getCurrentFrame)(),
durationInFrames: (0, imperative_state_1.getCurrentDuration)(),
})) {
return;
}
if (lastTimelinePositionWhileScrolling.zoomLevel === (0, imperative_state_1.getCurrentZoom)() &&
lastTimelinePositionWhileScrolling.durationInFrames ===
(0, imperative_state_1.getCurrentDuration)()) {
current.scrollLeft = lastTimelinePositionWhileScrolling.scrollLeft;
}
else {
(0, timeline_scroll_logic_1.ensureFrameIsInViewport)({
direction: 'center',
durationInFrames: (0, imperative_state_1.getCurrentDuration)(),
frame: lastTimelinePositionWhileScrolling.frame,
});
}
}
}, [playing]);
return null;
};
exports.TimelinePlayCursorSyncer = TimelinePlayCursorSyncer;

View File

@@ -0,0 +1,4 @@
import React from 'react';
export declare const TimelineScrollable: React.FC<{
readonly children: React.ReactNode;
}>;

View File

@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineScrollable = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const colors_1 = require("../../helpers/colors");
const is_menu_item_1 = require("../Menu/is-menu-item");
const timeline_refs_1 = require("./timeline-refs");
const outer = {
width: '100%',
height: '100%',
overflowX: 'auto',
overflowY: 'hidden',
position: 'relative',
backgroundColor: colors_1.TIMELINE_BACKGROUND,
};
const TimelineScrollable = ({ children }) => {
const containerStyle = (0, react_1.useMemo)(() => {
return {
width: '100%',
minHeight: '100%',
};
}, []);
return ((0, jsx_runtime_1.jsx)("div", { ref: timeline_refs_1.scrollableRef, style: outer, className: is_menu_item_1.HORIZONTAL_SCROLLBAR_CLASSNAME, children: (0, jsx_runtime_1.jsx)("div", { style: containerStyle, children: children }) }));
};
exports.TimelineScrollable = TimelineScrollable;

View File

@@ -0,0 +1,5 @@
import React from 'react';
import type { TSequence } from 'remotion';
export declare const TimelineSequence: React.FC<{
readonly s: TSequence;
}>;

View File

@@ -0,0 +1,115 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineSequence = 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_timeline_sequence_layout_1 = require("../../helpers/get-timeline-sequence-layout");
const timeline_layout_1 = require("../../helpers/timeline-layout");
const use_max_media_duration_1 = require("../../helpers/use-max-media-duration");
const AudioWaveform_1 = require("../AudioWaveform");
const LoopedTimelineIndicators_1 = require("./LoopedTimelineIndicators");
const TimelineSequenceFrame_1 = require("./TimelineSequenceFrame");
const TimelineVideoInfo_1 = require("./TimelineVideoInfo");
const TimelineWidthProvider_1 = require("./TimelineWidthProvider");
const AUDIO_GRADIENT = 'linear-gradient(rgb(16 171 58), rgb(43 165 63) 60%)';
const VIDEO_GRADIENT = 'linear-gradient(to top, #8e44ad, #9b59b6)';
const TimelineSequence = ({ s }) => {
const windowWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
if (windowWidth === null) {
return null;
}
return (0, jsx_runtime_1.jsx)(Inner, { windowWidth: windowWidth, s: s });
};
exports.TimelineSequence = TimelineSequence;
const Inner = ({ s, windowWidth }) => {
// If a duration is 1, it is essentially a still and it should have width 0
// Some compositions may not be longer than their media duration,
// if that is the case, it needs to be asynchronously determined
var _a, _b, _c;
const video = remotion_1.Internals.useVideo();
const maxMediaDuration = (0, use_max_media_duration_1.useMaxMediaDuration)(s, (_a = video === null || video === void 0 ? void 0 : video.fps) !== null && _a !== void 0 ? _a : 30);
if (!video) {
throw new TypeError('Expected video config');
}
const frame = (0, remotion_1.useCurrentFrame)();
const relativeFrame = frame - s.from;
const relativeFrameWithPremount = relativeFrame + ((_b = s.premountDisplay) !== null && _b !== void 0 ? _b : 0);
const relativeFrameWithPostmount = relativeFrame - s.duration;
const roundedFrame = Math.round(relativeFrame * 100) / 100;
const isInRange = relativeFrame >= 0 && relativeFrame < s.duration;
const isPremounting = relativeFrameWithPremount >= 0 &&
relativeFrameWithPremount < s.duration &&
!isInRange;
const isPostmounting = relativeFrameWithPostmount >= 0 &&
relativeFrameWithPostmount < ((_c = s.postmountDisplay) !== null && _c !== void 0 ? _c : 0) &&
!isInRange;
const { marginLeft, width, naturalWidth, premountWidth, postmountWidth } = (0, react_1.useMemo)(() => {
return (0, get_timeline_sequence_layout_1.getTimelineSequenceLayout)({
durationInFrames: s.loopDisplay
? s.loopDisplay.durationInFrames * s.loopDisplay.numberOfTimes
: s.duration,
startFrom: s.loopDisplay ? s.from + s.loopDisplay.startOffset : s.from,
startFromMedia: s.type === 'sequence' ? 0 : s.startMediaFrom,
maxMediaDuration,
video,
windowWidth,
premountDisplay: s.premountDisplay,
postmountDisplay: s.postmountDisplay,
});
}, [maxMediaDuration, s, video, windowWidth]);
const style = (0, react_1.useMemo)(() => {
return {
background: s.type === 'audio'
? AUDIO_GRADIENT
: s.type === 'video'
? VIDEO_GRADIENT
: colors_1.BLUE,
border: get_timeline_sequence_layout_1.SEQUENCE_BORDER_WIDTH + 'px solid rgba(255, 255, 255, 0.2)',
borderRadius: 2,
position: 'absolute',
height: (0, timeline_layout_1.getTimelineLayerHeight)(s.type === 'video' ? 'video' : 'other'),
marginLeft,
width,
color: 'white',
overflow: 'hidden',
opacity: isInRange ? 1 : 0.5,
};
}, [isInRange, marginLeft, s.type, width]);
if (maxMediaDuration === null) {
return null;
}
return ((0, jsx_runtime_1.jsxs)("div", { style: style, title: s.displayName, children: [premountWidth ? ((0, jsx_runtime_1.jsx)("div", { style: {
width: premountWidth,
height: '100%',
background: `repeating-linear-gradient(
-45deg,
transparent,
transparent 2px,
rgba(255, 255, 255, ${isPremounting ? 0.5 : 0.2}) 2px,
rgba(255, 255, 255, ${isPremounting ? 0.5 : 0.2}) 4px
)`,
position: 'absolute',
} })) : null, postmountWidth ? ((0, jsx_runtime_1.jsx)("div", { style: {
width: postmountWidth,
height: '100%',
background: `repeating-linear-gradient(
-45deg,
transparent,
transparent 2px,
rgba(255, 255, 255, ${isPostmounting ? 0.5 : 0.2}) 2px,
rgba(255, 255, 255, ${isPostmounting ? 0.5 : 0.2}) 4px
)`,
position: 'absolute',
right: 0,
} })) : null, s.type === 'audio' ? ((0, jsx_runtime_1.jsx)(AudioWaveform_1.AudioWaveform, { src: s.src, doesVolumeChange: s.doesVolumeChange, visualizationWidth: width, startFrom: s.startMediaFrom, durationInFrames: s.duration, volume: s.volume, playbackRate: s.playbackRate })) : null, s.type === 'video' ? ((0, jsx_runtime_1.jsx)(TimelineVideoInfo_1.TimelineVideoInfo, { src: s.src, visualizationWidth: width, naturalWidth: naturalWidth, trimBefore: s.startMediaFrom, durationInFrames: s.duration, playbackRate: s.playbackRate })) : null, s.loopDisplay === undefined ? null : ((0, jsx_runtime_1.jsx)(LoopedTimelineIndicators_1.LoopedTimelineIndicator, { loops: s.loopDisplay.numberOfTimes })), s.type !== 'audio' &&
s.type !== 'video' &&
s.loopDisplay === undefined &&
(isInRange || isPremounting || isPostmounting) ? ((0, jsx_runtime_1.jsx)("div", { style: {
paddingLeft: 5 + (premountWidth !== null && premountWidth !== void 0 ? premountWidth : 0),
height: '100%',
display: 'flex',
alignItems: 'center',
}, children: (0, jsx_runtime_1.jsx)(TimelineSequenceFrame_1.TimelineSequenceFrame, { premounted: isPremounting, postmounted: isPostmounting ? s.duration - 1 : null, roundedFrame: roundedFrame }) })) : null] }, s.id));
};

View File

@@ -0,0 +1,6 @@
import React from 'react';
export declare const TimelineSequenceFrame: React.FC<{
readonly roundedFrame: number;
readonly premounted: boolean;
readonly postmounted: number | null;
}>;

View File

@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineSequenceFrame = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const relativeFrameStyle = {
fontSize: 11,
fontFamily: 'Arial, Helvetica, sans-serif',
color: 'white',
opacity: 0.5,
};
const TimelineSequenceFrame = ({ roundedFrame, premounted, postmounted }) => {
return ((0, jsx_runtime_1.jsx)("div", { style: relativeFrameStyle, children: premounted
? '0 (Premounted)'
: postmounted !== null
? `${postmounted} (Postmounted)`
: roundedFrame }));
};
exports.TimelineSequenceFrame = TimelineSequenceFrame;

View File

@@ -0,0 +1,5 @@
import React from 'react';
export declare const redrawTimelineSliderFast: React.RefObject<{
draw: (frame: number, width?: number) => void;
} | null>;
export declare const TimelineSlider: React.FC;

View File

@@ -0,0 +1,79 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineSlider = exports.redrawTimelineSliderFast = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
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 TimelineSliderHandle_1 = require("./TimelineSliderHandle");
const TimelineWidthProvider_1 = require("./TimelineWidthProvider");
const imperative_state_1 = require("./imperative-state");
const timeline_refs_1 = require("./timeline-refs");
const container = {
position: 'absolute',
bottom: 0,
top: 0,
pointerEvents: 'none',
};
const line = {
height: '100vh',
width: 1,
position: 'fixed',
backgroundColor: '#f02c00',
};
exports.redrawTimelineSliderFast = (0, react_1.createRef)();
const TimelineSlider = () => {
const videoConfig = remotion_1.Internals.useUnsafeVideoConfig();
const timelineWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
if (videoConfig === null || timelineWidth === null) {
return null;
}
return (0, jsx_runtime_1.jsx)(Inner, {});
};
exports.TimelineSlider = TimelineSlider;
const Inner = () => {
const videoConfig = (0, remotion_1.useVideoConfig)();
const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
const ref = (0, react_1.useRef)(null);
const timelineWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
if (timelineWidth === null) {
throw new Error('Unexpectedly did not have timeline width');
}
const style = (0, react_1.useMemo)(() => {
const left = (0, get_left_of_timeline_slider_1.getXPositionOfItemInTimelineImperatively)(timelinePosition, videoConfig.durationInFrames, timelineWidth);
return {
...container,
transform: `translateX(${left}px)`,
};
}, [timelinePosition, videoConfig.durationInFrames, timelineWidth]);
(0, react_1.useImperativeHandle)(exports.redrawTimelineSliderFast, () => {
return {
draw: (frame, width) => {
var _a, _b;
const { current } = ref;
if (!current) {
throw new Error('unexpectedly did not have ref to timelineslider');
}
current.style.transform = `translateX(${(0, get_left_of_timeline_slider_1.getXPositionOfItemInTimelineImperatively)(frame, (0, imperative_state_1.getCurrentDuration)(), (_b = width !== null && width !== void 0 ? width : (_a = timeline_refs_1.sliderAreaRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth) !== null && _b !== void 0 ? _b : 0)}px)`;
},
};
}, []);
(0, react_1.useEffect)(() => {
const currentRef = ref.current;
if (!currentRef) {
return;
}
const { current } = timeline_refs_1.timelineVerticalScroll;
if (!current) {
return;
}
const onScroll = () => {
currentRef.style.top = current.scrollTop + 'px';
};
current.addEventListener('scroll', onScroll);
return () => {
current.removeEventListener('scroll', onScroll);
};
}, []);
return ((0, jsx_runtime_1.jsxs)("div", { ref: ref, style: style, children: [(0, jsx_runtime_1.jsx)("div", { style: line }), (0, jsx_runtime_1.jsx)(TimelineSliderHandle_1.TimelineSliderHandle, {})] }));
};

View File

@@ -0,0 +1,2 @@
import React from 'react';
export declare const TimelineSliderHandle: React.FC;

View File

@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineSliderHandle = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const container = {
width: 20,
height: 20,
position: 'fixed',
marginLeft: -8,
};
const TimelineSliderHandle = () => {
return ((0, jsx_runtime_1.jsx)("div", { style: container, children: (0, jsx_runtime_1.jsx)("svg", { width: 17, viewBox: "0 0 159 212", children: (0, jsx_runtime_1.jsx)("path", { d: "M17.0234375,1.07763419 L143.355469,1.07763419 C151.63974,1.07763419 158.355469,7.79336295 158.355469,16.0776342 L158.355469,69.390507 C158.355469,73.7938677 156.420655,77.9748242 153.064021,80.8248415 L89.3980057,134.881757 C83.7986799,139.635978 75.5802263,139.635978 69.9809005,134.881757 L6.66764807,81.1243622 C3.0872392,78.0843437 1.0234375,73.6246568 1.0234375,68.9277387 L1.0234375,17.0776342 C1.0234375,8.2410782 8.1868815,1.07763419 17.0234375,1.07763419 Z", fill: "#f02c00" }) }) }));
};
exports.TimelineSliderHandle = TimelineSliderHandle;

View File

@@ -0,0 +1 @@
export declare const getOriginalLocationFromStack: (stack: string, type: "sequence" | "visual-control") => Promise<import("../../../error-overlay/react-overlay/utils/get-source-map").OriginalPosition | null>;

View File

@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getOriginalLocationFromStack = void 0;
const source_map_1 = require("source-map");
const get_source_map_1 = require("../../../error-overlay/react-overlay/utils/get-source-map");
const get_location_of_sequence_1 = require("../../../helpers/get-location-of-sequence");
const waiters = [];
const sourceMapConsumerCache = {};
const isCreating = {};
const getSourceMapCache = async (fileName) => {
if (sourceMapConsumerCache[fileName]) {
return sourceMapConsumerCache[fileName];
}
if (isCreating[fileName]) {
return new Promise((resolve) => {
waiters.push({
id: String(Math.random()),
forFileName: fileName,
resolve,
});
});
}
isCreating[fileName] = true;
const res = await fetch(`${fileName}.map`);
const json = await res.json();
const map = await new Promise((resolve) => {
source_map_1.SourceMapConsumer.with(json, null, (consumer) => {
resolve(consumer);
});
});
waiters.filter((w) => {
if (w.forFileName === fileName) {
w.resolve(map);
return false;
}
return true;
});
sourceMapConsumerCache[fileName] = map;
isCreating[fileName] = false;
return map;
};
const getOriginalLocationFromStack = async (stack, type) => {
const location = type === 'sequence'
? (0, get_location_of_sequence_1.getLocationOfSequence)(stack)
: (0, get_location_of_sequence_1.getLocationOfFunctionCall)(stack, 'visualControl');
if (!location) {
return null;
}
const map = await getSourceMapCache(location.fileName);
const originalPosition = (0, get_source_map_1.getOriginalPosition)(map, location.lineNumber, location.columnNumber);
return originalPosition;
};
exports.getOriginalLocationFromStack = getOriginalLocationFromStack;

View File

@@ -0,0 +1,6 @@
import React from 'react';
import type { TSequence } from 'remotion';
export declare const TimelineStack: React.FC<{
readonly isCompact: boolean;
readonly sequence: TSequence;
}>;

View File

@@ -0,0 +1,176 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineStack = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const studio_shared_1 = require("@remotion/studio-shared");
const react_1 = require("react");
const source_map_1 = require("source-map");
const client_id_1 = require("../../../helpers/client-id");
const colors_1 = require("../../../helpers/colors");
const get_git_menu_item_1 = require("../../../helpers/get-git-menu-item");
const open_in_editor_1 = require("../../../helpers/open-in-editor");
const url_state_1 = require("../../../helpers/url-state");
const InitialCompositionLoader_1 = require("../../InitialCompositionLoader");
const NotificationCenter_1 = require("../../Notifications/NotificationCenter");
const Spinner_1 = require("../../Spinner");
const layout_1 = require("../../layout");
const get_stack_1 = require("./get-stack");
const source_attribution_1 = require("./source-attribution");
const publicPath = window.remotion_publicPath === '/' ? '' : window.remotion_publicPath;
const withoutSlashInTheEnd = publicPath.endsWith('/')
? publicPath.slice(0, -1)
: publicPath;
// @ts-expect-error
source_map_1.SourceMapConsumer.initialize({
'lib/mappings.wasm': withoutSlashInTheEnd + studio_shared_1.SOURCE_MAP_ENDPOINT,
});
const TimelineStack = ({ isCompact, sequence }) => {
const [originalLocation, setOriginalLocation] = (0, react_1.useState)(null);
const [stackHovered, setStackHovered] = (0, react_1.useState)(false);
const [titleHovered, setTitleHovered] = (0, react_1.useState)(false);
const [opening, setOpening] = (0, react_1.useState)(false);
const selectAsset = (0, InitialCompositionLoader_1.useSelectAsset)();
const connectionStatus = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx)
.previewServerState.type;
const assetPath = (0, react_1.useMemo)(() => {
if (sequence.type !== 'video' && sequence.type !== 'audio') {
return null;
}
const isStatic = sequence.src.startsWith(window.remotion_staticBase);
if (!isStatic) {
return null;
}
const relativePath = sequence.src.replace(window.remotion_staticBase + '/', '');
return relativePath;
}, [sequence]);
const navigateToAsset = (0, react_1.useCallback)((asset) => {
selectAsset(asset);
(0, url_state_1.pushUrl)(`/assets/${asset}`);
}, [selectAsset]);
const openEditor = (0, react_1.useCallback)(async (location) => {
if (!window.remotion_editorName) {
return;
}
setOpening(true);
try {
await (0, open_in_editor_1.openOriginalPositionInEditor)(location);
}
catch (err) {
(0, NotificationCenter_1.showNotification)(err.message, 2000);
}
finally {
setOpening(false);
}
}, []);
const canOpenInEditor = window.remotion_editorName &&
connectionStatus === 'connected' &&
originalLocation;
const canOpenInGitHub = window.remotion_gitSource && originalLocation;
const titleHoverable = (isCompact && (canOpenInEditor || canOpenInGitHub)) || assetPath;
const stackHoverable = !isCompact && (canOpenInEditor || canOpenInGitHub);
const onClickTitle = (0, react_1.useCallback)(() => {
if (!titleHoverable) {
return null;
}
if (assetPath) {
navigateToAsset(assetPath);
return;
}
if (!originalLocation) {
return;
}
if (canOpenInEditor) {
openEditor(originalLocation);
return;
}
if (canOpenInGitHub) {
window.open((0, get_git_menu_item_1.getGitRefUrl)(window.remotion_gitSource, originalLocation), '_blank');
}
}, [
assetPath,
canOpenInEditor,
canOpenInGitHub,
navigateToAsset,
openEditor,
originalLocation,
titleHoverable,
]);
const onClickStack = (0, react_1.useCallback)(() => {
if (!originalLocation) {
return;
}
if (canOpenInEditor) {
openEditor(originalLocation);
return;
}
if (canOpenInGitHub) {
window.open((0, get_git_menu_item_1.getGitRefUrl)(window.remotion_gitSource, originalLocation), '_blank');
}
}, [canOpenInEditor, canOpenInGitHub, openEditor, originalLocation]);
(0, react_1.useEffect)(() => {
if (!sequence.stack) {
return;
}
(0, get_stack_1.getOriginalLocationFromStack)(sequence.stack, 'sequence')
.then((frame) => {
setOriginalLocation(frame);
})
.catch((err) => {
// eslint-disable-next-line no-console
console.error('Could not get original location of Sequence', err);
});
}, [sequence.stack]);
const onStackPointerEnter = (0, react_1.useCallback)(() => {
setStackHovered(true);
}, []);
const onStackPointerLeave = (0, react_1.useCallback)(() => {
setStackHovered(false);
}, []);
const onTitlePointerEnter = (0, react_1.useCallback)(() => {
setTitleHovered(true);
}, []);
const onTitlePointerLeave = (0, react_1.useCallback)(() => {
setTitleHovered(false);
}, []);
const style = (0, react_1.useMemo)(() => {
return {
fontSize: 12,
color: opening
? colors_1.VERY_LIGHT_TEXT
: stackHovered && stackHoverable
? colors_1.LIGHT_TEXT
: colors_1.VERY_LIGHT_TEXT,
marginLeft: 10,
cursor: stackHoverable ? 'pointer' : undefined,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
flexShrink: 100000,
};
}, [opening, stackHovered, stackHoverable]);
const titleStyle = (0, react_1.useMemo)(() => {
const hoverEffect = titleHovered && titleHoverable;
return {
fontSize: 12,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
lineHeight: 1,
color: opening && isCompact ? colors_1.VERY_LIGHT_TEXT : colors_1.LIGHT_COLOR,
userSelect: 'none',
WebkitUserSelect: 'none',
borderBottom: hoverEffect ? '1px solid #fff' : 'none',
cursor: hoverEffect ? 'pointer' : undefined,
};
}, [titleHoverable, isCompact, opening, titleHovered]);
const text = sequence.displayName.length > 1000
? sequence.displayName.slice(0, 1000) + '...'
: sequence.displayName;
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { onPointerEnter: onTitlePointerEnter, onPointerLeave: onTitlePointerLeave, title: originalLocation
? (0, source_attribution_1.getOriginalSourceAttribution)(originalLocation)
: text || '<Sequence>', style: titleStyle, onClick: onClickTitle, children: text || '<Sequence>' }), isCompact || !originalLocation ? null : ((0, jsx_runtime_1.jsx)("div", { onPointerEnter: onStackPointerEnter, onPointerLeave: onStackPointerLeave, onClick: onClickStack, style: style, children: (0, source_attribution_1.getOriginalSourceAttribution)(originalLocation) })), opening ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(layout_1.Spacing, { x: 0.5 }), (0, jsx_runtime_1.jsx)(Spinner_1.Spinner, { duration: 0.5, size: 12 })] })) : null] }));
};
exports.TimelineStack = TimelineStack;

View File

@@ -0,0 +1,2 @@
import type { OriginalPosition } from '../../../error-overlay/react-overlay/utils/get-source-map';
export declare const getOriginalSourceAttribution: (originalLocation: OriginalPosition) => string;

View File

@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getOriginalSourceAttribution = void 0;
const getOriginalSourceAttribution = (originalLocation) => {
if (!originalLocation.source) {
return '';
}
const split = originalLocation.source.split('/');
const last = split[split.length - 1];
if (last.startsWith('index')) {
const lastTwo = split[split.length - 2];
return `${lastTwo}/${last}:${originalLocation.line}`;
}
return `${last}:${originalLocation.line}`;
};
exports.getOriginalSourceAttribution = getOriginalSourceAttribution;

View File

@@ -0,0 +1,5 @@
import React from 'react';
export declare const TIMELINE_TIME_INDICATOR_HEIGHT = 39;
export declare const TimelineTimePlaceholders: React.FC;
export declare const TimelineTimePadding: React.FC;
export declare const TimelineTimeIndicators: React.FC;

View File

@@ -0,0 +1,154 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineTimeIndicators = exports.TimelineTimePadding = exports.TimelineTimePlaceholders = exports.TIMELINE_TIME_INDICATOR_HEIGHT = 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 timeline_layout_1 = require("../../helpers/timeline-layout");
const render_frame_1 = require("../../state/render-frame");
const SplitterHandle_1 = require("../Splitter/SplitterHandle");
const TimeValue_1 = require("../TimeValue");
const TimelineWidthProvider_1 = require("./TimelineWidthProvider");
const timeline_refs_1 = require("./timeline-refs");
const timeline_scroll_logic_1 = require("./timeline-scroll-logic");
exports.TIMELINE_TIME_INDICATOR_HEIGHT = 39;
const container = {
height: exports.TIMELINE_TIME_INDICATOR_HEIGHT - 4,
boxShadow: `0 0 4px ${colors_1.TIMELINE_BACKGROUND}`,
position: 'absolute',
backgroundColor: colors_1.TIMELINE_BACKGROUND,
top: 0,
};
const tick = {
width: 1,
backgroundColor: 'rgba(255, 255, 255, 0.15)',
height: 20,
position: 'absolute',
};
const secondTick = {
...tick,
height: 15,
};
const tickLabel = {
fontSize: 12,
marginLeft: 8,
marginTop: 7,
color: colors_1.LIGHT_TEXT,
};
const timeValue = {
height: exports.TIMELINE_TIME_INDICATOR_HEIGHT,
position: 'absolute',
top: 0,
width: '100%',
paddingLeft: 10,
display: 'flex',
alignItems: 'center',
backgroundColor: colors_1.BACKGROUND,
borderBottom: `${timeline_layout_1.TIMELINE_ITEM_BORDER_BOTTOM}px solid ${colors_1.TIMELINE_TRACK_SEPARATOR}`,
};
const TimelineTimePlaceholders = () => {
return ((0, jsx_runtime_1.jsx)("div", { style: timeValue, children: (0, jsx_runtime_1.jsx)(TimeValue_1.TimeValue, {}) }));
};
exports.TimelineTimePlaceholders = TimelineTimePlaceholders;
const TimelineTimePadding = () => {
return ((0, jsx_runtime_1.jsx)("div", { style: {
height: exports.TIMELINE_TIME_INDICATOR_HEIGHT,
} }));
};
exports.TimelineTimePadding = TimelineTimePadding;
const TimelineTimeIndicators = () => {
const sliderTrack = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
const video = remotion_1.Internals.useVideo();
if (sliderTrack === null) {
return null;
}
if (video === null) {
return null;
}
return ((0, jsx_runtime_1.jsx)(Inner, { durationInFrames: video.durationInFrames, fps: video.fps, windowWidth: sliderTrack }));
};
exports.TimelineTimeIndicators = TimelineTimeIndicators;
const Inner = ({ windowWidth, durationInFrames, fps }) => {
const ref = (0, react_1.useRef)(null);
(0, react_1.useEffect)(() => {
const currentRef = ref.current;
if (!currentRef) {
return;
}
const { current } = timeline_refs_1.timelineVerticalScroll;
if (!current) {
return;
}
const onScroll = () => {
currentRef.style.top = current.scrollTop + 'px';
};
current.addEventListener('scroll', onScroll);
return () => {
current.removeEventListener('scroll', onScroll);
};
}, []);
const style = (0, react_1.useMemo)(() => {
return {
...container,
width: windowWidth - SplitterHandle_1.SPLITTER_HANDLE_SIZE / 2,
overflow: 'hidden',
// Since
marginLeft: SplitterHandle_1.SPLITTER_HANDLE_SIZE / 2,
pointerEvents: 'none',
};
}, [windowWidth]);
const ticks = (0, react_1.useMemo)(() => {
const frameInterval = (0, timeline_scroll_logic_1.getFrameIncrementFromWidth)(durationInFrames, windowWidth);
const MIN_SPACING_BETWEEN_TICKS_PX = 5;
const seconds = Math.floor(durationInFrames / fps);
const secondMarkerEveryNth = Math.ceil((MIN_SPACING_BETWEEN_TICKS_PX * fps) / (frameInterval * fps));
const frameMarkerEveryNth = Math.ceil(MIN_SPACING_BETWEEN_TICKS_PX / frameInterval);
// Big ticks showing for every second
const secondTicks = new Array(seconds)
.fill(true)
.map((_, index) => {
return {
frame: index * fps,
style: {
...secondTick,
left: frameInterval * index * fps +
timeline_layout_1.TIMELINE_PADDING -
SplitterHandle_1.SPLITTER_HANDLE_SIZE / 2,
},
showTime: index > 0,
};
})
.filter((_, idx) => idx % secondMarkerEveryNth === 0);
const frameTicks = new Array(durationInFrames)
.fill(true)
.map((_, index) => {
return {
frame: index,
style: {
...tick,
left: frameInterval * index +
timeline_layout_1.TIMELINE_PADDING -
SplitterHandle_1.SPLITTER_HANDLE_SIZE / 2,
height: index % fps === 0
? 10
: (index / frameMarkerEveryNth) % 2 === 0
? 5
: 2,
},
showTime: false,
};
})
.filter((_, idx) => idx % frameMarkerEveryNth === 0);
// Merge and deduplicate ticks
const hasTicks = [];
return [...secondTicks, ...frameTicks].filter((t) => {
const alreadyUsed = hasTicks.find((ht) => ht === t.frame) !== undefined;
hasTicks.push(t.frame);
return !alreadyUsed;
});
}, [durationInFrames, fps, windowWidth]);
return ((0, jsx_runtime_1.jsx)("div", { ref: ref, style: style, children: ticks.map((t) => {
return ((0, jsx_runtime_1.jsx)("div", { style: t.style, children: t.showTime ? ((0, jsx_runtime_1.jsx)("div", { style: tickLabel, children: (0, render_frame_1.renderFrame)(t.frame, fps) })) : null }, t.frame));
}) }));
};

View File

@@ -0,0 +1,6 @@
import React from 'react';
import type { TrackWithHash } from '../../helpers/get-timeline-sequence-sort-key';
export declare const TimelineTracks: React.FC<{
readonly timeline: TrackWithHash[];
readonly hasBeenCut: boolean;
}>;

View File

@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineTracks = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const timeline_layout_1 = require("../../helpers/timeline-layout");
const MaxTimelineTracks_1 = require("./MaxTimelineTracks");
const TimelineSequence_1 = require("./TimelineSequence");
const TimelineTimeIndicators_1 = require("./TimelineTimeIndicators");
const is_collapsed_1 = require("./is-collapsed");
const content = {
paddingLeft: timeline_layout_1.TIMELINE_PADDING,
paddingRight: timeline_layout_1.TIMELINE_PADDING,
paddingTop: 1,
};
const timelineContent = {
minHeight: '100%',
};
const TimelineTracks = ({ timeline, hasBeenCut }) => {
const timelineStyle = (0, react_1.useMemo)(() => {
return {
...timelineContent,
width: 100 + '%',
};
}, []);
return ((0, jsx_runtime_1.jsxs)("div", { style: timelineStyle, children: [(0, jsx_runtime_1.jsxs)("div", { style: content, children: [(0, jsx_runtime_1.jsx)(TimelineTimeIndicators_1.TimelineTimePadding, {}), timeline.map((track) => {
if ((0, is_collapsed_1.isTrackHidden)(track)) {
return null;
}
return ((0, jsx_runtime_1.jsx)("div", { style: {
height: (0, timeline_layout_1.getTimelineLayerHeight)(track.sequence.type === 'video' ? 'video' : 'other'),
marginBottom: timeline_layout_1.TIMELINE_ITEM_BORDER_BOTTOM,
}, children: (0, jsx_runtime_1.jsx)(TimelineSequence_1.TimelineSequence, { s: track.sequence }) }, track.sequence.id));
})] }), hasBeenCut ? (0, jsx_runtime_1.jsx)(MaxTimelineTracks_1.MaxTimelineTracksReached, {}) : null] }));
};
exports.TimelineTracks = TimelineTracks;

View File

@@ -0,0 +1,9 @@
import React from 'react';
export declare const TimelineVideoInfo: React.FC<{
readonly src: string;
readonly visualizationWidth: number;
readonly naturalWidth: number;
readonly trimBefore: number;
readonly durationInFrames: number;
readonly playbackRate: number;
}>;

View File

@@ -0,0 +1,288 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineVideoInfo = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const remotion_1 = require("remotion");
const extract_frames_1 = require("../../helpers/extract-frames");
const frame_database_1 = require("../../helpers/frame-database");
const resize_video_frame_1 = require("../../helpers/resize-video-frame");
const timeline_layout_1 = require("../../helpers/timeline-layout");
const HEIGHT = (0, timeline_layout_1.getTimelineLayerHeight)('video') - 2;
const containerStyle = {
height: HEIGHT,
width: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.3)',
display: 'flex',
borderTopLeftRadius: 2,
borderBottomLeftRadius: 2,
fontSize: 10,
fontFamily: 'Arial, Helvetica',
};
const WEBCODECS_TIMESCALE = 1000000;
const MAX_TIME_DEVIATION = WEBCODECS_TIMESCALE * 0.05;
const getDurationOfOneFrame = ({ visualizationWidth, aspectRatio, segmentDuration, }) => {
const framesFitInWidthUnrounded = visualizationWidth / (HEIGHT * aspectRatio);
return (segmentDuration / framesFitInWidthUnrounded) * WEBCODECS_TIMESCALE;
};
const fixRounding = (value) => {
if (value % 1 >= 0.49999999) {
return Math.ceil(value);
}
return Math.floor(value);
};
const calculateTimestampSlots = ({ visualizationWidth, fromSeconds, segmentDuration, aspectRatio, }) => {
const framesFitInWidthUnrounded = visualizationWidth / (HEIGHT * aspectRatio);
const framesFitInWidth = Math.ceil(framesFitInWidthUnrounded);
const durationOfOneFrame = getDurationOfOneFrame({
visualizationWidth,
aspectRatio,
segmentDuration,
});
const timestampTargets = [];
for (let i = 0; i < framesFitInWidth + 1; i++) {
const target = fromSeconds * WEBCODECS_TIMESCALE + durationOfOneFrame * (i + 0.5);
const snappedToDuration = (Math.round(fixRounding(target / durationOfOneFrame)) - 1) *
durationOfOneFrame;
timestampTargets.push(snappedToDuration);
}
return timestampTargets;
};
const ensureSlots = ({ filledSlots, naturalWidth, fromSeconds, toSeconds, aspectRatio, }) => {
const segmentDuration = toSeconds - fromSeconds;
const timestampTargets = calculateTimestampSlots({
visualizationWidth: naturalWidth,
fromSeconds,
segmentDuration,
aspectRatio,
});
for (const timestamp of timestampTargets) {
if (!filledSlots.has(timestamp)) {
filledSlots.set(timestamp, undefined);
}
}
};
const drawSlot = ({ frame, ctx, filledSlots, visualizationWidth, timestamp, segmentDuration, fromSeconds, }) => {
const durationOfOneFrame = getDurationOfOneFrame({
visualizationWidth,
aspectRatio: frame.displayWidth / frame.displayHeight,
segmentDuration,
});
const relativeTimestamp = timestamp - fromSeconds * WEBCODECS_TIMESCALE;
const frameIndex = relativeTimestamp / durationOfOneFrame;
const left = Math.floor((frameIndex * frame.displayWidth) / window.devicePixelRatio); // round to avoid antialiasing
ctx.drawImage(frame, left, 0, frame.displayWidth / window.devicePixelRatio, frame.displayHeight / window.devicePixelRatio);
filledSlots.set(timestamp, frame.timestamp);
};
const fillWithCachedFrames = ({ ctx, naturalWidth, filledSlots, src, segmentDuration, fromSeconds, }) => {
const prefix = (0, frame_database_1.getFrameDatabaseKeyPrefix)(src);
const keys = Array.from(frame_database_1.frameDatabase.keys()).filter((k) => k.startsWith(prefix));
const targets = Array.from(filledSlots.keys());
for (const timestamp of targets) {
let bestKey;
let bestDistance = Infinity;
for (const key of keys) {
const distance = Math.abs((0, frame_database_1.getTimestampFromFrameDatabaseKey)(key) - timestamp);
if (distance < bestDistance) {
bestDistance = distance;
bestKey = key;
}
}
if (!bestKey) {
continue;
}
const frame = frame_database_1.frameDatabase.get(bestKey);
if (!frame) {
continue;
}
const alreadyFilled = filledSlots.get(timestamp);
// Don't fill if a closer frame was already drawn
if (alreadyFilled &&
Math.abs(alreadyFilled - timestamp) <=
Math.abs(frame.frame.timestamp - timestamp)) {
continue;
}
frame.lastUsed = Date.now();
drawSlot({
ctx,
frame: frame.frame,
filledSlots,
visualizationWidth: naturalWidth,
timestamp,
segmentDuration,
fromSeconds,
});
}
};
const fillFrameWhereItFits = ({ frame, filledSlots, ctx, visualizationWidth, segmentDuration, fromSeconds, }) => {
const slots = Array.from(filledSlots.keys());
for (let i = 0; i < slots.length; i++) {
const slot = slots[i];
if (Math.abs(slot - frame.timestamp) > MAX_TIME_DEVIATION) {
continue;
}
const filled = filledSlots.get(slot);
// Don't fill if a better timestamp was already filled
if (filled &&
Math.abs(filled - slot) <= Math.abs(filled - frame.timestamp)) {
continue;
}
drawSlot({
ctx,
frame,
filledSlots,
visualizationWidth,
timestamp: slot,
segmentDuration,
fromSeconds,
});
}
};
const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore, durationInFrames, playbackRate, }) => {
const { fps } = (0, remotion_1.useVideoConfig)();
const ref = (0, react_1.useRef)(null);
const [error, setError] = (0, react_1.useState)(null);
const aspectRatio = (0, react_1.useRef)((0, frame_database_1.getAspectRatioFromCache)(src));
(0, react_1.useEffect)(() => {
return () => {
(0, frame_database_1.clearFramesForSrc)(src);
};
}, [src]);
// for rendering frames
(0, react_1.useEffect)(() => {
if (error) {
return;
}
const { current } = ref;
if (!current) {
return;
}
const controller = new AbortController();
const canvas = document.createElement('canvas');
canvas.width = visualizationWidth;
canvas.height = HEIGHT;
const ctx = canvas.getContext('2d');
if (!ctx) {
return;
}
current.appendChild(canvas);
// desired-timestamp -> filled-timestamp
const filledSlots = new Map();
const fromSeconds = trimBefore / fps;
// Trim is applied first, then playbackRate. Each composition frame
// advances the source video by `playbackRate` source frames.
const toSeconds = fromSeconds + (durationInFrames * playbackRate) / fps;
if (aspectRatio.current !== null) {
ensureSlots({
filledSlots,
naturalWidth,
fromSeconds,
toSeconds,
aspectRatio: aspectRatio.current,
});
fillWithCachedFrames({
ctx,
naturalWidth,
filledSlots,
src,
segmentDuration: toSeconds - fromSeconds,
fromSeconds,
});
const unfilled = Array.from(filledSlots.keys()).filter((timestamp) => !filledSlots.get(timestamp));
// Don't extract frames if all slots are filled
if (unfilled.length === 0) {
return () => {
current.removeChild(canvas);
(0, frame_database_1.clearOldFrames)();
};
}
}
(0, frame_database_1.clearOldFrames)();
(0, extract_frames_1.extractFrames)({
timestampsInSeconds: ({ track, }) => {
aspectRatio.current = track.width / track.height;
frame_database_1.aspectRatioCache.set(src, aspectRatio.current);
ensureSlots({
filledSlots,
fromSeconds,
toSeconds,
naturalWidth,
aspectRatio: aspectRatio.current,
});
return Array.from(filledSlots.keys()).map((timestamp) => timestamp / WEBCODECS_TIMESCALE);
},
src,
onVideoSample: (sample) => {
const frame = sample.toVideoFrame();
const scale = (HEIGHT / frame.displayHeight) * window.devicePixelRatio;
const transformed = (0, resize_video_frame_1.resizeVideoFrame)({
frame,
scale,
});
if (transformed !== frame) {
frame.close();
}
const databaseKey = (0, frame_database_1.makeFrameDatabaseKey)(src, transformed.timestamp);
const existingFrame = frame_database_1.frameDatabase.get(databaseKey);
if (existingFrame) {
existingFrame.frame.close();
}
frame_database_1.frameDatabase.set(databaseKey, {
frame: transformed,
lastUsed: Date.now(),
});
if (aspectRatio.current === null) {
throw new Error('Aspect ratio is not set');
}
ensureSlots({
filledSlots,
fromSeconds,
toSeconds,
naturalWidth,
aspectRatio: aspectRatio.current,
});
fillFrameWhereItFits({
ctx,
filledSlots,
visualizationWidth: naturalWidth,
frame: transformed,
segmentDuration: toSeconds - fromSeconds,
fromSeconds,
});
sample.close();
},
signal: controller.signal,
})
.then(() => {
fillWithCachedFrames({
ctx,
naturalWidth,
filledSlots,
src,
segmentDuration: toSeconds - fromSeconds,
fromSeconds,
});
})
.catch((e) => {
setError(e);
})
.finally(() => {
(0, frame_database_1.clearOldFrames)();
});
return () => {
controller.abort();
current.removeChild(canvas);
};
}, [
durationInFrames,
error,
fps,
naturalWidth,
playbackRate,
src,
trimBefore,
visualizationWidth,
]);
return (0, jsx_runtime_1.jsx)("div", { ref: ref, style: containerStyle });
};
exports.TimelineVideoInfo = TimelineVideoInfo;

View File

@@ -0,0 +1,6 @@
type TimelineWidthContextType = number | null;
export declare const TimelineWidthContext: import("react").Context<TimelineWidthContextType>;
export declare const TimelineWidthProvider: React.FC<{
children: React.ReactNode;
}>;
export {};

View File

@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineWidthProvider = exports.TimelineWidthContext = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const player_1 = require("@remotion/player");
const react_1 = require("react");
const timeline_refs_1 = require("./timeline-refs");
exports.TimelineWidthContext = (0, react_1.createContext)(null);
const TimelineWidthProvider = ({ children }) => {
var _a;
const size = player_1.PlayerInternals.useElementSize(timeline_refs_1.sliderAreaRef, {
triggerOnWindowResize: false,
shouldApplyCssTransforms: true,
});
return ((0, jsx_runtime_1.jsx)(exports.TimelineWidthContext.Provider, { value: (_a = size === null || size === void 0 ? void 0 : size.width) !== null && _a !== void 0 ? _a : null, children: children }));
};
exports.TimelineWidthProvider = TimelineWidthProvider;

View File

@@ -0,0 +1,2 @@
import React from 'react';
export declare const TimelineZoomControls: React.FC;

View File

@@ -0,0 +1,59 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineZoomControls = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const remotion_1 = require("remotion");
const is_current_selected_still_1 = require("../../helpers/is-current-selected-still");
const minus_1 = require("../../icons/minus");
const plus_1 = require("../../icons/plus");
const timeline_zoom_1 = require("../../state/timeline-zoom");
const z_index_1 = require("../../state/z-index");
const ControlButton_1 = require("../ControlButton");
const layout_1 = require("../layout");
const container = {
color: 'black',
flexDirection: 'row',
display: 'flex',
alignItems: 'center',
};
const buttonStyle = {
fontSize: 24,
};
const iconStyle = {
color: 'white',
width: 14,
};
const TimelineZoomControls = () => {
var _a;
const { canvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager);
const { setZoom, zoom: zoomMap } = (0, react_1.useContext)(timeline_zoom_1.TimelineZoomCtx);
const { tabIndex } = (0, z_index_1.useZIndex)();
const onMinusClicked = (0, react_1.useCallback)(() => {
if (canvasContent === null || canvasContent.type !== 'composition') {
return;
}
setZoom(canvasContent.compositionId, (z) => Math.max(timeline_zoom_1.TIMELINE_MIN_ZOOM, z - 0.2));
}, [canvasContent, setZoom]);
const onPlusClicked = (0, react_1.useCallback)(() => {
if (canvasContent === null || canvasContent.type !== 'composition') {
return;
}
setZoom(canvasContent.compositionId, (z) => Math.min(timeline_zoom_1.TIMELINE_MAX_ZOOM, z + 0.2));
}, [canvasContent, setZoom]);
const onChange = (0, react_1.useCallback)((e) => {
if (canvasContent === null || canvasContent.type !== 'composition') {
return;
}
setZoom(canvasContent.compositionId, () => Number(e.target.value));
}, [canvasContent, setZoom]);
const isStill = (0, is_current_selected_still_1.useIsStill)();
if (isStill ||
canvasContent === null ||
canvasContent.type !== 'composition') {
return null;
}
const zoom = (_a = zoomMap[canvasContent.compositionId]) !== null && _a !== void 0 ? _a : timeline_zoom_1.TIMELINE_MIN_ZOOM;
return ((0, jsx_runtime_1.jsxs)("div", { style: container, children: [(0, jsx_runtime_1.jsx)(ControlButton_1.ControlButton, { onClick: onMinusClicked, style: buttonStyle, title: "Zoom out timeline", role: 'ControlButton', type: "button", disabled: timeline_zoom_1.TIMELINE_MIN_ZOOM === zoom, children: (0, jsx_runtime_1.jsx)(minus_1.Minus, { style: iconStyle }) }), (0, jsx_runtime_1.jsx)(layout_1.Spacing, { x: 0.5 }), (0, jsx_runtime_1.jsx)("input", { title: `Timeline zoom (${zoom}x)`, alt: `Timeline zoom (${zoom}x)`, type: 'range', min: timeline_zoom_1.TIMELINE_MIN_ZOOM, step: 0.1, value: zoom, max: timeline_zoom_1.TIMELINE_MAX_ZOOM, onChange: onChange, className: "__remotion-timeline-slider", tabIndex: tabIndex }), (0, jsx_runtime_1.jsx)(layout_1.Spacing, { x: 0.5 }), (0, jsx_runtime_1.jsx)(ControlButton_1.ControlButton, { onClick: onPlusClicked, style: buttonStyle, title: "Zoom in timeline", role: 'button', type: "button", disabled: timeline_zoom_1.TIMELINE_MAX_ZOOM === zoom, children: (0, jsx_runtime_1.jsx)(plus_1.Plus, { color: "currentcolor", style: iconStyle }) })] }));
};
exports.TimelineZoomControls = TimelineZoomControls;

View File

@@ -0,0 +1,8 @@
export declare const getCurrentZoom: () => number;
export declare const setCurrentZoom: (z: number) => void;
export declare const getCurrentFrame: () => number;
export declare const setCurrentFrame: (f: number) => void;
export declare const getCurrentDuration: () => number;
export declare const setCurrentDuration: (d: number) => void;
export declare const getCurrentFps: () => number;
export declare const setCurrentFps: (d: number) => void;

View File

@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setCurrentFps = exports.getCurrentFps = exports.setCurrentDuration = exports.getCurrentDuration = exports.setCurrentFrame = exports.getCurrentFrame = exports.setCurrentZoom = exports.getCurrentZoom = void 0;
let currentFrame = 0;
let currentZoom = 1;
let currentDuration = 1;
let currentFps = 1;
const getCurrentZoom = () => {
return currentZoom;
};
exports.getCurrentZoom = getCurrentZoom;
const setCurrentZoom = (z) => {
currentZoom = z;
};
exports.setCurrentZoom = setCurrentZoom;
const getCurrentFrame = () => {
return currentFrame;
};
exports.getCurrentFrame = getCurrentFrame;
const setCurrentFrame = (f) => {
currentFrame = f;
};
exports.setCurrentFrame = setCurrentFrame;
const getCurrentDuration = () => {
return currentDuration;
};
exports.getCurrentDuration = getCurrentDuration;
const setCurrentDuration = (d) => {
currentDuration = d;
};
exports.setCurrentDuration = setCurrentDuration;
const getCurrentFps = () => {
return currentFps;
};
exports.getCurrentFps = getCurrentFps;
const setCurrentFps = (d) => {
currentFps = d;
};
exports.setCurrentFps = setCurrentFps;

View File

@@ -0,0 +1,2 @@
import type { TrackWithHash } from '../../helpers/get-timeline-sequence-sort-key';
export declare const isTrackHidden: (track: TrackWithHash) => boolean;

View File

@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isTrackHidden = void 0;
const isTrackHidden = (track) => {
if (!track.sequence.parent) {
return false;
}
return !track.sequence.showInTimeline;
};
exports.isTrackHidden = isTrackHidden;

View File

@@ -0,0 +1,4 @@
import React from 'react';
export declare const sliderAreaRef: React.RefObject<HTMLDivElement | null>;
export declare const scrollableRef: React.RefObject<HTMLDivElement | null>;
export declare const timelineVerticalScroll: React.RefObject<HTMLDivElement | null>;

View File

@@ -0,0 +1,10 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.timelineVerticalScroll = exports.scrollableRef = exports.sliderAreaRef = void 0;
const react_1 = __importDefault(require("react"));
exports.sliderAreaRef = react_1.default.createRef();
exports.scrollableRef = react_1.default.createRef();
exports.timelineVerticalScroll = react_1.default.createRef();

View File

@@ -0,0 +1,43 @@
export declare const canScrollTimelineIntoDirection: () => {
canScrollRight: boolean;
canScrollLeft: boolean;
};
export declare const getFrameWhileScrollingLeft: ({ durationInFrames, width, }: {
durationInFrames: number;
width: number;
}) => number;
export declare const isCursorInViewport: ({ frame, durationInFrames, }: {
frame: number;
durationInFrames: number;
}) => boolean;
export declare const ensureFrameIsInViewport: ({ direction, durationInFrames, frame, }: {
direction: "fit-left" | "fit-right" | "page-right" | "page-left" | "center";
durationInFrames: number;
frame: number;
}) => void;
export declare const scrollToTimelineXOffset: (scrollPos: number) => void;
export declare const getScrollPositionForCursorOnLeftEdge: ({ nextFrame, durationInFrames, }: {
nextFrame: number;
durationInFrames: number;
}) => number;
export declare const getScrollPositionForCursorOnRightEdge: ({ nextFrame, durationInFrames, }: {
nextFrame: number;
durationInFrames: number;
}) => number;
export declare const getFrameIncrementFromWidth: (durationInFrames: number, width: number) => number;
export declare const getFrameWhileScrollingRight: ({ durationInFrames, width, }: {
durationInFrames: number;
width: number;
}) => number;
export declare const getFrameFromX: ({ clientX, durationInFrames, width, extrapolate, }: {
clientX: number;
durationInFrames: number;
width: number;
extrapolate: "clamp" | "extend";
}) => number;
export declare const zoomAndPreserveCursor: ({ oldZoom, newZoom, currentFrame, currentDurationInFrames, }: {
oldZoom: number;
newZoom: number;
currentFrame: number;
currentDurationInFrames: number;
}) => void;

View File

@@ -0,0 +1,216 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.zoomAndPreserveCursor = exports.getFrameFromX = exports.getFrameWhileScrollingRight = exports.getFrameIncrementFromWidth = exports.getScrollPositionForCursorOnRightEdge = exports.getScrollPositionForCursorOnLeftEdge = exports.scrollToTimelineXOffset = exports.ensureFrameIsInViewport = exports.isCursorInViewport = exports.getFrameWhileScrollingLeft = exports.canScrollTimelineIntoDirection = void 0;
const remotion_1 = require("remotion");
const timeline_layout_1 = require("../../helpers/timeline-layout");
const TimelineSlider_1 = require("./TimelineSlider");
const timeline_refs_1 = require("./timeline-refs");
const canScrollTimelineIntoDirection = () => {
const current = timeline_refs_1.scrollableRef.current;
const { scrollWidth, scrollLeft, clientWidth } = current;
const canScrollRight = scrollWidth - scrollLeft - clientWidth > timeline_layout_1.TIMELINE_PADDING;
const canScrollLeft = scrollLeft > timeline_layout_1.TIMELINE_PADDING;
return { canScrollRight, canScrollLeft };
};
exports.canScrollTimelineIntoDirection = canScrollTimelineIntoDirection;
const SCROLL_INCREMENT = 200;
const calculateFrameWhileScrollingRight = ({ durationInFrames, width, scrollLeft, }) => {
var _a;
return ((0, exports.getFrameFromX)({
clientX: scrollLeft,
durationInFrames,
width,
extrapolate: 'clamp',
}) +
Math.ceil((((_a = timeline_refs_1.scrollableRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth) - timeline_layout_1.TIMELINE_PADDING) /
getFrameIncrement(durationInFrames)));
};
const getFrameWhileScrollingLeft = ({ durationInFrames, width, }) => {
var _a, _b;
const nextFrame = (0, exports.getFrameFromX)({
clientX: ((_a = timeline_refs_1.scrollableRef.current) === null || _a === void 0 ? void 0 : _a.scrollLeft) - SCROLL_INCREMENT,
durationInFrames,
width,
extrapolate: 'clamp',
});
const currentFrame = (0, exports.getFrameFromX)({
clientX: (_b = timeline_refs_1.scrollableRef.current) === null || _b === void 0 ? void 0 : _b.scrollLeft,
durationInFrames,
width,
extrapolate: 'clamp',
});
// Should go back at least 1 frame, but not less than 0
return Math.max(0, Math.min(currentFrame - 1, nextFrame));
};
exports.getFrameWhileScrollingLeft = getFrameWhileScrollingLeft;
const isCursorInViewport = ({ frame, durationInFrames, }) => {
var _a, _b, _c, _d;
const width = (_b = (_a = timeline_refs_1.scrollableRef.current) === null || _a === void 0 ? void 0 : _a.scrollWidth) !== null && _b !== void 0 ? _b : 0;
const scrollLeft = (_d = (_c = timeline_refs_1.scrollableRef.current) === null || _c === void 0 ? void 0 : _c.scrollLeft) !== null && _d !== void 0 ? _d : 0;
const scrollPosOnRightEdge = (0, exports.getScrollPositionForCursorOnRightEdge)({
nextFrame: frame,
durationInFrames,
});
const scrollPosOnLeftEdge = (0, exports.getScrollPositionForCursorOnLeftEdge)({
nextFrame: frame,
durationInFrames,
});
const currentFrameRight = calculateFrameWhileScrollingRight({
durationInFrames,
scrollLeft,
width,
});
return !(scrollPosOnRightEdge >=
(0, exports.getScrollPositionForCursorOnRightEdge)({
nextFrame: currentFrameRight,
durationInFrames,
}) || scrollPosOnLeftEdge < scrollLeft);
};
exports.isCursorInViewport = isCursorInViewport;
const ensureFrameIsInViewport = ({ direction, durationInFrames, frame, }) => {
var _a, _b, _c, _d, _e;
(_a = TimelineSlider_1.redrawTimelineSliderFast.current) === null || _a === void 0 ? void 0 : _a.draw(frame);
const width = (_c = (_b = timeline_refs_1.scrollableRef.current) === null || _b === void 0 ? void 0 : _b.scrollWidth) !== null && _c !== void 0 ? _c : 0;
const scrollLeft = (_e = (_d = timeline_refs_1.scrollableRef.current) === null || _d === void 0 ? void 0 : _d.scrollLeft) !== null && _e !== void 0 ? _e : 0;
if (direction === 'fit-left') {
const currentFrameLeft = (0, exports.getFrameFromX)({
clientX: scrollLeft,
durationInFrames,
width,
extrapolate: 'clamp',
});
const scrollPos = (0, exports.getScrollPositionForCursorOnLeftEdge)({
nextFrame: frame,
durationInFrames,
});
const needsToScrollLeft = scrollPos <=
(0, exports.getScrollPositionForCursorOnLeftEdge)({
nextFrame: currentFrameLeft,
durationInFrames,
});
if (needsToScrollLeft) {
(0, exports.scrollToTimelineXOffset)(scrollPos);
}
}
if (direction === 'fit-right') {
const currentFrameRight = calculateFrameWhileScrollingRight({
durationInFrames,
scrollLeft,
width,
});
const scrollPos = (0, exports.getScrollPositionForCursorOnRightEdge)({
nextFrame: frame,
durationInFrames,
});
const needsToScrollRight = scrollPos >=
(0, exports.getScrollPositionForCursorOnRightEdge)({
nextFrame: currentFrameRight,
durationInFrames,
});
if (needsToScrollRight) {
(0, exports.scrollToTimelineXOffset)(scrollPos);
}
}
if (direction === 'page-right' || direction === 'page-left') {
if (!(0, exports.isCursorInViewport)({ frame, durationInFrames })) {
(0, exports.scrollToTimelineXOffset)(direction === 'page-left'
? (0, exports.getScrollPositionForCursorOnRightEdge)({
nextFrame: frame,
durationInFrames,
})
: (0, exports.getScrollPositionForCursorOnLeftEdge)({
nextFrame: frame,
durationInFrames,
}));
}
}
if (direction === 'center') {
const scrollPosOnRightEdge = (0, exports.getScrollPositionForCursorOnRightEdge)({
nextFrame: frame,
durationInFrames,
});
const scrollPosOnLeftEdge = (0, exports.getScrollPositionForCursorOnLeftEdge)({
nextFrame: frame,
durationInFrames,
});
(0, exports.scrollToTimelineXOffset)((scrollPosOnLeftEdge + scrollPosOnRightEdge) / 2);
}
};
exports.ensureFrameIsInViewport = ensureFrameIsInViewport;
const scrollToTimelineXOffset = (scrollPos) => {
var _a;
(_a = timeline_refs_1.scrollableRef.current) === null || _a === void 0 ? void 0 : _a.scroll({
left: scrollPos,
});
};
exports.scrollToTimelineXOffset = scrollToTimelineXOffset;
const getScrollPositionForCursorOnLeftEdge = ({ nextFrame, durationInFrames, }) => {
const frameIncrement = getFrameIncrement(durationInFrames);
const scrollPos = frameIncrement * nextFrame;
return scrollPos;
};
exports.getScrollPositionForCursorOnLeftEdge = getScrollPositionForCursorOnLeftEdge;
const getScrollPositionForCursorOnRightEdge = ({ nextFrame, durationInFrames, }) => {
var _a, _b;
const frameIncrement = getFrameIncrement(durationInFrames);
const framesRemaining = durationInFrames - 1 - nextFrame;
const fromRight = framesRemaining * frameIncrement + timeline_layout_1.TIMELINE_PADDING;
const scrollPos = ((_a = timeline_refs_1.scrollableRef.current) === null || _a === void 0 ? void 0 : _a.scrollWidth) -
fromRight -
((_b = timeline_refs_1.scrollableRef.current) === null || _b === void 0 ? void 0 : _b.clientWidth) +
timeline_layout_1.TIMELINE_PADDING +
4; // clearfix;
return scrollPos;
};
exports.getScrollPositionForCursorOnRightEdge = getScrollPositionForCursorOnRightEdge;
const getFrameIncrement = (durationInFrames) => {
var _a, _b;
const width = (_b = (_a = timeline_refs_1.scrollableRef.current) === null || _a === void 0 ? void 0 : _a.scrollWidth) !== null && _b !== void 0 ? _b : 0;
return (0, exports.getFrameIncrementFromWidth)(durationInFrames, width);
};
const getFrameIncrementFromWidth = (durationInFrames, width) => {
return (width - timeline_layout_1.TIMELINE_PADDING * 2) / (durationInFrames - 1);
};
exports.getFrameIncrementFromWidth = getFrameIncrementFromWidth;
const getFrameWhileScrollingRight = ({ durationInFrames, width, }) => {
var _a, _b;
const nextFrame = calculateFrameWhileScrollingRight({
durationInFrames,
width,
scrollLeft: ((_a = timeline_refs_1.scrollableRef.current) === null || _a === void 0 ? void 0 : _a.scrollLeft) + SCROLL_INCREMENT,
});
const currentFrame = calculateFrameWhileScrollingRight({
durationInFrames,
width,
scrollLeft: (_b = timeline_refs_1.scrollableRef.current) === null || _b === void 0 ? void 0 : _b.scrollLeft,
});
// Should scroll by at least 1 frame, but not overshoot duration
return Math.min(durationInFrames - 1, Math.max(nextFrame, currentFrame + 1));
};
exports.getFrameWhileScrollingRight = getFrameWhileScrollingRight;
const getFrameFromX = ({ clientX, durationInFrames, width, extrapolate, }) => {
const pos = clientX - timeline_layout_1.TIMELINE_PADDING;
const frame = Math.round((0, remotion_1.interpolate)(pos, [0, width - timeline_layout_1.TIMELINE_PADDING * 2], [0, durationInFrames - 1], {
extrapolateLeft: extrapolate,
extrapolateRight: extrapolate,
}));
return frame;
};
exports.getFrameFromX = getFrameFromX;
const zoomAndPreserveCursor = ({ oldZoom, newZoom, currentFrame, currentDurationInFrames, }) => {
var _a, _b, _c;
const ratio = newZoom / oldZoom;
if (ratio === 1) {
return;
}
const { current } = timeline_refs_1.scrollableRef;
if (!current) {
return;
}
const frameIncrement = getFrameIncrement(currentDurationInFrames);
const prevCursorPosition = frameIncrement * currentFrame + timeline_layout_1.TIMELINE_PADDING;
const newCursorPosition = ratio * (prevCursorPosition - timeline_layout_1.TIMELINE_PADDING) + timeline_layout_1.TIMELINE_PADDING;
current.scrollLeft += newCursorPosition - prevCursorPosition;
(_a = TimelineSlider_1.redrawTimelineSliderFast.current) === null || _a === void 0 ? void 0 : _a.draw(currentFrame, ((_c = (_b = timeline_refs_1.scrollableRef.current) === null || _b === void 0 ? void 0 : _b.clientWidth) !== null && _c !== void 0 ? _c : 0) * ratio);
};
exports.zoomAndPreserveCursor = zoomAndPreserveCursor;