@remotion/studio-server 4.0.499 → 4.0.501

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.
@@ -0,0 +1,11 @@
1
+ import type { CaptionPatch } from '@remotion/studio-shared';
2
+ import type { SequenceNodePath } from 'remotion';
3
+ export declare const updateInlineCaptionPatches: ({ input, nodePath, patches, }: {
4
+ input: string;
5
+ nodePath: SequenceNodePath;
6
+ patches: CaptionPatch[];
7
+ }) => {
8
+ output: string;
9
+ logLine: number;
10
+ changedFields: string[][];
11
+ };
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.updateInlineCaptionPatches = void 0;
4
+ const can_update_sequence_props_1 = require("../preview-server/routes/can-update-sequence-props");
5
+ const parse_ast_1 = require("./parse-ast");
6
+ const captionKeys = [
7
+ 'text',
8
+ 'startMs',
9
+ 'endMs',
10
+ 'timestampMs',
11
+ 'confidence',
12
+ ];
13
+ const getPropertyKey = (property) => {
14
+ if (property.key.type === 'Identifier') {
15
+ return property.key.name;
16
+ }
17
+ if (property.key.type === 'StringLiteral') {
18
+ return property.key.value;
19
+ }
20
+ return null;
21
+ };
22
+ const getStaticValue = (value) => {
23
+ if (value.type === 'StringLiteral' || value.type === 'NumericLiteral') {
24
+ return value.value;
25
+ }
26
+ if (value.type === 'NullLiteral') {
27
+ return null;
28
+ }
29
+ throw new Error('Captions must use static literal values to be edited');
30
+ };
31
+ const getStaticCaption = (expression) => {
32
+ const values = new Map();
33
+ for (const property of expression.properties) {
34
+ if (property.type !== 'ObjectProperty' || property.computed) {
35
+ throw new Error('Captions must use static object properties to be edited');
36
+ }
37
+ const key = getPropertyKey(property);
38
+ if (key !== null && captionKeys.includes(key)) {
39
+ values.set(key, getStaticValue(property.value));
40
+ }
41
+ }
42
+ const text = values.get('text');
43
+ const startMs = values.get('startMs');
44
+ const endMs = values.get('endMs');
45
+ const timestampMs = values.get('timestampMs');
46
+ const confidence = values.get('confidence');
47
+ if (typeof text !== 'string' ||
48
+ typeof startMs !== 'number' ||
49
+ typeof endMs !== 'number' ||
50
+ (timestampMs !== null && typeof timestampMs !== 'number') ||
51
+ (confidence !== null && typeof confidence !== 'number')) {
52
+ throw new Error('Captions must have the standard static caption shape to edit');
53
+ }
54
+ return { text, startMs, endMs, timestampMs, confidence };
55
+ };
56
+ const isCaptionKey = (key) => captionKeys.includes(key);
57
+ const getReplacementValue = ({ value, previous, }) => {
58
+ var _a;
59
+ if (value === null || typeof value === 'number') {
60
+ return String(value);
61
+ }
62
+ if (previous.type === 'StringLiteral' &&
63
+ typeof ((_a = previous.extra) === null || _a === void 0 ? void 0 : _a.raw) === 'string' &&
64
+ previous.extra.raw.startsWith("'")) {
65
+ return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
66
+ }
67
+ return JSON.stringify(value);
68
+ };
69
+ const getSourceValueRange = ({ input, property, key, }) => {
70
+ var _a, _b;
71
+ const line = (_a = property.loc) === null || _a === void 0 ? void 0 : _a.start.line;
72
+ if (line === null || line === undefined) {
73
+ throw new Error(`Could not locate caption ${key} in the source file`);
74
+ }
75
+ const lines = input.split('\n');
76
+ const lineStart = lines
77
+ .slice(0, line - 1)
78
+ .reduce((length, current) => length + current.length + 1, 0);
79
+ const sourceLine = lines[line - 1];
80
+ if (sourceLine === undefined) {
81
+ throw new Error(`Could not locate caption ${key} in the source file`);
82
+ }
83
+ const keyStart = sourceLine.indexOf(key);
84
+ const valueStart = sourceLine.indexOf(':', keyStart) + 1;
85
+ const rawValue = property.value.type === 'StringLiteral' &&
86
+ typeof ((_b = property.value.extra) === null || _b === void 0 ? void 0 : _b.raw) === 'string'
87
+ ? property.value.extra.raw
88
+ : property.value.type === 'StringLiteral'
89
+ ? JSON.stringify(property.value.value)
90
+ : String(getStaticValue(property.value));
91
+ const valueOffset = sourceLine.indexOf(rawValue, valueStart);
92
+ if (keyStart === -1 || valueOffset === -1) {
93
+ throw new Error(`Could not locate caption ${key} in the source file`);
94
+ }
95
+ return {
96
+ start: lineStart + valueOffset,
97
+ end: lineStart + valueOffset + rawValue.length,
98
+ };
99
+ };
100
+ const updateCaption = ({ input, caption, patch, }) => {
101
+ const current = getStaticCaption(caption);
102
+ if (!captionKeys.every((key) => current[key] === patch.before[key])) {
103
+ throw new Error(`Caption ${patch.index} changed in the source file before this edit could be saved`);
104
+ }
105
+ const changedKeys = Object.keys(patch.changes);
106
+ if (changedKeys.length === 0 || !changedKeys.every(isCaptionKey)) {
107
+ throw new Error('Caption patches must change at least one caption field');
108
+ }
109
+ const replacements = [];
110
+ for (const key of changedKeys) {
111
+ const value = patch.changes[key];
112
+ if ((key === 'text' && typeof value !== 'string') ||
113
+ ((key === 'startMs' || key === 'endMs') &&
114
+ (typeof value !== 'number' || !Number.isFinite(value))) ||
115
+ ((key === 'timestampMs' || key === 'confidence') &&
116
+ value !== null &&
117
+ (typeof value !== 'number' || !Number.isFinite(value)))) {
118
+ throw new Error(`Caption ${key} has an invalid value`);
119
+ }
120
+ const property = caption.properties.find((candidate) => {
121
+ return (candidate.type === 'ObjectProperty' &&
122
+ !candidate.computed &&
123
+ getPropertyKey(candidate) === key);
124
+ });
125
+ if (!property || property.type !== 'ObjectProperty') {
126
+ throw new Error(`Caption ${patch.index} is missing ${key}`);
127
+ }
128
+ const { start, end } = getSourceValueRange({ input, property, key });
129
+ replacements.push({
130
+ start,
131
+ end,
132
+ value: getReplacementValue({
133
+ value: value,
134
+ previous: property.value,
135
+ }),
136
+ });
137
+ }
138
+ return { changedFields: changedKeys, replacements };
139
+ };
140
+ const updateInlineCaptionPatches = ({ input, nodePath, patches, }) => {
141
+ var _a, _b, _c;
142
+ var _d;
143
+ if (patches.length === 0) {
144
+ throw new Error('Expected at least one caption patch');
145
+ }
146
+ const ast = (0, parse_ast_1.parseAst)(input);
147
+ const jsxElement = (0, can_update_sequence_props_1.findJsxElementNodeAtNodePath)(ast, nodePath);
148
+ if (!jsxElement) {
149
+ throw new Error('Could not find a JSX element at the specified line to update');
150
+ }
151
+ const captionsAttribute = (_a = jsxElement.openingElement.attributes) === null || _a === void 0 ? void 0 : _a.find((attribute) => attribute.type === 'JSXAttribute' &&
152
+ attribute.name.type === 'JSXIdentifier' &&
153
+ attribute.name.name === 'captions');
154
+ if (!captionsAttribute ||
155
+ captionsAttribute.type !== 'JSXAttribute' ||
156
+ ((_b = captionsAttribute.value) === null || _b === void 0 ? void 0 : _b.type) !== 'JSXExpressionContainer' ||
157
+ captionsAttribute.value.expression.type !== 'ArrayExpression') {
158
+ throw new Error('Captions must be an inline JSX array to edit them');
159
+ }
160
+ const captions = captionsAttribute.value.expression.elements;
161
+ const changedFields = [];
162
+ const replacements = [];
163
+ for (const patch of patches) {
164
+ if (!Number.isInteger(patch.index) || patch.index < 0) {
165
+ throw new Error('Caption patch index must be a non-negative integer');
166
+ }
167
+ const caption = captions[patch.index];
168
+ if (!caption || caption.type !== 'ObjectExpression') {
169
+ throw new Error(`Could not find inline caption ${patch.index}`);
170
+ }
171
+ const result = updateCaption({ input, caption, patch });
172
+ changedFields.push(result.changedFields);
173
+ replacements.push(...result.replacements);
174
+ }
175
+ const output = replacements
176
+ .sort((a, b) => b.start - a.start)
177
+ .reduce((current, replacement) => current.slice(0, replacement.start) +
178
+ replacement.value +
179
+ current.slice(replacement.end), input);
180
+ return {
181
+ output,
182
+ logLine: (_d = (_c = jsxElement.openingElement.loc) === null || _c === void 0 ? void 0 : _c.start.line) !== null && _d !== void 0 ? _d : 1,
183
+ changedFields,
184
+ };
185
+ };
186
+ exports.updateInlineCaptionPatches = updateInlineCaptionPatches;
@@ -498,10 +498,12 @@ const migrateCssShorthand = ({ node, cssShorthand, }) => {
498
498
  }
499
499
  const shorthandValue = property.value.type === 'StringLiteral'
500
500
  ? property.value.value
501
- : property.value.type === 'TemplateLiteral' &&
502
- property.value.expressions.length === 0
503
- ? ((_d = (_c = property.value.quasis[0]) === null || _c === void 0 ? void 0 : _c.value.cooked) !== null && _d !== void 0 ? _d : null)
504
- : null;
501
+ : property.value.type === 'NumericLiteral'
502
+ ? property.value.value
503
+ : property.value.type === 'TemplateLiteral' &&
504
+ property.value.expressions.length === 0
505
+ ? ((_d = (_c = property.value.quasis[0]) === null || _c === void 0 ? void 0 : _c.value.cooked) !== null && _d !== void 0 ? _d : null)
506
+ : null;
505
507
  if (shorthandValue === null) {
506
508
  continue;
507
509
  }
@@ -3,20 +3,26 @@ export type CssShorthandProperty = {
3
3
  readonly parentKey: string;
4
4
  readonly shorthand: string;
5
5
  readonly longhands: readonly string[];
6
- readonly parse: (value: string) => ParsedCssShorthand | null;
6
+ readonly parse: (value: unknown) => ParsedCssShorthand | null;
7
7
  readonly isUnsupportedProperty: (propertyName: string) => boolean;
8
8
  };
9
9
  export declare const cssShorthandProperties: readonly [{
10
10
  readonly parentKey: "style";
11
11
  readonly shorthand: "background";
12
12
  readonly longhands: readonly ["backgroundColor", "backgroundImage", "backgroundPosition", "backgroundSize", "backgroundRepeat", "backgroundOrigin", "backgroundClip", "backgroundAttachment"];
13
- readonly parse: (value: string) => import("./parse-background-shorthand").ParsedBackgroundShorthand | null;
13
+ readonly parse: (value: unknown) => import("./parse-background-shorthand").ParsedBackgroundShorthand | null;
14
14
  readonly isUnsupportedProperty: () => false;
15
15
  }, {
16
16
  readonly parentKey: "style";
17
17
  readonly shorthand: "border";
18
18
  readonly longhands: readonly ["borderWidth", "borderStyle", "borderColor"];
19
- readonly parse: (value: string) => import("./parse-border-shorthand").ParsedBorderShorthand | null;
19
+ readonly parse: (value: unknown) => import("./parse-border-shorthand").ParsedBorderShorthand | null;
20
+ readonly isUnsupportedProperty: (propertyName: string) => boolean;
21
+ }, {
22
+ readonly parentKey: "style";
23
+ readonly shorthand: "borderRadius";
24
+ readonly longhands: readonly ["borderTopLeftRadius", "borderTopRightRadius", "borderBottomRightRadius", "borderBottomLeftRadius"];
25
+ readonly parse: (value: unknown) => import("./parse-border-radius-shorthand").ParsedBorderRadiusShorthand | null;
20
26
  readonly isUnsupportedProperty: (propertyName: string) => boolean;
21
27
  }];
22
28
  export declare const getCssShorthandForLonghand: ({ parentKey, longhand, }: {
@@ -2,15 +2,28 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getCssShorthandsForUpdates = exports.getCssShorthandForLonghand = exports.cssShorthandProperties = void 0;
4
4
  const parse_background_shorthand_1 = require("./parse-background-shorthand");
5
+ const parse_border_radius_shorthand_1 = require("./parse-border-radius-shorthand");
5
6
  const parse_border_shorthand_1 = require("./parse-border-shorthand");
6
7
  const borderSidePropertyRegex = /^border(?:Top|Right|Bottom|Left)(?:Width|Style|Color)?$/;
7
8
  const borderShorthand = {
8
9
  parentKey: 'style',
9
10
  shorthand: 'border',
10
11
  longhands: ['borderWidth', 'borderStyle', 'borderColor'],
11
- parse: parse_border_shorthand_1.parseBorderShorthand,
12
+ parse: (value) => typeof value === 'string' ? (0, parse_border_shorthand_1.parseBorderShorthand)(value) : null,
12
13
  isUnsupportedProperty: (propertyName) => borderSidePropertyRegex.test(propertyName),
13
14
  };
15
+ const borderRadiusShorthand = {
16
+ parentKey: 'style',
17
+ shorthand: 'borderRadius',
18
+ longhands: [
19
+ 'borderTopLeftRadius',
20
+ 'borderTopRightRadius',
21
+ 'borderBottomRightRadius',
22
+ 'borderBottomLeftRadius',
23
+ ],
24
+ parse: parse_border_radius_shorthand_1.parseBorderRadiusShorthand,
25
+ isUnsupportedProperty: (propertyName) => /^border(?:StartStart|StartEnd|EndStart|EndEnd)Radius$/.test(propertyName),
26
+ };
14
27
  const backgroundShorthand = {
15
28
  parentKey: 'style',
16
29
  shorthand: 'background',
@@ -24,12 +37,13 @@ const backgroundShorthand = {
24
37
  'backgroundClip',
25
38
  'backgroundAttachment',
26
39
  ],
27
- parse: parse_background_shorthand_1.parseBackgroundShorthand,
40
+ parse: (value) => typeof value === 'string' ? (0, parse_background_shorthand_1.parseBackgroundShorthand)(value) : null,
28
41
  isUnsupportedProperty: () => false,
29
42
  };
30
43
  exports.cssShorthandProperties = [
31
44
  backgroundShorthand,
32
45
  borderShorthand,
46
+ borderRadiusShorthand,
33
47
  ];
34
48
  const getCssShorthandForLonghand = ({ parentKey, longhand, }) => {
35
49
  var _a;
@@ -0,0 +1,7 @@
1
+ export type ParsedBorderRadiusShorthand = {
2
+ borderTopLeftRadius: number;
3
+ borderTopRightRadius: number;
4
+ borderBottomRightRadius: number;
5
+ borderBottomLeftRadius: number;
6
+ };
7
+ export declare const parseBorderRadiusShorthand: (value: unknown) => ParsedBorderRadiusShorthand | null;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseBorderRadiusShorthand = void 0;
4
+ const parsePixelRadius = (value) => {
5
+ if (value === '0') {
6
+ return 0;
7
+ }
8
+ const match = value.match(/^(\d+(?:\.\d+)?|\.\d+)px$/i);
9
+ return match ? Number(match[1]) : null;
10
+ };
11
+ const expandBorderRadius = (values) => {
12
+ if (values.length === 1) {
13
+ return [values[0], values[0], values[0], values[0]];
14
+ }
15
+ if (values.length === 2) {
16
+ return [values[0], values[1], values[0], values[1]];
17
+ }
18
+ if (values.length === 3) {
19
+ return [values[0], values[1], values[2], values[1]];
20
+ }
21
+ return [values[0], values[1], values[2], values[3]];
22
+ };
23
+ const parseBorderRadiusShorthand = (value) => {
24
+ let values;
25
+ if (typeof value === 'number') {
26
+ if (!Number.isFinite(value) || value < 0) {
27
+ return null;
28
+ }
29
+ values = [value];
30
+ }
31
+ else if (typeof value === 'string') {
32
+ const tokens = value.trim().split(/\s+/);
33
+ if (tokens.length < 1 || tokens.length > 4) {
34
+ return null;
35
+ }
36
+ const parsed = tokens.map(parsePixelRadius);
37
+ if (parsed.some((radius) => radius === null)) {
38
+ return null;
39
+ }
40
+ values = parsed;
41
+ }
42
+ else {
43
+ return null;
44
+ }
45
+ const [topLeft, topRight, bottomRight, bottomLeft] = expandBorderRadius(values);
46
+ return {
47
+ borderTopLeftRadius: topLeft,
48
+ borderTopRightRadius: topRight,
49
+ borderBottomRightRadius: bottomRight,
50
+ borderBottomLeftRadius: bottomLeft,
51
+ };
52
+ };
53
+ exports.parseBorderRadiusShorthand = parseBorderRadiusShorthand;
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.insertJsxElementIntoComposition = exports.resolveCompositionComponent = exports.resolveCompositionComponentWithFile = void 0;
40
40
  const node_fs_1 = __importDefault(require("node:fs"));
41
41
  const node_path_1 = __importDefault(require("node:path"));
42
+ const studio_codemods_1 = require("@remotion/studio-codemods");
42
43
  const studio_shared_1 = require("@remotion/studio-shared");
43
44
  const recast = __importStar(require("recast"));
44
45
  const no_react_1 = require("remotion/no-react");
@@ -1427,56 +1428,71 @@ const insertJsxElementIntoComposition = async ({ remotionRoot, compositionFile,
1427
1428
  remotionRoot,
1428
1429
  fileName: location.fileName,
1429
1430
  });
1430
- const ast = (0, parse_ast_1.parseAst)(input);
1431
- if (element.type === 'composition' &&
1432
- element.compositionId === compositionId) {
1433
- throw new Error('Cannot insert a composition into itself');
1434
- }
1435
- const sequenceWrapper = element.type === 'composition'
1436
- ? {
1437
- dimensions: { width: element.width, height: element.height },
1438
- durationInFrames: element.durationInFrames,
1439
- name: element.compositionId,
1431
+ let finalFile;
1432
+ let logLine;
1433
+ if (element.type === 'solid' && from === null && wrapInSequence === null) {
1434
+ const inserted = (0, studio_codemods_1.insertSolidIntoSource)({
1435
+ exportName: location.exportName,
1436
+ height: element.height,
1440
1437
  position: element.position,
1441
- from,
1438
+ source: input,
1439
+ width: element.width,
1440
+ });
1441
+ finalFile = inserted.output;
1442
+ logLine = inserted.line;
1443
+ }
1444
+ else {
1445
+ const ast = (0, parse_ast_1.parseAst)(input);
1446
+ if (element.type === 'composition' &&
1447
+ element.compositionId === compositionId) {
1448
+ throw new Error('Cannot insert a composition into itself');
1442
1449
  }
1443
- : from === null ||
1444
- element.type === 'asset' ||
1445
- element.type === 'svg' ||
1446
- element.type === 'component'
1447
- ? wrapInSequence
1448
- : {
1449
- dimensions: null,
1450
- durationInFrames: null,
1451
- name: null,
1450
+ const sequenceWrapper = element.type === 'composition'
1451
+ ? {
1452
+ dimensions: { width: element.width, height: element.height },
1453
+ durationInFrames: element.durationInFrames,
1454
+ name: element.compositionId,
1452
1455
  position: element.position,
1453
1456
  from,
1454
- };
1455
- const elementToInsert = await createInsertableJsxElement({
1456
- addPositionStyleToComponent: sequenceWrapper === null,
1457
- ast,
1458
- destinationFileName: location.fileName,
1459
- element,
1460
- from,
1461
- remotionRoot,
1462
- });
1463
- const finalElementToInsert = sequenceWrapper
1464
- ? createSequenceWrappedElement({
1465
- child: elementToInsert,
1466
- dimensions: sequenceWrapper.dimensions,
1467
- durationInFrames: (_a = sequenceWrapper.durationInFrames) !== null && _a !== void 0 ? _a : null,
1468
- from: sequenceWrapper.from,
1469
- name: sequenceWrapper.name,
1470
- position: sequenceWrapper.position,
1471
- sequenceLocalName: ensureSequenceImport(ast),
1472
- })
1473
- : elementToInsert;
1474
- const logLine = addElementToComponentRoot({
1475
- ast,
1476
- exportName: location.exportName,
1477
- element: finalElementToInsert,
1478
- });
1479
- const finalFile = (0, parse_ast_1.serializeAst)(ast);
1457
+ }
1458
+ : from === null ||
1459
+ element.type === 'asset' ||
1460
+ element.type === 'svg' ||
1461
+ element.type === 'component'
1462
+ ? wrapInSequence
1463
+ : {
1464
+ dimensions: null,
1465
+ durationInFrames: null,
1466
+ name: null,
1467
+ position: element.position,
1468
+ from,
1469
+ };
1470
+ const elementToInsert = await createInsertableJsxElement({
1471
+ addPositionStyleToComponent: sequenceWrapper === null,
1472
+ ast,
1473
+ destinationFileName: location.fileName,
1474
+ element,
1475
+ from,
1476
+ remotionRoot,
1477
+ });
1478
+ const finalElementToInsert = sequenceWrapper
1479
+ ? createSequenceWrappedElement({
1480
+ child: elementToInsert,
1481
+ dimensions: sequenceWrapper.dimensions,
1482
+ durationInFrames: (_a = sequenceWrapper.durationInFrames) !== null && _a !== void 0 ? _a : null,
1483
+ from: sequenceWrapper.from,
1484
+ name: sequenceWrapper.name,
1485
+ position: sequenceWrapper.position,
1486
+ sequenceLocalName: ensureSequenceImport(ast),
1487
+ })
1488
+ : elementToInsert;
1489
+ logLine = addElementToComponentRoot({
1490
+ ast,
1491
+ exportName: location.exportName,
1492
+ element: finalElementToInsert,
1493
+ });
1494
+ finalFile = (0, parse_ast_1.serializeAst)(ast);
1495
+ }
1480
1496
  const { output, formatted } = await (0, format_file_content_1.formatFileContent)({
1481
1497
  input: finalFile,
1482
1498
  prettierConfigOverride,
@@ -19,6 +19,7 @@ const delete_static_file_1 = require("./routes/delete-static-file");
19
19
  const download_remote_asset_1 = require("./routes/download-remote-asset");
20
20
  const duplicate_effect_1 = require("./routes/duplicate-effect");
21
21
  const duplicate_jsx_node_1 = require("./routes/duplicate-jsx-node");
22
+ const find_in_file_1 = require("./routes/find-in-file");
22
23
  const insert_element_1 = require("./routes/insert-element");
23
24
  const insert_jsx_element_1 = require("./routes/insert-jsx-element");
24
25
  const install_dependency_1 = require("./routes/install-dependency");
@@ -36,6 +37,7 @@ const reorder_effect_1 = require("./routes/reorder-effect");
36
37
  const reorder_sequence_1 = require("./routes/reorder-sequence");
37
38
  const restart_studio_1 = require("./routes/restart-studio");
38
39
  const save_effect_props_1 = require("./routes/save-effect-props");
40
+ const save_multiple_effect_props_1 = require("./routes/save-multiple-effect-props");
39
41
  const save_sequence_props_1 = require("./routes/save-sequence-props");
40
42
  const split_jsx_sequence_1 = require("./routes/split-jsx-sequence");
41
43
  const subscribe_to_default_props_1 = require("./routes/subscribe-to-default-props");
@@ -61,6 +63,7 @@ exports.allApiRoutes = {
61
63
  '/api/subscribe-to-file-existence': subscribe_to_file_existence_1.subscribeToFileExistence,
62
64
  '/api/remove-render': remove_render_1.handleRemoveRender,
63
65
  '/api/open-in-editor': open_in_editor_1.openInEditorHandler,
66
+ '/api/find-in-file': find_in_file_1.findInFileHandler,
64
67
  '/api/open-in-file-explorer': open_in_file_explorer_1.handleOpenInFileExplorer,
65
68
  '/api/register-client-render': register_client_render_1.registerClientRenderHandler,
66
69
  '/api/unregister-client-render': unregister_client_render_1.unregisterClientRenderHandler,
@@ -73,6 +76,7 @@ exports.allApiRoutes = {
73
76
  '/api/unsubscribe-from-sequence-props': unsubscribe_from_sequence_props_1.unsubscribeFromSequenceProps,
74
77
  '/api/save-sequence-props': save_sequence_props_1.saveSequencePropsHandler,
75
78
  '/api/save-effect-props': save_effect_props_1.saveEffectPropsHandler,
79
+ '/api/save-multiple-effect-props': save_multiple_effect_props_1.saveMultipleEffectPropsHandler,
76
80
  '/api/add-effect': add_effect_1.addEffectHandler,
77
81
  '/api/reorder-effect': reorder_effect_1.reorderEffectHandler,
78
82
  '/api/duplicate-effect': duplicate_effect_1.duplicateEffectHandler,
@@ -43,6 +43,7 @@ const parse_ast_1 = require("../../codemods/parse-ast");
43
43
  const css_shorthand_properties_1 = require("../../helpers/css-shorthand-properties");
44
44
  const get_ast_node_path_1 = require("../../helpers/get-ast-node-path");
45
45
  const import_agnostic_node_path_1 = require("../../helpers/import-agnostic-node-path");
46
+ const parse_border_radius_shorthand_1 = require("../../helpers/parse-border-radius-shorthand");
46
47
  const parse_keyframe_easing_expression_1 = require("../../helpers/parse-keyframe-easing-expression");
47
48
  const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
48
49
  const video_config_numeric_expression_1 = require("../../helpers/video-config-numeric-expression");
@@ -679,6 +680,70 @@ const getObjectPropertyName = (property) => {
679
680
  }
680
681
  return null;
681
682
  };
683
+ const BORDER_RADIUS_SHORTHAND = 'borderRadius';
684
+ const BORDER_RADIUS_LONGHANDS = [
685
+ 'borderTopLeftRadius',
686
+ 'borderTopRightRadius',
687
+ 'borderBottomRightRadius',
688
+ 'borderBottomLeftRadius',
689
+ ];
690
+ const BORDER_RADIUS_PROPERTIES = new Set([
691
+ BORDER_RADIUS_SHORTHAND,
692
+ ...BORDER_RADIUS_LONGHANDS,
693
+ ]);
694
+ const hasMixedBorderRadiusRepresentation = (jsxElement) => {
695
+ var _a;
696
+ const style = jsxElement.attributes.find((attribute) => attribute.type === 'JSXAttribute' &&
697
+ attribute.name.type !== 'JSXNamespacedName' &&
698
+ attribute.name.name === 'style');
699
+ if (!style ||
700
+ style.type !== 'JSXAttribute' ||
701
+ ((_a = style.value) === null || _a === void 0 ? void 0 : _a.type) !== 'JSXExpressionContainer' ||
702
+ style.value.expression.type !== 'ObjectExpression') {
703
+ return false;
704
+ }
705
+ let hasShorthand = false;
706
+ let hasLonghand = false;
707
+ for (const property of style.value.expression.properties) {
708
+ if (property.type !== 'ObjectProperty') {
709
+ continue;
710
+ }
711
+ const name = getObjectPropertyName(property);
712
+ hasShorthand || (hasShorthand = name === BORDER_RADIUS_SHORTHAND);
713
+ hasLonghand || (hasLonghand = BORDER_RADIUS_LONGHANDS.some((longhand) => longhand === name));
714
+ }
715
+ return hasShorthand && hasLonghand;
716
+ };
717
+ const getUniformBorderRadius = (value) => {
718
+ const parsed = (0, parse_border_radius_shorthand_1.parseBorderRadiusShorthand)(value);
719
+ if (!parsed) {
720
+ return null;
721
+ }
722
+ const values = Object.values(parsed);
723
+ return values.every((radius) => radius === values[0]) ? values[0] : null;
724
+ };
725
+ const getBorderRadiusShorthandStatus = ({ propValue, ast, videoConfigValues, }) => {
726
+ if ((0, exports.isStaticValue)(propValue)) {
727
+ const uniform = getUniformBorderRadius((0, exports.extractStaticValue)(propValue));
728
+ return uniform === null ? computedStatus() : staticStatus(uniform, null);
729
+ }
730
+ const numericExpression = (0, video_config_numeric_expression_1.parseVideoConfigNumericExpression)({
731
+ node: propValue,
732
+ videoConfigValues,
733
+ });
734
+ if (numericExpression !== null && numericExpression.value >= 0) {
735
+ return staticStatus(numericExpression.value, numericExpression);
736
+ }
737
+ const computed = (0, exports.getComputedStatus)(propValue, ast, videoConfigValues);
738
+ if (computed.status === 'keyframed' &&
739
+ computed.interpolationFunction === 'interpolate' &&
740
+ computed.keyframes.every((keyframe) => typeof keyframe.value === 'number' &&
741
+ Number.isFinite(keyframe.value) &&
742
+ keyframe.value >= 0)) {
743
+ return computed;
744
+ }
745
+ return computedStatus();
746
+ };
682
747
  const getNestedPropStatus = ({ jsxElement, ast, parentKey, childKey, videoConfigValues, allowSpecialValues, }) => {
683
748
  const attr = jsxElement.attributes.find((a) => a.type !== 'JSXSpreadAttribute' &&
684
749
  a.name.type !== 'JSXNamespacedName' &&
@@ -735,9 +800,6 @@ const getNestedPropStatus = ({ jsxElement, ast, parentKey, childKey, videoConfig
735
800
  const staticShorthandValue = (0, exports.extractStaticValue)(shorthandValue, {
736
801
  allowSpecialValues: false,
737
802
  });
738
- if (typeof staticShorthandValue !== 'string') {
739
- return computedStatus();
740
- }
741
803
  const parsed = cssShorthand.parse(staticShorthandValue);
742
804
  return parsed ? staticStatus(parsed[childKey], null) : computedStatus();
743
805
  }
@@ -746,6 +808,13 @@ const getNestedPropStatus = ({ jsxElement, ast, parentKey, childKey, videoConfig
746
808
  return staticStatus(undefined, null);
747
809
  }
748
810
  const propValue = prop.value;
811
+ if (parentKey === 'style' && childKey === BORDER_RADIUS_SHORTHAND) {
812
+ return getBorderRadiusShorthandStatus({
813
+ propValue,
814
+ ast,
815
+ videoConfigValues,
816
+ });
817
+ }
749
818
  const staticValueOptions = { allowSpecialValues };
750
819
  if (!(0, exports.isStaticValue)(propValue, staticValueOptions)) {
751
820
  const numericExpression = (0, video_config_numeric_expression_1.parseVideoConfigNumericExpression)({
@@ -774,7 +843,14 @@ const computeEffectsForJsx = ({ ast, jsxElement, effects, videoConfigValues, })
774
843
  const computeSequenceOnlyPropsRecord = ({ jsxElement, jsxElementNode, ast, keys, assetKeys, videoConfigValues, }) => {
775
844
  const allProps = getPropsStatus(jsxElement, ast, videoConfigValues, assetKeys);
776
845
  const filteredProps = {};
846
+ const mixedBorderRadius = hasMixedBorderRadiusRepresentation(jsxElement);
777
847
  for (const key of keys) {
848
+ if (mixedBorderRadius &&
849
+ key.startsWith('style.') &&
850
+ BORDER_RADIUS_PROPERTIES.has(key.slice('style.'.length))) {
851
+ filteredProps[key] = computedStatus();
852
+ continue;
853
+ }
778
854
  if (key === 'children') {
779
855
  const staticChildrenAttribute = (0, exports.getStaticJsxChildrenAttribute)(jsxElement);
780
856
  if (staticChildrenAttribute) {
@@ -0,0 +1,4 @@
1
+ import type { FindInFileRequest, FindInFileResponse } from '@remotion/studio-shared';
2
+ import type { ApiHandler } from '../api-types';
3
+ export { findSearchPosition } from '@remotion/studio-codemods';
4
+ export declare const findInFileHandler: ApiHandler<FindInFileRequest, FindInFileResponse>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.findInFileHandler = exports.findSearchPosition = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const studio_codemods_1 = require("@remotion/studio-codemods");
10
+ const studio_codemods_2 = require("@remotion/studio-codemods");
11
+ Object.defineProperty(exports, "findSearchPosition", { enumerable: true, get: function () { return studio_codemods_2.findSearchPosition; } });
12
+ const findInFileHandler = async ({ input, remotionRoot }) => {
13
+ const { fileName, lineNumber, columnNumber, search } = input;
14
+ const contents = await node_fs_1.default.promises.readFile(node_path_1.default.resolve(remotionRoot, fileName), 'utf-8');
15
+ return (0, studio_codemods_1.findSearchPosition)({
16
+ contents,
17
+ lineNumber,
18
+ columnNumber,
19
+ search,
20
+ });
21
+ };
22
+ exports.findInFileHandler = findInFileHandler;
@@ -69,7 +69,7 @@ const validateElement = (element) => {
69
69
  }
70
70
  validateDimensions(element.dimensions);
71
71
  };
72
- const insertElementHandler = ({ input: { compositionFile, compositionId, element, from, position }, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
72
+ const insertElementHandler = ({ input: { compositionFile, compositionId, element, from, position, overwriteExisting, }, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
73
73
  try {
74
74
  validateElement(element);
75
75
  validatePosition(position);
@@ -99,13 +99,27 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
99
99
  throw new Error('Element file must stay inside the Remotion project');
100
100
  }
101
101
  const elementFileExists = (0, node_fs_1.existsSync)(elementFileName);
102
- if (elementFileExists) {
103
- const existingSource = (0, node_fs_1.readFileSync)(elementFileName, 'utf-8');
104
- if (normalizeSourceForComparison(existingSource) !==
105
- normalizeSourceForComparison(element.sourceCode)) {
106
- throw new Error(`Element file already exists with different contents: ${derivedElementFileName}`);
107
- }
102
+ const existingElementSource = elementFileExists
103
+ ? (0, node_fs_1.readFileSync)(elementFileName, 'utf-8')
104
+ : null;
105
+ const elementSourcesDiffer = existingElementSource !== null &&
106
+ normalizeSourceForComparison(existingElementSource) !==
107
+ normalizeSourceForComparison(element.sourceCode);
108
+ if (elementSourcesDiffer && !overwriteExisting) {
109
+ return {
110
+ success: false,
111
+ type: 'file-conflict',
112
+ conflict: {
113
+ filePath: node_path_1.default
114
+ .relative(remotionRoot, elementFileName)
115
+ .split(node_path_1.default.sep)
116
+ .join('/'),
117
+ existingSource: existingElementSource,
118
+ incomingSource: element.sourceCode,
119
+ },
120
+ };
108
121
  }
122
+ const shouldWriteElementFile = !elementFileExists || elementSourcesDiffer;
109
123
  const importPath = makeRelativeImportPath({
110
124
  fromFile: location.fileName,
111
125
  toFile: elementFileName,
@@ -133,16 +147,16 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
133
147
  });
134
148
  (0, undo_stack_1.pushTransactionToUndoStack)({
135
149
  snapshots: [
136
- ...(elementFileExists
137
- ? []
138
- : [
150
+ ...(shouldWriteElementFile
151
+ ? [
139
152
  {
140
153
  filePath: elementFileName,
141
- oldContents: null,
154
+ oldContents: existingElementSource,
142
155
  newContents: element.sourceCode,
143
156
  logLine: 1,
144
157
  },
145
- ]),
158
+ ]
159
+ : []),
146
160
  {
147
161
  filePath: inserted.fileName,
148
162
  oldContents: inserted.oldContents,
@@ -159,11 +173,11 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
159
173
  entryType: 'insert-jsx-element',
160
174
  suppressHmrOnFileRestore: false,
161
175
  });
162
- if (!elementFileExists) {
176
+ if (shouldWriteElementFile) {
163
177
  (0, undo_stack_1.suppressUndoStackInvalidation)(elementFileName);
164
178
  }
165
179
  (0, undo_stack_1.suppressUndoStackInvalidation)(inserted.fileName);
166
- if (!elementFileExists) {
180
+ if (shouldWriteElementFile) {
167
181
  (0, file_watcher_1.writeFileAndNotifyFileWatchers)(elementFileName, element.sourceCode, undefined);
168
182
  }
169
183
  (0, file_watcher_1.writeFileAndNotifyFileWatchers)(inserted.fileName, inserted.output, undefined);
@@ -177,7 +191,12 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
177
191
  absolutePath: elementFileName,
178
192
  line: 1,
179
193
  });
180
- renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${renderer_1.RenderInternals.chalk.blueBright(elementLocationLabel)} ${elementFileExists ? 'Reused existing Element source' : 'Created Element source'}`);
194
+ const elementFileAction = elementSourcesDiffer
195
+ ? 'Overwrote existing Element source'
196
+ : elementFileExists
197
+ ? 'Reused existing Element source'
198
+ : 'Created Element source';
199
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${renderer_1.RenderInternals.chalk.blueBright(elementLocationLabel)} ${elementFileAction}`);
181
200
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${renderer_1.RenderInternals.chalk.blueBright(compositionLocationLabel)} Added <${componentName}>`);
182
201
  if (!inserted.formatted) {
183
202
  (0, log_update_1.warnAboutPrettierOnce)(logLevel);
@@ -190,6 +209,7 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
190
209
  catch (err) {
191
210
  return {
192
211
  success: false,
212
+ type: 'error',
193
213
  reason: err.message,
194
214
  stack: err.stack,
195
215
  };
@@ -0,0 +1,3 @@
1
+ import type { SaveMultipleEffectPropsRequest, SaveMultipleEffectPropsResponse } from '@remotion/studio-shared';
2
+ import type { ApiHandler } from '../api-types';
3
+ export declare const saveMultipleEffectPropsHandler: ApiHandler<SaveMultipleEffectPropsRequest, SaveMultipleEffectPropsResponse>;
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.saveMultipleEffectPropsHandler = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const renderer_1 = require("@remotion/renderer");
6
+ const studio_shared_1 = require("@remotion/studio-shared");
7
+ const parse_ast_1 = require("../../codemods/parse-ast");
8
+ const update_effect_props_1 = require("../../codemods/update-effect-props/update-effect-props");
9
+ const file_watcher_1 = require("../../file-watcher");
10
+ const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
11
+ const video_config_values_1 = require("../../helpers/video-config-values");
12
+ const undo_stack_1 = require("../undo-stack");
13
+ const watch_ignore_next_change_1 = require("../watch-ignore-next-change");
14
+ const can_update_effect_props_1 = require("./can-update-effect-props");
15
+ const can_update_sequence_props_1 = require("./can-update-sequence-props");
16
+ const log_effect_update_1 = require("./log-updates/log-effect-update");
17
+ const source_file_write_queue_1 = require("./source-file-write-queue");
18
+ const resolveEdit = ({ edit, index, }) => {
19
+ const defaultValue = edit.defaultValue === null ? null : JSON.parse(edit.defaultValue);
20
+ const update = edit.type === 'value'
21
+ ? {
22
+ key: edit.key,
23
+ value: JSON.parse(edit.value),
24
+ defaultValue,
25
+ }
26
+ : {
27
+ key: edit.key,
28
+ effectParam: edit.effectParam,
29
+ defaultValue,
30
+ };
31
+ return {
32
+ index,
33
+ edit,
34
+ update,
35
+ defaultValueString: defaultValue === null ? null : JSON.stringify(defaultValue),
36
+ };
37
+ };
38
+ const saveMultipleEffectPropsHandler = ({ input: { edits, clientId, undoLabel, redoLabel }, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
39
+ var _a;
40
+ if (edits.length === 0) {
41
+ throw new Error('No effect prop edits to save');
42
+ }
43
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[save-multiple-effect-props] Received request with ${edits.length} edit(s)`);
44
+ const editGroups = new Map();
45
+ for (const [index, edit] of edits.entries()) {
46
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
47
+ remotionRoot,
48
+ fileName: edit.fileName,
49
+ action: 'modify',
50
+ });
51
+ const group = (_a = editGroups.get(absolutePath)) !== null && _a !== void 0 ? _a : {
52
+ fileRelativeToRoot,
53
+ edits: [],
54
+ };
55
+ group.edits.push(resolveEdit({ edit, index }));
56
+ editGroups.set(absolutePath, group);
57
+ }
58
+ const snapshots = [];
59
+ const outputByPath = new Map();
60
+ const resultByIndex = new Map();
61
+ for (const [absolutePath, group] of editGroups) {
62
+ const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
63
+ let output = fileContents;
64
+ let firstLogLine = Number.POSITIVE_INFINITY;
65
+ for (const edit of group.edits) {
66
+ const result = await (0, update_effect_props_1.updateEffectProps)({
67
+ input: output,
68
+ sequenceNodePath: edit.edit.sequenceNodePath.nodePath,
69
+ effectIndex: edit.edit.effectIndex,
70
+ update: edit.update,
71
+ schema: edit.edit.schema,
72
+ });
73
+ output = result.output;
74
+ firstLogLine = Math.min(firstLogLine, result.logLine);
75
+ resultByIndex.set(edit.index, {
76
+ ...result,
77
+ edit,
78
+ fileRelativeToRoot: group.fileRelativeToRoot,
79
+ });
80
+ }
81
+ outputByPath.set(absolutePath, output);
82
+ snapshots.push({
83
+ filePath: absolutePath,
84
+ oldContents: fileContents,
85
+ newContents: output,
86
+ logLine: Number.isFinite(firstLogLine) ? firstLogLine : 1,
87
+ });
88
+ }
89
+ (0, undo_stack_1.pushTransactionToUndoStack)({
90
+ snapshots,
91
+ logLevel,
92
+ remotionRoot,
93
+ description: {
94
+ undoMessage: `↩️ ${undoLabel}`,
95
+ redoMessage: `↪️ ${redoLabel}`,
96
+ },
97
+ entryType: 'effect-props',
98
+ suppressHmrOnFileRestore: true,
99
+ });
100
+ for (const [absolutePath, output] of outputByPath) {
101
+ (0, undo_stack_1.suppressUndoStackInvalidation)(absolutePath);
102
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(absolutePath);
103
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(absolutePath, output, clientId);
104
+ }
105
+ for (const index of edits.keys()) {
106
+ const result = resultByIndex.get(index);
107
+ if (!result) {
108
+ throw new Error('Could not compute effect prop edit result');
109
+ }
110
+ (0, log_effect_update_1.logEffectUpdate)({
111
+ fileRelativeToRoot: result.fileRelativeToRoot,
112
+ line: result.logLine,
113
+ effectName: result.effectCallee,
114
+ propKey: result.edit.edit.key,
115
+ oldValueString: result.oldValueString,
116
+ newValueString: result.newValueString,
117
+ defaultValueString: result.edit.defaultValueString,
118
+ formatted: result.formatted,
119
+ logLevel,
120
+ removedProps: result.removedProps,
121
+ addedProps: [],
122
+ });
123
+ }
124
+ (0, undo_stack_1.printUndoHint)(logLevel);
125
+ const statusTargets = [
126
+ ...new Map(edits.map((edit) => [
127
+ JSON.stringify([
128
+ edit.fileName,
129
+ edit.sequenceNodePath.nodePath,
130
+ edit.effectIndex,
131
+ ]),
132
+ edit,
133
+ ])).values(),
134
+ ];
135
+ const results = statusTargets.map((edit) => {
136
+ const { absolutePath } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
137
+ remotionRoot,
138
+ fileName: edit.fileName,
139
+ action: 'modify',
140
+ });
141
+ const output = outputByPath.get(absolutePath);
142
+ if (!output) {
143
+ throw new Error('Could not compute effect prop edit status');
144
+ }
145
+ const ast = (0, parse_ast_1.parseAst)(output);
146
+ const jsx = (0, can_update_sequence_props_1.findJsxElementAtNodePath)(ast, edit.sequenceNodePath.nodePath);
147
+ const status = jsx
148
+ ? (0, can_update_effect_props_1.computeEffectPropStatus)({
149
+ ast,
150
+ jsx,
151
+ effectIndex: edit.effectIndex,
152
+ keys: (0, studio_shared_1.getAllSchemaKeys)(edit.schema),
153
+ videoConfigValues: (0, video_config_values_1.getVideoConfigIdentifierValues)({
154
+ ast,
155
+ videoConfigValues: edit.sequenceNodePath.videoConfigValues,
156
+ }),
157
+ })
158
+ : {
159
+ canUpdate: false,
160
+ effectIndex: edit.effectIndex,
161
+ reason: 'not-found',
162
+ };
163
+ return {
164
+ fileName: edit.fileName,
165
+ sequenceNodePath: edit.sequenceNodePath,
166
+ status,
167
+ };
168
+ });
169
+ return { results };
170
+ });
171
+ exports.saveMultipleEffectPropsHandler = saveMultipleEffectPropsHandler;
@@ -4,6 +4,7 @@ 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_inline_caption_patches_1 = require("../../codemods/update-inline-caption-patches");
7
8
  const update_keyframes_1 = require("../../codemods/update-keyframes/update-keyframes");
8
9
  const update_sequence_props_1 = require("../../codemods/update-sequence-props/update-sequence-props");
9
10
  const file_watcher_1 = require("../../file-watcher");
@@ -59,8 +60,8 @@ const shouldSuppressHmrForSequencePropEdits = (edits) => {
59
60
  (edit.sourceEdit === null || edit.sourceEdit === undefined));
60
61
  };
61
62
  exports.shouldSuppressHmrForSequencePropEdits = shouldSuppressHmrForSequencePropEdits;
62
- const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyframes, clientId, undoLabel, redoLabel, }, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
63
- var _a, _b, _c, _d;
63
+ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyframes, captionPatches = [], clientId, undoLabel, redoLabel, }, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
64
+ var _a, _b, _c, _d, _e;
64
65
  const keyframesToAdd = addedKeyframes === null ? [] : addedKeyframes;
65
66
  const keyframesToMove = movedKeyframes === null
66
67
  ? { sequenceKeyframes: [], effectKeyframes: [] }
@@ -68,11 +69,12 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
68
69
  const totalKeyframeMoves = keyframesToMove.sequenceKeyframes.length +
69
70
  keyframesToMove.effectKeyframes.length;
70
71
  if (edits.length === 0 &&
72
+ captionPatches.length === 0 &&
71
73
  keyframesToAdd.length === 0 &&
72
74
  totalKeyframeMoves === 0) {
73
75
  throw new Error('No sequence prop edits to save');
74
76
  }
75
- renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[save-sequence-props] Received request with ${edits.length} edit(s), ${keyframesToAdd.length} added keyframe(s), and ${totalKeyframeMoves} moved keyframe(s)`);
77
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[save-sequence-props] Received request with ${edits.length} edit(s), ${captionPatches.length} caption patch request(s), ${keyframesToAdd.length} added keyframe(s), and ${totalKeyframeMoves} moved keyframe(s)`);
76
78
  const editGroups = new Map();
77
79
  for (const [index, edit] of edits.entries()) {
78
80
  const parsedValue = parseSequencePropEditValue(edit.value);
@@ -85,6 +87,7 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
85
87
  const group = (_a = editGroups.get(absolutePath)) !== null && _a !== void 0 ? _a : {
86
88
  fileRelativeToRoot,
87
89
  edits: [],
90
+ captionPatches: [],
88
91
  addedKeyframes: [],
89
92
  movedSequenceKeyframes: [],
90
93
  effectKeyframes: [],
@@ -105,15 +108,33 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
105
108
  });
106
109
  editGroups.set(absolutePath, group);
107
110
  }
111
+ for (const captionPatch of captionPatches) {
112
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
113
+ remotionRoot,
114
+ fileName: captionPatch.fileName,
115
+ action: 'modify',
116
+ });
117
+ const group = (_b = editGroups.get(absolutePath)) !== null && _b !== void 0 ? _b : {
118
+ fileRelativeToRoot,
119
+ edits: [],
120
+ captionPatches: [],
121
+ addedKeyframes: [],
122
+ movedSequenceKeyframes: [],
123
+ effectKeyframes: [],
124
+ };
125
+ group.captionPatches.push(captionPatch);
126
+ editGroups.set(absolutePath, group);
127
+ }
108
128
  for (const keyframe of keyframesToAdd) {
109
129
  const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
110
130
  remotionRoot,
111
131
  fileName: keyframe.fileName,
112
132
  action: 'modify',
113
133
  });
114
- const group = (_b = editGroups.get(absolutePath)) !== null && _b !== void 0 ? _b : {
134
+ const group = (_c = editGroups.get(absolutePath)) !== null && _c !== void 0 ? _c : {
115
135
  fileRelativeToRoot,
116
136
  edits: [],
137
+ captionPatches: [],
117
138
  addedKeyframes: [],
118
139
  movedSequenceKeyframes: [],
119
140
  effectKeyframes: [],
@@ -127,9 +148,10 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
127
148
  fileName: keyframe.fileName,
128
149
  action: 'modify',
129
150
  });
130
- const group = (_c = editGroups.get(absolutePath)) !== null && _c !== void 0 ? _c : {
151
+ const group = (_d = editGroups.get(absolutePath)) !== null && _d !== void 0 ? _d : {
131
152
  fileRelativeToRoot,
132
153
  edits: [],
154
+ captionPatches: [],
133
155
  addedKeyframes: [],
134
156
  movedSequenceKeyframes: [],
135
157
  effectKeyframes: [],
@@ -143,9 +165,10 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
143
165
  fileName: keyframe.fileName,
144
166
  action: 'modify',
145
167
  });
146
- const group = (_d = editGroups.get(absolutePath)) !== null && _d !== void 0 ? _d : {
168
+ const group = (_e = editGroups.get(absolutePath)) !== null && _e !== void 0 ? _e : {
147
169
  fileRelativeToRoot,
148
170
  edits: [],
171
+ captionPatches: [],
149
172
  addedKeyframes: [],
150
173
  movedSequenceKeyframes: [],
151
174
  effectKeyframes: [],
@@ -157,6 +180,7 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
157
180
  const outputByPath = new Map();
158
181
  const resultByIndex = new Map();
159
182
  const sequenceKeyframeLogs = [];
183
+ const captionPatchLogs = [];
160
184
  const effectKeyframeLogs = [];
161
185
  for (const [absolutePath, group] of editGroups) {
162
186
  const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
@@ -183,6 +207,32 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
183
207
  });
184
208
  }
185
209
  }
210
+ for (const captionPatchRequest of group.captionPatches) {
211
+ const result = (0, update_inline_caption_patches_1.updateInlineCaptionPatches)({
212
+ input: output,
213
+ nodePath: captionPatchRequest.nodePath.nodePath,
214
+ patches: captionPatchRequest.patches,
215
+ });
216
+ output = result.output;
217
+ firstLogLine = Math.min(firstLogLine, result.logLine);
218
+ for (const [index, patch] of captionPatchRequest.patches.entries()) {
219
+ const changedFields = result.changedFields[index];
220
+ if (!changedFields) {
221
+ throw new Error('Could not determine changed caption fields');
222
+ }
223
+ captionPatchLogs.push({
224
+ fileRelativeToRoot: group.fileRelativeToRoot,
225
+ line: result.logLine,
226
+ index: patch.index,
227
+ oldValueString: changedFields
228
+ .map((field) => `${field}: ${patch.before[field]}`)
229
+ .join(', '),
230
+ newValueString: changedFields
231
+ .map((field) => `${field}: ${patch.changes[field]}`)
232
+ .join(', '),
233
+ });
234
+ }
235
+ }
186
236
  for (const keyframeGroup of groupBy(group.addedKeyframes, (keyframe) => JSON.stringify(keyframe.nodePath.nodePath))) {
187
237
  const [firstSequenceKeyframe] = keyframeGroup;
188
238
  if (!firstSequenceKeyframe) {
@@ -310,7 +360,8 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
310
360
  }
311
361
  const undoMessage = `↩️ ${undoLabel}`;
312
362
  const redoMessage = `↪️ ${redoLabel}`;
313
- const suppressHmr = (0, exports.shouldSuppressHmrForSequencePropEdits)(edits);
363
+ const suppressHmr = captionPatches.length === 0 &&
364
+ (0, exports.shouldSuppressHmrForSequencePropEdits)(edits);
314
365
  (0, undo_stack_1.pushTransactionToUndoStack)({
315
366
  snapshots,
316
367
  logLevel,
@@ -346,6 +397,20 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
346
397
  });
347
398
  }
348
399
  }
400
+ for (const log of captionPatchLogs) {
401
+ (0, log_update_1.logUpdate)({
402
+ fileRelativeToRoot: log.fileRelativeToRoot,
403
+ line: log.line,
404
+ key: `captions[${log.index}]`,
405
+ oldValueString: log.oldValueString,
406
+ newValueString: log.newValueString,
407
+ defaultValueString: null,
408
+ formatted: true,
409
+ logLevel,
410
+ removedProps: [],
411
+ addedProps: [],
412
+ });
413
+ }
349
414
  for (const log of sequenceKeyframeLogs) {
350
415
  (0, log_update_1.logUpdate)({
351
416
  fileRelativeToRoot: log.fileRelativeToRoot,
@@ -377,7 +442,7 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
377
442
  }
378
443
  (0, undo_stack_1.printUndoHint)(logLevel);
379
444
  const statusTargets = [
380
- ...new Map([...edits, ...keyframesToAdd].map((target) => [
445
+ ...new Map([...edits, ...captionPatches, ...keyframesToAdd].map((target) => [
381
446
  JSON.stringify(target.nodePath),
382
447
  target,
383
448
  ])).values(),
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.499",
6
+ "version": "4.0.501",
7
7
  "description": "Run a Remotion Studio with a server backend",
8
8
  "main": "dist",
9
9
  "scripts": {
@@ -23,7 +23,7 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@remotion/drag-and-drop": "4.0.499",
26
+ "@remotion/drag-and-drop": "4.0.501",
27
27
  "@babel/types": "7.24.0",
28
28
  "@babel/parser": "7.24.1",
29
29
  "@svgr/core": "8.1.0",
@@ -31,11 +31,12 @@
31
31
  "kiwi-schema": "0.5.0",
32
32
  "semver": "7.5.3",
33
33
  "prettier": "3.8.1",
34
- "remotion": "4.0.499",
34
+ "remotion": "4.0.501",
35
35
  "recast": "0.23.11",
36
- "@remotion/bundler": "4.0.499",
37
- "@remotion/renderer": "4.0.499",
38
- "@remotion/studio-shared": "4.0.499",
36
+ "@remotion/bundler": "4.0.501",
37
+ "@remotion/renderer": "4.0.501",
38
+ "@remotion/studio-codemods": "4.0.501",
39
+ "@remotion/studio-shared": "4.0.501",
39
40
  "memfs": "3.4.3",
40
41
  "open": "8.4.2"
41
42
  },
@@ -43,7 +44,7 @@
43
44
  "ast-types": "0.16.1",
44
45
  "react": "19.2.3",
45
46
  "@types/semver": "7.5.3",
46
- "@remotion/eslint-config-internal": "4.0.499",
47
+ "@remotion/eslint-config-internal": "4.0.501",
47
48
  "eslint": "9.19.0",
48
49
  "@types/node": "20.12.14",
49
50
  "@typescript/native-preview": "7.0.0-dev.20260217.1"