@remotion/studio 4.0.469 → 4.0.470

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 (90) hide show
  1. package/dist/Studio.js +1 -1
  2. package/dist/api/save-render-output.js +3 -12
  3. package/dist/components/AudioWaveform.js +19 -2
  4. package/dist/components/ContextMenu.js +5 -1
  5. package/dist/components/EditorContent.js +5 -4
  6. package/dist/components/Menu/MenuItem.d.ts +1 -1
  7. package/dist/components/MenuBuildIndicator.js +0 -1
  8. package/dist/components/NewComposition/InputDragger.js +1 -0
  9. package/dist/components/Preview.js +4 -1
  10. package/dist/components/SelectedOutlineOverlay.d.ts +18 -0
  11. package/dist/components/SelectedOutlineOverlay.js +645 -0
  12. package/dist/components/Timeline/Timeline.js +27 -13
  13. package/dist/components/Timeline/TimelineDeleteKeybindings.d.ts +2 -0
  14. package/dist/components/Timeline/TimelineDeleteKeybindings.js +86 -0
  15. package/dist/components/Timeline/TimelineDragHandler.js +19 -244
  16. package/dist/components/Timeline/{TimelineEffectGroupRow.d.ts → TimelineEffectItem.d.ts} +1 -1
  17. package/dist/components/Timeline/{TimelineEffectGroupRow.js → TimelineEffectItem.js} +50 -33
  18. package/dist/components/Timeline/{TimelineEffectFieldRow.d.ts → TimelineEffectPropItem.d.ts} +2 -1
  19. package/dist/components/Timeline/{TimelineEffectFieldRow.js → TimelineEffectPropItem.js} +97 -8
  20. package/dist/components/Timeline/TimelineExpandArrowButton.js +5 -1
  21. package/dist/components/Timeline/TimelineExpandedKeyframeRow.js +37 -5
  22. package/dist/components/Timeline/TimelineExpandedRow.d.ts +1 -0
  23. package/dist/components/Timeline/TimelineExpandedRow.js +9 -7
  24. package/dist/components/Timeline/TimelineExpandedSection.d.ts +1 -0
  25. package/dist/components/Timeline/TimelineExpandedSection.js +2 -2
  26. package/dist/components/Timeline/TimelineInOutDragHandler.d.ts +2 -0
  27. package/dist/components/Timeline/TimelineInOutDragHandler.js +324 -0
  28. package/dist/components/Timeline/TimelineInOutPointer.js +3 -1
  29. package/dist/components/Timeline/TimelineInOutPointerHandle.d.ts +1 -0
  30. package/dist/components/Timeline/TimelineInOutPointerHandle.js +5 -4
  31. package/dist/components/Timeline/TimelineKeyframeControls.d.ts +17 -0
  32. package/dist/components/Timeline/TimelineKeyframeControls.js +217 -0
  33. package/dist/components/Timeline/TimelineKeyframeDiamond.js +7 -6
  34. package/dist/components/Timeline/TimelineKeyframedValue.d.ts +8 -0
  35. package/dist/components/Timeline/TimelineKeyframedValue.js +36 -0
  36. package/dist/components/Timeline/TimelineList.js +3 -15
  37. package/dist/components/Timeline/TimelineRowChrome.d.ts +3 -1
  38. package/dist/components/Timeline/TimelineRowChrome.js +23 -5
  39. package/dist/components/Timeline/TimelineSelection.d.ts +53 -9
  40. package/dist/components/Timeline/TimelineSelection.js +305 -48
  41. package/dist/components/Timeline/TimelineSequence.d.ts +2 -0
  42. package/dist/components/Timeline/TimelineSequence.js +18 -6
  43. package/dist/components/Timeline/TimelineSequenceFrame.js +1 -0
  44. package/dist/components/Timeline/{TimelineListItem.d.ts → TimelineSequenceItem.d.ts} +2 -1
  45. package/dist/components/Timeline/{TimelineListItem.js → TimelineSequenceItem.js} +49 -33
  46. package/dist/components/Timeline/{TimelineFieldRow.d.ts → TimelineSequencePropItem.d.ts} +2 -1
  47. package/dist/components/Timeline/{TimelineFieldRow.js → TimelineSequencePropItem.js} +81 -5
  48. package/dist/components/Timeline/TimelineTimeIndicators.js +0 -1
  49. package/dist/components/Timeline/TimelineTrack.js +3 -1
  50. package/dist/components/Timeline/TimelineTranslateField.js +14 -22
  51. package/dist/components/Timeline/call-add-keyframe.d.ts +23 -0
  52. package/dist/components/Timeline/call-add-keyframe.js +54 -0
  53. package/dist/components/Timeline/call-delete-keyframe.d.ts +21 -0
  54. package/dist/components/Timeline/call-delete-keyframe.js +50 -0
  55. package/dist/components/Timeline/delete-selected-keyframe.d.ts +11 -0
  56. package/dist/components/Timeline/delete-selected-keyframe.js +51 -0
  57. package/dist/components/Timeline/delete-selected-timeline-item.d.ts +17 -0
  58. package/dist/components/Timeline/delete-selected-timeline-item.js +183 -0
  59. package/dist/components/Timeline/duplicate-selected-timeline-item.d.ts +13 -0
  60. package/dist/components/Timeline/duplicate-selected-timeline-item.js +66 -0
  61. package/dist/components/Timeline/find-track-for-node-path-info.d.ts +7 -0
  62. package/dist/components/Timeline/find-track-for-node-path-info.js +13 -0
  63. package/dist/components/Timeline/get-keyframe-navigation.d.ts +9 -0
  64. package/dist/components/Timeline/get-keyframe-navigation.js +26 -0
  65. package/dist/components/Timeline/parse-keyframe-field-from-node-path.d.ts +8 -0
  66. package/dist/components/Timeline/parse-keyframe-field-from-node-path.js +26 -0
  67. package/dist/components/Timeline/save-sequence-prop.d.ts +14 -2
  68. package/dist/components/Timeline/save-sequence-prop.js +42 -7
  69. package/dist/components/Timeline/timeline-row-layout.d.ts +1 -0
  70. package/dist/components/Timeline/timeline-row-layout.js +2 -1
  71. package/dist/components/Timeline/timeline-translate-utils.d.ts +2 -0
  72. package/dist/components/Timeline/timeline-translate-utils.js +20 -0
  73. package/dist/components/Timeline/use-expanded-track-keyframe-rows.js +10 -3
  74. package/dist/components/composition-menu-items.js +32 -1
  75. package/dist/error-overlay/remotion-overlay/OpenInEditor.js +0 -1
  76. package/dist/esm/{chunk-1mp51e0w.js → chunk-dny42qnq.js} +9896 -6174
  77. package/dist/esm/internals.mjs +9896 -6174
  78. package/dist/esm/previewEntry.mjs +9870 -6148
  79. package/dist/esm/renderEntry.mjs +3 -1
  80. package/dist/helpers/format-file-location.d.ts +9 -0
  81. package/dist/helpers/format-file-location.js +27 -0
  82. package/dist/helpers/get-box-quads-polyfill-internals.d.ts +82 -0
  83. package/dist/helpers/get-box-quads-polyfill-internals.js +2395 -0
  84. package/dist/helpers/get-box-quads-ponyfill.d.ts +10 -0
  85. package/dist/helpers/get-box-quads-ponyfill.js +23 -0
  86. package/dist/helpers/open-in-editor.d.ts +1 -1
  87. package/dist/helpers/open-in-editor.js +11 -26
  88. package/dist/helpers/use-menu-structure.js +8 -16
  89. package/dist/renderEntry.js +2 -2
  90. package/package.json +10 -10
@@ -0,0 +1,17 @@
1
+ import type { OverrideIdToNodePaths, TSequence } from 'remotion';
2
+ import type { SetCodeValues } from './save-sequence-prop';
3
+ import type { TimelineSelection } from './TimelineSelection';
4
+ export declare const deleteSelectedTimelineItem: ({ selection, sequences, overrideIdsToNodePaths, setCodeValues, clientId, }: {
5
+ selection: TimelineSelection;
6
+ sequences: TSequence[];
7
+ overrideIdsToNodePaths: OverrideIdToNodePaths;
8
+ setCodeValues: SetCodeValues;
9
+ clientId: string;
10
+ }) => Promise<void> | null;
11
+ export declare const deleteSelectedTimelineItems: ({ selections, sequences, overrideIdsToNodePaths, setCodeValues, clientId, }: {
12
+ selections: readonly TimelineSelection[];
13
+ sequences: TSequence[];
14
+ overrideIdsToNodePaths: OverrideIdToNodePaths;
15
+ setCodeValues: SetCodeValues;
16
+ clientId: string;
17
+ }) => Promise<void> | null;
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteSelectedTimelineItems = exports.deleteSelectedTimelineItem = void 0;
4
+ const call_api_1 = require("../call-api");
5
+ const NotificationCenter_1 = require("../Notifications/NotificationCenter");
6
+ const delete_selected_keyframe_1 = require("./delete-selected-keyframe");
7
+ const confirmDeletingDuplicatedSequences = (nodePathInfos) => {
8
+ const duplicatedNodePathInfos = nodePathInfos.filter((nodePathInfo) => nodePathInfo.numberOfSequencesWithThisNodePath > 1);
9
+ if (duplicatedNodePathInfos.length === 0) {
10
+ return true;
11
+ }
12
+ if (duplicatedNodePathInfos.length === 1) {
13
+ const [nodePathInfo] = duplicatedNodePathInfos;
14
+ const singleDuplicatedSequenceMessage = 'This sequence is programmatically duplicated ' +
15
+ nodePathInfo.numberOfSequencesWithThisNodePath +
16
+ ' times in the code. Deleting removes all instances. Continue?';
17
+ // eslint-disable-next-line no-alert -- native confirm before deleting all instances of a duplicated sequence
18
+ return window.confirm(singleDuplicatedSequenceMessage);
19
+ }
20
+ const multipleDuplicatedSequencesMessage = duplicatedNodePathInfos.length +
21
+ ' selected sequences are programmatically duplicated in the code. Deleting removes all instances. Continue?';
22
+ // eslint-disable-next-line no-alert -- native confirm before deleting all instances of duplicated sequences
23
+ return window.confirm(multipleDuplicatedSequencesMessage);
24
+ };
25
+ const deleteSequences = (nodePathInfos) => {
26
+ if (!confirmDeletingDuplicatedSequences(nodePathInfos)) {
27
+ return Promise.resolve();
28
+ }
29
+ return (0, call_api_1.callApi)('/api/delete-jsx-node', {
30
+ nodes: nodePathInfos.map((nodePathInfo) => {
31
+ const nodePath = nodePathInfo.sequenceSubscriptionKey;
32
+ return {
33
+ fileName: nodePath.absolutePath,
34
+ nodePath: nodePath.nodePath,
35
+ };
36
+ }),
37
+ })
38
+ .then((result) => {
39
+ if (result.success) {
40
+ (0, NotificationCenter_1.showNotification)(nodePathInfos.length === 1
41
+ ? 'Removed sequence from source file'
42
+ : 'Removed sequences from source files', 2000);
43
+ }
44
+ else {
45
+ (0, NotificationCenter_1.showNotification)(result.reason, 4000);
46
+ }
47
+ })
48
+ .catch((err) => {
49
+ (0, NotificationCenter_1.showNotification)(err.message, 4000);
50
+ });
51
+ };
52
+ const deleteEffects = (effects) => {
53
+ if (effects.length === 0) {
54
+ return Promise.resolve();
55
+ }
56
+ return (0, call_api_1.callApi)('/api/delete-effect', effects.map((effect) => {
57
+ const nodePath = effect.nodePathInfo.sequenceSubscriptionKey;
58
+ return effect.type === 'single-effect'
59
+ ? {
60
+ type: 'single-effect',
61
+ fileName: nodePath.absolutePath,
62
+ sequenceNodePath: nodePath,
63
+ effectIndex: effect.effectIndex,
64
+ }
65
+ : {
66
+ type: 'all-effects',
67
+ fileName: nodePath.absolutePath,
68
+ sequenceNodePath: nodePath,
69
+ };
70
+ }))
71
+ .then((result) => {
72
+ if (result.success) {
73
+ const singleEffect = effects[0];
74
+ (0, NotificationCenter_1.showNotification)(effects.length === 1 && (singleEffect === null || singleEffect === void 0 ? void 0 : singleEffect.type) === 'single-effect'
75
+ ? 'Removed effect from source file'
76
+ : effects.length === 1
77
+ ? 'Removed effects from source file'
78
+ : 'Removed effects from source files', 2000);
79
+ }
80
+ else {
81
+ (0, NotificationCenter_1.showNotification)(result.reason, 4000);
82
+ }
83
+ })
84
+ .catch((err) => {
85
+ (0, NotificationCenter_1.showNotification)(err.message, 4000);
86
+ });
87
+ };
88
+ const deleteSelectedTimelineItem = ({ selection, sequences, overrideIdsToNodePaths, setCodeValues, clientId, }) => {
89
+ if (selection.type === 'keyframe') {
90
+ return (0, delete_selected_keyframe_1.deleteSelectedKeyframe)({
91
+ nodePathInfo: selection.nodePathInfo,
92
+ frame: selection.frame,
93
+ sequences,
94
+ overrideIdsToNodePaths,
95
+ setCodeValues,
96
+ clientId,
97
+ });
98
+ }
99
+ switch (selection.type) {
100
+ case 'sequence':
101
+ return deleteSequences([selection.nodePathInfo]);
102
+ case 'sequence-effect':
103
+ return deleteEffects([
104
+ {
105
+ type: 'single-effect',
106
+ nodePathInfo: selection.nodePathInfo,
107
+ effectIndex: selection.i,
108
+ },
109
+ ]);
110
+ case 'sequence-prop':
111
+ case 'sequence-effect-prop':
112
+ return null;
113
+ case 'sequence-all-effects':
114
+ return deleteEffects([
115
+ { type: 'all-effects', nodePathInfo: selection.nodePathInfo },
116
+ ]);
117
+ default:
118
+ throw new Error(`Unexpected timeline selection type: ${selection}`);
119
+ }
120
+ };
121
+ exports.deleteSelectedTimelineItem = deleteSelectedTimelineItem;
122
+ const isSequenceRowSelection = (selection) => selection.type === 'sequence';
123
+ const isSequenceEffectSelection = (selection) => selection.type === 'sequence-effect';
124
+ const isSequenceAllEffectsSelection = (selection) => selection.type === 'sequence-all-effects';
125
+ const isKeyframeSelection = (selection) => selection.type === 'keyframe';
126
+ const assertTimelineSelectionsHaveSameType = (selections) => {
127
+ const firstSelection = selections[0];
128
+ if (!firstSelection) {
129
+ return;
130
+ }
131
+ for (const selection of selections) {
132
+ if (selection.type !== firstSelection.type) {
133
+ throw new Error(`Assertion failed: Cannot delete timeline selections of different types (${firstSelection.type}, ${selection.type})`);
134
+ }
135
+ }
136
+ };
137
+ const deleteSelectedTimelineItems = ({ selections, sequences, overrideIdsToNodePaths, setCodeValues, clientId, }) => {
138
+ const firstSelection = selections[0];
139
+ if (!firstSelection) {
140
+ return null;
141
+ }
142
+ assertTimelineSelectionsHaveSameType(selections);
143
+ switch (firstSelection.type) {
144
+ case 'sequence':
145
+ return deleteSequences(selections
146
+ .filter(isSequenceRowSelection)
147
+ .map((selection) => selection.nodePathInfo));
148
+ case 'sequence-effect':
149
+ return deleteEffects(selections.filter(isSequenceEffectSelection).map((selection) => ({
150
+ type: 'single-effect',
151
+ nodePathInfo: selection.nodePathInfo,
152
+ effectIndex: selection.i,
153
+ })));
154
+ case 'keyframe': {
155
+ const deletePromises = selections
156
+ .filter(isKeyframeSelection)
157
+ .map((selection) => (0, delete_selected_keyframe_1.deleteSelectedKeyframe)({
158
+ nodePathInfo: selection.nodePathInfo,
159
+ frame: selection.frame,
160
+ sequences,
161
+ overrideIdsToNodePaths,
162
+ setCodeValues,
163
+ clientId,
164
+ }))
165
+ .filter((promise) => promise !== null);
166
+ if (deletePromises.length === 0) {
167
+ return null;
168
+ }
169
+ return Promise.all(deletePromises).then(() => undefined);
170
+ }
171
+ case 'sequence-prop':
172
+ case 'sequence-effect-prop':
173
+ return null;
174
+ case 'sequence-all-effects':
175
+ return deleteEffects(selections.filter(isSequenceAllEffectsSelection).map((selection) => ({
176
+ type: 'all-effects',
177
+ nodePathInfo: selection.nodePathInfo,
178
+ })));
179
+ default:
180
+ throw new Error(`Unexpected timeline selection type: ${firstSelection}`);
181
+ }
182
+ };
183
+ exports.deleteSelectedTimelineItems = deleteSelectedTimelineItems;
@@ -0,0 +1,13 @@
1
+ import type { SequenceNodePathInfo } from '../../helpers/get-timeline-sequence-sort-key';
2
+ import type { TimelineSelection } from './TimelineSelection';
3
+ export declare const duplicateSequencesFromSource: (nodePathInfos: readonly SequenceNodePathInfo[]) => Promise<void>;
4
+ export declare const isDuplicatableSequenceRowSelection: (selection: TimelineSelection) => selection is {
5
+ readonly nodePathInfo: SequenceNodePathInfo;
6
+ } & {
7
+ readonly type: "sequence";
8
+ } & {
9
+ type: "sequence";
10
+ };
11
+ export declare const duplicateSelectedTimelineItems: ({ selections, }: {
12
+ selections: readonly TimelineSelection[];
13
+ }) => Promise<void> | null;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.duplicateSelectedTimelineItems = exports.isDuplicatableSequenceRowSelection = exports.duplicateSequencesFromSource = void 0;
4
+ const call_api_1 = require("../call-api");
5
+ const NotificationCenter_1 = require("../Notifications/NotificationCenter");
6
+ const confirmDuplicatingProgrammaticallyDuplicatedSequences = (nodePathInfos) => {
7
+ if (nodePathInfos.length === 0) {
8
+ return true;
9
+ }
10
+ if (nodePathInfos.length === 1) {
11
+ const [nodePathInfo] = nodePathInfos;
12
+ const singleDuplicatedSequenceMessage = 'This sequence is programmatically duplicated ' +
13
+ nodePathInfo.numberOfSequencesWithThisNodePath +
14
+ ' times in the code. Duplicating inserts another copy. Continue?';
15
+ // eslint-disable-next-line no-alert -- native confirm before applying duplicate codemod in .map callbacks
16
+ return window.confirm(singleDuplicatedSequenceMessage);
17
+ }
18
+ const multipleDuplicatedSequencesMessage = nodePathInfos.length +
19
+ ' selected sequences are programmatically duplicated in the code. Duplicating inserts another copy of each. Continue?';
20
+ // eslint-disable-next-line no-alert -- native confirm before applying duplicate codemod in .map callbacks
21
+ return window.confirm(multipleDuplicatedSequencesMessage);
22
+ };
23
+ const duplicateSequence = (nodePathInfo) => {
24
+ const nodePath = nodePathInfo.sequenceSubscriptionKey;
25
+ return (0, call_api_1.callApi)('/api/duplicate-jsx-node', {
26
+ fileName: nodePath.absolutePath,
27
+ nodePath: nodePath.nodePath,
28
+ });
29
+ };
30
+ const duplicateSequencesFromSource = (nodePathInfos) => {
31
+ const programmaticallyDuplicated = nodePathInfos.filter((nodePathInfo) => nodePathInfo.numberOfSequencesWithThisNodePath > 1);
32
+ const regular = nodePathInfos.filter((nodePathInfo) => nodePathInfo.numberOfSequencesWithThisNodePath <= 1);
33
+ const toDuplicate = [...regular];
34
+ if (programmaticallyDuplicated.length === 0 ||
35
+ confirmDuplicatingProgrammaticallyDuplicatedSequences(programmaticallyDuplicated)) {
36
+ toDuplicate.push(...programmaticallyDuplicated);
37
+ }
38
+ if (toDuplicate.length === 0) {
39
+ return Promise.resolve();
40
+ }
41
+ return Promise.all(toDuplicate.map(duplicateSequence))
42
+ .then((results) => {
43
+ const failedResult = results.find((result) => !result.success);
44
+ if (failedResult && !failedResult.success) {
45
+ (0, NotificationCenter_1.showNotification)(failedResult.reason, 4000);
46
+ return;
47
+ }
48
+ (0, NotificationCenter_1.showNotification)(toDuplicate.length === 1
49
+ ? 'Duplicated sequence in source file'
50
+ : 'Duplicated sequences in source files', 2000);
51
+ })
52
+ .catch((err) => {
53
+ (0, NotificationCenter_1.showNotification)(err.message, 4000);
54
+ });
55
+ };
56
+ exports.duplicateSequencesFromSource = duplicateSequencesFromSource;
57
+ const isDuplicatableSequenceRowSelection = (selection) => selection.type === 'sequence';
58
+ exports.isDuplicatableSequenceRowSelection = isDuplicatableSequenceRowSelection;
59
+ const duplicateSelectedTimelineItems = ({ selections, }) => {
60
+ const sequenceSelections = selections.filter(exports.isDuplicatableSequenceRowSelection);
61
+ if (sequenceSelections.length === 0) {
62
+ return null;
63
+ }
64
+ return (0, exports.duplicateSequencesFromSource)(sequenceSelections.map((selection) => selection.nodePathInfo));
65
+ };
66
+ exports.duplicateSelectedTimelineItems = duplicateSelectedTimelineItems;
@@ -0,0 +1,7 @@
1
+ import type { OverrideIdToNodePaths, TSequence } from 'remotion';
2
+ import type { SequenceNodePathInfo } from '../../helpers/get-timeline-sequence-sort-key';
3
+ export declare const findTrackForNodePathInfo: ({ sequences, overrideIdsToNodePaths, nodePathInfo, }: {
4
+ sequences: TSequence[];
5
+ overrideIdsToNodePaths: OverrideIdToNodePaths;
6
+ nodePathInfo: SequenceNodePathInfo;
7
+ }) => import("../../helpers/get-timeline-sequence-sort-key").TrackWithHash | undefined;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findTrackForNodePathInfo = void 0;
4
+ const studio_shared_1 = require("@remotion/studio-shared");
5
+ const calculate_timeline_1 = require("../../helpers/calculate-timeline");
6
+ const findTrackForNodePathInfo = ({ sequences, overrideIdsToNodePaths, nodePathInfo, }) => {
7
+ const tracks = (0, calculate_timeline_1.calculateTimeline)({ sequences, overrideIdsToNodePaths });
8
+ return tracks.find((candidate) => candidate.nodePathInfo !== null &&
9
+ (0, studio_shared_1.stringifySequenceSubscriptionKey)(candidate.nodePathInfo.sequenceSubscriptionKey) ===
10
+ (0, studio_shared_1.stringifySequenceSubscriptionKey)(nodePathInfo.sequenceSubscriptionKey) &&
11
+ candidate.nodePathInfo.index === nodePathInfo.index);
12
+ };
13
+ exports.findTrackForNodePathInfo = findTrackForNodePathInfo;
@@ -0,0 +1,9 @@
1
+ export declare const getPreviousKeyframeDisplayFrame: (keyframes: readonly {
2
+ frame: number;
3
+ }[], currentDisplayFrame: number) => number | null;
4
+ export declare const getNextKeyframeDisplayFrame: (keyframes: readonly {
5
+ frame: number;
6
+ }[], currentDisplayFrame: number) => number | null;
7
+ export declare const hasKeyframeAtSourceFrame: (keyframes: readonly {
8
+ frame: number;
9
+ }[], sourceFrame: number) => boolean;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasKeyframeAtSourceFrame = exports.getNextKeyframeDisplayFrame = exports.getPreviousKeyframeDisplayFrame = void 0;
4
+ const getPreviousKeyframeDisplayFrame = (keyframes, currentDisplayFrame) => {
5
+ let previous = null;
6
+ for (const keyframe of keyframes) {
7
+ if (keyframe.frame < currentDisplayFrame) {
8
+ previous = keyframe.frame;
9
+ }
10
+ }
11
+ return previous;
12
+ };
13
+ exports.getPreviousKeyframeDisplayFrame = getPreviousKeyframeDisplayFrame;
14
+ const getNextKeyframeDisplayFrame = (keyframes, currentDisplayFrame) => {
15
+ for (const keyframe of keyframes) {
16
+ if (keyframe.frame > currentDisplayFrame) {
17
+ return keyframe.frame;
18
+ }
19
+ }
20
+ return null;
21
+ };
22
+ exports.getNextKeyframeDisplayFrame = getNextKeyframeDisplayFrame;
23
+ const hasKeyframeAtSourceFrame = (keyframes, sourceFrame) => {
24
+ return keyframes.some((keyframe) => keyframe.frame === sourceFrame);
25
+ };
26
+ exports.hasKeyframeAtSourceFrame = hasKeyframeAtSourceFrame;
@@ -0,0 +1,8 @@
1
+ export declare const parseKeyframeFieldFromNodePath: (auxiliaryKeys: string[]) => {
2
+ readonly type: "sequence";
3
+ readonly fieldKey: string;
4
+ } | {
5
+ readonly type: "effect";
6
+ readonly effectIndex: number;
7
+ readonly fieldKey: string;
8
+ } | null;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseKeyframeFieldFromNodePath = void 0;
4
+ const parseKeyframeFieldFromNodePath = (auxiliaryKeys) => {
5
+ // Sequence control field: ['controls', fieldKey]
6
+ if (auxiliaryKeys[0] === 'controls' && auxiliaryKeys.length >= 2) {
7
+ return {
8
+ type: 'sequence',
9
+ fieldKey: auxiliaryKeys[1],
10
+ };
11
+ }
12
+ // Effect field: ['effects', effectIndex, fieldKey]
13
+ if (auxiliaryKeys[0] === 'effects' && auxiliaryKeys.length >= 3) {
14
+ const effectIndex = Number(auxiliaryKeys[1]);
15
+ if (!Number.isInteger(effectIndex) || effectIndex < 0) {
16
+ return null;
17
+ }
18
+ return {
19
+ type: 'effect',
20
+ effectIndex,
21
+ fieldKey: auxiliaryKeys[2],
22
+ };
23
+ }
24
+ return null;
25
+ };
26
+ exports.parseKeyframeFieldFromNodePath = parseKeyframeFieldFromNodePath;
@@ -1,12 +1,24 @@
1
1
  import type { CanUpdateSequencePropsResponse, SequencePropsSubscriptionKey, SequenceSchema } from 'remotion';
2
2
  export type SetCodeValues = (nodePath: SequencePropsSubscriptionKey, values: (prev: CanUpdateSequencePropsResponse) => CanUpdateSequencePropsResponse) => void;
3
- export declare const saveSequenceProp: ({ fileName, nodePath, fieldKey, value, defaultValue, schema, setCodeValues, clientId, }: {
3
+ export type SaveSequencePropChange = {
4
4
  fileName: string;
5
5
  nodePath: SequencePropsSubscriptionKey;
6
6
  fieldKey: string;
7
7
  value: unknown;
8
8
  defaultValue: string | null;
9
9
  schema: SequenceSchema;
10
+ };
11
+ type SaveSequencePropsOptions = {
12
+ changes: SaveSequencePropChange[];
10
13
  setCodeValues: SetCodeValues;
11
14
  clientId: string;
12
- }) => Promise<void>;
15
+ undoLabel: string | null;
16
+ redoLabel: string | null;
17
+ };
18
+ type SaveSequencePropOptions = SaveSequencePropChange & {
19
+ setCodeValues: SetCodeValues;
20
+ clientId: string;
21
+ };
22
+ export declare const saveSequenceProps: ({ changes, setCodeValues, clientId, undoLabel, redoLabel, }: SaveSequencePropsOptions) => Promise<void>;
23
+ export declare const saveSequenceProp: ({ fileName, nodePath, fieldKey, value, defaultValue, schema, setCodeValues, clientId, }: SaveSequencePropOptions) => Promise<void>;
24
+ export {};
@@ -1,9 +1,38 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.saveSequenceProp = void 0;
3
+ exports.saveSequenceProp = exports.saveSequenceProps = void 0;
4
4
  const studio_shared_1 = require("@remotion/studio-shared");
5
5
  const call_api_1 = require("../call-api");
6
6
  const save_prop_queue_1 = require("./save-prop-queue");
7
+ const saveSequenceProps = ({ changes, setCodeValues, clientId, undoLabel, redoLabel, }) => {
8
+ if (changes.length === 0) {
9
+ return Promise.resolve();
10
+ }
11
+ for (const change of changes) {
12
+ setCodeValues(change.nodePath, (prev) => (0, studio_shared_1.optimisticUpdateForCodeValues)({
13
+ previous: prev,
14
+ fieldKey: change.fieldKey,
15
+ value: change.value,
16
+ schema: change.schema,
17
+ }));
18
+ }
19
+ return (0, call_api_1.callApi)('/api/save-sequence-props', {
20
+ edits: changes.map((change) => {
21
+ return {
22
+ fileName: change.fileName,
23
+ nodePath: change.nodePath,
24
+ key: change.fieldKey,
25
+ value: JSON.stringify(change.value),
26
+ defaultValue: change.defaultValue,
27
+ schema: change.schema,
28
+ };
29
+ }),
30
+ clientId,
31
+ undoLabel,
32
+ redoLabel,
33
+ }).then(() => undefined);
34
+ };
35
+ exports.saveSequenceProps = saveSequenceProps;
7
36
  const saveSequenceProp = ({ fileName, nodePath, fieldKey, value, defaultValue, schema, setCodeValues, clientId, }) => {
8
37
  return (0, save_prop_queue_1.enqueueSavePropChange)({
9
38
  nodePath,
@@ -15,13 +44,19 @@ const saveSequenceProp = ({ fileName, nodePath, fieldKey, value, defaultValue, s
15
44
  schema,
16
45
  }),
17
46
  apiCall: () => (0, call_api_1.callApi)('/api/save-sequence-props', {
18
- fileName,
19
- nodePath,
20
- key: fieldKey,
21
- value: JSON.stringify(value),
22
- defaultValue,
23
- schema,
47
+ edits: [
48
+ {
49
+ fileName,
50
+ nodePath,
51
+ key: fieldKey,
52
+ value: JSON.stringify(value),
53
+ defaultValue,
54
+ schema,
55
+ },
56
+ ],
24
57
  clientId,
58
+ undoLabel: null,
59
+ redoLabel: null,
25
60
  }),
26
61
  errorLabel: 'Could not save sequence prop',
27
62
  });
@@ -1,6 +1,7 @@
1
1
  export declare const TIMELINE_ROW_BASE_PADDING = 5;
2
2
  export declare const TIMELINE_EYE_COLUMN_WIDTH = 22;
3
3
  export declare const TIMELINE_ARROW_COLUMN_WIDTH = 16;
4
+ export declare const TIMELINE_KEYFRAME_CONTROLS_WIDTH = 38;
4
5
  export declare const TIMELINE_FIELD_LABEL_EXTRA_WIDTH = 15;
5
6
  export declare const getTimelineRowIndentWidth: (depth: number) => number;
6
7
  export declare const getTimelineRowLeftChromeWidth: (depth: number) => number;
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getExpandedRowDepth = exports.getTimelineFieldLabelFlexBasis = exports.getTimelineRowLeftChromeWidth = exports.getTimelineRowIndentWidth = exports.TIMELINE_FIELD_LABEL_EXTRA_WIDTH = exports.TIMELINE_ARROW_COLUMN_WIDTH = exports.TIMELINE_EYE_COLUMN_WIDTH = exports.TIMELINE_ROW_BASE_PADDING = void 0;
3
+ exports.getExpandedRowDepth = exports.getTimelineFieldLabelFlexBasis = exports.getTimelineRowLeftChromeWidth = exports.getTimelineRowIndentWidth = exports.TIMELINE_FIELD_LABEL_EXTRA_WIDTH = exports.TIMELINE_KEYFRAME_CONTROLS_WIDTH = exports.TIMELINE_ARROW_COLUMN_WIDTH = exports.TIMELINE_EYE_COLUMN_WIDTH = exports.TIMELINE_ROW_BASE_PADDING = void 0;
4
4
  const timeline_indent_1 = require("./timeline-indent");
5
5
  exports.TIMELINE_ROW_BASE_PADDING = 5;
6
6
  exports.TIMELINE_EYE_COLUMN_WIDTH = 22;
7
7
  exports.TIMELINE_ARROW_COLUMN_WIDTH = 16;
8
+ exports.TIMELINE_KEYFRAME_CONTROLS_WIDTH = 38;
8
9
  exports.TIMELINE_FIELD_LABEL_EXTRA_WIDTH = 15;
9
10
  const getTimelineRowIndentWidth = (depth) => {
10
11
  return depth * timeline_indent_1.TIMELINE_INDENT;
@@ -0,0 +1,2 @@
1
+ export declare const parseTranslate: (value: string) => [number, number];
2
+ export declare const serializeTranslate: (x: number, y: number) => string;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serializeTranslate = exports.parseTranslate = void 0;
4
+ const PIXEL_PATTERN = /^(-?\d+(?:\.\d+)?)px(?:\s+(-?\d+(?:\.\d+)?)px)?$/;
5
+ const parseTranslate = (value) => {
6
+ const m = value.match(PIXEL_PATTERN);
7
+ if (!m) {
8
+ return [0, 0];
9
+ }
10
+ return [Number(m[1]), m[2] !== undefined ? Number(m[2]) : 0];
11
+ };
12
+ exports.parseTranslate = parseTranslate;
13
+ const formatTranslateCoordinate = (value) => {
14
+ const rounded = Math.round(value * 1000) / 1000;
15
+ return String(Object.is(rounded, -0) ? 0 : rounded);
16
+ };
17
+ const serializeTranslate = (x, y) => {
18
+ return `${formatTranslateCoordinate(x)}px ${formatTranslateCoordinate(y)}px`;
19
+ };
20
+ exports.serializeTranslate = serializeTranslate;
@@ -10,13 +10,20 @@ const get_node_keyframes_1 = require("./get-node-keyframes");
10
10
  const useExpandedTrackKeyframeRows = ({ sequence, nodePathInfo, keyframeDisplayOffset, }) => {
11
11
  const { getIsExpanded } = (0, react_1.useContext)(ExpandedTracksProvider_1.ExpandedTracksGetterContext);
12
12
  const { codeValues } = (0, react_1.useContext)(remotion_1.Internals.VisualModeCodeValuesContext);
13
+ const { getDragOverrides, getEffectDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeDragOverridesContext);
13
14
  const tree = (0, react_1.useMemo)(() => (0, timeline_layout_1.buildTimelineTree)({
14
15
  sequence,
15
16
  nodePathInfo,
16
- getDragOverrides: () => ({}),
17
- getEffectDragOverrides: () => ({}),
17
+ getDragOverrides,
18
+ getEffectDragOverrides,
18
19
  codeValues,
19
- }), [codeValues, nodePathInfo, sequence]);
20
+ }), [
21
+ codeValues,
22
+ getDragOverrides,
23
+ getEffectDragOverrides,
24
+ nodePathInfo,
25
+ sequence,
26
+ ]);
20
27
  const flat = (0, react_1.useMemo)(() => (0, timeline_layout_1.flattenVisibleTreeNodes)({ nodes: tree, getIsExpanded }), [tree, getIsExpanded]);
21
28
  const expandedHeight = (0, react_1.useMemo)(() => (0, timeline_layout_1.getExpandedTrackHeight)({
22
29
  sequence,
@@ -2,12 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getCompositionMenuItems = void 0;
4
4
  const no_react_1 = require("remotion/no-react");
5
+ const format_file_location_1 = require("../helpers/format-file-location");
5
6
  const open_in_editor_1 = require("../helpers/open-in-editor");
6
7
  const NotificationCenter_1 = require("./Notifications/NotificationCenter");
7
8
  const getCompositionMenuItems = ({ composition, connectionStatus, resolvedLocation, setSelectedModal, closeMenu, readOnlyStudio, }) => {
8
9
  const editorName = window.remotion_editorName;
10
+ const fileLocation = (0, format_file_location_1.formatFileLocation)({
11
+ location: resolvedLocation,
12
+ root: window.remotion_cwd,
13
+ });
9
14
  const showInEditorDisabled = !composition || connectionStatus !== 'connected' || !resolvedLocation;
10
15
  const openComponentInEditorDisabled = showInEditorDisabled || !(resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source);
16
+ const copyFileLocationDisabled = !composition || !fileLocation;
11
17
  return [
12
18
  editorName
13
19
  ? {
@@ -34,6 +40,31 @@ const getCompositionMenuItems = ({ composition, connectionStatus, resolvedLocati
34
40
  disabled: showInEditorDisabled,
35
41
  }
36
42
  : null,
43
+ {
44
+ id: 'copy-file-location',
45
+ keyHint: null,
46
+ label: `Copy file location`,
47
+ leftItem: null,
48
+ onClick: () => {
49
+ closeMenu();
50
+ if (!fileLocation) {
51
+ return;
52
+ }
53
+ navigator.clipboard
54
+ .writeText(fileLocation)
55
+ .then(() => {
56
+ (0, NotificationCenter_1.showNotification)('Copied file location to clipboard', 1000);
57
+ })
58
+ .catch((err) => {
59
+ (0, NotificationCenter_1.showNotification)(`Could not copy to clipboard: ${err.message}`, 1000);
60
+ });
61
+ },
62
+ quickSwitcherLabel: 'Copy composition file location',
63
+ subMenu: null,
64
+ type: 'item',
65
+ value: 'copy-file-location',
66
+ disabled: copyFileLocationDisabled,
67
+ },
37
68
  editorName
38
69
  ? {
39
70
  id: 'open-component-in-editor',
@@ -62,7 +93,7 @@ const getCompositionMenuItems = ({ composition, connectionStatus, resolvedLocati
62
93
  disabled: openComponentInEditorDisabled,
63
94
  }
64
95
  : null,
65
- editorName
96
+ editorName || fileLocation
66
97
  ? {
67
98
  type: 'divider',
68
99
  id: 'show-in-editor-divider',
@@ -43,7 +43,6 @@ const OpenInEditor = ({ stack, canHaveKeyboardShortcuts }) => {
43
43
  const openInBrowser = (0, react_1.useCallback)(() => {
44
44
  dispatch({ type: 'start' });
45
45
  (0, open_in_editor_1.openInEditor)(stack)
46
- .then((res) => res.json())
47
46
  .then((data) => {
48
47
  if (data.success) {
49
48
  dispatchIfMounted({ type: 'succeed' });