@remotion/studio 4.0.496 → 4.0.497

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/components/AudioWaveform.js +1 -11
  2. package/dist/components/Canvas.js +22 -203
  3. package/dist/components/CompositionSelectorItem.js +2 -10
  4. package/dist/components/InspectorPanel/AlignmentControls.js +2 -0
  5. package/dist/components/InspectorPanel/ConnectedCompositionsSection.js +27 -2
  6. package/dist/components/InspectorPanel/SequenceSelectionInspector.js +65 -8
  7. package/dist/components/InspectorPanel/use-composition-actions.js +6 -2
  8. package/dist/components/NewComposition/CodemodFooter.d.ts +1 -1
  9. package/dist/components/NewComposition/CodemodFooter.js +23 -4
  10. package/dist/components/NewComposition/InputDragger.d.ts +6 -0
  11. package/dist/components/NewComposition/InputDragger.js +12 -3
  12. package/dist/components/NewComposition/NewComposition.js +1 -1
  13. package/dist/components/NewComposition/use-rename-static-file.js +1 -1
  14. package/dist/components/SelectedOutlineElement.js +56 -88
  15. package/dist/components/SelectedOutlineOverlay.d.ts +1 -1
  16. package/dist/components/SelectedOutlineOverlay.js +9 -10
  17. package/dist/components/Timeline/Timeline.js +10 -3
  18. package/dist/components/Timeline/TimelineAssetDropIndicator.d.ts +2 -0
  19. package/dist/components/Timeline/TimelineAssetDropIndicator.js +37 -0
  20. package/dist/components/Timeline/TimelineClipboardKeybindings.js +1 -7
  21. package/dist/components/Timeline/TimelineScrollable.js +3 -2
  22. package/dist/components/Timeline/TimelineSelection.d.ts +4 -0
  23. package/dist/components/Timeline/TimelineSelection.js +40 -5
  24. package/dist/components/Timeline/TimelineSequence.js +20 -8
  25. package/dist/components/Timeline/TimelineSequenceFrame.js +2 -4
  26. package/dist/components/Timeline/TimelineSequenceItem.js +2 -0
  27. package/dist/components/Timeline/TimelineSequencePropItem.js +4 -0
  28. package/dist/components/Timeline/TimelineSequenceRightEdgeDragHandle.d.ts +15 -3
  29. package/dist/components/Timeline/TimelineSequenceRightEdgeDragHandle.js +96 -18
  30. package/dist/components/Timeline/TimelineTimeIndicators.d.ts +16 -0
  31. package/dist/components/Timeline/TimelineTimeIndicators.js +154 -35
  32. package/dist/components/Timeline/TimelineTranslateField.js +2 -1
  33. package/dist/components/Timeline/TimelineVideoInfo.d.ts +1 -0
  34. package/dist/components/Timeline/TimelineVideoInfo.js +4 -1
  35. package/dist/components/Timeline/delete-selected-timeline-item.d.ts +2 -0
  36. package/dist/components/Timeline/delete-selected-timeline-item.js +5 -4
  37. package/dist/components/Timeline/disable-sequence-interactivity.js +2 -0
  38. package/dist/components/Timeline/get-timeline-media-visualization-layout.d.ts +8 -0
  39. package/dist/components/Timeline/get-timeline-media-visualization-layout.js +10 -0
  40. package/dist/components/Timeline/get-timeline-video-info-widths.js +11 -3
  41. package/dist/components/Timeline/keyframe-clipboard.js +21 -5
  42. package/dist/components/Timeline/reset-selected-timeline-props.js +2 -0
  43. package/dist/components/Timeline/save-sequence-prop.d.ts +5 -3
  44. package/dist/components/Timeline/save-sequence-prop.js +43 -18
  45. package/dist/components/Timeline/should-clear-selection-on-pointer-down.d.ts +2 -0
  46. package/dist/components/Timeline/should-clear-selection-on-pointer-down.js +1 -1
  47. package/dist/components/Timeline/timeline-asset-drop-context.d.ts +1 -0
  48. package/dist/components/Timeline/timeline-asset-drop-context.js +5 -0
  49. package/dist/components/Timeline/timeline-scroll-logic.d.ts +7 -0
  50. package/dist/components/Timeline/timeline-scroll-logic.js +10 -1
  51. package/dist/components/Timeline/use-rename-sequence.js +2 -0
  52. package/dist/components/Timeline/use-sequence-freeze-frame-menu-item.js +2 -0
  53. package/dist/components/Timeline/use-timeline-asset-drop.d.ts +1 -0
  54. package/dist/components/Timeline/use-timeline-asset-drop.js +168 -0
  55. package/dist/components/composition-menu-items.d.ts +15 -1
  56. package/dist/components/composition-menu-items.js +137 -107
  57. package/dist/components/drop-handler-data.d.ts +13 -0
  58. package/dist/components/drop-handler-data.js +117 -0
  59. package/dist/components/handle-drop.d.ts +13 -0
  60. package/dist/components/handle-drop.js +115 -0
  61. package/dist/components/import-assets.d.ts +26 -8
  62. package/dist/components/import-assets.js +161 -49
  63. package/dist/components/selected-outline-drag.d.ts +11 -1
  64. package/dist/components/selected-outline-drag.js +74 -1
  65. package/dist/esm/{chunk-0v4n7nkh.js → chunk-5wj95bdd.js} +4262 -3392
  66. package/dist/esm/internals.mjs +4262 -3392
  67. package/dist/esm/previewEntry.mjs +4464 -3594
  68. package/dist/esm/renderEntry.mjs +1 -1
  69. package/dist/helpers/is-video-with-last-frame-hold.d.ts +2 -0
  70. package/dist/helpers/is-video-with-last-frame-hold.js +9 -0
  71. package/dist/helpers/url-state.d.ts +1 -0
  72. package/dist/helpers/url-state.js +5 -1
  73. package/dist/helpers/use-max-media-duration.js +5 -3
  74. package/dist/helpers/use-menu-structure.js +2 -0
  75. package/dist/icons/duplicate.d.ts +5 -0
  76. package/dist/icons/duplicate.js +10 -0
  77. package/dist/icons/snowflake.d.ts +5 -0
  78. package/dist/icons/snowflake.js +10 -0
  79. package/dist/icons/trash.d.ts +5 -0
  80. package/dist/icons/trash.js +8 -0
  81. package/package.json +11 -11
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TimelineTimeIndicators = exports.TimelineTimePadding = exports.TimelineTimePlaceholders = exports.TIMELINE_TIME_INDICATOR_HEIGHT = void 0;
3
+ exports.TimelineTimeIndicators = exports.getTimelineTickScale = exports.getNiceSecondInterval = exports.TimelineTimePadding = exports.TimelineTimePlaceholders = exports.TIMELINE_TIME_INDICATOR_HEIGHT = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const remotion_1 = require("remotion");
@@ -33,6 +33,10 @@ const secondTick = {
33
33
  const TICK_LABEL_FONT_SIZE = 12;
34
34
  const TICK_LABEL_MARGIN_LEFT = 8;
35
35
  const TICK_LABEL_MIN_GAP = 16;
36
+ const MIN_SPACING_BETWEEN_FRAME_TICKS_PX = 5;
37
+ const MIN_SPACING_BETWEEN_TIME_TICKS_PX = 16;
38
+ const MIN_SPACING_BETWEEN_MEDIUM_FRAME_TICKS_PX = 24;
39
+ const MIN_SPACING_BETWEEN_MEDIUM_TIME_TICKS_PX = 48;
36
40
  const tickLabel = {
37
41
  fontSize: TICK_LABEL_FONT_SIZE,
38
42
  marginLeft: TICK_LABEL_MARGIN_LEFT,
@@ -60,6 +64,101 @@ const TimelineTimePadding = () => {
60
64
  } }));
61
65
  };
62
66
  exports.TimelineTimePadding = TimelineTimePadding;
67
+ const NICE_SECOND_INTERVALS = [
68
+ 1,
69
+ 2,
70
+ 5,
71
+ 10,
72
+ 15,
73
+ 30,
74
+ 60,
75
+ 2 * 60,
76
+ 3 * 60,
77
+ 5 * 60,
78
+ 10 * 60,
79
+ 15 * 60,
80
+ 20 * 60,
81
+ 30 * 60,
82
+ 60 * 60,
83
+ ];
84
+ const getNiceSecondInterval = (rawNthSecond) => {
85
+ for (const n of NICE_SECOND_INTERVALS) {
86
+ if (n >= rawNthSecond) {
87
+ return n;
88
+ }
89
+ }
90
+ return Math.ceil(rawNthSecond / 3600) * 3600;
91
+ };
92
+ exports.getNiceSecondInterval = getNiceSecondInterval;
93
+ const getIntegerDivisors = (value) => {
94
+ const lowerDivisors = [];
95
+ const upperDivisors = [];
96
+ for (let candidate = 1; candidate <= Math.sqrt(value); candidate++) {
97
+ if (value % candidate !== 0) {
98
+ continue;
99
+ }
100
+ lowerDivisors.push(candidate);
101
+ if (candidate !== value / candidate) {
102
+ upperDivisors.unshift(value / candidate);
103
+ }
104
+ }
105
+ return [...lowerDivisors, ...upperDivisors];
106
+ };
107
+ const getFrameIntervals = (fps) => {
108
+ if (Number.isInteger(fps)) {
109
+ return getIntegerDivisors(fps);
110
+ }
111
+ return [1, 2, 5, 10, 15, 20, 30, 60].filter((interval) => interval < fps);
112
+ };
113
+ const getSecondIntervals = (labelEverySeconds) => {
114
+ const hourlyIntervals = getIntegerDivisors(labelEverySeconds / 3600).map((hours) => hours * 3600);
115
+ return [...new Set([...NICE_SECOND_INTERVALS, ...hourlyIntervals])].sort((a, b) => a - b);
116
+ };
117
+ const getTimelineTickScale = ({ fps, frameInterval, rawSecondMarkerEveryNth, }) => {
118
+ const labelEverySeconds = (0, exports.getNiceSecondInterval)(rawSecondMarkerEveryNth);
119
+ const frameIntervals = getFrameIntervals(fps);
120
+ const rawFrameInterval = MIN_SPACING_BETWEEN_FRAME_TICKS_PX / frameInterval;
121
+ const minorFrameInterval = frameIntervals.find((interval) => interval >= rawFrameInterval && interval < fps);
122
+ if (minorFrameInterval !== undefined) {
123
+ const rawMediumFrameInterval = MIN_SPACING_BETWEEN_MEDIUM_FRAME_TICKS_PX / frameInterval;
124
+ const mediumFrameInterval = frameIntervals.find((interval) => interval >= rawMediumFrameInterval &&
125
+ interval < fps &&
126
+ interval % minorFrameInterval === 0);
127
+ return {
128
+ labelEverySeconds,
129
+ mediumTickEvery: mediumFrameInterval === undefined
130
+ ? null
131
+ : { interval: mediumFrameInterval, unit: 'frames' },
132
+ minorTickEvery: { interval: minorFrameInterval, unit: 'frames' },
133
+ };
134
+ }
135
+ const pixelsPerSecond = frameInterval * fps;
136
+ const secondIntervals = getSecondIntervals(labelEverySeconds);
137
+ const rawMinorSecondInterval = MIN_SPACING_BETWEEN_TIME_TICKS_PX / pixelsPerSecond;
138
+ const minorSecondInterval = secondIntervals.find((interval) => interval >= rawMinorSecondInterval &&
139
+ interval < labelEverySeconds &&
140
+ labelEverySeconds % interval === 0);
141
+ if (minorSecondInterval === undefined) {
142
+ return {
143
+ labelEverySeconds,
144
+ mediumTickEvery: null,
145
+ minorTickEvery: null,
146
+ };
147
+ }
148
+ const rawMediumSecondInterval = MIN_SPACING_BETWEEN_MEDIUM_TIME_TICKS_PX / pixelsPerSecond;
149
+ const mediumSecondInterval = secondIntervals.find((interval) => interval >= rawMediumSecondInterval &&
150
+ interval < labelEverySeconds &&
151
+ interval % minorSecondInterval === 0 &&
152
+ labelEverySeconds % interval === 0);
153
+ return {
154
+ labelEverySeconds,
155
+ mediumTickEvery: mediumSecondInterval === undefined
156
+ ? null
157
+ : { interval: mediumSecondInterval, unit: 'seconds' },
158
+ minorTickEvery: { interval: minorSecondInterval, unit: 'seconds' },
159
+ };
160
+ };
161
+ exports.getTimelineTickScale = getTimelineTickScale;
63
162
  const TimelineTimeIndicators = () => {
64
163
  const sliderTrack = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
65
164
  const video = remotion_1.Internals.useVideo();
@@ -101,53 +200,73 @@ const TimelineTimeIndicatorsInner = ({ windowWidth, durationInFrames, fps }) =>
101
200
  }, [windowWidth]);
102
201
  const ticks = (0, react_1.useMemo)(() => {
103
202
  const frameInterval = (0, timeline_scroll_logic_1.getFrameIncrementFromWidth)(durationInFrames, windowWidth);
104
- const MIN_SPACING_BETWEEN_TICKS_PX = 5;
105
203
  const maxTickLabelWidth = (0, render_frame_1.renderFrame)(durationInFrames - 1, fps).length *
106
204
  TICK_LABEL_FONT_SIZE *
107
205
  0.6;
108
206
  const minSpacingBetweenTickLabelsPx = TICK_LABEL_MARGIN_LEFT + maxTickLabelWidth + TICK_LABEL_MIN_GAP;
109
207
  const seconds = Math.floor(durationInFrames / fps);
110
- const secondMarkerEveryNth = Math.ceil(minSpacingBetweenTickLabelsPx / (frameInterval * fps));
111
- const frameMarkerEveryNth = Math.ceil(MIN_SPACING_BETWEEN_TICKS_PX / frameInterval);
112
- // Big ticks showing for every second
113
- const secondTicks = new Array(seconds)
114
- .fill(true)
115
- .map((_, index) => {
116
- return {
208
+ const rawSecondMarkerEveryNth = minSpacingBetweenTickLabelsPx / (frameInterval * fps);
209
+ const tickScale = (0, exports.getTimelineTickScale)({
210
+ fps,
211
+ frameInterval,
212
+ rawSecondMarkerEveryNth,
213
+ });
214
+ // Labeled ticks use human-friendly time intervals.
215
+ const secondTicks = [];
216
+ for (let index = 0; index < seconds; index += tickScale.labelEverySeconds) {
217
+ secondTicks.push({
117
218
  frame: index * fps,
118
219
  style: {
119
220
  ...secondTick,
120
221
  left: frameInterval * index * fps + timeline_layout_1.TIMELINE_PADDING,
121
222
  },
122
223
  showTime: index > 0,
123
- };
124
- })
125
- .filter((_, idx) => idx % secondMarkerEveryNth === 0);
126
- const frameTicks = new Array(durationInFrames)
127
- .fill(true)
128
- .map((_, index) => {
129
- return {
130
- frame: index,
131
- style: {
132
- ...tick,
133
- left: frameInterval * index + timeline_layout_1.TIMELINE_PADDING,
134
- height: index % fps === 0
135
- ? 10
136
- : (index / frameMarkerEveryNth) % 2 === 0
224
+ });
225
+ }
226
+ const hasSecondTick = new Set(secondTicks.map((t) => t.frame));
227
+ const subdivisionTicks = [];
228
+ const { minorTickEvery, mediumTickEvery } = tickScale;
229
+ if ((minorTickEvery === null || minorTickEvery === void 0 ? void 0 : minorTickEvery.unit) === 'frames') {
230
+ for (let frame = 0; frame < durationInFrames; frame += minorTickEvery.interval) {
231
+ if (hasSecondTick.has(frame)) {
232
+ continue;
233
+ }
234
+ subdivisionTicks.push({
235
+ frame,
236
+ style: {
237
+ ...tick,
238
+ left: frameInterval * frame + timeline_layout_1.TIMELINE_PADDING,
239
+ height: (Number.isInteger(fps) && frame % fps === 0) ||
240
+ ((mediumTickEvery === null || mediumTickEvery === void 0 ? void 0 : mediumTickEvery.unit) === 'frames' &&
241
+ frame % mediumTickEvery.interval === 0)
137
242
  ? 5
138
243
  : 2,
139
- },
140
- showTime: false,
141
- };
142
- })
143
- .filter((_, idx) => idx % frameMarkerEveryNth === 0);
144
- // Merge and deduplicate ticks
145
- const hasTicks = [];
146
- return [...secondTicks, ...frameTicks].filter((t) => {
147
- const alreadyUsed = hasTicks.find((ht) => ht === t.frame) !== undefined;
148
- hasTicks.push(t.frame);
149
- return !alreadyUsed;
150
- });
244
+ },
245
+ showTime: false,
246
+ });
247
+ }
248
+ }
249
+ if ((minorTickEvery === null || minorTickEvery === void 0 ? void 0 : minorTickEvery.unit) === 'seconds') {
250
+ for (let second = 0; second < seconds; second += minorTickEvery.interval) {
251
+ const frame = second * fps;
252
+ if (hasSecondTick.has(frame)) {
253
+ continue;
254
+ }
255
+ subdivisionTicks.push({
256
+ frame,
257
+ style: {
258
+ ...tick,
259
+ left: frameInterval * frame + timeline_layout_1.TIMELINE_PADDING,
260
+ height: (mediumTickEvery === null || mediumTickEvery === void 0 ? void 0 : mediumTickEvery.unit) === 'seconds' &&
261
+ second % mediumTickEvery.interval === 0
262
+ ? 5
263
+ : 2,
264
+ },
265
+ showTime: false,
266
+ });
267
+ }
268
+ }
269
+ return [...secondTicks, ...subdivisionTicks];
151
270
  }, [durationInFrames, fps, windowWidth]);
152
271
  return (jsx_runtime_1.jsx("div", { ref: ref, style: style, children: ticks.map((t) => {
153
272
  return (jsx_runtime_1.jsx("div", { style: t.style, children: t.showTime ? (jsx_runtime_1.jsx("div", { style: tickLabel, children: (0, render_frame_1.renderFrame)(t.frame, fps) })) : null }, t.frame));
@@ -17,6 +17,7 @@ const containerStyle = {
17
17
  display: 'flex',
18
18
  gap: 4,
19
19
  };
20
+ const translateDragSensitivity = 3;
20
21
  const TimelineTranslateField = ({ field, propStatus, effectiveValue, onSave, onDragValueChange, onDragEnd, }) => {
21
22
  const [dragX, setDragX] = (0, react_1.useState)(null);
22
23
  const [dragY, setDragY] = (0, react_1.useState)(null);
@@ -100,7 +101,7 @@ const TimelineTranslateField = ({ field, propStatus, effectiveValue, onSave, onD
100
101
  }
101
102
  }, [propStatus, onSave, dragX, codeX, decimalPlaces]);
102
103
  return (jsx_runtime_1.jsxs("span", { style: containerStyle, children: [
103
- jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: dragX !== null && dragX !== void 0 ? dragX : codeX, style: leftDraggerStyle, status: "ok", small: true, onValueChange: onXChange, onValueChangeEnd: onXChangeEnd, onTextChange: onXTextChange, min: -Infinity, max: Infinity, step: step, formatter: formatter, rightAlign: false, snapToStep: false, dragDecimalPlaces: decimalPlaces }), jsx_runtime_1.jsx("div", { style: { marginLeft: -6, marginRight: -6 } }), jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: dragY !== null && dragY !== void 0 ? dragY : codeY, style: rightDraggerStyle, status: "ok", small: true, onValueChange: onYChange, onValueChangeEnd: onYChangeEnd, onTextChange: onYTextChange, min: -Infinity, max: Infinity, step: step, formatter: formatter, rightAlign: false, snapToStep: false, dragDecimalPlaces: decimalPlaces })
104
+ jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: dragX !== null && dragX !== void 0 ? dragX : codeX, style: leftDraggerStyle, status: "ok", small: true, onValueChange: onXChange, onValueChangeEnd: onXChangeEnd, onTextChange: onXTextChange, min: -Infinity, max: Infinity, step: step, formatter: formatter, rightAlign: false, snapToStep: false, dragDecimalPlaces: decimalPlaces, dragSensitivity: translateDragSensitivity }), jsx_runtime_1.jsx("div", { style: { marginLeft: -6, marginRight: -6 } }), jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: dragY !== null && dragY !== void 0 ? dragY : codeY, style: rightDraggerStyle, status: "ok", small: true, onValueChange: onYChange, onValueChangeEnd: onYChangeEnd, onTextChange: onYTextChange, min: -Infinity, max: Infinity, step: step, formatter: formatter, rightAlign: false, snapToStep: false, dragDecimalPlaces: decimalPlaces, dragSensitivity: translateDragSensitivity })
104
105
  ] }));
105
106
  };
106
107
  exports.TimelineTranslateField = TimelineTranslateField;
@@ -15,4 +15,5 @@ export declare const TimelineVideoInfo: React.FC<{
15
15
  readonly postmountWidth: number;
16
16
  readonly loopDisplay: LoopDisplay | undefined;
17
17
  readonly frozenMediaFrame: number | null;
18
+ readonly extendLastFrame: boolean;
18
19
  }>;
@@ -26,7 +26,7 @@ const filmstripContainerStyle = {
26
26
  fontFamily: 'Arial, Helvetica',
27
27
  };
28
28
  const MAX_FROZEN_FRAME_CACHE_DEVIATION = timeline_utils_1.WEBCODECS_TIMESCALE * 0.05;
29
- const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, startMediaFrom, mediaFrameAtSequenceZero, sequenceFrameOffset, durationInFrames, playbackRate, volume, doesVolumeChange, premountWidth, postmountWidth, loopDisplay, frozenMediaFrame, }) => {
29
+ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, startMediaFrom, mediaFrameAtSequenceZero, sequenceFrameOffset, durationInFrames, playbackRate, volume, doesVolumeChange, premountWidth, postmountWidth, loopDisplay, frozenMediaFrame, extendLastFrame, }) => {
30
30
  const { fps } = (0, remotion_1.useVideoConfig)();
31
31
  const ref = (0, react_1.useRef)(null);
32
32
  const [error, setError] = (0, react_1.useState)(null);
@@ -108,6 +108,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, startMediaFr
108
108
  };
109
109
  }
110
110
  (0, timeline_utils_1.extractFrames)({
111
+ repeatLastFrame: extendLastFrame,
111
112
  timestampsInSeconds: ({ track, }) => {
112
113
  aspectRatio.current = track.width / track.height;
113
114
  timeline_utils_1.aspectRatioCache.set(src, aspectRatio.current);
@@ -216,6 +217,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, startMediaFr
216
217
  }
217
218
  }
218
219
  (0, timeline_utils_1.extractFrames)({
220
+ repeatLastFrame: extendLastFrame,
219
221
  timestampsInSeconds: ({ track, }) => {
220
222
  aspectRatio.current = track.width / track.height;
221
223
  timeline_utils_1.aspectRatioCache.set(src, aspectRatio.current);
@@ -307,6 +309,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, startMediaFr
307
309
  }, [
308
310
  durationInFrames,
309
311
  error,
312
+ extendLastFrame,
310
313
  fps,
311
314
  frozenMediaFrame,
312
315
  loopDisplay,
@@ -1,7 +1,9 @@
1
1
  import type { OverrideIdToNodePaths, PropStatuses, TSequence } from 'remotion';
2
+ import type { SequenceNodePathInfo } from '../../helpers/get-timeline-sequence-sort-key';
2
3
  import type { ConfirmationDialogFunction } from '../ConfirmationDialog-types';
3
4
  import type { SetPropStatuses } from './save-sequence-prop';
4
5
  import { type TimelineSelection } from './TimelineSelection';
6
+ export declare const deleteSequencesFromSource: (nodePathInfos: SequenceNodePathInfo[], confirm: ConfirmationDialogFunction) => Promise<boolean>;
5
7
  export declare const deleteSelectedTimelineItem: ({ selection, sequences, overrideIdsToNodePaths, setPropStatuses, clientId, confirm, }: {
6
8
  selection: TimelineSelection;
7
9
  sequences: TSequence[];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deleteSelectedTimelineItems = exports.getTimelineSelectionAfterDeletingItems = exports.deleteSelectedTimelineItem = void 0;
3
+ exports.deleteSelectedTimelineItems = exports.getTimelineSelectionAfterDeletingItems = exports.deleteSelectedTimelineItem = exports.deleteSequencesFromSource = void 0;
4
4
  const remotion_1 = require("remotion");
5
5
  const call_api_1 = require("../call-api");
6
6
  const NotificationCenter_1 = require("../Notifications/NotificationCenter");
@@ -32,7 +32,7 @@ const confirmDeletingDuplicatedSequences = (nodePathInfos, confirm) => {
32
32
  confirmLabel: 'Delete',
33
33
  });
34
34
  };
35
- const deleteSequences = async (nodePathInfos, confirm) => {
35
+ const deleteSequencesFromSource = async (nodePathInfos, confirm) => {
36
36
  if (!(await confirmDeletingDuplicatedSequences(nodePathInfos, confirm))) {
37
37
  return false;
38
38
  }
@@ -61,6 +61,7 @@ const deleteSequences = async (nodePathInfos, confirm) => {
61
61
  return true;
62
62
  });
63
63
  };
64
+ exports.deleteSequencesFromSource = deleteSequencesFromSource;
64
65
  const deleteEffects = (effects) => {
65
66
  if (effects.length === 0) {
66
67
  return Promise.resolve(false);
@@ -115,7 +116,7 @@ const deleteSelectedTimelineItem = ({ selection, sequences, overrideIdsToNodePat
115
116
  case 'guide':
116
117
  return null;
117
118
  case 'sequence':
118
- return deleteSequences([selection.nodePathInfo], confirm);
119
+ return (0, exports.deleteSequencesFromSource)([selection.nodePathInfo], confirm);
119
120
  case 'sequence-effect':
120
121
  return deleteEffects([
121
122
  {
@@ -286,7 +287,7 @@ const deleteSelectedTimelineItems = ({ selections, sequences, overrideIdsToNodeP
286
287
  case 'guide':
287
288
  return null;
288
289
  case 'sequence':
289
- return deleteSequences(selections
290
+ return (0, exports.deleteSequencesFromSource)(selections
290
291
  .filter(isSequenceRowSelection)
291
292
  .map((selection) => selection.nodePathInfo), confirm);
292
293
  case 'sequence-effect':
@@ -5,6 +5,8 @@ const no_react_1 = require("remotion/no-react");
5
5
  const save_sequence_prop_1 = require("./save-sequence-prop");
6
6
  const disableSequenceInteractivity = ({ clientId, fileName, nodePath, setPropStatuses, }) => {
7
7
  return (0, save_sequence_prop_1.saveSequenceProps)({
8
+ addedKeyframes: null,
9
+ movedKeyframes: null,
8
10
  changes: [
9
11
  {
10
12
  fileName,
@@ -0,0 +1,8 @@
1
+ export declare const getTimelineMediaVisualizationLayout: ({ visualizationWidth, premountWidth, postmountWidth, }: {
2
+ readonly visualizationWidth: number;
3
+ readonly premountWidth: number;
4
+ readonly postmountWidth: number;
5
+ }) => {
6
+ marginLeft: number;
7
+ width: number;
8
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTimelineMediaVisualizationLayout = void 0;
4
+ const getTimelineMediaVisualizationLayout = ({ visualizationWidth, premountWidth, postmountWidth, }) => {
5
+ return {
6
+ marginLeft: premountWidth,
7
+ width: Math.max(0, visualizationWidth - premountWidth - postmountWidth),
8
+ };
9
+ };
10
+ exports.getTimelineMediaVisualizationLayout = getTimelineMediaVisualizationLayout;
@@ -1,11 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getTimelineVideoInfoWidths = void 0;
4
+ const get_timeline_media_visualization_layout_1 = require("./get-timeline-media-visualization-layout");
4
5
  const getTimelineVideoInfoWidths = ({ visualizationWidth, naturalWidth, premountWidth, postmountWidth, }) => {
5
- const mountWidth = premountWidth + postmountWidth;
6
6
  return {
7
- mediaVisualizationWidth: Math.max(0, visualizationWidth - mountWidth),
8
- mediaNaturalWidth: Math.max(0, naturalWidth - mountWidth),
7
+ mediaVisualizationWidth: (0, get_timeline_media_visualization_layout_1.getTimelineMediaVisualizationLayout)({
8
+ visualizationWidth,
9
+ premountWidth,
10
+ postmountWidth,
11
+ }).width,
12
+ mediaNaturalWidth: (0, get_timeline_media_visualization_layout_1.getTimelineMediaVisualizationLayout)({
13
+ visualizationWidth: naturalWidth,
14
+ premountWidth,
15
+ postmountWidth,
16
+ }).width,
9
17
  };
10
18
  };
11
19
  exports.getTimelineVideoInfoWidths = getTimelineVideoInfoWidths;
@@ -182,6 +182,10 @@ const getKeyframeClipboardDataFromSelections = ({ selections, sequences, overrid
182
182
  version: 1,
183
183
  remotionClipboard: 'keyframe',
184
184
  fieldType: firstResolved.fieldType,
185
+ field: {
186
+ type: firstResolved.identity.type,
187
+ fieldKey: firstResolved.identity.fieldKey,
188
+ },
185
189
  keyframes: keyframes.map((keyframe) => {
186
190
  return {
187
191
  frameOffset: keyframe.frame - firstFrame,
@@ -214,15 +218,27 @@ const isKeyframeValueCompatible = ({ value, fieldType, }) => {
214
218
  return typeof value === 'string';
215
219
  };
216
220
  const getPasteKeyframeTarget = ({ selectedItems, payload, timelinePosition, sequences, overrideIdsToNodePaths, propStatuses, }) => {
217
- var _a, _b;
221
+ var _a, _b, _c;
218
222
  if (selectedItems.length === 0) {
219
223
  return { type: 'none' };
220
224
  }
221
- const selection = selectedItems[0];
225
+ const resolvedSelectedItems = selectedItems.length === 1 &&
226
+ ((_a = selectedItems[0]) === null || _a === void 0 ? void 0 : _a.type) === 'sequence' &&
227
+ payload.field !== null &&
228
+ payload.field.type === 'sequence'
229
+ ? [
230
+ {
231
+ type: 'sequence-prop',
232
+ nodePathInfo: selectedItems[0].nodePathInfo,
233
+ key: payload.field.fieldKey,
234
+ },
235
+ ]
236
+ : selectedItems;
237
+ const selection = resolvedSelectedItems[0];
222
238
  if (!selection || getKeyframeFieldIdentity(selection) === null) {
223
239
  return { type: 'none' };
224
240
  }
225
- const resolvedFields = selectedItems.map((item) => resolveKeyframeField({
241
+ const resolvedFields = resolvedSelectedItems.map((item) => resolveKeyframeField({
226
242
  selection: item,
227
243
  sequences,
228
244
  overrideIdsToNodePaths,
@@ -239,8 +255,8 @@ const getPasteKeyframeTarget = ({ selectedItems, payload, timelinePosition, sequ
239
255
  sourceFrame: timelinePosition - resolved.keyframeDisplayOffset + keyframe.frameOffset,
240
256
  value: keyframe.value,
241
257
  }));
242
- const firstFrame = (_a = keyframes[0]) === null || _a === void 0 ? void 0 : _a.sourceFrame;
243
- const lastFrame = (_b = keyframes.at(-1)) === null || _b === void 0 ? void 0 : _b.sourceFrame;
258
+ const firstFrame = (_b = keyframes[0]) === null || _b === void 0 ? void 0 : _b.sourceFrame;
259
+ const lastFrame = (_c = keyframes.at(-1)) === null || _c === void 0 ? void 0 : _c.sourceFrame;
244
260
  if (firstFrame === undefined || lastFrame === undefined) {
245
261
  return { type: 'incompatible' };
246
262
  }
@@ -155,6 +155,8 @@ const resetSelectedTimelineProps = ({ selections, sequences, overrideIdsToNodePa
155
155
  const resetPromises = [];
156
156
  if (sequencePropTargets.length > 0) {
157
157
  resetPromises.push((0, save_sequence_prop_1.saveSequenceProps)({
158
+ addedKeyframes: null,
159
+ movedKeyframes: null,
158
160
  changes: sequencePropTargets.map((target) => ({
159
161
  fileName: target.fileName,
160
162
  nodePath: target.nodePath,
@@ -1,5 +1,6 @@
1
1
  import { type SaveSequencePropSourceEdit } from '@remotion/studio-shared';
2
2
  import type { CanUpdateSequencePropsResponse, SequencePropsSubscriptionKey, InteractivitySchema } from 'remotion';
3
+ import type { AddSequenceKeyframeChange } from './call-add-keyframe';
3
4
  import { type MoveEffectKeyframeChange, type MoveSequenceKeyframeChange } from './call-move-keyframe';
4
5
  export type SetPropStatuses = (nodePath: SequencePropsSubscriptionKey, values: (prev: CanUpdateSequencePropsResponse) => CanUpdateSequencePropsResponse) => void;
5
6
  export type SaveSequencePropChange = {
@@ -13,14 +14,15 @@ export type SaveSequencePropChange = {
13
14
  };
14
15
  type SaveSequencePropsOptions = {
15
16
  changes: SaveSequencePropChange[];
16
- movedKeyframes?: {
17
+ addedKeyframes: AddSequenceKeyframeChange[] | null;
18
+ movedKeyframes: {
17
19
  sequenceKeyframes: MoveSequenceKeyframeChange[];
18
20
  effectKeyframes: MoveEffectKeyframeChange[];
19
- };
21
+ } | null;
20
22
  setPropStatuses: SetPropStatuses;
21
23
  clientId: string;
22
24
  undoLabel: string;
23
25
  redoLabel: string;
24
26
  };
25
- export declare const saveSequenceProps: ({ changes, movedKeyframes, setPropStatuses, clientId, undoLabel, redoLabel, }: SaveSequencePropsOptions) => Promise<void>;
27
+ export declare const saveSequenceProps: ({ changes, addedKeyframes, movedKeyframes, setPropStatuses, clientId, undoLabel, redoLabel, }: SaveSequencePropsOptions) => Promise<void>;
26
28
  export {};
@@ -11,16 +11,18 @@ const serializeSequencePropValue = (value) => {
11
11
  }
12
12
  return { type: 'json', serialized: JSON.stringify(value) };
13
13
  };
14
- const saveSequenceProps = ({ changes, movedKeyframes, setPropStatuses, clientId, undoLabel, redoLabel, }) => {
15
- var _a, _b;
16
- const sequenceKeyframes = (_a = movedKeyframes === null || movedKeyframes === void 0 ? void 0 : movedKeyframes.sequenceKeyframes) !== null && _a !== void 0 ? _a : [];
17
- const effectKeyframes = (_b = movedKeyframes === null || movedKeyframes === void 0 ? void 0 : movedKeyframes.effectKeyframes) !== null && _b !== void 0 ? _b : [];
14
+ const saveSequenceProps = ({ changes, addedKeyframes, movedKeyframes, setPropStatuses, clientId, undoLabel, redoLabel, }) => {
15
+ const keyframesToAdd = addedKeyframes === null ? [] : addedKeyframes;
16
+ const sequenceKeyframes = movedKeyframes === null ? [] : movedKeyframes.sequenceKeyframes;
17
+ const effectKeyframes = movedKeyframes === null ? [] : movedKeyframes.effectKeyframes;
18
18
  if (changes.length === 0 &&
19
+ keyframesToAdd.length === 0 &&
19
20
  sequenceKeyframes.length === 0 &&
20
21
  effectKeyframes.length === 0) {
21
22
  return Promise.resolve();
22
23
  }
23
24
  if (changes.length === 1 &&
25
+ keyframesToAdd.length === 0 &&
24
26
  sequenceKeyframes.length === 0 &&
25
27
  effectKeyframes.length === 0) {
26
28
  const change = changes[0];
@@ -51,6 +53,8 @@ const saveSequenceProps = ({ changes, movedKeyframes, setPropStatuses, clientId,
51
53
  sourceEdit: (_a = change.sourceEdit) !== null && _a !== void 0 ? _a : null,
52
54
  },
53
55
  ],
56
+ addedKeyframes: null,
57
+ movedKeyframes: null,
54
58
  clientId,
55
59
  undoLabel,
56
60
  redoLabel,
@@ -64,6 +68,15 @@ const saveSequenceProps = ({ changes, movedKeyframes, setPropStatuses, clientId,
64
68
  effectKeyframes,
65
69
  setPropStatuses,
66
70
  });
71
+ for (const keyframe of keyframesToAdd) {
72
+ setPropStatuses(keyframe.nodePath, (prev) => (0, studio_shared_1.optimisticAddSequenceKeyframe)({
73
+ previous: prev,
74
+ fieldKey: keyframe.fieldKey,
75
+ frame: keyframe.sourceFrame,
76
+ value: keyframe.value,
77
+ schema: keyframe.schema,
78
+ }));
79
+ }
67
80
  for (const change of changes) {
68
81
  setPropStatuses(change.nodePath, (prev) => (0, studio_shared_1.optimisticUpdateForPropStatuses)({
69
82
  previous: prev,
@@ -86,25 +99,37 @@ const saveSequenceProps = ({ changes, movedKeyframes, setPropStatuses, clientId,
86
99
  sourceEdit: (_a = change.sourceEdit) !== null && _a !== void 0 ? _a : null,
87
100
  };
88
101
  }),
89
- movedKeyframes: {
90
- sequenceKeyframes: sequenceKeyframes.map((keyframe) => ({
102
+ addedKeyframes: addedKeyframes === null
103
+ ? null
104
+ : addedKeyframes.map((keyframe) => ({
91
105
  fileName: keyframe.fileName,
92
106
  nodePath: keyframe.nodePath,
93
107
  key: keyframe.fieldKey,
94
- fromFrame: keyframe.fromFrame,
95
- toFrame: keyframe.toFrame,
96
- schema: keyframe.schema,
97
- })),
98
- effectKeyframes: effectKeyframes.map((keyframe) => ({
99
- fileName: keyframe.fileName,
100
- sequenceNodePath: keyframe.nodePath,
101
- effectIndex: keyframe.effectIndex,
102
- key: keyframe.fieldKey,
103
- fromFrame: keyframe.fromFrame,
104
- toFrame: keyframe.toFrame,
108
+ frame: keyframe.sourceFrame,
109
+ value: JSON.stringify(keyframe.value),
105
110
  schema: keyframe.schema,
106
111
  })),
107
- },
112
+ movedKeyframes: movedKeyframes === null
113
+ ? null
114
+ : {
115
+ sequenceKeyframes: sequenceKeyframes.map((keyframe) => ({
116
+ fileName: keyframe.fileName,
117
+ nodePath: keyframe.nodePath,
118
+ key: keyframe.fieldKey,
119
+ fromFrame: keyframe.fromFrame,
120
+ toFrame: keyframe.toFrame,
121
+ schema: keyframe.schema,
122
+ })),
123
+ effectKeyframes: effectKeyframes.map((keyframe) => ({
124
+ fileName: keyframe.fileName,
125
+ sequenceNodePath: keyframe.nodePath,
126
+ effectIndex: keyframe.effectIndex,
127
+ key: keyframe.fieldKey,
128
+ fromFrame: keyframe.fromFrame,
129
+ toFrame: keyframe.toFrame,
130
+ schema: keyframe.schema,
131
+ })),
132
+ },
108
133
  clientId,
109
134
  undoLabel,
110
135
  redoLabel,
@@ -1,5 +1,7 @@
1
1
  export declare const PREVENT_CLEAR_SELECTION_ON_POINTER_DOWN_ATTR = "data-remotion-prevent-selection-clear";
2
2
  export declare const shouldClearSelectionOnPointerDown: (event: {
3
3
  readonly button: number;
4
+ readonly ctrlKey: boolean | null;
5
+ readonly metaKey: boolean | null;
4
6
  readonly target?: EventTarget | null | undefined;
5
7
  }) => boolean;
@@ -13,7 +13,7 @@ const isSelectionClearBlocked = (target) => {
13
13
  return (target.closest(`[${exports.PREVENT_CLEAR_SELECTION_ON_POINTER_DOWN_ATTR}]`) !== null);
14
14
  };
15
15
  const shouldClearSelectionOnPointerDown = (event) => {
16
- if (event.button !== 0) {
16
+ if (event.button !== 0 || event.metaKey === true || event.ctrlKey === true) {
17
17
  return false;
18
18
  }
19
19
  return !isSelectionClearBlocked(event.target);
@@ -0,0 +1 @@
1
+ export declare const TimelineAssetDropFrameContext: import("react").Context<number | null>;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TimelineAssetDropFrameContext = void 0;
4
+ const react_1 = require("react");
5
+ exports.TimelineAssetDropFrameContext = (0, react_1.createContext)(null);
@@ -35,6 +35,13 @@ export declare const getFrameFromX: ({ clientX, durationInFrames, width, extrapo
35
35
  width: number;
36
36
  extrapolate: "clamp" | "extend";
37
37
  }) => number;
38
+ export declare const getFrameFromTimelineDrop: ({ clientX, durationInFrames, scrollLeft, timelineLeft, timelineWidth, }: {
39
+ clientX: number;
40
+ durationInFrames: number;
41
+ scrollLeft: number;
42
+ timelineLeft: number;
43
+ timelineWidth: number;
44
+ }) => number;
38
45
  /**
39
46
  * Horizontal position inside the scrollable timeline content (0 … scrollWidth)
40
47
  * for a viewport `clientX`, so pinch-anchoring matches the pointer (not a