@remotion/studio-server 4.0.485 → 4.0.486

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.
@@ -4,15 +4,41 @@ exports.saveSequencePropsHandler = exports.shouldSuppressHmrForSequencePropEdits
4
4
  const node_fs_1 = require("node:fs");
5
5
  const renderer_1 = require("@remotion/renderer");
6
6
  const studio_shared_1 = require("@remotion/studio-shared");
7
+ const update_keyframes_1 = require("../../codemods/update-keyframes/update-keyframes");
7
8
  const update_sequence_props_1 = require("../../codemods/update-sequence-props/update-sequence-props");
8
9
  const file_watcher_1 = require("../../file-watcher");
9
10
  const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
10
11
  const undo_stack_1 = require("../undo-stack");
11
12
  const watch_ignore_next_change_1 = require("../watch-ignore-next-change");
12
13
  const can_update_sequence_props_1 = require("./can-update-sequence-props");
14
+ const log_effect_update_1 = require("./log-updates/log-effect-update");
13
15
  const log_update_1 = require("./log-updates/log-update");
14
16
  const source_file_write_queue_1 = require("./source-file-write-queue");
17
+ const parseSequencePropEditValue = (value) => {
18
+ if (value.type === 'undefined') {
19
+ return undefined;
20
+ }
21
+ return JSON.parse(value.serialized);
22
+ };
23
+ const stringifySequencePropEditValue = (value) => {
24
+ if (value === undefined) {
25
+ return 'undefined';
26
+ }
27
+ return JSON.stringify(value);
28
+ };
29
+ const groupBy = (items, getKey) => {
30
+ var _a;
31
+ const groups = new Map();
32
+ for (const item of items) {
33
+ const key = getKey(item);
34
+ const group = (_a = groups.get(key)) !== null && _a !== void 0 ? _a : [];
35
+ group.push(item);
36
+ groups.set(key, group);
37
+ }
38
+ return [...groups.values()];
39
+ };
15
40
  const convertSequencePropEditToCodemodChange = (edit) => {
41
+ var _a;
16
42
  return {
17
43
  nodePath: edit.nodePath.nodePath,
18
44
  updates: [
@@ -20,6 +46,7 @@ const convertSequencePropEditToCodemodChange = (edit) => {
20
46
  key: edit.key,
21
47
  value: edit.value,
22
48
  defaultValue: edit.defaultValue,
49
+ googleFont: ((_a = edit.sourceEdit) === null || _a === void 0 ? void 0 : _a.type) === 'google-font' ? edit.sourceEdit.font : null,
23
50
  },
24
51
  ],
25
52
  schema: edit.schema,
@@ -27,18 +54,21 @@ const convertSequencePropEditToCodemodChange = (edit) => {
27
54
  };
28
55
  exports.convertSequencePropEditToCodemodChange = convertSequencePropEditToCodemodChange;
29
56
  const shouldSuppressHmrForSequencePropEdits = (edits) => {
30
- return edits.every((edit) => edit.key !== 'showInTimeline');
57
+ return edits.every((edit) => edit.key !== 'showInTimeline' &&
58
+ (edit.sourceEdit === null || edit.sourceEdit === undefined));
31
59
  };
32
60
  exports.shouldSuppressHmrForSequencePropEdits = shouldSuppressHmrForSequencePropEdits;
33
- const saveSequencePropsHandler = ({ input: { edits, clientId, undoLabel, redoLabel }, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
34
- var _a;
35
- if (edits.length === 0) {
61
+ const saveSequencePropsHandler = ({ input: { edits, movedKeyframes = { sequenceKeyframes: [], effectKeyframes: [] }, clientId, undoLabel, redoLabel, }, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
62
+ var _a, _b, _c;
63
+ const totalKeyframeMoves = movedKeyframes.sequenceKeyframes.length +
64
+ movedKeyframes.effectKeyframes.length;
65
+ if (edits.length === 0 && totalKeyframeMoves === 0) {
36
66
  throw new Error('No sequence prop edits to save');
37
67
  }
38
- renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[save-sequence-props] Received request with ${edits.length} edit(s)`);
68
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[save-sequence-props] Received request with ${edits.length} edit(s) and ${totalKeyframeMoves} moved keyframe(s)`);
39
69
  const editGroups = new Map();
40
70
  for (const [index, edit] of edits.entries()) {
41
- const parsedValue = JSON.parse(edit.value);
71
+ const parsedValue = parseSequencePropEditValue(edit.value);
42
72
  const parsedDefaultValue = edit.defaultValue !== null ? JSON.parse(edit.defaultValue) : null;
43
73
  const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
44
74
  remotionRoot,
@@ -48,6 +78,8 @@ const saveSequencePropsHandler = ({ input: { edits, clientId, undoLabel, redoLab
48
78
  const group = (_a = editGroups.get(absolutePath)) !== null && _a !== void 0 ? _a : {
49
79
  fileRelativeToRoot,
50
80
  edits: [],
81
+ sequenceKeyframes: [],
82
+ effectKeyframes: [],
51
83
  };
52
84
  group.edits.push({
53
85
  index,
@@ -55,42 +87,165 @@ const saveSequencePropsHandler = ({ input: { edits, clientId, undoLabel, redoLab
55
87
  nodePath: edit.nodePath,
56
88
  key: edit.key,
57
89
  value: parsedValue,
58
- valueString: JSON.stringify(parsedValue),
90
+ valueString: stringifySequencePropEditValue(parsedValue),
59
91
  defaultValue: parsedDefaultValue,
60
92
  defaultValueString: parsedDefaultValue !== null
61
93
  ? JSON.stringify(parsedDefaultValue)
62
94
  : null,
63
95
  schema: edit.schema,
96
+ sourceEdit: edit.sourceEdit,
64
97
  });
65
98
  editGroups.set(absolutePath, group);
66
99
  }
100
+ for (const [index, keyframe,] of movedKeyframes.sequenceKeyframes.entries()) {
101
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
102
+ remotionRoot,
103
+ fileName: keyframe.fileName,
104
+ action: 'modify',
105
+ });
106
+ const group = (_b = editGroups.get(absolutePath)) !== null && _b !== void 0 ? _b : {
107
+ fileRelativeToRoot,
108
+ edits: [],
109
+ sequenceKeyframes: [],
110
+ effectKeyframes: [],
111
+ };
112
+ group.sequenceKeyframes.push({ ...keyframe, index });
113
+ editGroups.set(absolutePath, group);
114
+ }
115
+ for (const [index, keyframe] of movedKeyframes.effectKeyframes.entries()) {
116
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
117
+ remotionRoot,
118
+ fileName: keyframe.fileName,
119
+ action: 'modify',
120
+ });
121
+ const group = (_c = editGroups.get(absolutePath)) !== null && _c !== void 0 ? _c : {
122
+ fileRelativeToRoot,
123
+ edits: [],
124
+ sequenceKeyframes: [],
125
+ effectKeyframes: [],
126
+ };
127
+ group.effectKeyframes.push({ ...keyframe, index });
128
+ editGroups.set(absolutePath, group);
129
+ }
67
130
  const snapshots = [];
68
131
  const outputByPath = new Map();
69
132
  const resultByIndex = new Map();
133
+ const sequenceKeyframeLogs = [];
134
+ const effectKeyframeLogs = [];
70
135
  for (const [absolutePath, group] of editGroups) {
71
136
  const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
72
- const { output, formatted, results: updateResults, } = await (0, update_sequence_props_1.updateMultipleSequenceProps)({
73
- input: fileContents,
74
- changes: group.edits.map(exports.convertSequencePropEditToCodemodChange),
75
- prettierConfigOverride: null,
76
- });
77
- const [{ logLine: firstLogLine }] = updateResults;
137
+ let output = fileContents;
138
+ let firstLogLine = Number.POSITIVE_INFINITY;
139
+ if (group.edits.length > 0) {
140
+ const { output: sequencePropsOutput, formatted, results: updateResults, } = await (0, update_sequence_props_1.updateMultipleSequenceProps)({
141
+ input: output,
142
+ changes: group.edits.map(exports.convertSequencePropEditToCodemodChange),
143
+ prettierConfigOverride: null,
144
+ });
145
+ output = sequencePropsOutput;
146
+ const firstUpdate = updateResults[0];
147
+ if (firstUpdate) {
148
+ firstLogLine = Math.min(firstLogLine, firstUpdate.logLine);
149
+ }
150
+ for (const [resultIndex, result] of updateResults.entries()) {
151
+ const edit = group.edits[resultIndex];
152
+ resultByIndex.set(edit.index, {
153
+ oldValueString: result.oldValueStrings[0],
154
+ logLine: result.logLine,
155
+ removedProps: result.removedProps,
156
+ formatted,
157
+ });
158
+ }
159
+ }
160
+ for (const keyframeGroup of groupBy(group.sequenceKeyframes, (keyframe) => JSON.stringify(keyframe.nodePath.nodePath))) {
161
+ const [firstSequenceKeyframe] = keyframeGroup;
162
+ if (!firstSequenceKeyframe) {
163
+ continue;
164
+ }
165
+ const updates = groupBy(keyframeGroup, (keyframe) => keyframe.key).map((keyframes) => {
166
+ const [firstMove] = keyframes;
167
+ if (!firstMove) {
168
+ throw new Error('Expected keyframe');
169
+ }
170
+ return {
171
+ key: firstMove.key,
172
+ operation: {
173
+ type: 'move',
174
+ moves: keyframes.map((keyframe) => ({
175
+ fromFrame: keyframe.fromFrame,
176
+ toFrame: keyframe.toFrame,
177
+ })),
178
+ },
179
+ };
180
+ });
181
+ const result = await (0, update_keyframes_1.updateSequenceKeyframes)({
182
+ input: output,
183
+ nodePath: firstSequenceKeyframe.nodePath.nodePath,
184
+ schema: firstSequenceKeyframe.schema,
185
+ updates,
186
+ });
187
+ output = result.output;
188
+ firstLogLine = Math.min(firstLogLine, result.logLine);
189
+ for (const [updateIndex, update] of updates.entries()) {
190
+ sequenceKeyframeLogs.push({
191
+ fileRelativeToRoot: group.fileRelativeToRoot,
192
+ line: result.logLine,
193
+ key: update.key,
194
+ oldValueString: result.oldValueStrings[updateIndex],
195
+ newValueString: result.newValueStrings[updateIndex],
196
+ formatted: result.formatted,
197
+ });
198
+ }
199
+ }
200
+ for (const keyframeGroup of groupBy(group.effectKeyframes, (keyframe) => `${JSON.stringify(keyframe.sequenceNodePath.nodePath)}:${keyframe.effectIndex}`)) {
201
+ const [firstEffectKeyframe] = keyframeGroup;
202
+ if (!firstEffectKeyframe) {
203
+ continue;
204
+ }
205
+ const updates = groupBy(keyframeGroup, (keyframe) => keyframe.key).map((keyframes) => {
206
+ const [firstMove] = keyframes;
207
+ if (!firstMove) {
208
+ throw new Error('Expected keyframe');
209
+ }
210
+ return {
211
+ key: firstMove.key,
212
+ operation: {
213
+ type: 'move',
214
+ moves: keyframes.map((keyframe) => ({
215
+ fromFrame: keyframe.fromFrame,
216
+ toFrame: keyframe.toFrame,
217
+ })),
218
+ },
219
+ };
220
+ });
221
+ const result = await (0, update_keyframes_1.updateEffectKeyframes)({
222
+ input: output,
223
+ sequenceNodePath: firstEffectKeyframe.sequenceNodePath.nodePath,
224
+ effectIndex: firstEffectKeyframe.effectIndex,
225
+ schema: firstEffectKeyframe.schema,
226
+ updates,
227
+ });
228
+ output = result.output;
229
+ firstLogLine = Math.min(firstLogLine, result.logLine);
230
+ for (const [updateIndex, update] of updates.entries()) {
231
+ effectKeyframeLogs.push({
232
+ fileRelativeToRoot: group.fileRelativeToRoot,
233
+ line: result.logLine,
234
+ effectName: result.effectCallee,
235
+ propKey: update.key,
236
+ oldValueString: result.oldValueStrings[updateIndex],
237
+ newValueString: result.newValueStrings[updateIndex],
238
+ formatted: result.formatted,
239
+ });
240
+ }
241
+ }
78
242
  outputByPath.set(absolutePath, output);
79
243
  snapshots.push({
80
244
  filePath: absolutePath,
81
245
  oldContents: fileContents,
82
246
  newContents: output,
83
- logLine: firstLogLine,
247
+ logLine: Number.isFinite(firstLogLine) ? firstLogLine : 1,
84
248
  });
85
- for (const [resultIndex, result] of updateResults.entries()) {
86
- const edit = group.edits[resultIndex];
87
- resultByIndex.set(edit.index, {
88
- oldValueString: result.oldValueStrings[0],
89
- logLine: result.logLine,
90
- removedProps: result.removedProps,
91
- formatted,
92
- });
93
- }
94
249
  }
95
250
  const undoMessage = `↩️ ${undoLabel}`;
96
251
  const redoMessage = `↪️ ${redoLabel}`;
@@ -130,6 +285,35 @@ const saveSequencePropsHandler = ({ input: { edits, clientId, undoLabel, redoLab
130
285
  });
131
286
  }
132
287
  }
288
+ for (const log of sequenceKeyframeLogs) {
289
+ (0, log_update_1.logUpdate)({
290
+ fileRelativeToRoot: log.fileRelativeToRoot,
291
+ line: log.line,
292
+ key: log.key,
293
+ oldValueString: log.oldValueString,
294
+ newValueString: log.newValueString,
295
+ defaultValueString: null,
296
+ formatted: log.formatted,
297
+ logLevel,
298
+ removedProps: [],
299
+ addedProps: [],
300
+ });
301
+ }
302
+ for (const log of effectKeyframeLogs) {
303
+ (0, log_effect_update_1.logEffectUpdate)({
304
+ fileRelativeToRoot: log.fileRelativeToRoot,
305
+ line: log.line,
306
+ effectName: log.effectName,
307
+ propKey: log.propKey,
308
+ oldValueString: log.oldValueString,
309
+ newValueString: log.newValueString,
310
+ defaultValueString: null,
311
+ formatted: log.formatted,
312
+ logLevel,
313
+ removedProps: [],
314
+ addedProps: [],
315
+ });
316
+ }
133
317
  (0, undo_stack_1.printUndoHint)(logLevel);
134
318
  const results = edits.map((edit) => {
135
319
  const { absolutePath } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
@@ -3,7 +3,7 @@ export interface UndoEntryDescription {
3
3
  undoMessage: string;
4
4
  redoMessage: string;
5
5
  }
6
- type UndoEntryType = 'visual-control' | 'default-props' | 'sequence-props' | 'effect-props' | 'keyframe-add' | 'keyframe-delete' | 'add-effect' | 'delete-effect' | 'duplicate-effect' | 'paste-effects' | 'reorder-effect' | 'reorder-sequence' | 'delete-jsx-node' | 'duplicate-jsx-node' | 'split-jsx-sequence' | 'insert-jsx-element' | 'delete-composition' | 'rename-composition' | 'new-composition' | 'duplicate-composition' | 'delete-folder' | 'rename-folder';
6
+ type UndoEntryType = 'visual-control' | 'default-props' | 'sequence-props' | 'effect-props' | 'keyframe-add' | 'keyframe-delete' | 'add-effect' | 'delete-effect' | 'duplicate-effect' | 'paste-effects' | 'reorder-effect' | 'reorder-sequence' | 'delete-jsx-node' | 'duplicate-jsx-node' | 'split-jsx-sequence' | 'insert-jsx-element' | 'delete-composition' | 'rename-composition' | 'new-composition' | 'duplicate-composition' | 'move-composition-to-folder' | 'new-folder' | 'delete-folder' | 'rename-folder';
7
7
  type UndoEntrySnapshot = {
8
8
  filePath: string;
9
9
  oldContents: string | null;
@@ -59,6 +59,10 @@ type UndoEntry = {
59
59
  entryType: 'new-composition';
60
60
  } | {
61
61
  entryType: 'duplicate-composition';
62
+ } | {
63
+ entryType: 'move-composition-to-folder';
64
+ } | {
65
+ entryType: 'new-folder';
62
66
  } | {
63
67
  entryType: 'delete-folder';
64
68
  } | {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/studio-server"
4
4
  },
5
5
  "name": "@remotion/studio-server",
6
- "version": "4.0.485",
6
+ "version": "4.0.486",
7
7
  "description": "Run a Remotion Studio with a server backend",
8
8
  "main": "dist",
9
9
  "scripts": {
@@ -27,11 +27,11 @@
27
27
  "@babel/parser": "7.24.1",
28
28
  "semver": "7.5.3",
29
29
  "prettier": "3.8.1",
30
- "remotion": "4.0.485",
30
+ "remotion": "4.0.486",
31
31
  "recast": "0.23.11",
32
- "@remotion/bundler": "4.0.485",
33
- "@remotion/renderer": "4.0.485",
34
- "@remotion/studio-shared": "4.0.485",
32
+ "@remotion/bundler": "4.0.486",
33
+ "@remotion/renderer": "4.0.486",
34
+ "@remotion/studio-shared": "4.0.486",
35
35
  "memfs": "3.4.3",
36
36
  "open": "8.4.2"
37
37
  },
@@ -39,7 +39,7 @@
39
39
  "ast-types": "0.16.1",
40
40
  "react": "19.2.3",
41
41
  "@types/semver": "7.5.3",
42
- "@remotion/eslint-config-internal": "4.0.485",
42
+ "@remotion/eslint-config-internal": "4.0.486",
43
43
  "eslint": "9.19.0",
44
44
  "@types/node": "20.12.14",
45
45
  "@typescript/native-preview": "7.0.0-dev.20260217.1"