@remotion/studio 4.0.474 → 4.0.476

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 (94) hide show
  1. package/dist/components/Canvas.js +6 -0
  2. package/dist/components/ColorPicker/ColorPicker.js +4 -31
  3. package/dist/components/ColorPicker/ColorPickerPopup.d.ts +6 -0
  4. package/dist/components/ColorPicker/ColorPickerPopup.js +11 -6
  5. package/dist/components/CompositionSelectorItem.js +4 -4
  6. package/dist/components/Editor.js +4 -1
  7. package/dist/components/GlobalKeybindings.js +12 -0
  8. package/dist/components/KeyboardShortcutsExplainer.js +24 -0
  9. package/dist/components/Modals.js +2 -1
  10. package/dist/components/NewComposition/ComboBox.js +1 -0
  11. package/dist/components/NewComposition/InputDragger.d.ts +6 -0
  12. package/dist/components/NewComposition/InputDragger.js +40 -14
  13. package/dist/components/NewComposition/RenameComposition.js +8 -1
  14. package/dist/components/NewComposition/RenameFolder.js +8 -1
  15. package/dist/components/NewComposition/RenameStaticFile.js +11 -1
  16. package/dist/components/Notifications/Notification.js +5 -4
  17. package/dist/components/Notifications/NotificationCenter.js +1 -1
  18. package/dist/components/ObserveDefaultPropsContext.js +6 -2
  19. package/dist/components/PlayPause.js +22 -66
  20. package/dist/components/PreviewToolbar.js +17 -3
  21. package/dist/components/RenderModal/RenderModalJSONPropsEditor.js +2 -1
  22. package/dist/components/SelectedOutlineOverlay.d.ts +104 -42
  23. package/dist/components/SelectedOutlineOverlay.js +1278 -336
  24. package/dist/components/SelectedOutlineUvControls.d.ts +17 -0
  25. package/dist/components/SelectedOutlineUvControls.js +167 -0
  26. package/dist/components/StudioCanvasCapture.d.ts +5 -0
  27. package/dist/components/StudioCanvasCapture.js +40 -0
  28. package/dist/components/Timeline/EasingEditorModal.d.ts +11 -0
  29. package/dist/components/Timeline/EasingEditorModal.js +247 -0
  30. package/dist/components/Timeline/KeyframeSettingsModal.js +1 -0
  31. package/dist/components/Timeline/Timeline.js +10 -7
  32. package/dist/components/Timeline/TimelineDeleteKeybindings.js +64 -35
  33. package/dist/components/Timeline/TimelineDragHandler.js +2 -2
  34. package/dist/components/Timeline/TimelineEffectItem.js +1 -2
  35. package/dist/components/Timeline/TimelineEffectPropItem.js +1 -1
  36. package/dist/components/Timeline/TimelineExpandedKeyframeRow.d.ts +1 -0
  37. package/dist/components/Timeline/TimelineExpandedKeyframeRow.js +2 -2
  38. package/dist/components/Timeline/TimelineExpandedTrackKeyframes.js +1 -1
  39. package/dist/components/Timeline/TimelineHeightContainer.js +2 -0
  40. package/dist/components/Timeline/TimelineItemStack.js +3 -56
  41. package/dist/components/Timeline/TimelineKeyframeControls.d.ts +7 -0
  42. package/dist/components/Timeline/TimelineKeyframeControls.js +259 -62
  43. package/dist/components/Timeline/TimelineKeyframeDiamond.js +4 -2
  44. package/dist/components/Timeline/TimelineKeyframeEasingLine.js +128 -3
  45. package/dist/components/Timeline/TimelineKeyframeTracksContext.d.ts +7 -0
  46. package/dist/components/Timeline/TimelineKeyframeTracksContext.js +17 -0
  47. package/dist/components/Timeline/TimelineMediaInfo.js +4 -24
  48. package/dist/components/Timeline/TimelinePrimitiveFieldValue.js +4 -0
  49. package/dist/components/Timeline/TimelineRotationField.js +21 -39
  50. package/dist/components/Timeline/TimelineScaleField.js +1 -1
  51. package/dist/components/Timeline/TimelineScrollable.js +19 -3
  52. package/dist/components/Timeline/TimelineSelection.d.ts +67 -3
  53. package/dist/components/Timeline/TimelineSelection.js +289 -32
  54. package/dist/components/Timeline/TimelineSequence.js +17 -9
  55. package/dist/components/Timeline/TimelineSequenceItem.js +328 -168
  56. package/dist/components/Timeline/TimelineSequenceName.d.ts +4 -2
  57. package/dist/components/Timeline/TimelineSequenceName.js +70 -19
  58. package/dist/components/Timeline/TimelineSequencePropItem.js +1 -1
  59. package/dist/components/Timeline/TimelineTimeIndicators.js +4 -2
  60. package/dist/components/Timeline/TimelineTransformOriginField.d.ts +11 -0
  61. package/dist/components/Timeline/TimelineTransformOriginField.js +138 -0
  62. package/dist/components/Timeline/TimelineTranslateField.js +1 -1
  63. package/dist/components/Timeline/TimelineUvCoordinateField.js +1 -1
  64. package/dist/components/Timeline/call-add-keyframe.d.ts +17 -0
  65. package/dist/components/Timeline/call-add-keyframe.js +65 -1
  66. package/dist/components/Timeline/delete-selected-timeline-item.js +23 -13
  67. package/dist/components/Timeline/disable-sequence-interactivity.d.ts +8 -0
  68. package/dist/components/Timeline/disable-sequence-interactivity.js +24 -0
  69. package/dist/components/Timeline/reset-selected-timeline-props.js +15 -7
  70. package/dist/components/Timeline/timeline-rotation-utils.d.ts +2 -0
  71. package/dist/components/Timeline/timeline-rotation-utils.js +34 -0
  72. package/dist/components/Timeline/transform-origin-utils.d.ts +24 -0
  73. package/dist/components/Timeline/transform-origin-utils.js +170 -0
  74. package/dist/components/Timeline/update-selected-easing.d.ts +35 -0
  75. package/dist/components/Timeline/update-selected-easing.js +133 -0
  76. package/dist/components/Timeline/use-expanded-track-keyframe-rows.d.ts +1 -0
  77. package/dist/components/Timeline/use-expanded-track-keyframe-rows.js +28 -0
  78. package/dist/components/Timeline/use-timeline-keyframe-drag.js +8 -13
  79. package/dist/components/canvas-capture-enabled.d.ts +1 -0
  80. package/dist/components/canvas-capture-enabled.js +4 -0
  81. package/dist/components/effect-drag-and-drop.d.ts +11 -0
  82. package/dist/components/effect-drag-and-drop.js +73 -0
  83. package/dist/components/selected-outline-geometry.d.ts +20 -0
  84. package/dist/components/selected-outline-geometry.js +18 -0
  85. package/dist/components/selected-outline-uv.d.ts +46 -0
  86. package/dist/components/selected-outline-uv.js +240 -0
  87. package/dist/esm/{chunk-xjvc8qen.js → chunk-0atarw3p.js} +8779 -5352
  88. package/dist/esm/internals.mjs +8779 -5352
  89. package/dist/esm/previewEntry.mjs +8789 -5362
  90. package/dist/esm/renderEntry.mjs +1 -1
  91. package/dist/helpers/colors.d.ts +0 -1
  92. package/dist/helpers/colors.js +1 -2
  93. package/dist/state/modals.d.ts +2 -1
  94. package/package.json +11 -10
@@ -1,17 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TimelineKeyframeControls = exports.shouldShowTimelineKeyframeControls = void 0;
3
+ exports.TimelineKeyframeControls = exports.shouldShowTimelineKeyframeControls = exports.getSelectedKeyframeControlNodePathInfos = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  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
9
  const colors_1 = require("../../helpers/colors");
10
+ const timeline_layout_1 = require("../../helpers/timeline-layout");
11
+ const timeline_node_path_key_1 = require("../../helpers/timeline-node-path-key");
10
12
  const call_add_keyframe_1 = require("./call-add-keyframe");
11
13
  const call_delete_keyframe_1 = require("./call-delete-keyframe");
12
14
  const get_keyframe_navigation_1 = require("./get-keyframe-navigation");
13
15
  const get_timeline_keyframes_1 = require("./get-timeline-keyframes");
14
16
  const TimelineKeyframeDiamondIcon_1 = require("./TimelineKeyframeDiamondIcon");
17
+ const TimelineKeyframeTracksContext_1 = require("./TimelineKeyframeTracksContext");
15
18
  const TimelineSelection_1 = require("./TimelineSelection");
16
19
  const controlsContainerStyle = {
17
20
  alignItems: 'center',
@@ -44,7 +47,181 @@ const diamondButtonStyle = {
44
47
  background: 'none',
45
48
  };
46
49
  const svgStyle = { display: 'block' };
47
- const getCurrentKeyframeValue = ({ propStatus, jsxFrame, defaultValue, dragOverrideValue, }) => {
50
+ const isKeyframeControlSelection = (selection) => {
51
+ return (selection.type === 'sequence-prop' ||
52
+ selection.type === 'sequence-effect-prop');
53
+ };
54
+ const getSelectedKeyframeControlNodePathInfos = ({ clickedNodePathInfo, selectedItems, }) => {
55
+ const clickedSelection = (0, TimelineSelection_1.getTimelineSelectionFromNodePathInfo)(clickedNodePathInfo);
56
+ if (clickedSelection === null ||
57
+ !isKeyframeControlSelection(clickedSelection)) {
58
+ return [clickedNodePathInfo];
59
+ }
60
+ const clickedSelectionKey = (0, TimelineSelection_1.getTimelineSelectionKey)(clickedSelection);
61
+ const selectedKeyframeControls = selectedItems.filter(isKeyframeControlSelection);
62
+ const clickedIsSelected = selectedKeyframeControls.some((selection) => (0, TimelineSelection_1.getTimelineSelectionKey)(selection) === clickedSelectionKey);
63
+ if (!clickedIsSelected || selectedKeyframeControls.length <= 1) {
64
+ return [clickedNodePathInfo];
65
+ }
66
+ return selectedKeyframeControls.map((selection) => selection.nodePathInfo);
67
+ };
68
+ exports.getSelectedKeyframeControlNodePathInfos = getSelectedKeyframeControlNodePathInfos;
69
+ const findFieldNode = (nodes, nodePathInfoKey) => {
70
+ for (const node of nodes) {
71
+ if ((0, timeline_node_path_key_1.timelineNodePathInfoToKey)(node.nodePathInfo) === nodePathInfoKey) {
72
+ return node.kind === 'field' ? node : null;
73
+ }
74
+ if (node.kind === 'group') {
75
+ const child = findFieldNode(node.children, nodePathInfoKey);
76
+ if (child !== null) {
77
+ return child;
78
+ }
79
+ }
80
+ }
81
+ return null;
82
+ };
83
+ const findTrackForNodePathInfo = ({ tracks, nodePathInfo, }) => {
84
+ var _a;
85
+ return ((_a = tracks.find((track) => {
86
+ if (track.nodePathInfo === null) {
87
+ return false;
88
+ }
89
+ return ((0, timeline_node_path_key_1.timelineNodePathInfoToKey)({
90
+ ...track.nodePathInfo,
91
+ auxiliaryKeys: [],
92
+ }) ===
93
+ (0, timeline_node_path_key_1.timelineNodePathInfoToKey)({
94
+ ...nodePathInfo,
95
+ auxiliaryKeys: [],
96
+ }));
97
+ })) !== null && _a !== void 0 ? _a : null);
98
+ };
99
+ const resolveKeyframeControlTarget = ({ nodePathInfo, tracks, propStatuses, getDragOverrides, getEffectDragOverrides, timelinePosition, }) => {
100
+ var _a;
101
+ var _b, _c, _d;
102
+ const track = findTrackForNodePathInfo({ tracks, nodePathInfo });
103
+ if (track === null ||
104
+ track.nodePathInfo === null ||
105
+ !track.sequence.controls) {
106
+ return null;
107
+ }
108
+ const tree = (0, timeline_layout_1.buildTimelineTree)({
109
+ sequence: track.sequence,
110
+ nodePathInfo: track.nodePathInfo,
111
+ getDragOverrides,
112
+ getEffectDragOverrides,
113
+ propStatuses,
114
+ });
115
+ const fieldNode = findFieldNode(tree, (0, timeline_node_path_key_1.timelineNodePathInfoToKey)(nodePathInfo));
116
+ if (fieldNode === null || fieldNode.field === null) {
117
+ return null;
118
+ }
119
+ const nodePath = nodePathInfo.sequenceSubscriptionKey;
120
+ if (fieldNode.field.kind === 'sequence-field') {
121
+ const sequencePropStatuses = remotion_1.Internals.getPropStatusesCtx(propStatuses, nodePath);
122
+ const sequenceSelectedPropStatus = (_b = sequencePropStatuses === null || sequencePropStatuses === void 0 ? void 0 : sequencePropStatuses[fieldNode.field.key]) !== null && _b !== void 0 ? _b : null;
123
+ if (sequenceSelectedPropStatus === null) {
124
+ return null;
125
+ }
126
+ return {
127
+ nodePathInfo,
128
+ fieldKey: fieldNode.field.key,
129
+ propStatus: sequenceSelectedPropStatus,
130
+ nodePath,
131
+ fileName: nodePath.absolutePath,
132
+ keyframeDisplayOffset: track.keyframeDisplayOffset,
133
+ sourceFrame: timelinePosition - track.keyframeDisplayOffset,
134
+ defaultValue: fieldNode.field.fieldSchema.default,
135
+ dragOverrideValue: ((_c = getDragOverrides(nodePath)) !== null && _c !== void 0 ? _c : {})[fieldNode.field.key],
136
+ schema: track.sequence.controls.schema,
137
+ effectIndex: null,
138
+ };
139
+ }
140
+ const effectStatus = remotion_1.Internals.getEffectPropStatusesCtx({
141
+ propStatuses,
142
+ nodePath,
143
+ effectIndex: fieldNode.field.effectIndex,
144
+ });
145
+ const effectSelectedPropStatus = effectStatus.type === 'can-update-effect'
146
+ ? ((_d = (_a = effectStatus.props) === null || _a === void 0 ? void 0 : _a[fieldNode.field.key]) !== null && _d !== void 0 ? _d : null)
147
+ : null;
148
+ if (effectSelectedPropStatus === null) {
149
+ return null;
150
+ }
151
+ return {
152
+ nodePathInfo,
153
+ fieldKey: fieldNode.field.key,
154
+ propStatus: effectSelectedPropStatus,
155
+ nodePath,
156
+ fileName: nodePath.absolutePath,
157
+ keyframeDisplayOffset: track.keyframeDisplayOffset,
158
+ sourceFrame: timelinePosition - track.keyframeDisplayOffset,
159
+ defaultValue: fieldNode.field.fieldSchema.default,
160
+ dragOverrideValue: getEffectDragOverrides(nodePath, fieldNode.field.effectIndex)[fieldNode.field.key],
161
+ schema: fieldNode.field.effectSchema,
162
+ effectIndex: fieldNode.field.effectIndex,
163
+ };
164
+ };
165
+ const hasTargetKeyframeAtCurrentFrame = (target) => {
166
+ if (!isKeyframedStatus(target.propStatus)) {
167
+ return false;
168
+ }
169
+ return (0, get_keyframe_navigation_1.hasKeyframeAtSourceFrame)(target.propStatus.keyframes, target.sourceFrame);
170
+ };
171
+ const getAddChange = (target) => {
172
+ if (target.propStatus.status === 'computed' ||
173
+ !(0, studio_shared_1.isSchemaFieldKeyframable)({ schema: target.schema, key: target.fieldKey }) ||
174
+ hasTargetKeyframeAtCurrentFrame(target)) {
175
+ return null;
176
+ }
177
+ const value = getCurrentKeyframeValue({
178
+ propStatus: target.propStatus,
179
+ jsxFrame: target.sourceFrame,
180
+ defaultValue: target.defaultValue,
181
+ dragOverrideValue: target.dragOverrideValue,
182
+ });
183
+ if (value === null) {
184
+ return null;
185
+ }
186
+ const change = {
187
+ fileName: target.fileName,
188
+ nodePath: target.nodePath,
189
+ fieldKey: target.fieldKey,
190
+ sourceFrame: target.sourceFrame,
191
+ value,
192
+ schema: target.schema,
193
+ };
194
+ if (target.effectIndex === null) {
195
+ return change;
196
+ }
197
+ return {
198
+ ...change,
199
+ effectIndex: target.effectIndex,
200
+ };
201
+ };
202
+ const getDeleteChange = (target) => {
203
+ if (!hasTargetKeyframeAtCurrentFrame(target)) {
204
+ return null;
205
+ }
206
+ const change = {
207
+ fileName: target.fileName,
208
+ nodePath: target.nodePath,
209
+ fieldKey: target.fieldKey,
210
+ sourceFrame: target.sourceFrame,
211
+ schema: target.schema,
212
+ };
213
+ if (target.effectIndex === null) {
214
+ return change;
215
+ }
216
+ return {
217
+ ...change,
218
+ effectIndex: target.effectIndex,
219
+ };
220
+ };
221
+ const hasEffectIndex = (change) => {
222
+ return 'effectIndex' in change;
223
+ };
224
+ function getCurrentKeyframeValue({ propStatus, jsxFrame, defaultValue, dragOverrideValue, }) {
48
225
  if (isKeyframedStatus(propStatus)) {
49
226
  return remotion_1.Internals.getEffectiveVisualModeValue({
50
227
  propStatus,
@@ -64,7 +241,7 @@ const getCurrentKeyframeValue = ({ propStatus, jsxFrame, defaultValue, dragOverr
64
241
  });
65
242
  }
66
243
  return null;
67
- };
244
+ }
68
245
  const shouldShowTimelineKeyframeControls = ({ propStatus, selected, keyframable, }) => {
69
246
  if (propStatus === null) {
70
247
  return false;
@@ -75,15 +252,19 @@ const shouldShowTimelineKeyframeControls = ({ propStatus, selected, keyframable,
75
252
  if (selected) {
76
253
  return true;
77
254
  }
78
- return TimelineSelection_1.SELECTION_ENABLED && isKeyframedStatus(propStatus);
255
+ return isKeyframedStatus(propStatus);
79
256
  };
80
257
  exports.shouldShowTimelineKeyframeControls = shouldShowTimelineKeyframeControls;
81
- const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, keyframeDisplayOffset, defaultValue, dragOverrideValue, schema, effectIndex, }) => {
258
+ const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, keyframeDisplayOffset, defaultValue, dragOverrideValue, schema, effectIndex, nodePathInfo, }) => {
82
259
  const videoConfig = (0, remotion_1.useVideoConfig)();
83
260
  const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
84
261
  const setFrame = remotion_1.Internals.useTimelineSetFrame();
85
262
  const { setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
263
+ const { propStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesContext);
264
+ const { getDragOverrides, getEffectDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeDragOverridesContext);
86
265
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
266
+ const { selectedItems } = (0, TimelineSelection_1.useTimelineSelection)();
267
+ const tracks = (0, TimelineKeyframeTracksContext_1.useTimelineKeyframeTracks)();
87
268
  const clientId = previewServerState.type === 'connected'
88
269
  ? previewServerState.clientId
89
270
  : null;
@@ -95,12 +276,6 @@ const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, ke
95
276
  }
96
277
  return (0, get_keyframe_navigation_1.hasKeyframeAtSourceFrame)(propStatus.keyframes, jsxFrame);
97
278
  }, [jsxFrame, propStatus]);
98
- const currentKeyframeValue = (0, react_1.useMemo)(() => getCurrentKeyframeValue({
99
- propStatus,
100
- jsxFrame,
101
- defaultValue,
102
- dragOverrideValue,
103
- }), [defaultValue, dragOverrideValue, jsxFrame, propStatus]);
104
279
  const previousDisplayFrame = (0, react_1.useMemo)(() => (0, get_keyframe_navigation_1.getPreviousKeyframeDisplayFrame)(keyframes, timelinePosition, videoConfig.durationInFrames), [keyframes, timelinePosition, videoConfig.durationInFrames]);
105
280
  const nextDisplayFrame = (0, react_1.useMemo)(() => (0, get_keyframe_navigation_1.getNextKeyframeDisplayFrame)(keyframes, timelinePosition, videoConfig.durationInFrames), [keyframes, timelinePosition, videoConfig.durationInFrames]);
106
281
  const keyframable = (0, studio_shared_1.isSchemaFieldKeyframable)({
@@ -110,6 +285,62 @@ const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, ke
110
285
  const canAddKeyframe = keyframable;
111
286
  const canToggleKeyframe = propStatus.status !== 'computed' &&
112
287
  (hasKeyframeAtCurrentFrame || canAddKeyframe);
288
+ const selectedNodePathInfos = (0, react_1.useMemo)(() => (0, exports.getSelectedKeyframeControlNodePathInfos)({
289
+ clickedNodePathInfo: nodePathInfo,
290
+ selectedItems,
291
+ }), [nodePathInfo, selectedItems]);
292
+ const clickedTarget = (0, react_1.useMemo)(() => ({
293
+ nodePathInfo,
294
+ fieldKey,
295
+ propStatus,
296
+ nodePath,
297
+ fileName,
298
+ keyframeDisplayOffset,
299
+ sourceFrame: jsxFrame,
300
+ defaultValue,
301
+ dragOverrideValue,
302
+ schema,
303
+ effectIndex,
304
+ }), [
305
+ defaultValue,
306
+ dragOverrideValue,
307
+ effectIndex,
308
+ fieldKey,
309
+ fileName,
310
+ jsxFrame,
311
+ keyframeDisplayOffset,
312
+ nodePath,
313
+ nodePathInfo,
314
+ propStatus,
315
+ schema,
316
+ ]);
317
+ const keyframeToggleTargets = (0, react_1.useMemo)(() => {
318
+ const clickedNodePathInfoKey = (0, timeline_node_path_key_1.timelineNodePathInfoToKey)(nodePathInfo);
319
+ return selectedNodePathInfos.flatMap((selectedNodePathInfo) => {
320
+ if ((0, timeline_node_path_key_1.timelineNodePathInfoToKey)(selectedNodePathInfo) ===
321
+ clickedNodePathInfoKey) {
322
+ return [clickedTarget];
323
+ }
324
+ const target = resolveKeyframeControlTarget({
325
+ nodePathInfo: selectedNodePathInfo,
326
+ tracks,
327
+ propStatuses,
328
+ getDragOverrides,
329
+ getEffectDragOverrides,
330
+ timelinePosition,
331
+ });
332
+ return target === null ? [] : [target];
333
+ });
334
+ }, [
335
+ clickedTarget,
336
+ getDragOverrides,
337
+ getEffectDragOverrides,
338
+ nodePathInfo,
339
+ propStatuses,
340
+ selectedNodePathInfos,
341
+ timelinePosition,
342
+ tracks,
343
+ ]);
113
344
  const seekToDisplayFrame = (0, react_1.useCallback)((frame) => {
114
345
  setFrame((current) => {
115
346
  const next = { ...current, [videoConfig.id]: frame };
@@ -134,71 +365,37 @@ const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, ke
134
365
  if (!clientId || !canToggleKeyframe) {
135
366
  return;
136
367
  }
137
- if (hasKeyframeAtCurrentFrame && isKeyframedStatus(propStatus)) {
138
- if (effectIndex === null) {
139
- await (0, call_delete_keyframe_1.callDeleteSequenceKeyframe)({
140
- fileName,
141
- nodePath,
142
- fieldKey,
143
- sourceFrame: jsxFrame,
144
- schema,
145
- setPropStatuses,
146
- clientId,
147
- });
148
- return;
149
- }
150
- await (0, call_delete_keyframe_1.callDeleteEffectKeyframe)({
151
- fileName,
152
- nodePath,
153
- effectIndex,
154
- fieldKey,
155
- sourceFrame: jsxFrame,
156
- schema,
368
+ if (hasKeyframeAtCurrentFrame) {
369
+ const deleteChanges = keyframeToggleTargets.flatMap((target) => {
370
+ const change = getDeleteChange(target);
371
+ return change === null ? [] : [change];
372
+ });
373
+ await (0, call_delete_keyframe_1.callDeleteKeyframes)({
374
+ sequenceKeyframes: deleteChanges.filter((change) => !hasEffectIndex(change)),
375
+ effectKeyframes: deleteChanges.filter((change) => hasEffectIndex(change)),
157
376
  setPropStatuses,
158
377
  clientId,
159
378
  });
160
379
  return;
161
380
  }
162
- const value = currentKeyframeValue;
163
- if (value === null) {
164
- return;
165
- }
166
- if (effectIndex === null) {
167
- await (0, call_add_keyframe_1.callAddSequenceKeyframe)({
168
- fileName,
169
- nodePath,
170
- fieldKey,
171
- sourceFrame: jsxFrame,
172
- value,
173
- schema,
174
- setPropStatuses,
175
- clientId,
176
- });
381
+ const addChanges = keyframeToggleTargets.flatMap((target) => {
382
+ const change = getAddChange(target);
383
+ return change === null ? [] : [change];
384
+ });
385
+ if (addChanges.length === 0) {
177
386
  return;
178
387
  }
179
- await (0, call_add_keyframe_1.callAddEffectKeyframe)({
180
- fileName,
181
- nodePath,
182
- effectIndex,
183
- fieldKey,
184
- sourceFrame: jsxFrame,
185
- value,
186
- schema,
388
+ await (0, call_add_keyframe_1.callAddKeyframes)({
389
+ sequenceKeyframes: addChanges.filter((change) => !hasEffectIndex(change)),
390
+ effectKeyframes: addChanges.filter((change) => hasEffectIndex(change)),
187
391
  setPropStatuses,
188
392
  clientId,
189
393
  });
190
394
  }, [
191
395
  canToggleKeyframe,
192
396
  clientId,
193
- effectIndex,
194
- fieldKey,
195
- fileName,
196
397
  hasKeyframeAtCurrentFrame,
197
- currentKeyframeValue,
198
- jsxFrame,
199
- nodePath,
200
- propStatus,
201
- schema,
398
+ keyframeToggleTargets,
202
399
  setPropStatuses,
203
400
  ]);
204
401
  const previousDisabled = previousDisplayFrame === null;
@@ -60,7 +60,9 @@ const diamondBase = {
60
60
  const TimelineKeyframeDiamondUnmemoized = ({ frame, rowHeight, nodePathInfo }) => {
61
61
  const videoConfig = (0, remotion_1.useVideoConfig)();
62
62
  const timelineWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
63
- const { selected, onSelect, selectable } = (0, TimelineSelection_1.useTimelineKeyframeSelection)(nodePathInfo, frame);
63
+ const ref = (0, react_1.useRef)(null);
64
+ const { selected, onSelect, selectable, selectionItem } = (0, TimelineSelection_1.useTimelineKeyframeSelection)(nodePathInfo, frame);
65
+ (0, TimelineSelection_1.useTimelineMarqueeSelectableItem)(selectionItem, ref);
64
66
  const { isKeyframeDragging } = (0, TimelineKeyframeDragState_1.useTimelineKeyframeDragState)();
65
67
  const visuallySelected = selected || isKeyframeDragging({ nodePathInfo, frame });
66
68
  const style = (0, react_1.useMemo)(() => {
@@ -85,6 +87,6 @@ const TimelineKeyframeDiamondUnmemoized = ({ frame, rowHeight, nodePathInfo }) =
85
87
  if (style === null) {
86
88
  return null;
87
89
  }
88
- return (jsx_runtime_1.jsx("button", { type: "button", style: style, title: `Keyframe at frame ${frame}`, "aria-label": `Select keyframe at frame ${frame}`, onPointerDown: selectable ? onPointerDown : undefined, children: jsx_runtime_1.jsx(TimelineKeyframeDiamondIcon_1.TimelineKeyframeDiamondIcon, { color: colors_1.LIGHT_TEXT, selected: visuallySelected, size: diamondSize }) }));
90
+ return (jsx_runtime_1.jsx("button", { ref: ref, [TimelineSelection_1.TIMELINE_MARQUEE_ITEM_ATTR]: true, type: "button", style: style, title: `Keyframe at frame ${frame}`, "aria-label": `Select keyframe at frame ${frame}`, onPointerDown: selectable ? onPointerDown : undefined, children: jsx_runtime_1.jsx(TimelineKeyframeDiamondIcon_1.TimelineKeyframeDiamondIcon, { color: colors_1.LIGHT_TEXT, selected: visuallySelected, size: diamondSize }) }));
89
91
  };
90
92
  exports.TimelineKeyframeDiamond = react_1.default.memo(TimelineKeyframeDiamondUnmemoized);
@@ -35,13 +35,18 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.TimelineKeyframeEasingLine = void 0;
37
37
  const jsx_runtime_1 = require("react/jsx-runtime");
38
+ const studio_shared_1 = require("@remotion/studio-shared");
38
39
  const react_1 = __importStar(require("react"));
39
40
  const remotion_1 = require("remotion");
41
+ const client_id_1 = require("../../helpers/client-id");
40
42
  const colors_1 = require("../../helpers/colors");
41
43
  const get_left_of_timeline_slider_1 = require("../../helpers/get-left-of-timeline-slider");
42
44
  const timeline_layout_1 = require("../../helpers/timeline-layout");
45
+ const modals_1 = require("../../state/modals");
46
+ const ContextMenu_1 = require("../ContextMenu");
43
47
  const TimelineSelection_1 = require("./TimelineSelection");
44
48
  const TimelineWidthProvider_1 = require("./TimelineWidthProvider");
49
+ const update_selected_easing_1 = require("./update-selected-easing");
45
50
  const hitTargetHeight = 12;
46
51
  const lineHeight = 2;
47
52
  const easingLineButton = {
@@ -53,7 +58,7 @@ const easingLineButton = {
53
58
  transform: 'translateY(-50%)',
54
59
  };
55
60
  const easingLine = {
56
- backgroundColor: colors_1.LINE_COLOR,
61
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
57
62
  borderRadius: lineHeight / 2,
58
63
  height: lineHeight,
59
64
  left: 0,
@@ -63,14 +68,132 @@ const easingLine = {
63
68
  transform: 'translateY(-50%)',
64
69
  };
65
70
  const TimelineKeyframeEasingLineUnmemoized = ({ fromFrame, toFrame, rowHeight, nodePathInfo, segmentIndex }) => {
71
+ const buttonRef = (0, react_1.useRef)(null);
66
72
  const videoConfig = (0, remotion_1.useVideoConfig)();
67
73
  const timelineWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
68
- const { selected, onSelect, selectable } = (0, TimelineSelection_1.useTimelineEasingSelection)({
74
+ const { selected, onSelect, selectable, selectionItem } = (0, TimelineSelection_1.useTimelineEasingSelection)({
69
75
  nodePathInfo,
70
76
  fromFrame,
71
77
  toFrame,
72
78
  segmentIndex,
73
79
  });
80
+ (0, TimelineSelection_1.useTimelineMarqueeSelectableItem)(selectionItem, buttonRef);
81
+ const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
82
+ const sequencesRef = (0, react_1.useContext)(remotion_1.Internals.SequenceManagerRefContext);
83
+ const propStatusesRef = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesRefContext);
84
+ const { setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
85
+ const { overrideIdToNodePathMappings } = (0, react_1.useContext)(remotion_1.Internals.OverrideIdsToNodePathsGettersContext);
86
+ const currentSelection = (0, TimelineSelection_1.useCurrentTimelineSelectionStateAsRef)();
87
+ const { setSelectedModal } = (0, react_1.useContext)(modals_1.ModalsContext);
88
+ const getTargetSelections = (0, react_1.useCallback)(() => {
89
+ const selectedEasings = (0, update_selected_easing_1.getEasingSelections)(currentSelection.current.selectedItems);
90
+ return selected ? selectedEasings : [selectionItem];
91
+ }, [currentSelection, selected, selectionItem]);
92
+ const updateEasing = (0, react_1.useCallback)((easing) => {
93
+ if (previewServerState.type !== 'connected') {
94
+ return;
95
+ }
96
+ const promise = (0, update_selected_easing_1.updateSelectedTimelineEasings)({
97
+ selections: getTargetSelections(),
98
+ sequences: sequencesRef.current,
99
+ overrideIdsToNodePaths: overrideIdToNodePathMappings,
100
+ propStatuses: propStatusesRef.current,
101
+ setPropStatuses,
102
+ clientId: previewServerState.clientId,
103
+ easing,
104
+ });
105
+ promise === null || promise === void 0 ? void 0 : promise.catch(() => undefined);
106
+ }, [
107
+ getTargetSelections,
108
+ overrideIdToNodePathMappings,
109
+ previewServerState,
110
+ propStatusesRef,
111
+ sequencesRef,
112
+ setPropStatuses,
113
+ ]);
114
+ const onOpenEasingEditor = (0, react_1.useCallback)(() => {
115
+ if (previewServerState.type !== 'connected') {
116
+ return;
117
+ }
118
+ const initialEasing = (0, update_selected_easing_1.getTimelineEasingValueForSelection)({
119
+ selection: selectionItem,
120
+ sequences: sequencesRef.current,
121
+ overrideIdsToNodePaths: overrideIdToNodePathMappings,
122
+ propStatuses: propStatusesRef.current,
123
+ });
124
+ if (initialEasing === null) {
125
+ return;
126
+ }
127
+ setSelectedModal({
128
+ type: 'easing-editor',
129
+ initialEasing,
130
+ selections: getTargetSelections(),
131
+ });
132
+ }, [
133
+ getTargetSelections,
134
+ overrideIdToNodePathMappings,
135
+ previewServerState,
136
+ propStatusesRef,
137
+ selectionItem,
138
+ sequencesRef,
139
+ setSelectedModal,
140
+ ]);
141
+ const contextMenuValues = (0, react_1.useMemo)(() => {
142
+ return [
143
+ {
144
+ type: 'item',
145
+ id: 'linear',
146
+ keyHint: null,
147
+ label: 'Linear',
148
+ leftItem: null,
149
+ disabled: previewServerState.type !== 'connected',
150
+ onClick: () => updateEasing('linear'),
151
+ quickSwitcherLabel: null,
152
+ subMenu: null,
153
+ value: 'linear',
154
+ },
155
+ ...studio_shared_1.KEYFRAME_EASING_PRESETS.map((preset) => ({
156
+ type: 'item',
157
+ id: preset.id,
158
+ keyHint: null,
159
+ label: preset.label,
160
+ leftItem: null,
161
+ disabled: previewServerState.type !== 'connected',
162
+ onClick: () => updateEasing(preset.easing),
163
+ quickSwitcherLabel: null,
164
+ subMenu: null,
165
+ value: preset.id,
166
+ })),
167
+ {
168
+ type: 'divider',
169
+ id: 'edit-easing-divider',
170
+ },
171
+ {
172
+ type: 'item',
173
+ id: 'edit-easing',
174
+ keyHint: null,
175
+ label: 'Edit...',
176
+ leftItem: null,
177
+ disabled: previewServerState.type !== 'connected',
178
+ onClick: onOpenEasingEditor,
179
+ quickSwitcherLabel: null,
180
+ subMenu: null,
181
+ value: 'edit-easing',
182
+ },
183
+ ];
184
+ }, [onOpenEasingEditor, previewServerState.type, updateEasing]);
185
+ const onOpenContextMenu = (0, react_1.useCallback)((event) => {
186
+ if (!selectable) {
187
+ return false;
188
+ }
189
+ if (!selected) {
190
+ onSelect({
191
+ shiftKey: event.shiftKey,
192
+ toggleKey: event.metaKey || event.ctrlKey,
193
+ });
194
+ }
195
+ return contextMenuValues;
196
+ }, [contextMenuValues, onSelect, selectable, selected]);
74
197
  const style = (0, react_1.useMemo)(() => {
75
198
  if (timelineWidth === null) {
76
199
  return null;
@@ -115,6 +238,8 @@ const TimelineKeyframeEasingLineUnmemoized = ({ fromFrame, toFrame, rowHeight, n
115
238
  if (style === null) {
116
239
  return null;
117
240
  }
118
- return (jsx_runtime_1.jsx("button", { type: "button", style: style, title: `Easing from frame ${fromFrame} to ${toFrame}`, "aria-label": `Select easing from frame ${fromFrame} to ${toFrame}`, onPointerDown: selectable ? onPointerDown : undefined, children: jsx_runtime_1.jsx("div", { style: lineStyle }) }));
241
+ return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
242
+ jsx_runtime_1.jsx("button", { ref: buttonRef, [TimelineSelection_1.TIMELINE_MARQUEE_ITEM_ATTR]: true, type: "button", style: style, title: `Easing from frame ${fromFrame} to ${toFrame}`, "aria-label": `Select easing from frame ${fromFrame} to ${toFrame}`, onPointerDown: selectable ? onPointerDown : undefined, children: jsx_runtime_1.jsx("div", { style: lineStyle }) }), jsx_runtime_1.jsx(ContextMenu_1.ContextMenuForTarget, { triggerRef: buttonRef, values: contextMenuValues, onOpen: onOpenContextMenu })
243
+ ] }));
119
244
  };
120
245
  exports.TimelineKeyframeEasingLine = react_1.default.memo(TimelineKeyframeEasingLineUnmemoized);
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { TrackWithHash } from '../../helpers/get-timeline-sequence-sort-key';
3
+ export declare const TimelineKeyframeTracksProvider: React.FC<{
4
+ readonly tracks: readonly TrackWithHash[];
5
+ readonly children: React.ReactNode;
6
+ }>;
7
+ export declare const useTimelineKeyframeTracks: () => readonly TrackWithHash[];
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useTimelineKeyframeTracks = exports.TimelineKeyframeTracksProvider = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const TimelineKeyframeTracksContext = (0, react_1.createContext)([]);
7
+ // Keyframe diamonds need track metadata to resolve other selected rows. The
8
+ // selection state only stores identities, and a mounted-row registry would miss
9
+ // collapsed or otherwise unmounted rows.
10
+ const TimelineKeyframeTracksProvider = ({ tracks, children }) => {
11
+ return (jsx_runtime_1.jsx(TimelineKeyframeTracksContext.Provider, { value: tracks, children: children }));
12
+ };
13
+ exports.TimelineKeyframeTracksProvider = TimelineKeyframeTracksProvider;
14
+ const useTimelineKeyframeTracks = () => {
15
+ return (0, react_1.useContext)(TimelineKeyframeTracksContext);
16
+ };
17
+ exports.useTimelineKeyframeTracks = useTimelineKeyframeTracks;
@@ -5,9 +5,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const remotion_1 = require("remotion");
7
7
  const colors_1 = require("../../helpers/colors");
8
- const use_select_asset_1 = require("../use-select-asset");
9
8
  const timeline_asset_link_1 = require("./timeline-asset-link");
10
- const TimelineSelection_1 = require("./TimelineSelection");
11
9
  const lineStyle = {
12
10
  whiteSpace: 'nowrap',
13
11
  overflow: 'hidden',
@@ -18,24 +16,10 @@ const lineStyle = {
18
16
  display: 'inline-block',
19
17
  };
20
18
  const useAssetLink = (src) => {
21
- const selectAsset = (0, use_select_asset_1.useSelectAsset)();
22
- const [hovered, setHovered] = (0, react_1.useState)(false);
23
19
  const linkInfo = (0, react_1.useMemo)(() => (0, timeline_asset_link_1.getTimelineAssetLinkInfo)(src), [src]);
24
- const interactive = !TimelineSelection_1.SELECTION_ENABLED && linkInfo !== null;
25
- const onClick = (0, react_1.useCallback)((e) => {
26
- if (!linkInfo) {
27
- return;
28
- }
29
- e.preventDefault();
30
- e.stopPropagation();
31
- (0, timeline_asset_link_1.openTimelineAssetLink)(linkInfo, selectAsset);
32
- }, [linkInfo, selectAsset]);
33
- const onPointerEnter = (0, react_1.useCallback)(() => setHovered(true), []);
34
- const onPointerLeave = (0, react_1.useCallback)(() => setHovered(false), []);
35
20
  const fileNameStyle = (0, react_1.useMemo)(() => ({
36
21
  ...lineStyle,
37
- color: interactive && hovered ? colors_1.LIGHT_TEXT : colors_1.VERY_LIGHT_TEXT,
38
- cursor: interactive ? 'pointer' : undefined,
22
+ color: colors_1.VERY_LIGHT_TEXT,
39
23
  textDecoration: 'none',
40
24
  display: 'inline-block',
41
25
  overflow: 'hidden',
@@ -43,19 +27,15 @@ const useAssetLink = (src) => {
43
27
  textOverflow: 'ellipsis',
44
28
  userSelect: 'none',
45
29
  WebkitUserSelect: 'none',
46
- }), [interactive, hovered]);
30
+ }), []);
47
31
  return {
48
32
  linkInfo,
49
- interactive,
50
- onClick,
51
- onPointerEnter,
52
- onPointerLeave,
53
33
  fileNameStyle,
54
34
  };
55
35
  };
56
36
  const TimelineMediaInfo = ({ src }) => {
57
37
  const fileName = (0, react_1.useMemo)(() => remotion_1.Internals.getAssetDisplayName(src), [src]);
58
- const { linkInfo, interactive, onClick, onPointerEnter, onPointerLeave, fileNameStyle, } = useAssetLink(src);
59
- return (jsx_runtime_1.jsx("div", { style: fileNameStyle, title: linkInfo ? linkInfo.title : fileName, onClick: interactive ? onClick : undefined, onPointerEnter: interactive ? onPointerEnter : undefined, onPointerLeave: interactive ? onPointerLeave : undefined, children: fileName }));
38
+ const { linkInfo, fileNameStyle } = useAssetLink(src);
39
+ return (jsx_runtime_1.jsx("div", { style: fileNameStyle, title: linkInfo ? linkInfo.title : fileName, children: fileName }));
60
40
  };
61
41
  exports.TimelineMediaInfo = TimelineMediaInfo;