@remotion/studio 4.0.476 → 4.0.478

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 (74) hide show
  1. package/dist/components/Canvas.js +40 -1
  2. package/dist/components/CanvasIfSizeIsAvailable.js +2 -6
  3. package/dist/components/ControlButton.d.ts +1 -0
  4. package/dist/components/ControlButton.js +7 -2
  5. package/dist/components/EditorGuides/Guide.js +151 -21
  6. package/dist/components/EditorRuler/Ruler.js +20 -24
  7. package/dist/components/EditorRuler/index.js +26 -13
  8. package/dist/components/InlineAction.js +1 -0
  9. package/dist/components/MenuToolbar.d.ts +1 -0
  10. package/dist/components/MenuToolbar.js +4 -1
  11. package/dist/components/OutlineToggle.js +1 -1
  12. package/dist/components/SelectedOutlineElement.d.ts +17 -0
  13. package/dist/components/SelectedOutlineElement.js +1009 -0
  14. package/dist/components/SelectedOutlineOverlay.d.ts +4 -210
  15. package/dist/components/SelectedOutlineOverlay.js +68 -1637
  16. package/dist/components/SelectedOutlineUvControls.js +1 -1
  17. package/dist/components/ShowGuidesProvider.js +4 -4
  18. package/dist/components/Timeline/SubscribeToNodePaths.d.ts +2 -1
  19. package/dist/components/Timeline/SubscribeToNodePaths.js +2 -1
  20. package/dist/components/Timeline/Timeline.js +3 -1
  21. package/dist/components/Timeline/TimelineClipboardKeybindings.js +9 -10
  22. package/dist/components/Timeline/TimelineDeleteKeybindings.js +15 -4
  23. package/dist/components/Timeline/TimelineKeyframeEasingLine.js +7 -11
  24. package/dist/components/Timeline/TimelineList.js +1 -1
  25. package/dist/components/Timeline/TimelineSelection.d.ts +27 -13
  26. package/dist/components/Timeline/TimelineSelection.js +47 -28
  27. package/dist/components/Timeline/TimelineSequence.js +156 -3
  28. package/dist/components/Timeline/TimelineSequenceFrame.d.ts +1 -0
  29. package/dist/components/Timeline/TimelineSequenceFrame.js +17 -6
  30. package/dist/components/Timeline/TimelineSequenceItem.d.ts +1 -0
  31. package/dist/components/Timeline/TimelineSequenceItem.js +90 -130
  32. package/dist/components/Timeline/TimelineVideoInfo.d.ts +1 -0
  33. package/dist/components/Timeline/TimelineVideoInfo.js +93 -8
  34. package/dist/components/Timeline/delete-selected-timeline-item.js +4 -0
  35. package/dist/components/Timeline/duplicate-selected-timeline-item.d.ts +8 -2
  36. package/dist/components/Timeline/duplicate-selected-timeline-item.js +32 -3
  37. package/dist/components/Timeline/get-sequence-context-menu-items.d.ts +20 -0
  38. package/dist/components/Timeline/get-sequence-context-menu-items.js +160 -0
  39. package/dist/components/Timeline/sequence-props-subscription-store.d.ts +2 -1
  40. package/dist/components/Timeline/sequence-props-subscription-store.js +11 -3
  41. package/dist/components/Timeline/should-clear-selection-on-pointer-down.d.ts +2 -0
  42. package/dist/components/Timeline/should-clear-selection-on-pointer-down.js +16 -2
  43. package/dist/components/Timeline/timeline-video-filmstrip-times.d.ts +17 -0
  44. package/dist/components/Timeline/timeline-video-filmstrip-times.js +22 -0
  45. package/dist/components/Timeline/update-selected-easing.d.ts +4 -6
  46. package/dist/components/Timeline/use-sequence-props-subscription.d.ts +2 -1
  47. package/dist/components/Timeline/use-sequence-props-subscription.js +3 -1
  48. package/dist/components/Timeline/use-timeline-keyframe-drag.d.ts +37 -1
  49. package/dist/components/Timeline/use-timeline-keyframe-drag.js +282 -1
  50. package/dist/components/import-assets.d.ts +45 -8
  51. package/dist/components/import-assets.js +227 -12
  52. package/dist/components/selected-outline-drag.d.ts +140 -0
  53. package/dist/components/selected-outline-drag.js +475 -0
  54. package/dist/components/selected-outline-measurement.d.ts +67 -0
  55. package/dist/components/selected-outline-measurement.js +355 -0
  56. package/dist/components/selected-outline-types.d.ts +121 -0
  57. package/dist/components/selected-outline-types.js +15 -0
  58. package/dist/components/selected-outline-uv.d.ts +1 -0
  59. package/dist/components/selected-outline-uv.js +12 -0
  60. package/dist/error-overlay/remotion-overlay/Overlay.js +3 -0
  61. package/dist/esm/{chunk-0atarw3p.js → chunk-hrw9799x.js} +12812 -11386
  62. package/dist/esm/internals.mjs +12812 -11386
  63. package/dist/esm/previewEntry.mjs +21059 -19629
  64. package/dist/esm/renderEntry.mjs +1 -1
  65. package/dist/helpers/editor-guide-selection.d.ts +31 -0
  66. package/dist/helpers/editor-guide-selection.js +58 -0
  67. package/dist/helpers/editor-ruler.d.ts +3 -3
  68. package/dist/helpers/editor-ruler.js +16 -18
  69. package/dist/helpers/get-preview-file-type.js +1 -1
  70. package/dist/helpers/ruler-canvas-size.d.ts +5 -0
  71. package/dist/helpers/ruler-canvas-size.js +17 -0
  72. package/dist/state/editor-guides.d.ts +2 -2
  73. package/dist/state/editor-guides.js +2 -2
  74. package/package.json +11 -11
@@ -37,12 +37,24 @@ exports.TimelineSequence = void 0;
37
37
  const jsx_runtime_1 = require("react/jsx-runtime");
38
38
  const react_1 = __importStar(require("react"));
39
39
  const remotion_1 = require("remotion");
40
+ const client_id_1 = require("../../helpers/client-id");
40
41
  const colors_1 = require("../../helpers/colors");
42
+ const format_file_location_1 = require("../../helpers/format-file-location");
41
43
  const get_timeline_sequence_layout_1 = require("../../helpers/get-timeline-sequence-layout");
44
+ const open_in_editor_1 = require("../../helpers/open-in-editor");
42
45
  const timeline_layout_1 = require("../../helpers/timeline-layout");
43
46
  const use_max_media_duration_1 = require("../../helpers/use-max-media-duration");
44
47
  const AudioWaveform_1 = require("../AudioWaveform");
48
+ const call_api_1 = require("../call-api");
49
+ const ConfirmationDialog_1 = require("../ConfirmationDialog");
50
+ const ContextMenu_1 = require("../ContextMenu");
51
+ const NotificationCenter_1 = require("../Notifications/NotificationCenter");
52
+ const use_select_asset_1 = require("../use-select-asset");
53
+ const disable_sequence_interactivity_1 = require("./disable-sequence-interactivity");
54
+ const duplicate_selected_timeline_item_1 = require("./duplicate-selected-timeline-item");
55
+ const get_sequence_context_menu_items_1 = require("./get-sequence-context-menu-items");
45
56
  const LoopedTimelineIndicators_1 = require("./LoopedTimelineIndicators");
57
+ const timeline_asset_link_1 = require("./timeline-asset-link");
46
58
  const TimelineImageInfo_1 = require("./TimelineImageInfo");
47
59
  const TimelineSelection_1 = require("./TimelineSelection");
48
60
  const TimelineSequenceFrame_1 = require("./TimelineSequenceFrame");
@@ -60,7 +72,7 @@ const TimelineSequenceFn = ({ s, nodePathInfo, sequenceFrameOffset }) => {
60
72
  }
61
73
  return (jsx_runtime_1.jsx(TimelineSequenceInner, { windowWidth: windowWidth, s: s, nodePathInfo: nodePathInfo, sequenceFrameOffset: sequenceFrameOffset }));
62
74
  };
63
- const TimelineSequenceCurrentFrame = ({ s, displayDurationInFrames, premountWidth, postmountWidth, style, children, nodePathInfo, sequenceFrameOffset, fromCanUpdate, onMoveDragPointerDown, }) => {
75
+ const TimelineSequenceCurrentFrame = ({ s, displayDurationInFrames, premountWidth, postmountWidth, style, children, nodePathInfo, sequenceFrameOffset, fromCanUpdate, frozenFrame, onMoveDragPointerDown, }) => {
64
76
  var _a, _b;
65
77
  const ref = (0, react_1.useRef)(null);
66
78
  const { onSelect, selectable, selected, selectionItem } = (0, TimelineSelection_1.useTimelineRowSelection)(nodePathInfo);
@@ -135,7 +147,7 @@ const TimelineSequenceCurrentFrame = ({ s, displayDurationInFrames, premountWidt
135
147
  height: '100%',
136
148
  display: 'flex',
137
149
  alignItems: 'center',
138
- }, children: jsx_runtime_1.jsx(TimelineSequenceFrame_1.TimelineSequenceFrame, { premounted: isPremounting, postmounted: isPostmounting ? s.duration - 1 : null, roundedFrame: roundedFrame }) })) : null] }));
150
+ }, children: jsx_runtime_1.jsx(TimelineSequenceFrame_1.TimelineSequenceFrame, { premounted: isPremounting, postmounted: isPostmounting ? s.duration - 1 : null, roundedFrame: roundedFrame, frozenFrame: frozenFrame }) })) : null] }));
139
151
  };
140
152
  const TimelineSequenceInner = ({ s, windowWidth, nodePathInfo, sequenceFrameOffset }) => {
141
153
  // If a duration is 1, it is essentially a still and it should have width 0
@@ -169,6 +181,146 @@ const TimelineSequenceInner = ({ s, windowWidth, nodePathInfo, sequenceFrameOffs
169
181
  }, [propStatuses, nodePath]);
170
182
  const durationCanUpdate = Boolean(((_a = propStatusesForOverride === null || propStatusesForOverride === void 0 ? void 0 : propStatusesForOverride.durationInFrames) === null || _a === void 0 ? void 0 : _a.status) === 'static');
171
183
  const fromCanUpdate = Boolean(((_b = propStatusesForOverride === null || propStatusesForOverride === void 0 ? void 0 : propStatusesForOverride.from) === null || _b === void 0 ? void 0 : _b.status) === 'static');
184
+ const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
185
+ const previewConnected = previewServerState.type === 'connected';
186
+ const { setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
187
+ const selectAsset = (0, use_select_asset_1.useSelectAsset)();
188
+ const confirm = (0, ConfirmationDialog_1.useConfirmationDialog)();
189
+ const { onSelect, selectable } = (0, TimelineSelection_1.useTimelineRowSelection)(nodePathInfo);
190
+ const fileLocation = (0, react_1.useMemo)(() => (0, format_file_location_1.formatFileLocation)({
191
+ location: originalLocation,
192
+ root: window.remotion_cwd,
193
+ }), [originalLocation]);
194
+ const canOpenInEditor = Boolean(window.remotion_editorName && previewConnected && originalLocation);
195
+ const openInEditor = (0, react_1.useCallback)(() => {
196
+ if (!canOpenInEditor || !originalLocation) {
197
+ return;
198
+ }
199
+ (0, open_in_editor_1.openOriginalPositionInEditor)(originalLocation).catch((err) => {
200
+ (0, NotificationCenter_1.showNotification)(err.message, 2000);
201
+ });
202
+ }, [canOpenInEditor, originalLocation]);
203
+ const canDeleteFromSource = Boolean(nodePath && (validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source));
204
+ const deleteDisabled = !previewConnected || !s.controls || !canDeleteFromSource;
205
+ const duplicateDisabled = deleteDisabled;
206
+ const disableInteractivityDisabled = !previewConnected ||
207
+ !s.showInTimeline ||
208
+ !nodePath ||
209
+ !(validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source);
210
+ const mediaSrc = s.type === 'audio' || s.type === 'video' || s.type === 'image'
211
+ ? s.src
212
+ : null;
213
+ const assetLinkInfo = (0, react_1.useMemo)(() => (mediaSrc ? (0, timeline_asset_link_1.getTimelineAssetLinkInfo)(mediaSrc) : null), [mediaSrc]);
214
+ const onDuplicateSequenceFromSource = (0, react_1.useCallback)(() => {
215
+ if (!(validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source) || !nodePathInfo || duplicateDisabled) {
216
+ return;
217
+ }
218
+ (0, duplicate_selected_timeline_item_1.duplicateSequencesFromSource)([nodePathInfo], confirm).catch(() => undefined);
219
+ }, [confirm, duplicateDisabled, nodePathInfo, validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source]);
220
+ const onDeleteSequenceFromSource = (0, react_1.useCallback)(async () => {
221
+ if (!(validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source) || !nodePath || deleteDisabled) {
222
+ return;
223
+ }
224
+ if (nodePathInfo && nodePathInfo.numberOfSequencesWithThisNodePath > 1) {
225
+ const shouldDelete = await confirm({
226
+ title: 'Delete sequence?',
227
+ message: 'This sequence is programmatically duplicated ' +
228
+ nodePathInfo.numberOfSequencesWithThisNodePath +
229
+ ' times in the code. Deleting removes all instances. Continue?',
230
+ confirmLabel: 'Delete',
231
+ });
232
+ if (!shouldDelete) {
233
+ return;
234
+ }
235
+ }
236
+ try {
237
+ const result = await (0, call_api_1.callApi)('/api/delete-jsx-node', {
238
+ nodes: [
239
+ {
240
+ fileName: validatedLocation.source,
241
+ nodePath: nodePath.nodePath,
242
+ },
243
+ ],
244
+ });
245
+ if (result.success) {
246
+ (0, NotificationCenter_1.showNotification)('Removed sequence from source file', 2000);
247
+ }
248
+ else {
249
+ (0, NotificationCenter_1.showNotification)(result.reason, 4000);
250
+ }
251
+ }
252
+ catch (err) {
253
+ (0, NotificationCenter_1.showNotification)(err.message, 4000);
254
+ }
255
+ }, [
256
+ confirm,
257
+ deleteDisabled,
258
+ nodePath,
259
+ nodePathInfo,
260
+ validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source,
261
+ ]);
262
+ const onDisableSequenceInteractivity = (0, react_1.useCallback)(() => {
263
+ if (disableInteractivityDisabled ||
264
+ !nodePath ||
265
+ !(validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source) ||
266
+ previewServerState.type !== 'connected') {
267
+ return;
268
+ }
269
+ (0, disable_sequence_interactivity_1.disableSequenceInteractivity)({
270
+ fileName: validatedLocation.source,
271
+ nodePath,
272
+ setPropStatuses,
273
+ clientId: previewServerState.clientId,
274
+ });
275
+ }, [
276
+ disableInteractivityDisabled,
277
+ nodePath,
278
+ previewServerState,
279
+ setPropStatuses,
280
+ validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source,
281
+ ]);
282
+ const contextMenuValues = (0, react_1.useMemo)(() => {
283
+ if (!previewConnected) {
284
+ return [];
285
+ }
286
+ return (0, get_sequence_context_menu_items_1.getSequenceContextMenuItems)({
287
+ assetLinkInfo,
288
+ canOpenInEditor,
289
+ deleteDisabled,
290
+ disableInteractivityDisabled,
291
+ duplicateDisabled,
292
+ fileLocation,
293
+ includeSourceEditItems: true,
294
+ onDeleteSequenceFromSource,
295
+ onDisableSequenceInteractivity,
296
+ onDuplicateSequenceFromSource,
297
+ openInEditor,
298
+ originalLocation,
299
+ selectAsset,
300
+ sequence: s,
301
+ });
302
+ }, [
303
+ assetLinkInfo,
304
+ canOpenInEditor,
305
+ deleteDisabled,
306
+ disableInteractivityDisabled,
307
+ duplicateDisabled,
308
+ fileLocation,
309
+ onDeleteSequenceFromSource,
310
+ onDisableSequenceInteractivity,
311
+ onDuplicateSequenceFromSource,
312
+ openInEditor,
313
+ originalLocation,
314
+ previewConnected,
315
+ s,
316
+ selectAsset,
317
+ ]);
318
+ const onContextMenuOpen = (0, react_1.useCallback)(() => {
319
+ if (selectable) {
320
+ onSelect({ shiftKey: false, toggleKey: false });
321
+ }
322
+ }, [onSelect, selectable]);
323
+ const { frozenFrame } = s;
172
324
  const { onPointerDown: onMoveDragPointerDown } = (0, TimelineSequenceRightEdgeDragHandle_1.useTimelineSequenceFromDrag)({
173
325
  nodePathInfo,
174
326
  windowWidth,
@@ -226,6 +378,7 @@ const TimelineSequenceInner = ({ s, windowWidth, nodePathInfo, sequenceFrameOffs
226
378
  if (maxMediaDuration === null && !s.loopDisplay) {
227
379
  return null;
228
380
  }
229
- return (jsx_runtime_1.jsxs(TimelineSequenceCurrentFrame, { s: s, displayDurationInFrames: displayDurationInFrames, premountWidth: premountWidth, postmountWidth: postmountWidth, style: style, nodePathInfo: nodePathInfo, sequenceFrameOffset: sequenceFrameOffset, fromCanUpdate: fromCanUpdate, onMoveDragPointerDown: onMoveDragPointerDown, children: [s.type === 'audio' ? (jsx_runtime_1.jsx(AudioWaveform_1.AudioWaveform, { src: s.src, height: timeline_layout_1.TIMELINE_LAYER_HEIGHT_AUDIO, doesVolumeChange: s.doesVolumeChange, visualizationWidth: width, startFrom: s.startMediaFrom, durationInFrames: s.duration, volume: s.volume, playbackRate: s.playbackRate, loopDisplay: s.loopDisplay })) : null, s.type === 'video' ? (jsx_runtime_1.jsx(TimelineVideoInfo_1.TimelineVideoInfo, { src: s.src, visualizationWidth: width, naturalWidth: naturalWidth, trimBefore: s.startMediaFrom, durationInFrames: s.duration, playbackRate: s.playbackRate, volume: s.volume, doesVolumeChange: s.doesVolumeChange, premountWidth: premountWidth !== null && premountWidth !== void 0 ? premountWidth : 0, postmountWidth: postmountWidth !== null && postmountWidth !== void 0 ? postmountWidth : 0, loopDisplay: s.loopDisplay })) : null, s.type === 'image' ? (jsx_runtime_1.jsx(TimelineImageInfo_1.TimelineImageInfo, { src: s.src, visualizationWidth: width })) : null, s.loopDisplay === undefined ? null : (jsx_runtime_1.jsx(LoopedTimelineIndicators_1.LoopedTimelineIndicator, { loops: s.loopDisplay.numberOfTimes })), showRightEdgeDragHandle && nodePathInfo && validatedLocation ? (jsx_runtime_1.jsx(TimelineSequenceRightEdgeDragHandle_1.TimelineSequenceRightEdgeDragHandle, { nodePathInfo: nodePathInfo, windowWidth: windowWidth, timelineDurationInFrames: (_f = video.durationInFrames) !== null && _f !== void 0 ? _f : 1 })) : null] }));
381
+ const sequence = (jsx_runtime_1.jsxs(TimelineSequenceCurrentFrame, { s: s, displayDurationInFrames: displayDurationInFrames, premountWidth: premountWidth, postmountWidth: postmountWidth, style: style, nodePathInfo: nodePathInfo, sequenceFrameOffset: sequenceFrameOffset, fromCanUpdate: fromCanUpdate, frozenFrame: frozenFrame, onMoveDragPointerDown: onMoveDragPointerDown, children: [s.type === 'audio' ? (jsx_runtime_1.jsx(AudioWaveform_1.AudioWaveform, { src: s.src, height: timeline_layout_1.TIMELINE_LAYER_HEIGHT_AUDIO, doesVolumeChange: s.doesVolumeChange, visualizationWidth: width, startFrom: s.startMediaFrom, durationInFrames: s.duration, volume: s.volume, playbackRate: s.playbackRate, loopDisplay: s.loopDisplay })) : null, s.type === 'video' ? (jsx_runtime_1.jsx(TimelineVideoInfo_1.TimelineVideoInfo, { src: s.src, visualizationWidth: width, naturalWidth: naturalWidth, trimBefore: s.startMediaFrom, durationInFrames: s.duration, playbackRate: s.playbackRate, volume: s.volume, doesVolumeChange: s.doesVolumeChange, premountWidth: premountWidth !== null && premountWidth !== void 0 ? premountWidth : 0, postmountWidth: postmountWidth !== null && postmountWidth !== void 0 ? postmountWidth : 0, loopDisplay: s.loopDisplay, frozenMediaFrame: s.frozenMediaFrame })) : null, s.type === 'image' ? (jsx_runtime_1.jsx(TimelineImageInfo_1.TimelineImageInfo, { src: s.src, visualizationWidth: width })) : null, s.loopDisplay === undefined ? null : (jsx_runtime_1.jsx(LoopedTimelineIndicators_1.LoopedTimelineIndicator, { loops: s.loopDisplay.numberOfTimes })), showRightEdgeDragHandle && nodePathInfo && validatedLocation ? (jsx_runtime_1.jsx(TimelineSequenceRightEdgeDragHandle_1.TimelineSequenceRightEdgeDragHandle, { nodePathInfo: nodePathInfo, windowWidth: windowWidth, timelineDurationInFrames: (_f = video.durationInFrames) !== null && _f !== void 0 ? _f : 1 })) : null] }));
382
+ return previewConnected ? (jsx_runtime_1.jsx(ContextMenu_1.ContextMenu, { values: contextMenuValues, onOpen: onContextMenuOpen, children: sequence })) : (sequence);
230
383
  };
231
384
  exports.TimelineSequence = react_1.default.memo(TimelineSequenceFn);
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  export declare const TimelineSequenceFrame: React.FC<{
3
3
  readonly roundedFrame: number;
4
+ readonly frozenFrame: number | null;
4
5
  readonly premounted: boolean;
5
6
  readonly postmounted: number | null;
6
7
  }>;
@@ -3,19 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TimelineSequenceFrame = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const relativeFrameStyle = {
6
+ alignItems: 'center',
7
+ display: 'flex',
6
8
  fontSize: 11,
7
9
  fontFamily: 'Arial, Helvetica, sans-serif',
10
+ gap: 4,
8
11
  color: 'white',
9
12
  opacity: 0.5,
10
13
  whiteSpace: 'nowrap',
11
14
  pointerEvents: 'none',
12
15
  userSelect: 'none',
13
16
  };
14
- const TimelineSequenceFrame = ({ roundedFrame, premounted, postmounted }) => {
15
- return (jsx_runtime_1.jsx("div", { style: relativeFrameStyle, children: premounted
16
- ? '0 (Premounted)'
17
- : postmounted !== null
18
- ? `${postmounted} (Postmounted)`
19
- : roundedFrame }));
17
+ const snowflakeStyle = {
18
+ flexShrink: 0,
19
+ height: 12,
20
+ width: 12,
21
+ };
22
+ const FreezeFrameIcon = () => {
23
+ return (jsx_runtime_1.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 512 512", style: snowflakeStyle, fill: "currentColor", children: jsx_runtime_1.jsx("path", { d: "M288 32l0-32-64 0 0 88.4-64-64-39.6 39.6 103.6 103.6 0 56.4-56.4 0-103.6-103.6-39.6 39.6 64 64-88.4 0 0 64 88.4 0c-33.5 33.5-54.9 54.9-64 64L64 391.6c2.5-2.5 37.1-37.1 103.6-103.6l56.4 0 0 56.4C157.5 410.9 122.9 445.5 120.4 448L160 487.6c9.1-9.1 30.5-30.5 64-64l0 88.4 64 0 0-88.4 64 64 39.6-39.6-103.6-103.6 0-56.4 56.4 0 103.6 103.6 39.6-39.6-64-64 88.4 0 0-64-88.4 0c33.5-33.5 54.9-54.9 64-64L448 120.4c-2.5 2.5-37.1 37.1-103.6 103.6l-56.4 0 0-56.4C354.5 101.1 389.1 66.5 391.6 64L352 24.4c-9.1 9.1-30.5 30.5-64 64L288 32z" }) }));
24
+ };
25
+ const TimelineSequenceFrame = ({ roundedFrame, frozenFrame, premounted, postmounted }) => {
26
+ return (jsx_runtime_1.jsxs("div", { style: relativeFrameStyle, children: [frozenFrame === null ? null : jsx_runtime_1.jsx(FreezeFrameIcon, {}), frozenFrame !== null && frozenFrame !== void 0 ? frozenFrame : (premounted
27
+ ? '0 (Premounted)'
28
+ : postmounted !== null
29
+ ? `${postmounted} (Postmounted)`
30
+ : roundedFrame)] }));
20
31
  };
21
32
  exports.TimelineSequenceFrame = TimelineSequenceFrame;
@@ -6,5 +6,6 @@ export declare const TimelineSequenceItem: React.FC<{
6
6
  readonly nestedDepth: number;
7
7
  readonly nodePathInfo: SequenceNodePathInfo | null;
8
8
  readonly keyframeDisplayOffset: number;
9
+ readonly sequenceFrameOffset: number;
9
10
  readonly trackIndex: number;
10
11
  }>;
@@ -37,7 +37,6 @@ exports.TimelineSequenceItem = void 0;
37
37
  const jsx_runtime_1 = require("react/jsx-runtime");
38
38
  const react_1 = __importStar(require("react"));
39
39
  const remotion_1 = require("remotion");
40
- const no_react_1 = require("remotion/no-react");
41
40
  const client_id_1 = require("../../helpers/client-id");
42
41
  const format_file_location_1 = require("../../helpers/format-file-location");
43
42
  const timeline_layout_1 = require("../../helpers/timeline-layout");
@@ -52,6 +51,7 @@ const NotificationCenter_1 = require("../Notifications/NotificationCenter");
52
51
  const use_select_asset_1 = require("../use-select-asset");
53
52
  const disable_sequence_interactivity_1 = require("./disable-sequence-interactivity");
54
53
  const duplicate_selected_timeline_item_1 = require("./duplicate-selected-timeline-item");
54
+ const get_sequence_context_menu_items_1 = require("./get-sequence-context-menu-items");
55
55
  const save_sequence_prop_1 = require("./save-sequence-prop");
56
56
  const timeline_asset_link_1 = require("./timeline-asset-link");
57
57
  const TimelineExpandArrowButton_1 = require("./TimelineExpandArrowButton");
@@ -137,7 +137,7 @@ const getSequenceReorderDragData = (dataTransfer) => {
137
137
  const getDestinationIndex = ({ fromIndex, insertionIndex, }) => {
138
138
  return insertionIndex > fromIndex ? insertionIndex - 1 : insertionIndex;
139
139
  };
140
- const TimelineSequenceItem = ({ nestedDepth, sequence, nodePathInfo, keyframeDisplayOffset, trackIndex, }) => {
140
+ const TimelineSequenceItem = ({ nestedDepth, sequence, nodePathInfo, keyframeDisplayOffset, sequenceFrameOffset, trackIndex, }) => {
141
141
  var _a, _b, _c;
142
142
  var _d, _e;
143
143
  const nodePath = (_d = nodePathInfo === null || nodePathInfo === void 0 ? void 0 : nodePathInfo.sequenceSubscriptionKey) !== null && _d !== void 0 ? _d : null;
@@ -156,6 +156,7 @@ const TimelineSequenceItem = ({ nestedDepth, sequence, nodePathInfo, keyframeDis
156
156
  const [isRenaming, setIsRenaming] = (0, react_1.useState)(false);
157
157
  const [sequenceDropIndicator, setSequenceDropIndicator] = (0, react_1.useState)(null);
158
158
  const [sequenceDropRejection, setSequenceDropRejection] = (0, react_1.useState)(null);
159
+ const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
159
160
  const { canOpenInEditor, openInEditor, originalLocation } = (0, use_open_sequence_in_editor_1.useOpenSequenceInEditor)(sequence);
160
161
  const fileLocation = (0, react_1.useMemo)(() => (0, format_file_location_1.formatFileLocation)({
161
162
  location: originalLocation,
@@ -615,167 +616,126 @@ const TimelineSequenceItem = ({ nestedDepth, sequence, nodePathInfo, keyframeDis
615
616
  }
616
617
  setIsRenaming(true);
617
618
  }, [canRenameThisSequence]);
619
+ const freezeStatus = propStatusesForOverride === null || propStatusesForOverride === void 0 ? void 0 : propStatusesForOverride.freeze;
620
+ const isFrozen = (freezeStatus === null || freezeStatus === void 0 ? void 0 : freezeStatus.status) === 'static' &&
621
+ typeof freezeStatus.codeValue === 'number';
622
+ const canToggleFreeze = previewConnected &&
623
+ Boolean(sequence.controls) &&
624
+ nodePath !== null &&
625
+ validatedLocation !== null &&
626
+ freezeStatus !== undefined &&
627
+ freezeStatus !== null &&
628
+ freezeStatus.status === 'static';
629
+ const onToggleFreezeFrame = (0, react_1.useCallback)(() => {
630
+ if (!canToggleFreeze ||
631
+ !sequence.controls ||
632
+ !nodePath ||
633
+ !validatedLocation ||
634
+ previewServerState.type !== 'connected') {
635
+ return;
636
+ }
637
+ const rawFreezeFrame = Math.round(timelinePosition - sequence.from + sequenceFrameOffset);
638
+ const maxFrame = Number.isFinite(sequence.duration)
639
+ ? Math.max(0, sequence.duration - 1)
640
+ : rawFreezeFrame;
641
+ const freezeFrame = Math.min(Math.max(0, rawFreezeFrame), maxFrame);
642
+ const remove = isFrozen;
643
+ (0, save_sequence_prop_1.saveSequenceProps)({
644
+ changes: [
645
+ {
646
+ fileName: validatedLocation.source,
647
+ nodePath,
648
+ fieldKey: 'freeze',
649
+ value: remove ? null : freezeFrame,
650
+ defaultValue: null,
651
+ schema: sequence.controls.schema,
652
+ },
653
+ ],
654
+ setPropStatuses,
655
+ clientId: previewServerState.clientId,
656
+ undoLabel: remove ? 'Unfreeze sequence' : 'Freeze sequence',
657
+ redoLabel: remove ? 'Freeze sequence again' : 'Unfreeze sequence again',
658
+ });
659
+ }, [
660
+ canToggleFreeze,
661
+ isFrozen,
662
+ nodePath,
663
+ previewServerState,
664
+ sequence.controls,
665
+ sequence.duration,
666
+ sequence.from,
667
+ sequenceFrameOffset,
668
+ setPropStatuses,
669
+ timelinePosition,
670
+ validatedLocation,
671
+ ]);
618
672
  const contextMenuValues = (0, react_1.useMemo)(() => {
619
673
  if (!previewConnected) {
620
674
  return [];
621
675
  }
622
- const editorName = window.remotion_editorName;
623
- const { documentationLink } = sequence;
624
- return [
625
- editorName
626
- ? {
676
+ return (0, get_sequence_context_menu_items_1.getSequenceContextMenuItems)({
677
+ assetLinkInfo,
678
+ canOpenInEditor,
679
+ deleteDisabled,
680
+ disableInteractivityDisabled,
681
+ duplicateDisabled,
682
+ fileLocation,
683
+ includeSourceEditItems: true,
684
+ onDeleteSequenceFromSource,
685
+ onDisableSequenceInteractivity,
686
+ onDuplicateSequenceFromSource,
687
+ openInEditor,
688
+ originalLocation,
689
+ selectAsset,
690
+ sequence,
691
+ sourceActions: [
692
+ {
627
693
  type: 'item',
628
- id: 'show-in-editor',
694
+ id: 'rename-sequence',
629
695
  keyHint: null,
630
- label: `Show in ${editorName}`,
696
+ label: 'Rename...',
631
697
  leftItem: null,
632
- disabled: !canOpenInEditor,
698
+ disabled: !canRenameThisSequence,
633
699
  onClick: () => {
634
- openInEditor();
700
+ onRenameSequence();
635
701
  },
636
702
  quickSwitcherLabel: null,
637
703
  subMenu: null,
638
- value: 'show-in-editor',
639
- }
640
- : null,
641
- {
642
- type: 'item',
643
- id: 'copy-file-location',
644
- keyHint: null,
645
- label: 'Copy file location',
646
- leftItem: null,
647
- disabled: !fileLocation,
648
- onClick: () => {
649
- if (!fileLocation) {
650
- return;
651
- }
652
- navigator.clipboard
653
- .writeText(fileLocation)
654
- .then(() => {
655
- (0, NotificationCenter_1.showNotification)('Copied file location to clipboard', 1000);
656
- })
657
- .catch((err) => {
658
- (0, NotificationCenter_1.showNotification)(`Could not copy to clipboard: ${err.message}`, 1000);
659
- });
704
+ value: 'rename-sequence',
660
705
  },
661
- quickSwitcherLabel: null,
662
- subMenu: null,
663
- value: 'copy-file-location',
664
- },
665
- documentationLink
666
- ? {
667
- type: 'item',
668
- id: 'open-component-docs',
669
- keyHint: null,
670
- label: 'Open component docs',
671
- leftItem: null,
672
- disabled: false,
673
- onClick: () => {
674
- window.open(documentationLink, '_blank', 'noopener,noreferrer');
675
- },
676
- quickSwitcherLabel: null,
677
- subMenu: null,
678
- value: 'open-component-docs',
679
- }
680
- : null,
681
- assetLinkInfo
682
- ? {
706
+ {
683
707
  type: 'item',
684
- id: 'show-asset',
708
+ id: 'toggle-freeze-frame',
685
709
  keyHint: null,
686
- label: 'Show asset',
710
+ label: isFrozen ? 'Unfreeze frame' : 'Freeze frame',
687
711
  leftItem: null,
688
- disabled: false,
712
+ disabled: !canToggleFreeze,
689
713
  onClick: () => {
690
- (0, timeline_asset_link_1.openTimelineAssetLink)(assetLinkInfo, selectAsset);
714
+ onToggleFreezeFrame();
691
715
  },
692
716
  quickSwitcherLabel: null,
693
717
  subMenu: null,
694
- value: 'show-asset',
695
- }
696
- : null,
697
- documentationLink
698
- ? {
699
- type: 'divider',
700
- id: 'open-component-docs-divider',
701
- }
702
- : null,
703
- {
704
- type: 'item',
705
- id: 'rename-sequence',
706
- keyHint: 'Enter',
707
- label: 'Rename...',
708
- leftItem: null,
709
- disabled: !canRenameThisSequence,
710
- onClick: () => {
711
- onRenameSequence();
718
+ value: 'toggle-freeze-frame',
712
719
  },
713
- quickSwitcherLabel: null,
714
- subMenu: null,
715
- value: 'rename-sequence',
716
- },
717
- {
718
- type: 'item',
719
- id: 'disable-interactivity',
720
- keyHint: null,
721
- label: 'Disable interactivity',
722
- leftItem: null,
723
- disabled: disableInteractivityDisabled,
724
- onClick: () => {
725
- onDisableSequenceInteractivity();
726
- },
727
- quickSwitcherLabel: null,
728
- subMenu: null,
729
- value: 'disable-interactivity',
730
- },
731
- {
732
- type: 'item',
733
- id: 'duplicate-sequence',
734
- keyHint: null,
735
- label: 'Duplicate',
736
- leftItem: null,
737
- disabled: duplicateDisabled,
738
- onClick: () => {
739
- if (duplicateDisabled) {
740
- return;
741
- }
742
- onDuplicateSequenceFromSource();
743
- },
744
- quickSwitcherLabel: null,
745
- subMenu: null,
746
- value: 'duplicate-sequence',
747
- },
748
- {
749
- type: 'item',
750
- id: 'delete-sequence',
751
- keyHint: null,
752
- label: 'Delete',
753
- leftItem: null,
754
- disabled: deleteDisabled,
755
- onClick: () => {
756
- if (deleteDisabled) {
757
- return;
758
- }
759
- onDeleteSequenceFromSource();
760
- },
761
- quickSwitcherLabel: null,
762
- subMenu: null,
763
- value: 'delete-sequence',
764
- },
765
- ].filter(no_react_1.NoReactInternals.truthy);
720
+ ],
721
+ });
766
722
  }, [
767
723
  assetLinkInfo,
768
724
  canOpenInEditor,
769
725
  canRenameThisSequence,
726
+ canToggleFreeze,
770
727
  deleteDisabled,
771
728
  disableInteractivityDisabled,
772
729
  duplicateDisabled,
773
730
  fileLocation,
731
+ isFrozen,
774
732
  onDeleteSequenceFromSource,
775
733
  onDisableSequenceInteractivity,
776
734
  onDuplicateSequenceFromSource,
777
735
  onRenameSequence,
736
+ onToggleFreezeFrame,
778
737
  openInEditor,
738
+ originalLocation,
779
739
  previewConnected,
780
740
  selectAsset,
781
741
  sequence,
@@ -12,4 +12,5 @@ export declare const TimelineVideoInfo: React.FC<{
12
12
  readonly premountWidth: number;
13
13
  readonly postmountWidth: number;
14
14
  readonly loopDisplay: LoopDisplay | undefined;
15
+ readonly frozenMediaFrame: number | null;
15
16
  }>;