@remotion/studio-server 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.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.allApiRoutes = void 0;
4
4
  const add_effect_1 = require("./routes/add-effect");
5
5
  const add_effect_keyframe_1 = require("./routes/add-effect-keyframe");
6
+ const add_keyframes_1 = require("./routes/add-keyframes");
6
7
  const add_render_1 = require("./routes/add-render");
7
8
  const add_sequence_keyframe_1 = require("./routes/add-sequence-keyframe");
8
9
  const apply_codemod_1 = require("./routes/apply-codemod");
@@ -71,6 +72,7 @@ exports.allApiRoutes = {
71
72
  '/api/move-keyframes': move_keyframes_1.moveKeyframesHandler,
72
73
  '/api/add-sequence-keyframe': add_sequence_keyframe_1.addSequenceKeyframeHandler,
73
74
  '/api/add-effect-keyframe': add_effect_keyframe_1.addEffectKeyframeHandler,
75
+ '/api/add-keyframes': add_keyframes_1.addKeyframesHandler,
74
76
  '/api/update-sequence-keyframe-settings': update_sequence_keyframe_settings_1.updateSequenceKeyframeSettingsHandler,
75
77
  '/api/update-effect-keyframe-settings': update_effect_keyframe_settings_1.updateEffectKeyframeSettingsHandler,
76
78
  '/api/delete-effect': delete_effect_1.deleteEffectHandler,
@@ -0,0 +1,7 @@
1
+ import type { AddKeyframesRequest, AddKeyframesResponse } from '@remotion/studio-shared';
2
+ import type { ApiHandler } from '../api-types';
3
+ export declare const addKeyframes: ({ sequenceKeyframes, effectKeyframes, clientId, remotionRoot, logLevel, }: AddKeyframesRequest & {
4
+ readonly remotionRoot: string;
5
+ readonly logLevel: "error" | "info" | "trace" | "verbose" | "warn";
6
+ }) => Promise<void>;
7
+ export declare const addKeyframesHandler: ApiHandler<AddKeyframesRequest, AddKeyframesResponse>;
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addKeyframesHandler = exports.addKeyframes = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const renderer_1 = require("@remotion/renderer");
6
+ const update_keyframes_1 = require("../../codemods/update-keyframes/update-keyframes");
7
+ const file_watcher_1 = require("../../file-watcher");
8
+ const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
9
+ const undo_stack_1 = require("../undo-stack");
10
+ const watch_ignore_next_change_1 = require("../watch-ignore-next-change");
11
+ const log_effect_update_1 = require("./log-updates/log-effect-update");
12
+ const log_update_1 = require("./log-updates/log-update");
13
+ const save_props_mutex_1 = require("./save-props-mutex");
14
+ const groupBy = (items, getKey) => {
15
+ var _a;
16
+ const groups = new Map();
17
+ for (const item of items) {
18
+ const key = getKey(item);
19
+ const group = (_a = groups.get(key)) !== null && _a !== void 0 ? _a : [];
20
+ group.push(item);
21
+ groups.set(key, group);
22
+ }
23
+ return [...groups.values()];
24
+ };
25
+ const getBatchDescription = ({ totalKeyframes, firstKeyframe, }) => {
26
+ if (totalKeyframes === 1) {
27
+ return {
28
+ undoMessage: `↩️ ${firstKeyframe.key} keyframe removed at frame ${firstKeyframe.frame}`,
29
+ redoMessage: `↪️ ${firstKeyframe.key} keyframe added at frame ${firstKeyframe.frame}`,
30
+ };
31
+ }
32
+ return {
33
+ undoMessage: `↩️ ${totalKeyframes} keyframes removed`,
34
+ redoMessage: `↪️ ${totalKeyframes} keyframes added`,
35
+ };
36
+ };
37
+ const addKeyframes = async ({ sequenceKeyframes, effectKeyframes, clientId, remotionRoot, logLevel, }) => {
38
+ var _a, _b;
39
+ const totalKeyframes = sequenceKeyframes.length + effectKeyframes.length;
40
+ if (totalKeyframes === 0) {
41
+ throw new Error('No keyframes were specified for adding');
42
+ }
43
+ const fileGroups = new Map();
44
+ for (const [index, keyframe] of sequenceKeyframes.entries()) {
45
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
46
+ remotionRoot,
47
+ fileName: keyframe.fileName,
48
+ action: 'modify',
49
+ });
50
+ const group = (_a = fileGroups.get(absolutePath)) !== null && _a !== void 0 ? _a : {
51
+ fileRelativeToRoot,
52
+ sequenceKeyframes: [],
53
+ effectKeyframes: [],
54
+ };
55
+ group.sequenceKeyframes.push({
56
+ ...keyframe,
57
+ index,
58
+ absolutePath,
59
+ fileRelativeToRoot,
60
+ });
61
+ fileGroups.set(absolutePath, group);
62
+ }
63
+ for (const [index, keyframe] of effectKeyframes.entries()) {
64
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
65
+ remotionRoot,
66
+ fileName: keyframe.fileName,
67
+ action: 'modify',
68
+ });
69
+ const group = (_b = fileGroups.get(absolutePath)) !== null && _b !== void 0 ? _b : {
70
+ fileRelativeToRoot,
71
+ sequenceKeyframes: [],
72
+ effectKeyframes: [],
73
+ };
74
+ group.effectKeyframes.push({
75
+ ...keyframe,
76
+ index,
77
+ absolutePath,
78
+ fileRelativeToRoot,
79
+ });
80
+ fileGroups.set(absolutePath, group);
81
+ }
82
+ const snapshots = [];
83
+ const sequenceLogs = [];
84
+ const effectLogs = [];
85
+ for (const [absolutePath, group] of fileGroups) {
86
+ const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
87
+ let output = fileContents;
88
+ let firstLogLine = Number.POSITIVE_INFINITY;
89
+ for (const keyframeGroup of groupBy(group.sequenceKeyframes, (keyframe) => JSON.stringify(keyframe.nodePath.nodePath))) {
90
+ const [firstSequenceKeyframe] = keyframeGroup;
91
+ if (!firstSequenceKeyframe) {
92
+ continue;
93
+ }
94
+ const result = await (0, update_keyframes_1.updateSequenceKeyframes)({
95
+ input: output,
96
+ nodePath: firstSequenceKeyframe.nodePath.nodePath,
97
+ schema: firstSequenceKeyframe.schema,
98
+ updates: keyframeGroup.map((keyframe) => ({
99
+ key: keyframe.key,
100
+ operation: {
101
+ type: 'add',
102
+ frame: keyframe.frame,
103
+ value: JSON.parse(keyframe.value),
104
+ },
105
+ })),
106
+ });
107
+ output = result.output;
108
+ firstLogLine = Math.min(firstLogLine, result.logLine);
109
+ for (const [keyframeIndex, keyframe] of keyframeGroup.entries()) {
110
+ sequenceLogs.push({
111
+ fileRelativeToRoot: keyframe.fileRelativeToRoot,
112
+ line: result.logLine,
113
+ key: keyframe.key,
114
+ oldValueString: result.oldValueStrings[keyframeIndex],
115
+ newValueString: result.newValueStrings[keyframeIndex],
116
+ formatted: result.formatted,
117
+ });
118
+ }
119
+ }
120
+ for (const keyframeGroup of groupBy(group.effectKeyframes, (keyframe) => `${JSON.stringify(keyframe.sequenceNodePath.nodePath)}:${keyframe.effectIndex}`)) {
121
+ const [firstEffectKeyframe] = keyframeGroup;
122
+ if (!firstEffectKeyframe) {
123
+ continue;
124
+ }
125
+ const result = await (0, update_keyframes_1.updateEffectKeyframes)({
126
+ input: output,
127
+ sequenceNodePath: firstEffectKeyframe.sequenceNodePath.nodePath,
128
+ effectIndex: firstEffectKeyframe.effectIndex,
129
+ schema: firstEffectKeyframe.schema,
130
+ updates: keyframeGroup.map((keyframe) => ({
131
+ key: keyframe.key,
132
+ operation: {
133
+ type: 'add',
134
+ frame: keyframe.frame,
135
+ value: JSON.parse(keyframe.value),
136
+ },
137
+ })),
138
+ });
139
+ output = result.output;
140
+ firstLogLine = Math.min(firstLogLine, result.logLine);
141
+ for (const [keyframeIndex, keyframe] of keyframeGroup.entries()) {
142
+ effectLogs.push({
143
+ fileRelativeToRoot: keyframe.fileRelativeToRoot,
144
+ line: result.logLine,
145
+ effectName: result.effectCallee,
146
+ propKey: keyframe.key,
147
+ oldValueString: result.oldValueStrings[keyframeIndex],
148
+ newValueString: result.newValueStrings[keyframeIndex],
149
+ formatted: result.formatted,
150
+ });
151
+ }
152
+ }
153
+ snapshots.push({
154
+ filePath: absolutePath,
155
+ oldContents: fileContents,
156
+ newContents: output,
157
+ logLine: Number.isFinite(firstLogLine) ? firstLogLine : 1,
158
+ });
159
+ }
160
+ const [firstKeyframe] = sequenceKeyframes.length > 0 ? sequenceKeyframes : effectKeyframes;
161
+ if (!firstKeyframe) {
162
+ throw new Error('No keyframes were specified for adding');
163
+ }
164
+ (0, undo_stack_1.pushTransactionToUndoStack)({
165
+ snapshots,
166
+ logLevel,
167
+ remotionRoot,
168
+ description: getBatchDescription({ totalKeyframes, firstKeyframe }),
169
+ entryType: sequenceKeyframes.length > 0 && effectKeyframes.length > 0
170
+ ? // Dead code for now: sequence props and effect props cannot be selected
171
+ // together yet. Keep the mixed transaction type for the planned UI.
172
+ 'keyframe-add'
173
+ : sequenceKeyframes.length > 0
174
+ ? 'sequence-props'
175
+ : 'effect-props',
176
+ suppressHmrOnFileRestore: true,
177
+ });
178
+ for (const snapshot of snapshots) {
179
+ (0, undo_stack_1.suppressUndoStackInvalidation)(snapshot.filePath);
180
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(snapshot.filePath);
181
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(snapshot.filePath, snapshot.newContents, clientId);
182
+ }
183
+ for (const log of sequenceLogs) {
184
+ (0, log_update_1.logUpdate)({
185
+ fileRelativeToRoot: log.fileRelativeToRoot,
186
+ line: log.line,
187
+ key: log.key,
188
+ oldValueString: log.oldValueString,
189
+ newValueString: log.newValueString,
190
+ defaultValueString: null,
191
+ formatted: log.formatted,
192
+ logLevel,
193
+ removedProps: [],
194
+ addedProps: [],
195
+ });
196
+ }
197
+ for (const log of effectLogs) {
198
+ (0, log_effect_update_1.logEffectUpdate)({
199
+ fileRelativeToRoot: log.fileRelativeToRoot,
200
+ line: log.line,
201
+ effectName: log.effectName,
202
+ propKey: log.propKey,
203
+ oldValueString: log.oldValueString,
204
+ newValueString: log.newValueString,
205
+ defaultValueString: null,
206
+ formatted: log.formatted,
207
+ logLevel,
208
+ removedProps: [],
209
+ addedProps: [],
210
+ });
211
+ }
212
+ (0, undo_stack_1.printUndoHint)(logLevel);
213
+ };
214
+ exports.addKeyframes = addKeyframes;
215
+ const addKeyframesHandler = ({ input: { sequenceKeyframes, effectKeyframes, clientId }, remotionRoot, logLevel, }) => (0, save_props_mutex_1.withSavePropsLock)(async () => {
216
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[add-keyframes] Received request to add ${sequenceKeyframes.length + effectKeyframes.length} keyframe(s)`);
217
+ await (0, exports.addKeyframes)({
218
+ sequenceKeyframes,
219
+ effectKeyframes,
220
+ clientId,
221
+ remotionRoot,
222
+ logLevel,
223
+ });
224
+ return { success: true };
225
+ });
226
+ exports.addKeyframesHandler = addKeyframesHandler;
@@ -106,8 +106,12 @@ const extractDefaultPropsFromSource = (input, compositionId) => {
106
106
  this.traverse(path);
107
107
  return;
108
108
  }
109
- if ((0, can_update_sequence_props_1.isStaticValue)(expression)) {
110
- const value = (0, can_update_sequence_props_1.extractStaticValue)(expression);
109
+ if ((0, can_update_sequence_props_1.isStaticValue)(expression, {
110
+ allowSpecialValues: true,
111
+ })) {
112
+ const value = (0, can_update_sequence_props_1.extractStaticValue)(expression, {
113
+ allowSpecialValues: true,
114
+ });
111
115
  if (value !== null &&
112
116
  typeof value === 'object' &&
113
117
  !Array.isArray(value)) {
@@ -1,8 +1,11 @@
1
1
  import type { Expression, File, JSXOpeningElement } from '@babel/types';
2
2
  import type { SubscribeToSequencePropsResponse } from '@remotion/studio-shared';
3
3
  import type { CanUpdateSequencePropsResponseTrue, CanUpdateSequencePropStatus, SequenceNodePath } from 'remotion';
4
- export declare const isStaticValue: (node: Expression) => boolean;
5
- export declare const extractStaticValue: (node: Expression) => unknown;
4
+ type StaticValueOptions = {
5
+ allowSpecialValues: boolean;
6
+ };
7
+ export declare const isStaticValue: (node: Expression, options?: StaticValueOptions) => boolean;
8
+ export declare const extractStaticValue: (node: Expression, options?: StaticValueOptions) => unknown;
6
9
  export declare const getComputedStatus: (node: Expression, ast: File) => CanUpdateSequencePropStatus;
7
10
  export declare const findJsxElementAtNodePath: (ast: File, nodePath: SequenceNodePath) => JSXOpeningElement | null;
8
11
  export declare const findNodePathForJsxElement: (ast: File, target: JSXOpeningElement) => SequenceNodePath | null;
@@ -28,3 +31,4 @@ export declare const computeSequencePropsStatusFromFilenameByLine: ({ fileName,
28
31
  remotionRoot: string;
29
32
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
30
33
  }) => SubscribeToSequencePropsResponse;
34
+ export {};
@@ -38,6 +38,7 @@ const node_fs_1 = require("node:fs");
38
38
  const renderer_1 = require("@remotion/renderer");
39
39
  const studio_shared_1 = require("@remotion/studio-shared");
40
40
  const recast = __importStar(require("recast"));
41
+ const no_react_1 = require("remotion/no-react");
41
42
  const parse_ast_1 = require("../../codemods/parse-ast");
42
43
  const get_ast_node_path_1 = require("../../helpers/get-ast-node-path");
43
44
  const import_agnostic_node_path_1 = require("../../helpers/import-agnostic-node-path");
@@ -51,7 +52,42 @@ const staticStatus = (codeValue) => ({
51
52
  const computedStatus = () => ({
52
53
  status: 'computed',
53
54
  });
54
- const isStaticValue = (node) => {
55
+ // Mirrors the encoding that staticFile() from "remotion" applies at runtime
56
+ const encodeStaticFilePath = (path) => {
57
+ return path.split('/').map(encodeURIComponent).join('/');
58
+ };
59
+ // Detects calls that the Studio knows how to serialize back to source:
60
+ // staticFile("...") and new Date("...")
61
+ const getSpecialValueCall = (node) => {
62
+ if (node.type === 'CallExpression') {
63
+ const call = node;
64
+ if (call.callee.type === 'Identifier' &&
65
+ call.callee.name === 'staticFile' &&
66
+ call.arguments.length === 1 &&
67
+ call.arguments[0].type === 'StringLiteral') {
68
+ return { type: 'static-file', value: call.arguments[0].value };
69
+ }
70
+ return null;
71
+ }
72
+ if (node.type === 'NewExpression') {
73
+ const newExpr = node;
74
+ if (newExpr.callee.type === 'Identifier' &&
75
+ newExpr.callee.name === 'Date' &&
76
+ newExpr.arguments.length === 1 &&
77
+ newExpr.arguments[0].type === 'StringLiteral') {
78
+ return { type: 'date', value: newExpr.arguments[0].value };
79
+ }
80
+ return null;
81
+ }
82
+ return null;
83
+ };
84
+ const defaultStaticValueOptions = {
85
+ allowSpecialValues: false,
86
+ };
87
+ const isStaticValue = (node, options = defaultStaticValueOptions) => {
88
+ if (options.allowSpecialValues && getSpecialValueCall(node) !== null) {
89
+ return true;
90
+ }
55
91
  switch (node.type) {
56
92
  case 'NumericLiteral':
57
93
  case 'StringLiteral':
@@ -63,18 +99,29 @@ const isStaticValue = (node) => {
63
99
  node.operator === '+') &&
64
100
  node.argument.type === 'NumericLiteral');
65
101
  case 'TSAsExpression':
66
- return (0, exports.isStaticValue)(node.expression);
102
+ return (0, exports.isStaticValue)(node.expression, options);
67
103
  case 'ArrayExpression':
68
- return node.elements.every((el) => el !== null && el.type !== 'SpreadElement' && (0, exports.isStaticValue)(el));
104
+ return node.elements.every((el) => el !== null &&
105
+ el.type !== 'SpreadElement' &&
106
+ (0, exports.isStaticValue)(el, options));
69
107
  case 'ObjectExpression':
70
108
  return node.properties.every((prop) => prop.type === 'ObjectProperty' &&
71
- (0, exports.isStaticValue)(prop.value));
109
+ (0, exports.isStaticValue)(prop.value, options));
72
110
  default:
73
111
  return false;
74
112
  }
75
113
  };
76
114
  exports.isStaticValue = isStaticValue;
77
- const extractStaticValue = (node) => {
115
+ const extractStaticValue = (node, options = defaultStaticValueOptions) => {
116
+ if (options.allowSpecialValues) {
117
+ const specialValue = getSpecialValueCall(node);
118
+ if (specialValue !== null) {
119
+ if (specialValue.type === 'static-file') {
120
+ return `${no_react_1.NoReactInternals.FILE_TOKEN}${encodeStaticFilePath(specialValue.value)}`;
121
+ }
122
+ return `${no_react_1.NoReactInternals.DATE_TOKEN}${specialValue.value}`;
123
+ }
124
+ }
78
125
  switch (node.type) {
79
126
  case 'NumericLiteral':
80
127
  case 'StringLiteral':
@@ -91,13 +138,13 @@ const extractStaticValue = (node) => {
91
138
  return undefined;
92
139
  }
93
140
  case 'TSAsExpression':
94
- return (0, exports.extractStaticValue)(node.expression);
141
+ return (0, exports.extractStaticValue)(node.expression, options);
95
142
  case 'ArrayExpression':
96
143
  return node.elements.map((el) => {
97
144
  if (el === null || el.type === 'SpreadElement') {
98
145
  return undefined;
99
146
  }
100
- return (0, exports.extractStaticValue)(el);
147
+ return (0, exports.extractStaticValue)(el, options);
101
148
  });
102
149
  case 'ObjectExpression': {
103
150
  const obj = node;
@@ -112,7 +159,7 @@ const extractStaticValue = (node) => {
112
159
  ? String(p.key.value)
113
160
  : undefined;
114
161
  if (key !== undefined) {
115
- result[key] = (0, exports.extractStaticValue)(p.value);
162
+ result[key] = (0, exports.extractStaticValue)(p.value, options);
116
163
  }
117
164
  }
118
165
  }
@@ -260,9 +307,6 @@ const getInterpolationMetadata = (interpolationFunction, callExpression, keyfram
260
307
  }
261
308
  const value = property.value;
262
309
  if (key === 'easing') {
263
- if (interpolationFunction === 'interpolateColors') {
264
- return null;
265
- }
266
310
  const parsedEasing = getKeyframeEasingArray({
267
311
  easingNode: value,
268
312
  segments,
@@ -10,7 +10,7 @@ const formatInnerPropChange = ({ key, oldValueString, newValueString, defaultVal
10
10
  if (defaultValueString !== null && oldValueString === defaultValueString) {
11
11
  return (0, formatting_1.formatAddition)({ valueString: newValueString, key });
12
12
  }
13
- return `${(0, formatting_1.formatPropDelta)({ valueString: oldValueString, key })} \u2192 ${(0, formatting_1.formatPropDelta)({ valueString: newValueString, key })}`;
13
+ return (0, formatting_1.formatPropChangeDelta)({ key, oldValueString, newValueString });
14
14
  };
15
15
  const formatPropChange = ({ key, oldValueString, newValueString, defaultValueString, removedProps, addedProps, }) => {
16
16
  const suffix = (0, format_side_props_1.formatSideProps)({ removedProps, addedProps });
@@ -7,6 +7,7 @@ export declare const equals: (str: string) => string;
7
7
  export declare const punctuation: (str: string) => string;
8
8
  export declare const stringValue: (str: string) => string;
9
9
  export declare const numberValue: (str: string) => string;
10
+ export declare const inlineAddition: (str: string) => string;
10
11
  export declare const strikeThrough: (str: string) => string;
11
12
  export declare const strikeThroughOrRemovedPrefix: (str: string) => string;
12
13
  export declare const addedPrefixIfNoColor: (str: string) => string;
@@ -15,5 +16,10 @@ export type PropDelta = {
15
16
  valueString: string;
16
17
  };
17
18
  export declare const formatPropDelta: ({ key, valueString }: PropDelta) => string;
19
+ export declare const formatPropChangeDelta: ({ key, oldValueString, newValueString, }: {
20
+ key: string;
21
+ oldValueString: string;
22
+ newValueString: string;
23
+ }) => string;
18
24
  export declare const formatDeletion: (prop: PropDelta) => string;
19
25
  export declare const formatAddition: (prop: PropDelta) => string;