@remotion/studio 4.0.472 → 4.0.473

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 (133) hide show
  1. package/dist/api/rename-static-file.d.ts +6 -0
  2. package/dist/api/rename-static-file.js +18 -0
  3. package/dist/components/AssetSelector.js +45 -4
  4. package/dist/components/AssetSelectorItem.js +153 -27
  5. package/dist/components/Canvas.js +60 -11
  6. package/dist/components/ConfirmationDialog-types.d.ts +8 -0
  7. package/dist/components/ConfirmationDialog-types.js +2 -0
  8. package/dist/components/ConfirmationDialog.d.ts +7 -0
  9. package/dist/components/ConfirmationDialog.js +103 -0
  10. package/dist/components/ContextMenu.d.ts +9 -1
  11. package/dist/components/ContextMenu.js +49 -5
  12. package/dist/components/CurrentAsset.d.ts +1 -0
  13. package/dist/components/CurrentAsset.js +13 -2
  14. package/dist/components/EditorContent.js +15 -2
  15. package/dist/components/EditorContexts.js +2 -1
  16. package/dist/components/EditorRuler/Ruler.js +2 -0
  17. package/dist/components/ExplorerPanel.d.ts +0 -4
  18. package/dist/components/ExplorerPanel.js +8 -4
  19. package/dist/components/ExplorerPanelRef.d.ts +4 -0
  20. package/dist/components/ExplorerPanelRef.js +5 -0
  21. package/dist/components/FilePreview.d.ts +1 -1
  22. package/dist/components/InitialCompositionLoader.d.ts +0 -1
  23. package/dist/components/InitialCompositionLoader.js +5 -27
  24. package/dist/components/Menu/MenuItem.js +7 -1
  25. package/dist/components/Menu/SubMenu.js +5 -1
  26. package/dist/components/Menu/portals.js +17 -8
  27. package/dist/components/MenuToolbar.js +5 -1
  28. package/dist/components/ModalContainer.js +6 -1
  29. package/dist/components/Modals.js +6 -2
  30. package/dist/components/NewComposition/ComboBox.js +8 -2
  31. package/dist/components/NewComposition/DeleteStaticFile.d.ts +4 -0
  32. package/dist/components/NewComposition/DeleteStaticFile.js +44 -0
  33. package/dist/components/NewComposition/RenameStaticFile.d.ts +4 -0
  34. package/dist/components/NewComposition/RenameStaticFile.js +118 -0
  35. package/dist/components/OptionsPanel.js +5 -1
  36. package/dist/components/OutlineToggle.d.ts +2 -0
  37. package/dist/components/OutlineToggle.js +20 -0
  38. package/dist/components/Preview.d.ts +0 -2
  39. package/dist/components/Preview.js +23 -33
  40. package/dist/components/PreviewToolbar.js +19 -6
  41. package/dist/components/RenderButton.js +8 -2
  42. package/dist/components/RenderPreview.js +2 -2
  43. package/dist/components/SelectedOutlineOverlay.d.ts +24 -0
  44. package/dist/components/SelectedOutlineOverlay.js +190 -22
  45. package/dist/components/ShowOutlinesProvider.d.ts +4 -0
  46. package/dist/components/ShowOutlinesProvider.js +24 -0
  47. package/dist/components/SizeSelector.js +3 -3
  48. package/dist/components/Splitter/SplitterHandle.js +2 -0
  49. package/dist/components/StaticFilePreview.js +2 -2
  50. package/dist/components/Timeline/KeyframeSettingsModal.d.ts +15 -0
  51. package/dist/components/Timeline/KeyframeSettingsModal.js +150 -0
  52. package/dist/components/Timeline/Timeline.js +3 -13
  53. package/dist/components/Timeline/TimelineClipboardKeybindings.d.ts +25 -2
  54. package/dist/components/Timeline/TimelineClipboardKeybindings.js +234 -20
  55. package/dist/components/Timeline/TimelineDeleteKeybindings.js +12 -2
  56. package/dist/components/Timeline/TimelineEffectItem.js +1 -0
  57. package/dist/components/Timeline/TimelineEffectPropItem.js +52 -2
  58. package/dist/components/Timeline/TimelineKeyframeControls.js +5 -15
  59. package/dist/components/Timeline/TimelineKeyframeDiamond.js +24 -21
  60. package/dist/components/Timeline/TimelineKeyframeDiamondIcon.d.ts +6 -0
  61. package/dist/components/Timeline/TimelineKeyframeDiamondIcon.js +14 -0
  62. package/dist/components/Timeline/TimelineKeyframeDragState.d.ts +17 -0
  63. package/dist/components/Timeline/TimelineKeyframeDragState.js +39 -0
  64. package/dist/components/Timeline/TimelineList.js +2 -2
  65. package/dist/components/Timeline/TimelineMediaInfo.d.ts +0 -13
  66. package/dist/components/Timeline/TimelineMediaInfo.js +8 -73
  67. package/dist/components/Timeline/TimelineScaleField.js +1 -1
  68. package/dist/components/Timeline/TimelineSequenceItem.d.ts +1 -0
  69. package/dist/components/Timeline/TimelineSequenceItem.js +276 -22
  70. package/dist/components/Timeline/TimelineSequencePropItem.js +81 -16
  71. package/dist/components/Timeline/TimelineSequenceRightEdgeDragHandle.js +25 -28
  72. package/dist/components/Timeline/apply-effect-response-to-code-values.d.ts +5 -0
  73. package/dist/components/Timeline/apply-effect-response-to-code-values.js +19 -0
  74. package/dist/components/Timeline/call-add-keyframe.js +2 -0
  75. package/dist/components/Timeline/call-move-keyframe.d.ts +19 -0
  76. package/dist/components/Timeline/call-move-keyframe.js +71 -0
  77. package/dist/components/Timeline/call-update-keyframe-settings.d.ts +22 -0
  78. package/dist/components/Timeline/call-update-keyframe-settings.js +52 -0
  79. package/dist/components/Timeline/delete-selected-timeline-item.d.ts +7 -4
  80. package/dist/components/Timeline/delete-selected-timeline-item.js +33 -21
  81. package/dist/components/Timeline/duplicate-selected-timeline-item.d.ts +4 -2
  82. package/dist/components/Timeline/duplicate-selected-timeline-item.js +39 -34
  83. package/dist/components/Timeline/get-bounded-keyframe-drag-delta.d.ts +8 -0
  84. package/dist/components/Timeline/get-bounded-keyframe-drag-delta.js +12 -0
  85. package/dist/components/Timeline/get-keyframe-navigation.d.ts +2 -2
  86. package/dist/components/Timeline/get-keyframe-navigation.js +14 -6
  87. package/dist/components/Timeline/reset-selected-timeline-props.js +3 -2
  88. package/dist/components/Timeline/save-effect-prop.d.ts +14 -3
  89. package/dist/components/Timeline/save-effect-prop.js +36 -18
  90. package/dist/components/Timeline/save-prop-queue.d.ts +2 -1
  91. package/dist/components/Timeline/save-prop-queue.js +5 -2
  92. package/dist/components/Timeline/save-sequence-prop.d.ts +2 -7
  93. package/dist/components/Timeline/save-sequence-prop.js +33 -30
  94. package/dist/components/Timeline/should-clear-selection-on-pointer-down.d.ts +3 -0
  95. package/dist/components/Timeline/should-clear-selection-on-pointer-down.js +7 -0
  96. package/dist/components/Timeline/timeline-asset-link.d.ts +13 -0
  97. package/dist/components/Timeline/timeline-asset-link.js +37 -0
  98. package/dist/components/Timeline/timeline-translate-utils.js +4 -1
  99. package/dist/components/Timeline/use-timeline-keyframe-drag.d.ts +10 -0
  100. package/dist/components/Timeline/use-timeline-keyframe-drag.js +378 -0
  101. package/dist/components/import-assets.d.ts +16 -0
  102. package/dist/components/import-assets.js +155 -18
  103. package/dist/components/load-canvas-content-from-url.d.ts +1 -0
  104. package/dist/components/load-canvas-content-from-url.js +9 -3
  105. package/dist/components/use-select-asset.d.ts +1 -0
  106. package/dist/components/use-select-asset.js +30 -0
  107. package/dist/error-overlay/error-origin.d.ts +3 -0
  108. package/dist/error-overlay/error-origin.js +42 -0
  109. package/dist/error-overlay/react-overlay/listen-to-runtime-errors.js +6 -2
  110. package/dist/error-overlay/remotion-overlay/ErrorLoader.js +38 -0
  111. package/dist/error-overlay/remotion-overlay/ShortcutHint.js +1 -1
  112. package/dist/error-overlay/remotion-overlay/log-studio-error.d.ts +3 -0
  113. package/dist/error-overlay/remotion-overlay/log-studio-error.js +27 -0
  114. package/dist/esm/{chunk-48grt472.js → chunk-q0jkt0zq.js} +21961 -19299
  115. package/dist/esm/internals.mjs +21961 -19299
  116. package/dist/esm/previewEntry.mjs +20600 -17914
  117. package/dist/esm/renderEntry.mjs +1 -1
  118. package/dist/helpers/get-asset-metadata.js +2 -2
  119. package/dist/helpers/get-preview-file-type.d.ts +2 -0
  120. package/dist/helpers/get-preview-file-type.js +33 -0
  121. package/dist/helpers/install-required-package.d.ts +1 -0
  122. package/dist/helpers/install-required-package.js +39 -0
  123. package/dist/helpers/remote-asset-drag.d.ts +4 -0
  124. package/dist/helpers/remote-asset-drag.js +73 -0
  125. package/dist/helpers/use-asset-drag-events.d.ts +5 -2
  126. package/dist/helpers/use-asset-drag-events.js +13 -2
  127. package/dist/hot-middleware-client/client.js +6 -0
  128. package/dist/state/editor-outlines.d.ts +8 -0
  129. package/dist/state/editor-outlines.js +18 -0
  130. package/dist/state/modals.d.ts +19 -2
  131. package/package.json +10 -10
  132. package/dist/helpers/detect-file-type.d.ts +0 -69
  133. package/dist/helpers/detect-file-type.js +0 -278
@@ -78,9 +78,8 @@ const container = {
78
78
  backgroundColor: colors_1.BACKGROUND,
79
79
  };
80
80
  const noop = () => undefined;
81
- const TimelineClearSelectionArea = ({ children }) => {
81
+ const TimelineContextMenuArea = ({ children }) => {
82
82
  var _a, _b;
83
- const { clearSelection } = (0, TimelineSelection_1.useTimelineSelection)();
84
83
  const { compositions, canvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager);
85
84
  const videoConfig = remotion_1.Internals.useUnsafeVideoConfig();
86
85
  const [isAddingSolid, setIsAddingSolid] = (0, react_1.useState)(false);
@@ -101,15 +100,6 @@ const TimelineClearSelectionArea = ({ children }) => {
101
100
  compositionFile,
102
101
  compositionId: currentCompositionId,
103
102
  });
104
- // Selection-triggering click handlers in children call e.stopPropagation(),
105
- // so any pointerdown that bubbles up here is by definition on empty space
106
- // and should clear the current selection.
107
- const onPointerDown = (0, react_1.useCallback)((e) => {
108
- if (e.button !== 0) {
109
- return;
110
- }
111
- clearSelection();
112
- }, [clearSelection]);
113
103
  const canInsertSolid = previewConnected &&
114
104
  (compositionComponentInfo === null || compositionComponentInfo === void 0 ? void 0 : compositionComponentInfo.canAddSequence) === true &&
115
105
  currentCompositionId !== null &&
@@ -203,7 +193,7 @@ const TimelineClearSelectionArea = ({ children }) => {
203
193
  },
204
194
  ];
205
195
  }, [insertSolid, canInsertSolid, insertAsset, canInsertAsset]);
206
- return (jsx_runtime_1.jsx(ContextMenu_1.ContextMenu, { ref: timeline_refs_1.timelineVerticalScroll, values: contextMenuItems, onOpen: null, style: container, className: 'css-reset ' + is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME, onPointerDown: onPointerDown, children: children }));
196
+ return (jsx_runtime_1.jsx(ContextMenu_1.ContextMenu, { ref: timeline_refs_1.timelineVerticalScroll, values: contextMenuItems, onOpen: null, style: container, className: 'css-reset ' + is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME, children: children }));
207
197
  };
208
198
  const TimelineInner = () => {
209
199
  var _a;
@@ -233,7 +223,7 @@ const TimelineInner = () => {
233
223
  : filtered;
234
224
  }, [filtered]);
235
225
  const hasBeenCut = filtered.length > shown.length;
236
- return (jsx_runtime_1.jsxs(TimelineClearSelectionArea, { children: [sequences.map((sequence) => {
226
+ return (jsx_runtime_1.jsxs(TimelineContextMenuArea, { children: [sequences.map((sequence) => {
237
227
  if (!sequence.controls || !previewConnected || !sequence.getStack()) {
238
228
  return null;
239
229
  }
@@ -1,5 +1,6 @@
1
- import { type EffectClipboardSnapshot } from '@remotion/studio-shared';
1
+ import { type EffectClipboardSnapshot, type EffectPropClipboardData } from '@remotion/studio-shared';
2
2
  import type React from 'react';
3
+ import { type CodeValues, type OverrideIdToNodePaths, type SequencePropsSubscriptionKey, type SequenceSchema, type TSequence } from 'remotion';
3
4
  import type { SequenceNodePathInfo } from '../../helpers/get-timeline-sequence-sort-key';
4
5
  import { type TimelineSelection } from './TimelineSelection';
5
6
  export type PasteEffectsTarget = {
@@ -12,9 +13,31 @@ export type PasteEffectsTarget = {
12
13
  } | {
13
14
  readonly type: 'unsupported';
14
15
  };
16
+ export type PasteEffectPropTarget = {
17
+ readonly type: 'valid';
18
+ readonly fileName: string;
19
+ readonly nodePath: SequencePropsSubscriptionKey;
20
+ readonly effectIndex: number;
21
+ readonly fieldKey: string;
22
+ readonly defaultValue: string | null;
23
+ readonly schema: SequenceSchema;
24
+ } | {
25
+ readonly type: 'none' | 'multiple' | 'unsupported' | 'effect-type-mismatch' | 'prop-mismatch' | 'uncopyable';
26
+ };
15
27
  export declare const getPasteEffectsTarget: (selectedItems: readonly TimelineSelection[]) => PasteEffectsTarget;
16
28
  export declare const getSnapshotsFromSelection: ({ selection, codeValues, }: {
17
29
  selection: TimelineSelection;
18
- codeValues: import("remotion").CodeValues;
30
+ codeValues: CodeValues;
19
31
  }) => EffectClipboardSnapshot[] | null;
32
+ export declare const getEffectPropClipboardDataFromSelection: ({ selection, codeValues, }: {
33
+ selection: TimelineSelection;
34
+ codeValues: CodeValues;
35
+ }) => EffectPropClipboardData | null;
36
+ export declare const getPasteEffectPropTarget: ({ selectedItems, payload, codeValues, sequences, overrideIdsToNodePaths, }: {
37
+ readonly selectedItems: readonly TimelineSelection[];
38
+ readonly payload: EffectPropClipboardData;
39
+ readonly codeValues: CodeValues;
40
+ readonly sequences: TSequence[];
41
+ readonly overrideIdsToNodePaths: OverrideIdToNodePaths;
42
+ }) => PasteEffectPropTarget;
20
43
  export declare const TimelineClipboardKeybindings: React.FC;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TimelineClipboardKeybindings = exports.getSnapshotsFromSelection = exports.getPasteEffectsTarget = void 0;
3
+ exports.TimelineClipboardKeybindings = exports.getPasteEffectPropTarget = exports.getEffectPropClipboardDataFromSelection = exports.getSnapshotsFromSelection = exports.getPasteEffectsTarget = void 0;
4
4
  const studio_shared_1 = require("@remotion/studio-shared");
5
5
  const react_1 = require("react");
6
6
  const remotion_1 = require("remotion");
@@ -8,6 +8,8 @@ const client_id_1 = require("../../helpers/client-id");
8
8
  const use_keybinding_1 = require("../../helpers/use-keybinding");
9
9
  const call_api_1 = require("../call-api");
10
10
  const NotificationCenter_1 = require("../Notifications/NotificationCenter");
11
+ const find_track_for_node_path_info_1 = require("./find-track-for-node-path-info");
12
+ const save_effect_prop_1 = require("./save-effect-prop");
11
13
  const TimelineSelection_1 = require("./TimelineSelection");
12
14
  const makeClipboardText = (payload) => JSON.stringify(payload);
13
15
  const makeTargetKey = (nodePath) => {
@@ -17,6 +19,10 @@ const makeTargetKey = (nodePath) => {
17
19
  sequenceKeys: nodePath.sequenceKeys,
18
20
  });
19
21
  };
22
+ const isVisibleFieldSchema = (fieldSchema) => fieldSchema !== undefined && fieldSchema.type !== 'hidden';
23
+ const getDefaultValue = (fieldSchema) => fieldSchema.default !== undefined
24
+ ? JSON.stringify(fieldSchema.default)
25
+ : null;
20
26
  const getTargetSequenceNodePathInfo = (selection) => {
21
27
  if (selection.type === 'sequence' ||
22
28
  selection.type === 'sequence-effect' ||
@@ -61,35 +67,45 @@ exports.getPasteEffectsTarget = getPasteEffectsTarget;
61
67
  const isClipboardInterpolationFunction = (value) => {
62
68
  return (0, studio_shared_1.isKeyframeInterpolationFunction)(value);
63
69
  };
70
+ const effectPropStatusToClipboardParam = (prop) => {
71
+ if (prop.status === 'computed') {
72
+ return null;
73
+ }
74
+ if (prop.status === 'static') {
75
+ if (prop.codeValue === undefined) {
76
+ return null;
77
+ }
78
+ return {
79
+ type: 'static',
80
+ value: prop.codeValue,
81
+ };
82
+ }
83
+ if (!isClipboardInterpolationFunction(prop.interpolationFunction)) {
84
+ return null;
85
+ }
86
+ return {
87
+ type: 'keyframed',
88
+ interpolationFunction: prop.interpolationFunction,
89
+ keyframes: prop.keyframes,
90
+ easing: prop.easing,
91
+ clamping: prop.clamping,
92
+ ...(prop.posterize === undefined ? {} : { posterize: prop.posterize }),
93
+ };
94
+ };
64
95
  const effectStatusToSnapshot = (effect) => {
65
96
  if (effect.importPath === null) {
66
97
  return null;
67
98
  }
68
99
  const params = {};
69
100
  for (const [key, prop] of Object.entries(effect.props)) {
70
- if (prop.status === 'computed') {
71
- return null;
72
- }
73
- if (prop.status === 'static') {
74
- if (prop.codeValue !== undefined) {
75
- params[key] = {
76
- type: 'static',
77
- value: prop.codeValue,
78
- };
79
- }
101
+ if (prop.status === 'static' && prop.codeValue === undefined) {
80
102
  continue;
81
103
  }
82
- if (!isClipboardInterpolationFunction(prop.interpolationFunction)) {
104
+ const param = effectPropStatusToClipboardParam(prop);
105
+ if (param === null) {
83
106
  return null;
84
107
  }
85
- params[key] = {
86
- type: 'keyframed',
87
- interpolationFunction: prop.interpolationFunction,
88
- keyframes: prop.keyframes,
89
- easing: prop.easing,
90
- clamping: prop.clamping,
91
- ...(prop.posterize === undefined ? {} : { posterize: prop.posterize }),
92
- };
108
+ params[key] = param;
93
109
  }
94
110
  return {
95
111
  callee: effect.callee,
@@ -127,12 +143,121 @@ const getSnapshotsFromSelection = ({ selection, codeValues, }) => {
127
143
  return snapshots;
128
144
  };
129
145
  exports.getSnapshotsFromSelection = getSnapshotsFromSelection;
146
+ const getEffectPropClipboardDataFromSelection = ({ selection, codeValues, }) => {
147
+ if (selection.type !== 'sequence-effect-prop') {
148
+ return null;
149
+ }
150
+ const sequenceStatus = codeValues[remotion_1.Internals.makeSequencePropsSubscriptionKey(selection.nodePathInfo.sequenceSubscriptionKey)];
151
+ if (!sequenceStatus || !sequenceStatus.canUpdate) {
152
+ return null;
153
+ }
154
+ const effect = sequenceStatus.effects.find((item) => item.effectIndex === selection.i);
155
+ if (!(effect === null || effect === void 0 ? void 0 : effect.canUpdate) || effect.importPath === null) {
156
+ return null;
157
+ }
158
+ const prop = effect.props[selection.key];
159
+ if (!prop) {
160
+ return null;
161
+ }
162
+ const param = effectPropStatusToClipboardParam(prop);
163
+ if (param === null) {
164
+ return null;
165
+ }
166
+ return {
167
+ type: 'effect-prop',
168
+ version: 1,
169
+ remotionClipboard: 'effect-prop',
170
+ effect: {
171
+ callee: effect.callee,
172
+ importPath: effect.importPath,
173
+ },
174
+ key: selection.key,
175
+ param,
176
+ };
177
+ };
178
+ exports.getEffectPropClipboardDataFromSelection = getEffectPropClipboardDataFromSelection;
179
+ const getPasteEffectPropTarget = ({ selectedItems, payload, codeValues, sequences, overrideIdsToNodePaths, }) => {
180
+ var _a;
181
+ if (selectedItems.length === 0) {
182
+ return { type: 'none' };
183
+ }
184
+ if (selectedItems.length !== 1) {
185
+ return { type: 'multiple' };
186
+ }
187
+ const [selection] = selectedItems;
188
+ if (!selection) {
189
+ return { type: 'none' };
190
+ }
191
+ if (!selection.nodePathInfo.supportsEffects) {
192
+ return { type: 'unsupported' };
193
+ }
194
+ const target = selection.type === 'sequence-effect-prop'
195
+ ? {
196
+ effectIndex: selection.i,
197
+ fieldKey: selection.key,
198
+ }
199
+ : selection.type === 'sequence-effect'
200
+ ? {
201
+ effectIndex: selection.i,
202
+ fieldKey: payload.key,
203
+ }
204
+ : null;
205
+ if (target === null) {
206
+ return { type: 'none' };
207
+ }
208
+ if (selection.type === 'sequence-effect-prop' &&
209
+ selection.key !== payload.key) {
210
+ return { type: 'prop-mismatch' };
211
+ }
212
+ const track = (0, find_track_for_node_path_info_1.findTrackForNodePathInfo)({
213
+ sequences,
214
+ overrideIdsToNodePaths,
215
+ nodePathInfo: selection.nodePathInfo,
216
+ });
217
+ const sequence = (_a = track === null || track === void 0 ? void 0 : track.sequence) !== null && _a !== void 0 ? _a : null;
218
+ if (!sequence) {
219
+ return { type: 'none' };
220
+ }
221
+ const effect = sequence.effects[target.effectIndex];
222
+ const fieldSchema = effect === null || effect === void 0 ? void 0 : effect.schema[target.fieldKey];
223
+ if (!effect || !isVisibleFieldSchema(fieldSchema)) {
224
+ return { type: 'prop-mismatch' };
225
+ }
226
+ const sequenceStatus = codeValues[remotion_1.Internals.makeSequencePropsSubscriptionKey(selection.nodePathInfo.sequenceSubscriptionKey)];
227
+ if (!(sequenceStatus === null || sequenceStatus === void 0 ? void 0 : sequenceStatus.canUpdate)) {
228
+ return { type: 'uncopyable' };
229
+ }
230
+ const effectStatus = sequenceStatus.effects.find((item) => item.effectIndex === target.effectIndex);
231
+ if (!(effectStatus === null || effectStatus === void 0 ? void 0 : effectStatus.canUpdate)) {
232
+ return { type: 'uncopyable' };
233
+ }
234
+ if (effectStatus.importPath !== payload.effect.importPath) {
235
+ return { type: 'effect-type-mismatch' };
236
+ }
237
+ const propStatus = effectStatus.props[target.fieldKey];
238
+ if (!propStatus || propStatus.status === 'computed') {
239
+ return { type: 'uncopyable' };
240
+ }
241
+ return {
242
+ type: 'valid',
243
+ fileName: selection.nodePathInfo.sequenceSubscriptionKey.absolutePath,
244
+ nodePath: selection.nodePathInfo.sequenceSubscriptionKey,
245
+ effectIndex: target.effectIndex,
246
+ fieldKey: target.fieldKey,
247
+ defaultValue: getDefaultValue(fieldSchema),
248
+ schema: effect.schema,
249
+ };
250
+ };
251
+ exports.getPasteEffectPropTarget = getPasteEffectPropTarget;
130
252
  const TimelineClipboardKeybindings = () => {
131
253
  const keybindings = (0, use_keybinding_1.useKeybinding)();
132
254
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
133
255
  const { canSelect } = (0, TimelineSelection_1.useTimelineSelection)();
134
256
  const currentSelection = (0, TimelineSelection_1.useCurrentTimelineSelectionStateAsRef)();
135
257
  const { codeValues } = (0, react_1.useContext)(remotion_1.Internals.VisualModeCodeValuesContext);
258
+ const { setCodeValues } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
259
+ const { sequences } = (0, react_1.useContext)(remotion_1.Internals.SequenceManager);
260
+ const { overrideIdToNodePathMappings } = (0, react_1.useContext)(remotion_1.Internals.OverrideIdsToNodePathsGettersContext);
136
261
  (0, react_1.useEffect)(() => {
137
262
  if (!canSelect || previewServerState.type !== 'connected') {
138
263
  return;
@@ -142,10 +267,36 @@ const TimelineClipboardKeybindings = () => {
142
267
  event: 'keydown',
143
268
  key: 'c',
144
269
  callback: (e) => {
270
+ var _a;
145
271
  const { selectedItems } = currentSelection.current;
146
272
  if (selectedItems.length === 0) {
147
273
  return;
148
274
  }
275
+ if (selectedItems.some((selection) => selection.type === 'sequence-effect-prop')) {
276
+ e.preventDefault();
277
+ if (selectedItems.length !== 1 ||
278
+ ((_a = selectedItems[0]) === null || _a === void 0 ? void 0 : _a.type) !== 'sequence-effect-prop') {
279
+ (0, NotificationCenter_1.showNotification)('Select one effect prop to copy its value', 3000);
280
+ return;
281
+ }
282
+ const payload = (0, exports.getEffectPropClipboardDataFromSelection)({
283
+ selection: selectedItems[0],
284
+ codeValues,
285
+ });
286
+ if (payload === null) {
287
+ (0, NotificationCenter_1.showNotification)('Cannot copy effect prop because its value cannot be copied', 3000);
288
+ return;
289
+ }
290
+ navigator.clipboard
291
+ .writeText(makeClipboardText(payload))
292
+ .then(() => {
293
+ (0, NotificationCenter_1.showNotification)('Copied effect prop to clipboard', 1000);
294
+ })
295
+ .catch((err) => {
296
+ (0, NotificationCenter_1.showNotification)(`Could not copy effect prop: ${err.message}`, 2000);
297
+ });
298
+ return;
299
+ }
149
300
  const firstSelection = selectedItems[0];
150
301
  const type = firstSelection ? getCopyType(firstSelection) : null;
151
302
  if (type === null) {
@@ -201,6 +352,66 @@ const TimelineClipboardKeybindings = () => {
201
352
  navigator.clipboard
202
353
  .readText()
203
354
  .then((text) => {
355
+ const effectPropResult = (0, studio_shared_1.parseEffectPropClipboardDataResult)(text);
356
+ if (effectPropResult.status !== 'invalid') {
357
+ e.preventDefault();
358
+ if (effectPropResult.status === 'unsupported-version') {
359
+ (0, NotificationCenter_1.showNotification)('Cannot paste effect prop copied from a different Remotion Studio version', 4000);
360
+ return;
361
+ }
362
+ const effectPropTarget = (0, exports.getPasteEffectPropTarget)({
363
+ selectedItems,
364
+ payload: effectPropResult.data,
365
+ codeValues,
366
+ sequences,
367
+ overrideIdsToNodePaths: overrideIdToNodePathMappings,
368
+ });
369
+ if (effectPropTarget.type !== 'valid') {
370
+ switch (effectPropTarget.type) {
371
+ case 'multiple':
372
+ (0, NotificationCenter_1.showNotification)('Select one target effect prop or effect to paste onto', 3000);
373
+ return;
374
+ case 'none':
375
+ (0, NotificationCenter_1.showNotification)('Select a matching effect prop or effect to paste onto', 3000);
376
+ return;
377
+ case 'unsupported':
378
+ (0, NotificationCenter_1.showNotification)('This sequence does not support effects', 3000);
379
+ return;
380
+ case 'effect-type-mismatch':
381
+ (0, NotificationCenter_1.showNotification)('Select an effect of the same type to paste this prop', 3000);
382
+ return;
383
+ case 'prop-mismatch':
384
+ (0, NotificationCenter_1.showNotification)('Select the same effect prop, or an effect with that prop', 3000);
385
+ return;
386
+ case 'uncopyable':
387
+ (0, NotificationCenter_1.showNotification)('Cannot paste onto an effect prop that cannot be updated', 3000);
388
+ return;
389
+ default:
390
+ throw new Error(`Unexpected paste target: ${effectPropTarget}`);
391
+ }
392
+ }
393
+ return (0, save_effect_prop_1.saveEffectProp)({
394
+ fileName: effectPropTarget.fileName,
395
+ nodePath: effectPropTarget.nodePath,
396
+ effectIndex: effectPropTarget.effectIndex,
397
+ fieldKey: effectPropTarget.fieldKey,
398
+ ...(effectPropResult.data.param.type === 'static'
399
+ ? {
400
+ type: 'value',
401
+ value: effectPropResult.data.param.value,
402
+ }
403
+ : {
404
+ type: 'effect-param',
405
+ effectParam: effectPropResult.data.param,
406
+ }),
407
+ defaultValue: effectPropTarget.defaultValue,
408
+ schema: effectPropTarget.schema,
409
+ setCodeValues,
410
+ clientId,
411
+ }).then(() => {
412
+ (0, NotificationCenter_1.showNotification)('Pasted effect prop', 2000);
413
+ });
414
+ }
204
415
  const result = (0, studio_shared_1.parseEffectClipboardDataResult)(text);
205
416
  if (result.status === 'invalid') {
206
417
  return;
@@ -258,7 +469,10 @@ const TimelineClipboardKeybindings = () => {
258
469
  codeValues,
259
470
  currentSelection,
260
471
  keybindings,
472
+ overrideIdToNodePathMappings,
261
473
  previewServerState,
474
+ sequences,
475
+ setCodeValues,
262
476
  ]);
263
477
  return null;
264
478
  };
@@ -5,6 +5,7 @@ const react_1 = require("react");
5
5
  const remotion_1 = require("remotion");
6
6
  const client_id_1 = require("../../helpers/client-id");
7
7
  const use_keybinding_1 = require("../../helpers/use-keybinding");
8
+ const ConfirmationDialog_1 = require("../ConfirmationDialog");
8
9
  const delete_selected_timeline_item_1 = require("./delete-selected-timeline-item");
9
10
  const duplicate_selected_timeline_item_1 = require("./duplicate-selected-timeline-item");
10
11
  const reset_selected_timeline_props_1 = require("./reset-selected-timeline-props");
@@ -18,6 +19,7 @@ const TimelineDeleteKeybindings = () => {
18
19
  const { setCodeValues } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
19
20
  const { canSelect } = (0, TimelineSelection_1.useTimelineSelection)();
20
21
  const currentSelection = (0, TimelineSelection_1.useCurrentTimelineSelectionStateAsRef)();
22
+ const confirm = (0, ConfirmationDialog_1.useConfirmationDialog)();
21
23
  (0, react_1.useEffect)(() => {
22
24
  if (!canSelect || previewServerState.type !== 'connected') {
23
25
  return;
@@ -49,12 +51,18 @@ const TimelineDeleteKeybindings = () => {
49
51
  overrideIdsToNodePaths: overrideIdToNodePathMappings,
50
52
  setCodeValues,
51
53
  clientId,
54
+ confirm,
52
55
  });
53
56
  if (deletePromise === null) {
54
57
  return;
55
58
  }
56
- clearSelection();
57
- deletePromise.catch(() => undefined);
59
+ deletePromise
60
+ .then((deleted) => {
61
+ if (deleted) {
62
+ clearSelection();
63
+ }
64
+ })
65
+ .catch(() => undefined);
58
66
  },
59
67
  commandCtrlKey: false,
60
68
  preventDefault: true,
@@ -71,6 +79,7 @@ const TimelineDeleteKeybindings = () => {
71
79
  }
72
80
  const duplicatePromise = (0, duplicate_selected_timeline_item_1.duplicateSelectedTimelineItems)({
73
81
  selections: selectedItems,
82
+ confirm,
74
83
  });
75
84
  if (duplicatePromise === null) {
76
85
  return;
@@ -89,6 +98,7 @@ const TimelineDeleteKeybindings = () => {
89
98
  }, [
90
99
  canSelect,
91
100
  codeValues,
101
+ confirm,
92
102
  currentSelection,
93
103
  keybindings,
94
104
  overrideIdToNodePathMappings,
@@ -178,6 +178,7 @@ const TimelineEffectItem = ({ label, nodePathInfo, effectIndex, effectSchema, do
178
178
  ? JSON.stringify(fieldSchema.default)
179
179
  : null;
180
180
  (0, save_effect_prop_1.saveEffectProp)({
181
+ type: 'value',
181
182
  fileName: validatedLocation.source,
182
183
  nodePath,
183
184
  effectIndex,
@@ -6,6 +6,7 @@ const studio_shared_1 = require("@remotion/studio-shared");
6
6
  const react_1 = require("react");
7
7
  const remotion_1 = require("remotion");
8
8
  const client_id_1 = require("../../helpers/client-id");
9
+ const modals_1 = require("../../state/modals");
9
10
  const call_api_1 = require("../call-api");
10
11
  const ContextMenu_1 = require("../ContextMenu");
11
12
  const call_add_keyframe_1 = require("./call-add-keyframe");
@@ -118,6 +119,7 @@ const Value = ({ field, nodePath, validatedLocation, keyframeDisplayOffset }) =>
118
119
  schema: field.effectSchema,
119
120
  }),
120
121
  apiCall: () => (0, call_api_1.callApi)('/api/save-effect-props', {
122
+ type: 'value',
121
123
  fileName: validatedLocation.source,
122
124
  sequenceNodePath: nodePath,
123
125
  effectIndex: field.effectIndex,
@@ -213,6 +215,7 @@ const TimelineEffectPropItem = ({ field, validatedLocation, rowDepth, nodePath,
213
215
  var _a;
214
216
  var _b, _c;
215
217
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
218
+ const { setSelectedModal } = (0, react_1.useContext)(modals_1.ModalsContext);
216
219
  const { setCodeValues } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
217
220
  const { codeValues } = (0, react_1.useContext)(remotion_1.Internals.VisualModeCodeValuesContext);
218
221
  const { getEffectDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeDragOverridesContext);
@@ -267,6 +270,7 @@ const TimelineEffectPropItem = ({ field, validatedLocation, rowDepth, nodePath,
267
270
  ? JSON.stringify(field.fieldSchema.default)
268
271
  : null;
269
272
  (0, save_effect_prop_1.saveEffectProp)({
273
+ type: 'value',
270
274
  fileName: validatedLocation.source,
271
275
  nodePath,
272
276
  effectIndex: field.effectIndex,
@@ -289,8 +293,33 @@ const TimelineEffectPropItem = ({ field, validatedLocation, rowDepth, nodePath,
289
293
  setCodeValues,
290
294
  validatedLocation.source,
291
295
  ]);
296
+ const onOpenKeyframeSettings = (0, react_1.useCallback)(() => {
297
+ var _a;
298
+ if (propStatus === null || !isKeyframedStatus(propStatus)) {
299
+ return;
300
+ }
301
+ setSelectedModal({
302
+ type: 'keyframe-settings',
303
+ fileName: validatedLocation.source,
304
+ nodePath,
305
+ fieldKey: field.key,
306
+ fieldLabel: (_a = field.description) !== null && _a !== void 0 ? _a : field.key,
307
+ status: propStatus,
308
+ schema: field.effectSchema,
309
+ effectIndex: field.effectIndex,
310
+ });
311
+ }, [
312
+ field.description,
313
+ field.effectIndex,
314
+ field.effectSchema,
315
+ field.key,
316
+ nodePath,
317
+ propStatus,
318
+ setSelectedModal,
319
+ validatedLocation.source,
320
+ ]);
292
321
  const contextMenuValues = (0, react_1.useMemo)(() => {
293
- return [
322
+ const values = [
294
323
  {
295
324
  type: 'item',
296
325
  id: 'reset-effect-field',
@@ -304,7 +333,28 @@ const TimelineEffectPropItem = ({ field, validatedLocation, rowDepth, nodePath,
304
333
  value: 'reset-effect-field',
305
334
  },
306
335
  ];
307
- }, [canShowReset, onReset]);
336
+ if (propStatus !== null && isKeyframedStatus(propStatus)) {
337
+ values.push({
338
+ type: 'item',
339
+ id: 'keyframe-settings-effect-field',
340
+ keyHint: null,
341
+ label: 'Keyframe settings...',
342
+ leftItem: null,
343
+ disabled: previewServerState.type !== 'connected',
344
+ onClick: onOpenKeyframeSettings,
345
+ quickSwitcherLabel: null,
346
+ subMenu: null,
347
+ value: 'keyframe-settings-effect-field',
348
+ });
349
+ }
350
+ return values;
351
+ }, [
352
+ canShowReset,
353
+ onOpenKeyframeSettings,
354
+ onReset,
355
+ previewServerState,
356
+ propStatus,
357
+ ]);
308
358
  const row = (jsx_runtime_1.jsxs(TimelineRowChrome_1.TimelineRowChrome, { depth: rowDepth, eye: jsx_runtime_1.jsx(TimelineLayerEye_1.TimelineLayerEyeSpacer, {}), keyframeControls: keyframeControls, arrow: jsx_runtime_1.jsx(TimelineExpandArrowButton_1.TimelineExpandArrowSpacer, {}), style: style, selected: selection.selected, selectable: selection.selectable, onSelect: selection.onSelect, showSelectedBackground: true, containsSelection: false, outerHeight: null, children: [
309
359
  jsx_runtime_1.jsx(TimelineFieldLabel_1.TimelineFieldLabel, { rowDepth: rowDepth, selected: selection.selected, label: (_c = field.description) !== null && _c !== void 0 ? _c : field.key }), jsx_runtime_1.jsx("div", { style: timeline_field_row_layout_1.timelineFieldValueColumnStyle, children: jsx_runtime_1.jsx(Value, { field: field, nodePath: nodePath, validatedLocation: validatedLocation, keyframeDisplayOffset: keyframeDisplayOffset }) })
310
360
  ] }));
@@ -11,6 +11,7 @@ const call_add_keyframe_1 = require("./call-add-keyframe");
11
11
  const call_delete_keyframe_1 = require("./call-delete-keyframe");
12
12
  const get_keyframe_navigation_1 = require("./get-keyframe-navigation");
13
13
  const get_timeline_keyframes_1 = require("./get-timeline-keyframes");
14
+ const TimelineKeyframeDiamondIcon_1 = require("./TimelineKeyframeDiamondIcon");
14
15
  const TimelineSelection_1 = require("./TimelineSelection");
15
16
  const controlsContainerStyle = {
16
17
  alignItems: 'center',
@@ -41,14 +42,6 @@ const isKeyframedStatus = (status) => {
41
42
  const diamondButtonStyle = {
42
43
  ...navButtonStyle,
43
44
  background: 'none',
44
- translate: '0 -0.5px',
45
- };
46
- const diamondIconStyle = {
47
- borderRadius: 1,
48
- boxShadow: '0 0 0 1px rgba(0, 0, 0, 0.4)',
49
- height: 7,
50
- transform: 'rotate(45deg)',
51
- width: 7,
52
45
  };
53
46
  const svgStyle = { display: 'block' };
54
47
  const getCurrentKeyframeValue = ({ propStatus, jsxFrame, defaultValue, dragOverrideValue, }) => {
@@ -108,8 +101,8 @@ const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, ke
108
101
  defaultValue,
109
102
  dragOverrideValue,
110
103
  }), [defaultValue, dragOverrideValue, jsxFrame, propStatus]);
111
- const previousDisplayFrame = (0, react_1.useMemo)(() => (0, get_keyframe_navigation_1.getPreviousKeyframeDisplayFrame)(keyframes, timelinePosition), [keyframes, timelinePosition]);
112
- const nextDisplayFrame = (0, react_1.useMemo)(() => (0, get_keyframe_navigation_1.getNextKeyframeDisplayFrame)(keyframes, timelinePosition), [keyframes, timelinePosition]);
104
+ const previousDisplayFrame = (0, react_1.useMemo)(() => (0, get_keyframe_navigation_1.getPreviousKeyframeDisplayFrame)(keyframes, timelinePosition, videoConfig.durationInFrames), [keyframes, timelinePosition, videoConfig.durationInFrames]);
105
+ const nextDisplayFrame = (0, react_1.useMemo)(() => (0, get_keyframe_navigation_1.getNextKeyframeDisplayFrame)(keyframes, timelinePosition, videoConfig.durationInFrames), [keyframes, timelinePosition, videoConfig.durationInFrames]);
113
106
  const keyframable = (0, studio_shared_1.isSchemaFieldKeyframable)({
114
107
  schema,
115
108
  key: fieldKey,
@@ -225,12 +218,9 @@ const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, ke
225
218
  cursor: canToggleKeyframe && clientId ? 'pointer' : 'default',
226
219
  opacity: canToggleKeyframe && clientId ? 1 : 0.35,
227
220
  }), [canToggleKeyframe, clientId]);
228
- const diamondIcon = (0, react_1.useMemo)(() => ({
229
- ...diamondIconStyle,
230
- backgroundColor: hasKeyframeAtCurrentFrame ? colors_1.BLUE : colors_1.LIGHT_TEXT,
231
- }), [hasKeyframeAtCurrentFrame]);
221
+ const diamondColor = hasKeyframeAtCurrentFrame ? colors_1.BLUE : colors_1.LIGHT_TEXT;
232
222
  return (jsx_runtime_1.jsxs("div", { style: controlsContainerStyle, children: [
233
- jsx_runtime_1.jsx("button", { type: "button", style: previousStyle, disabled: previousDisabled, onPointerDown: previousDisabled ? undefined : onPrevious, "aria-label": "Go to previous keyframe", title: "Previous keyframe", children: jsx_runtime_1.jsx("svg", { width: "14", height: "14", viewBox: "0 0 10 10", style: svgStyle, children: jsx_runtime_1.jsx("path", { d: "M7 1.5L3 5L7 8.5Z", fill: "#ccc" }) }) }), jsx_runtime_1.jsx("button", { type: "button", style: diamondStyle, disabled: !canToggleKeyframe || !clientId, onPointerDown: canToggleKeyframe && clientId ? onToggleKeyframe : undefined, "aria-label": hasKeyframeAtCurrentFrame ? 'Remove keyframe' : 'Add keyframe', title: hasKeyframeAtCurrentFrame ? 'Remove keyframe' : 'Add keyframe', children: jsx_runtime_1.jsx("span", { style: diamondIcon }) }), jsx_runtime_1.jsx("button", { type: "button", style: nextStyle, disabled: nextDisabled, onPointerDown: nextDisabled ? undefined : onNext, "aria-label": "Go to next keyframe", title: "Next keyframe", children: jsx_runtime_1.jsx("svg", { width: "14", height: "14", viewBox: "0 0 10 10", style: svgStyle, children: jsx_runtime_1.jsx("path", { d: "M3 1.5L7 5L3 8.5Z", fill: "#ccc" }) }) })
223
+ jsx_runtime_1.jsx("button", { type: "button", style: previousStyle, disabled: previousDisabled, onPointerDown: previousDisabled ? undefined : onPrevious, "aria-label": "Go to previous keyframe", title: "Previous keyframe", children: jsx_runtime_1.jsx("svg", { width: "14", height: "14", viewBox: "0 0 10 10", style: svgStyle, children: jsx_runtime_1.jsx("path", { d: "M7 1.5L3 5L7 8.5Z", fill: "#ccc" }) }) }), jsx_runtime_1.jsx("button", { type: "button", style: diamondStyle, disabled: !canToggleKeyframe || !clientId, onPointerDown: canToggleKeyframe && clientId ? onToggleKeyframe : undefined, "aria-label": hasKeyframeAtCurrentFrame ? 'Remove keyframe' : 'Add keyframe', title: hasKeyframeAtCurrentFrame ? 'Remove keyframe' : 'Add keyframe', children: jsx_runtime_1.jsx(TimelineKeyframeDiamondIcon_1.TimelineKeyframeDiamondIcon, { color: diamondColor, size: 12 }) }), jsx_runtime_1.jsx("button", { type: "button", style: nextStyle, disabled: nextDisabled, onPointerDown: nextDisabled ? undefined : onNext, "aria-label": "Go to next keyframe", title: "Next keyframe", children: jsx_runtime_1.jsx("svg", { width: "14", height: "14", viewBox: "0 0 10 10", style: svgStyle, children: jsx_runtime_1.jsx("path", { d: "M3 1.5L7 5L3 8.5Z", fill: "#ccc" }) }) })
234
224
  ] }));
235
225
  };
236
226
  exports.TimelineKeyframeControls = TimelineKeyframeControls;