@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.
- package/dist/codemods/duplicate-composition.js +8 -0
- package/dist/codemods/recast-mods.js +209 -5
- package/dist/codemods/update-sequence-props/update-sequence-props.d.ts +2 -0
- package/dist/codemods/update-sequence-props/update-sequence-props.js +344 -2
- package/dist/helpers/import-agnostic-node-path.js +30 -4
- package/dist/helpers/resolve-composition-component.d.ts +1 -0
- package/dist/helpers/resolve-composition-component.js +206 -9
- package/dist/preview-server/routes/apply-codemod.js +19 -0
- package/dist/preview-server/routes/insert-jsx-element.js +51 -2
- package/dist/preview-server/routes/save-sequence-props.d.ts +3 -1
- package/dist/preview-server/routes/save-sequence-props.js +207 -23
- package/dist/preview-server/undo-stack.d.ts +5 -1
- package/package.json +6 -6
|
@@ -94,6 +94,14 @@ const parseAndApplyCodemod = ({ input, codeMod, }) => {
|
|
|
94
94
|
localName: codeMod.componentName,
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
|
+
if (codeMod.type === 'new-folder') {
|
|
98
|
+
(0, imports_1.ensureNamedImport)({
|
|
99
|
+
ast: newAst,
|
|
100
|
+
importedName: 'Folder',
|
|
101
|
+
sourcePath: 'remotion',
|
|
102
|
+
localName: 'Folder',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
97
105
|
const output = (0, parse_ast_1.serializeAst)(newAst);
|
|
98
106
|
return { changesMade, newContents: output };
|
|
99
107
|
};
|
|
@@ -36,12 +36,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.applyCodemod = void 0;
|
|
37
37
|
const recast = __importStar(require("recast"));
|
|
38
38
|
const apply_visual_control_1 = require("./apply-visual-control");
|
|
39
|
+
const delete_jsx_node_1 = require("./delete-jsx-node");
|
|
39
40
|
const b = recast.types.builders;
|
|
40
41
|
const applyCodemod = ({ file, codeMod, }) => {
|
|
41
42
|
const changesMade = [];
|
|
42
43
|
if (codeMod.type === 'apply-visual-control') {
|
|
43
44
|
return (0, apply_visual_control_1.applyVisualControl)({ file, transformation: codeMod, changesMade });
|
|
44
45
|
}
|
|
46
|
+
if (codeMod.type === 'move-composition-to-folder') {
|
|
47
|
+
return moveCompositionToFolder({
|
|
48
|
+
file,
|
|
49
|
+
transformation: codeMod,
|
|
50
|
+
changesMade,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
45
53
|
const body = file.program.body.map((node) => {
|
|
46
54
|
return mapAll(node, codeMod, changesMade, null);
|
|
47
55
|
});
|
|
@@ -104,6 +112,15 @@ const mapReturnStatement = (statement, transformation, changesMade, parentFolder
|
|
|
104
112
|
argument: addNewCompositionToRootJsx(statement.argument, transformation, changesMade),
|
|
105
113
|
};
|
|
106
114
|
}
|
|
115
|
+
if (transformation.type === 'new-folder' &&
|
|
116
|
+
transformation.parentName === null &&
|
|
117
|
+
(statement.argument.type === 'JSXElement' ||
|
|
118
|
+
statement.argument.type === 'JSXFragment')) {
|
|
119
|
+
return {
|
|
120
|
+
...statement,
|
|
121
|
+
argument: addNewFolderToRootJsx(statement.argument, transformation, changesMade),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
107
124
|
const replacement = transformLoneJsxElement(statement.argument, transformation, changesMade, parentFolderName);
|
|
108
125
|
if (replacement !== null) {
|
|
109
126
|
return { ...statement, argument: replacement };
|
|
@@ -177,6 +194,19 @@ const newCompositionElement = (transformation) => {
|
|
|
177
194
|
children: [],
|
|
178
195
|
};
|
|
179
196
|
};
|
|
197
|
+
const newFolderElement = (transformation) => {
|
|
198
|
+
return {
|
|
199
|
+
type: 'JSXElement',
|
|
200
|
+
openingElement: {
|
|
201
|
+
type: 'JSXOpeningElement',
|
|
202
|
+
name: jsxId('Folder'),
|
|
203
|
+
attributes: [jsxAttributeWithString('name', transformation.folderName)],
|
|
204
|
+
selfClosing: true,
|
|
205
|
+
},
|
|
206
|
+
closingElement: null,
|
|
207
|
+
children: [],
|
|
208
|
+
};
|
|
209
|
+
};
|
|
180
210
|
const addNewCompositionToRootJsx = (root, transformation, changesMade) => {
|
|
181
211
|
if (changesMade.length > 0) {
|
|
182
212
|
return root;
|
|
@@ -192,6 +222,21 @@ const addNewCompositionToRootJsx = (root, transformation, changesMade) => {
|
|
|
192
222
|
}
|
|
193
223
|
return wrapInJsxFragment([root, newCompositionElement(transformation)]);
|
|
194
224
|
};
|
|
225
|
+
const addNewFolderToRootJsx = (root, transformation, changesMade) => {
|
|
226
|
+
if (changesMade.length > 0) {
|
|
227
|
+
return root;
|
|
228
|
+
}
|
|
229
|
+
changesMade.push({
|
|
230
|
+
description: 'Added new folder',
|
|
231
|
+
});
|
|
232
|
+
if (root.type === 'JSXFragment') {
|
|
233
|
+
return {
|
|
234
|
+
...root,
|
|
235
|
+
children: [...root.children, newFolderElement(transformation)],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return wrapInJsxFragment([root, newFolderElement(transformation)]);
|
|
239
|
+
};
|
|
195
240
|
const addNewCompositionToFolder = (folderElement, transformation, changesMade) => {
|
|
196
241
|
var _a;
|
|
197
242
|
if (changesMade.length > 0) {
|
|
@@ -216,6 +261,146 @@ const addNewCompositionToFolder = (folderElement, transformation, changesMade) =
|
|
|
216
261
|
],
|
|
217
262
|
};
|
|
218
263
|
};
|
|
264
|
+
const addNewFolderToFolder = (folderElement, transformation, changesMade) => {
|
|
265
|
+
var _a;
|
|
266
|
+
if (changesMade.length > 0) {
|
|
267
|
+
return folderElement;
|
|
268
|
+
}
|
|
269
|
+
changesMade.push({
|
|
270
|
+
description: 'Added new folder',
|
|
271
|
+
});
|
|
272
|
+
return {
|
|
273
|
+
...folderElement,
|
|
274
|
+
openingElement: {
|
|
275
|
+
...folderElement.openingElement,
|
|
276
|
+
selfClosing: false,
|
|
277
|
+
},
|
|
278
|
+
closingElement: (_a = folderElement.closingElement) !== null && _a !== void 0 ? _a : {
|
|
279
|
+
type: 'JSXClosingElement',
|
|
280
|
+
name: folderElement.openingElement.name,
|
|
281
|
+
},
|
|
282
|
+
children: [...folderElement.children, newFolderElement(transformation)],
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
const getChildFolderParentName = ({ folderName, parentFolderName, }) => {
|
|
286
|
+
return [parentFolderName, folderName].filter(Boolean).join('/');
|
|
287
|
+
};
|
|
288
|
+
const appendCompositionToFolder = ({ compositionElement, folderElement, }) => {
|
|
289
|
+
var _a;
|
|
290
|
+
folderElement.openingElement.selfClosing = false;
|
|
291
|
+
folderElement.closingElement = (_a = folderElement.closingElement) !== null && _a !== void 0 ? _a : {
|
|
292
|
+
type: 'JSXClosingElement',
|
|
293
|
+
name: folderElement.openingElement.name,
|
|
294
|
+
};
|
|
295
|
+
folderElement.children.push(stripParenthesizedExtra(compositionElement));
|
|
296
|
+
};
|
|
297
|
+
const appendCompositionToRoot = ({ compositionElement, file, }) => {
|
|
298
|
+
let appended = false;
|
|
299
|
+
recast.types.visit(file, {
|
|
300
|
+
visitReturnStatement(astPath) {
|
|
301
|
+
if (appended) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
const { argument } = astPath.node;
|
|
305
|
+
if ((argument === null || argument === void 0 ? void 0 : argument.type) !== 'JSXFragment' && (argument === null || argument === void 0 ? void 0 : argument.type) !== 'JSXElement') {
|
|
306
|
+
this.traverse(astPath);
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
if (argument.type === 'JSXFragment') {
|
|
310
|
+
argument.children.push(stripParenthesizedExtra(compositionElement));
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
astPath.node.argument = wrapInJsxFragment([
|
|
314
|
+
argument,
|
|
315
|
+
compositionElement,
|
|
316
|
+
]);
|
|
317
|
+
}
|
|
318
|
+
appended = true;
|
|
319
|
+
return false;
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
if (!appended) {
|
|
323
|
+
throw new Error('Could not find a root JSX element');
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const moveCompositionToFolder = ({ file, transformation, changesMade, }) => {
|
|
327
|
+
let sourcePath = null;
|
|
328
|
+
let sourceParentFolderName = null;
|
|
329
|
+
let sourceIsDirectJsxChild = false;
|
|
330
|
+
let targetFolder = null;
|
|
331
|
+
const folderStack = [];
|
|
332
|
+
const isDirectJsxChild = (astPath) => {
|
|
333
|
+
var _a;
|
|
334
|
+
const parent = (_a = astPath.parentPath) === null || _a === void 0 ? void 0 : _a.node;
|
|
335
|
+
return (((parent === null || parent === void 0 ? void 0 : parent.type) === 'JSXElement' || (parent === null || parent === void 0 ? void 0 : parent.type) === 'JSXFragment') &&
|
|
336
|
+
parent.children.includes(astPath.node));
|
|
337
|
+
};
|
|
338
|
+
const visitJsxElementWithFolderContext = (astPath) => {
|
|
339
|
+
const node = astPath.node;
|
|
340
|
+
const compositionId = getCompositionIdFromJSXElement(node);
|
|
341
|
+
if (compositionId === transformation.idToMove) {
|
|
342
|
+
sourcePath = astPath;
|
|
343
|
+
sourceParentFolderName = folderStack.join('/') || null;
|
|
344
|
+
sourceIsDirectJsxChild = isDirectJsxChild(astPath);
|
|
345
|
+
}
|
|
346
|
+
const folderName = getFolderNameFromJSXElement(node);
|
|
347
|
+
const parentName = folderStack.join('/') || null;
|
|
348
|
+
if (transformation.folderName !== null &&
|
|
349
|
+
folderName === transformation.folderName &&
|
|
350
|
+
parentName === transformation.parentName) {
|
|
351
|
+
targetFolder = node;
|
|
352
|
+
}
|
|
353
|
+
if (folderName) {
|
|
354
|
+
folderStack.push(folderName);
|
|
355
|
+
}
|
|
356
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
357
|
+
if (node.children[i].type !== 'JSXElement') {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
visitJsxElementWithFolderContext(astPath.get('children', i));
|
|
361
|
+
}
|
|
362
|
+
if (folderName) {
|
|
363
|
+
folderStack.pop();
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
recast.types.visit(file, {
|
|
367
|
+
visitJSXElement(astPath) {
|
|
368
|
+
visitJsxElementWithFolderContext(astPath);
|
|
369
|
+
return false;
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
if (!sourcePath) {
|
|
373
|
+
throw new Error(`Could not find composition "${transformation.idToMove}"`);
|
|
374
|
+
}
|
|
375
|
+
if (!sourceIsDirectJsxChild) {
|
|
376
|
+
throw new Error(`Cannot move composition "${transformation.idToMove}" because it is not a direct JSX child`);
|
|
377
|
+
}
|
|
378
|
+
if (transformation.folderName === null && sourceParentFolderName === null) {
|
|
379
|
+
return { newAst: file, changesMade };
|
|
380
|
+
}
|
|
381
|
+
if (transformation.folderName !== null && !targetFolder) {
|
|
382
|
+
const folderLabel = `${transformation.parentName ? `${transformation.parentName}/` : ''}${transformation.folderName}`;
|
|
383
|
+
throw new Error(`Could not find folder "${folderLabel}"`);
|
|
384
|
+
}
|
|
385
|
+
const compositionElement = sourcePath
|
|
386
|
+
.node;
|
|
387
|
+
(0, delete_jsx_node_1.deleteJsxElementAtPath)(sourcePath);
|
|
388
|
+
if (transformation.folderName === null) {
|
|
389
|
+
appendCompositionToRoot({ compositionElement, file });
|
|
390
|
+
changesMade.push({ description: 'Moved composition to root' });
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
if (targetFolder === null) {
|
|
394
|
+
throw new Error('Could not find target folder');
|
|
395
|
+
}
|
|
396
|
+
appendCompositionToFolder({
|
|
397
|
+
compositionElement,
|
|
398
|
+
folderElement: targetFolder,
|
|
399
|
+
});
|
|
400
|
+
changesMade.push({ description: 'Moved composition into folder' });
|
|
401
|
+
}
|
|
402
|
+
return { newAst: file, changesMade };
|
|
403
|
+
};
|
|
219
404
|
// When a <Composition> JSX element appears in a position where it cannot
|
|
220
405
|
// simply be removed from a parent's children list (e.g. as the sole return
|
|
221
406
|
// value of a wrapper component or as the concise body of an arrow function),
|
|
@@ -237,7 +422,10 @@ const transformLoneJsxElement = (expression, transformation, changesMade, parent
|
|
|
237
422
|
parentFolderName === transformation.parentName) ||
|
|
238
423
|
(transformation.type === 'new-composition' &&
|
|
239
424
|
folderName === transformation.folderName &&
|
|
240
|
-
parentFolderName === transformation.parentName)
|
|
425
|
+
parentFolderName === transformation.parentName) ||
|
|
426
|
+
(transformation.type === 'new-folder' &&
|
|
427
|
+
getChildFolderParentName({ folderName, parentFolderName }) ===
|
|
428
|
+
transformation.parentName));
|
|
241
429
|
if (!isFolderMatch) {
|
|
242
430
|
return null;
|
|
243
431
|
}
|
|
@@ -256,7 +444,11 @@ const transformLoneJsxElement = (expression, transformation, changesMade, parent
|
|
|
256
444
|
parentFolderName === transformation.parentName) ||
|
|
257
445
|
(transformation.type === 'new-composition' &&
|
|
258
446
|
folderName === transformation.folderName &&
|
|
259
|
-
parentFolderName === transformation.parentName)
|
|
447
|
+
parentFolderName === transformation.parentName) ||
|
|
448
|
+
(transformation.type === 'new-folder' &&
|
|
449
|
+
folderName !== null &&
|
|
450
|
+
getChildFolderParentName({ folderName, parentFolderName }) ===
|
|
451
|
+
transformation.parentName);
|
|
260
452
|
if (!isMatch) {
|
|
261
453
|
return null;
|
|
262
454
|
}
|
|
@@ -283,9 +475,6 @@ const mapJsxElementOrFragment = (jsxFragment, transformation, changesMade, paren
|
|
|
283
475
|
.flat(1),
|
|
284
476
|
};
|
|
285
477
|
};
|
|
286
|
-
const getChildFolderParentName = ({ folderName, parentFolderName, }) => {
|
|
287
|
-
return [parentFolderName, folderName].filter(Boolean).join('/');
|
|
288
|
-
};
|
|
289
478
|
const mapJsxChild = (c, transformation, changesMade, parentFolderName) => {
|
|
290
479
|
const compId = getCompositionIdFromJSXElement(c);
|
|
291
480
|
const folderName = getFolderNameFromJSXElement(c);
|
|
@@ -354,6 +543,12 @@ const mapJsxChild = (c, transformation, changesMade, parentFolderName) => {
|
|
|
354
543
|
parentFolderName === transformation.parentName) {
|
|
355
544
|
return [addNewCompositionToFolder(c, transformation, changesMade)];
|
|
356
545
|
}
|
|
546
|
+
if (transformation.type === 'new-folder' &&
|
|
547
|
+
folderName !== null &&
|
|
548
|
+
getChildFolderParentName({ folderName, parentFolderName }) ===
|
|
549
|
+
transformation.parentName) {
|
|
550
|
+
return [addNewFolderToFolder(c, transformation, changesMade)];
|
|
551
|
+
}
|
|
357
552
|
const childParentFolderName = folderName
|
|
358
553
|
? getChildFolderParentName({ folderName, parentFolderName })
|
|
359
554
|
: parentFolderName;
|
|
@@ -374,6 +569,15 @@ const mapRecognizedType = (expression, transformation, changesMade, parentFolder
|
|
|
374
569
|
body: addNewCompositionToRootJsx(expression.body, transformation, changesMade),
|
|
375
570
|
};
|
|
376
571
|
}
|
|
572
|
+
if (transformation.type === 'new-folder' &&
|
|
573
|
+
transformation.parentName === null &&
|
|
574
|
+
(expression.body.type === 'JSXElement' ||
|
|
575
|
+
expression.body.type === 'JSXFragment')) {
|
|
576
|
+
return {
|
|
577
|
+
...expression,
|
|
578
|
+
body: addNewFolderToRootJsx(expression.body, transformation, changesMade),
|
|
579
|
+
};
|
|
580
|
+
}
|
|
377
581
|
if (expression.type === 'ArrowFunctionExpression' &&
|
|
378
582
|
expression.body.type === 'JSXElement') {
|
|
379
583
|
const replacement = transformLoneJsxElement(expression.body, transformation, changesMade, parentFolderName);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { GoogleFontSourceEdit } from '@remotion/studio-shared';
|
|
1
2
|
import type { InteractivitySchema, SequenceNodePath } from 'remotion';
|
|
2
3
|
export type SequencePropUpdate = {
|
|
3
4
|
key: string;
|
|
4
5
|
value: unknown;
|
|
5
6
|
defaultValue: unknown | null;
|
|
7
|
+
googleFont?: GoogleFontSourceEdit | null;
|
|
6
8
|
};
|
|
7
9
|
export type RemovedProp = {
|
|
8
10
|
key: string;
|
|
@@ -134,6 +134,343 @@ const updateJsxTextContent = ({ jsxElement, value, }) => {
|
|
|
134
134
|
}
|
|
135
135
|
return staticTextContent.value;
|
|
136
136
|
};
|
|
137
|
+
const collectTopLevelIdentifierNames = (ast) => {
|
|
138
|
+
const names = new Set();
|
|
139
|
+
recast.types.visit(ast, {
|
|
140
|
+
visitIdentifier(path) {
|
|
141
|
+
names.add(path.node.name);
|
|
142
|
+
this.traverse(path);
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
return names;
|
|
146
|
+
};
|
|
147
|
+
const toSafeIdentifier = (value) => {
|
|
148
|
+
const sanitized = value.replace(/[^a-zA-Z0-9_$]/g, '');
|
|
149
|
+
if (!sanitized) {
|
|
150
|
+
return 'loadGoogleFont';
|
|
151
|
+
}
|
|
152
|
+
return /^[a-zA-Z_$]/.test(sanitized) ? sanitized : `_${sanitized}`;
|
|
153
|
+
};
|
|
154
|
+
const getSafeLoadFontIdentifier = ({ font, usedNames, }) => {
|
|
155
|
+
const baseName = toSafeIdentifier(`load${font.importName}`);
|
|
156
|
+
if (!usedNames.has(baseName)) {
|
|
157
|
+
usedNames.add(baseName);
|
|
158
|
+
return baseName;
|
|
159
|
+
}
|
|
160
|
+
let index = 2;
|
|
161
|
+
while (usedNames.has(`${baseName}${index}`)) {
|
|
162
|
+
index++;
|
|
163
|
+
}
|
|
164
|
+
const name = `${baseName}${index}`;
|
|
165
|
+
usedNames.add(name);
|
|
166
|
+
return name;
|
|
167
|
+
};
|
|
168
|
+
const getImportedLoadFontIdentifier = ({ ast, font, }) => {
|
|
169
|
+
var _a;
|
|
170
|
+
var _b, _c;
|
|
171
|
+
const moduleName = `@remotion/google-fonts/${font.importName}`;
|
|
172
|
+
for (const statement of ast.program.body) {
|
|
173
|
+
if (statement.type !== 'ImportDeclaration' ||
|
|
174
|
+
statement.source.value !== moduleName) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
for (const specifier of (_b = statement.specifiers) !== null && _b !== void 0 ? _b : []) {
|
|
178
|
+
if (specifier.type === 'ImportSpecifier' &&
|
|
179
|
+
specifier.imported.type === 'Identifier' &&
|
|
180
|
+
specifier.imported.name === 'loadFont') {
|
|
181
|
+
return (_c = (_a = specifier.local) === null || _a === void 0 ? void 0 : _a.name) !== null && _c !== void 0 ? _c : 'loadFont';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
};
|
|
187
|
+
const getInsertIndexAfterImportsOrDirectives = (ast) => {
|
|
188
|
+
const lastImportIndex = ast.program.body.reduce((lastIndex, statement, index) => {
|
|
189
|
+
return statement.type === 'ImportDeclaration' ? index : lastIndex;
|
|
190
|
+
}, -1);
|
|
191
|
+
if (lastImportIndex !== -1) {
|
|
192
|
+
return lastImportIndex + 1;
|
|
193
|
+
}
|
|
194
|
+
return ast.program.body.findIndex((statement) => {
|
|
195
|
+
return !(statement.type === 'ExpressionStatement' &&
|
|
196
|
+
'directive' in statement &&
|
|
197
|
+
statement.directive);
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
const insertAfterImportsOrDirectives = (ast, statement) => {
|
|
201
|
+
const insertIndex = getInsertIndexAfterImportsOrDirectives(ast);
|
|
202
|
+
ast.program.body.splice(insertIndex === -1 ? ast.program.body.length : insertIndex, 0, statement);
|
|
203
|
+
};
|
|
204
|
+
const ensureLoadFontImport = ({ ast, font, usedNames, }) => {
|
|
205
|
+
var _a;
|
|
206
|
+
var _b;
|
|
207
|
+
const existing = getImportedLoadFontIdentifier({ ast, font });
|
|
208
|
+
if (existing) {
|
|
209
|
+
return existing;
|
|
210
|
+
}
|
|
211
|
+
const moduleName = `@remotion/google-fonts/${font.importName}`;
|
|
212
|
+
const localName = getSafeLoadFontIdentifier({ font, usedNames });
|
|
213
|
+
const specifier = b.importSpecifier(b.identifier('loadFont'), b.identifier(localName));
|
|
214
|
+
for (const statement of ast.program.body) {
|
|
215
|
+
if (statement.type === 'ImportDeclaration' &&
|
|
216
|
+
statement.source.value === moduleName &&
|
|
217
|
+
!((_a = statement.specifiers) === null || _a === void 0 ? void 0 : _a.some((existingSpecifier) => existingSpecifier.type === 'ImportNamespaceSpecifier'))) {
|
|
218
|
+
statement.specifiers = [
|
|
219
|
+
...((_b = statement.specifiers) !== null && _b !== void 0 ? _b : []),
|
|
220
|
+
specifier,
|
|
221
|
+
];
|
|
222
|
+
return localName;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const declaration = b.importDeclaration([specifier], b.stringLiteral(moduleName));
|
|
226
|
+
insertAfterImportsOrDirectives(ast, declaration);
|
|
227
|
+
return localName;
|
|
228
|
+
};
|
|
229
|
+
const getStringArrayObjectProperty = ({ node, key, }) => {
|
|
230
|
+
if (node.type !== 'ObjectExpression') {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
const property = node.properties.find((prop) => {
|
|
234
|
+
return (prop.type === 'ObjectProperty' &&
|
|
235
|
+
prop.key.type === 'Identifier' &&
|
|
236
|
+
prop.key.name === key);
|
|
237
|
+
});
|
|
238
|
+
if (!property || property.type !== 'ObjectProperty') {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
if (property.value.type !== 'ArrayExpression') {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const values = [];
|
|
245
|
+
for (const element of property.value.elements) {
|
|
246
|
+
if (!element || element.type !== 'StringLiteral') {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
values.push(element.value);
|
|
250
|
+
}
|
|
251
|
+
return values;
|
|
252
|
+
};
|
|
253
|
+
const arraysEqual = (a, bValues) => {
|
|
254
|
+
return (a.length === bValues.length &&
|
|
255
|
+
a.every((value, index) => value === bValues[index]));
|
|
256
|
+
};
|
|
257
|
+
const isMatchingLoadFontCall = ({ statement, localName, font, }) => {
|
|
258
|
+
if (statement.type !== 'ExpressionStatement' ||
|
|
259
|
+
statement.expression.type !== 'CallExpression' ||
|
|
260
|
+
statement.expression.callee.type !== 'Identifier' ||
|
|
261
|
+
statement.expression.callee.name !== localName) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
const [style, options] = statement.expression.arguments;
|
|
265
|
+
if ((style === null || style === void 0 ? void 0 : style.type) !== 'StringLiteral' || style.value !== font.style) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
if (!options || options.type !== 'ObjectExpression') {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
const weights = getStringArrayObjectProperty({ node: options, key: 'weights' });
|
|
272
|
+
const subsets = getStringArrayObjectProperty({ node: options, key: 'subsets' });
|
|
273
|
+
return (weights !== null &&
|
|
274
|
+
subsets !== null &&
|
|
275
|
+
arraysEqual(weights, font.weights) &&
|
|
276
|
+
arraysEqual(subsets, font.subsets));
|
|
277
|
+
};
|
|
278
|
+
const hasTopLevelLoadFontCall = ({ ast, localName, font, }) => {
|
|
279
|
+
return ast.program.body.some((statement) => {
|
|
280
|
+
return isMatchingLoadFontCall({ statement, localName, font });
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
const getGoogleFontImportName = (moduleName) => {
|
|
284
|
+
const prefix = '@remotion/google-fonts/';
|
|
285
|
+
return moduleName.startsWith(prefix) ? moduleName.slice(prefix.length) : null;
|
|
286
|
+
};
|
|
287
|
+
const normalizeFontFamilyName = (fontFamily) => {
|
|
288
|
+
return fontFamily.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
289
|
+
};
|
|
290
|
+
const getPrimaryFontFamilyName = (fontFamily) => {
|
|
291
|
+
const trimmed = fontFamily.trim();
|
|
292
|
+
if (!trimmed) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
const quote = trimmed[0];
|
|
296
|
+
if (quote === '"' || quote === "'") {
|
|
297
|
+
const closingQuote = trimmed.indexOf(quote, 1);
|
|
298
|
+
return closingQuote === -1
|
|
299
|
+
? trimmed.slice(1)
|
|
300
|
+
: trimmed.slice(1, closingQuote);
|
|
301
|
+
}
|
|
302
|
+
return trimmed.split(',')[0].trim();
|
|
303
|
+
};
|
|
304
|
+
const collectUsedFontFamilyNames = (ast) => {
|
|
305
|
+
const used = new Set();
|
|
306
|
+
const add = (fontFamily) => {
|
|
307
|
+
const primary = getPrimaryFontFamilyName(fontFamily);
|
|
308
|
+
if (primary) {
|
|
309
|
+
used.add(normalizeFontFamilyName(primary));
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
recast.types.visit(ast, {
|
|
313
|
+
visitObjectProperty(path) {
|
|
314
|
+
var _a;
|
|
315
|
+
const { node } = path;
|
|
316
|
+
if (node.key.type === 'Identifier' && node.key.name === 'fontFamily') {
|
|
317
|
+
if (node.value.type === 'StringLiteral') {
|
|
318
|
+
add(node.value.value);
|
|
319
|
+
}
|
|
320
|
+
if (node.value.type === 'TemplateLiteral' &&
|
|
321
|
+
node.value.expressions.length === 0 &&
|
|
322
|
+
node.value.quasis.length === 1) {
|
|
323
|
+
add((_a = node.value.quasis[0].value.cooked) !== null && _a !== void 0 ? _a : node.value.quasis[0].value.raw);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
this.traverse(path);
|
|
327
|
+
},
|
|
328
|
+
visitJSXAttribute(path) {
|
|
329
|
+
var _a;
|
|
330
|
+
const { node } = path;
|
|
331
|
+
if (node.name.type === 'JSXIdentifier' &&
|
|
332
|
+
node.name.name === 'fontFamily' &&
|
|
333
|
+
((_a = node.value) === null || _a === void 0 ? void 0 : _a.type) === 'StringLiteral') {
|
|
334
|
+
add(node.value.value);
|
|
335
|
+
}
|
|
336
|
+
this.traverse(path);
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
return used;
|
|
340
|
+
};
|
|
341
|
+
const isIdentifierImportedBySpecifier = ({ parent, name, }) => {
|
|
342
|
+
return (parent !== null &&
|
|
343
|
+
typeof parent === 'object' &&
|
|
344
|
+
'type' in parent &&
|
|
345
|
+
parent.type === 'ImportSpecifier' &&
|
|
346
|
+
'local' in parent &&
|
|
347
|
+
parent.local !== null &&
|
|
348
|
+
typeof parent.local === 'object' &&
|
|
349
|
+
'type' in parent.local &&
|
|
350
|
+
parent.local.type === 'Identifier' &&
|
|
351
|
+
'name' in parent.local &&
|
|
352
|
+
parent.local.name === name);
|
|
353
|
+
};
|
|
354
|
+
const hasNonImportIdentifierReference = ({ ast, name, }) => {
|
|
355
|
+
let hasReference = false;
|
|
356
|
+
recast.types.visit(ast, {
|
|
357
|
+
visitIdentifier(path) {
|
|
358
|
+
if (path.node.name !== name) {
|
|
359
|
+
this.traverse(path);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (isIdentifierImportedBySpecifier({ parent: path.parent.node, name })) {
|
|
363
|
+
this.traverse(path);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
hasReference = true;
|
|
367
|
+
return false;
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
return hasReference;
|
|
371
|
+
};
|
|
372
|
+
const googleFontLoadingComment = 'Remotion Studio generated Google Font loading';
|
|
373
|
+
const hasStudioGeneratedGoogleFontLoadingComment = (statement) => {
|
|
374
|
+
var _a;
|
|
375
|
+
return ((_a = statement.leadingComments) !== null && _a !== void 0 ? _a : []).some((comment) => comment.value.includes(googleFontLoadingComment));
|
|
376
|
+
};
|
|
377
|
+
const isTopLevelCallToIdentifier = ({ statement, localName, }) => {
|
|
378
|
+
return (statement.type === 'ExpressionStatement' &&
|
|
379
|
+
statement.expression.type === 'CallExpression' &&
|
|
380
|
+
statement.expression.callee.type === 'Identifier' &&
|
|
381
|
+
statement.expression.callee.name === localName);
|
|
382
|
+
};
|
|
383
|
+
const removeUnusedGoogleFontSourceEdits = (ast) => {
|
|
384
|
+
var _a;
|
|
385
|
+
var _b, _c;
|
|
386
|
+
const usedFonts = collectUsedFontFamilyNames(ast);
|
|
387
|
+
const loadFontLocalNamesToRemove = new Set();
|
|
388
|
+
for (const statement of ast.program.body) {
|
|
389
|
+
if (statement.type !== 'ImportDeclaration' ||
|
|
390
|
+
typeof statement.source.value !== 'string') {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
const importName = getGoogleFontImportName(statement.source.value);
|
|
394
|
+
if (!importName) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (usedFonts.has(normalizeFontFamilyName(importName))) {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
for (const specifier of (_b = statement.specifiers) !== null && _b !== void 0 ? _b : []) {
|
|
401
|
+
if (specifier.type === 'ImportSpecifier' &&
|
|
402
|
+
specifier.imported.type === 'Identifier' &&
|
|
403
|
+
specifier.imported.name === 'loadFont') {
|
|
404
|
+
loadFontLocalNamesToRemove.add((_c = (_a = specifier.local) === null || _a === void 0 ? void 0 : _a.name) !== null && _c !== void 0 ? _c : 'loadFont');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (loadFontLocalNamesToRemove.size === 0) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
ast.program.body = ast.program.body.filter((statement) => {
|
|
412
|
+
for (const localName of loadFontLocalNamesToRemove) {
|
|
413
|
+
if (isTopLevelCallToIdentifier({ statement, localName }) &&
|
|
414
|
+
hasStudioGeneratedGoogleFontLoadingComment(statement)) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return true;
|
|
419
|
+
});
|
|
420
|
+
ast.program.body = ast.program.body.filter((statement) => {
|
|
421
|
+
var _a;
|
|
422
|
+
if (statement.type !== 'ImportDeclaration') {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
statement.specifiers = ((_a = statement.specifiers) !== null && _a !== void 0 ? _a : []).filter((specifier) => {
|
|
426
|
+
var _a, _b;
|
|
427
|
+
var _c, _d;
|
|
428
|
+
return !(specifier.type === 'ImportSpecifier' &&
|
|
429
|
+
specifier.imported.type === 'Identifier' &&
|
|
430
|
+
specifier.imported.name === 'loadFont' &&
|
|
431
|
+
loadFontLocalNamesToRemove.has((_c = (_a = specifier.local) === null || _a === void 0 ? void 0 : _a.name) !== null && _c !== void 0 ? _c : 'loadFont') &&
|
|
432
|
+
!hasNonImportIdentifierReference({
|
|
433
|
+
ast,
|
|
434
|
+
name: (_d = (_b = specifier.local) === null || _b === void 0 ? void 0 : _b.name) !== null && _d !== void 0 ? _d : 'loadFont',
|
|
435
|
+
}));
|
|
436
|
+
});
|
|
437
|
+
return statement.specifiers.length > 0;
|
|
438
|
+
});
|
|
439
|
+
};
|
|
440
|
+
const insertLoadFontCall = ({ ast, font, localName, }) => {
|
|
441
|
+
if (hasTopLevelLoadFontCall({ ast, localName, font })) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const call = b.expressionStatement(b.callExpression(b.identifier(localName), [
|
|
445
|
+
b.stringLiteral(font.style),
|
|
446
|
+
b.objectExpression([
|
|
447
|
+
b.objectProperty(b.identifier('weights'), b.arrayExpression(font.weights.map((weight) => b.stringLiteral(weight)))),
|
|
448
|
+
b.objectProperty(b.identifier('subsets'), b.arrayExpression(font.subsets.map((subset) => b.stringLiteral(subset)))),
|
|
449
|
+
]),
|
|
450
|
+
]));
|
|
451
|
+
call.comments = [b.commentLine(` ${googleFontLoadingComment}`)];
|
|
452
|
+
insertAfterImportsOrDirectives(ast, call);
|
|
453
|
+
};
|
|
454
|
+
const applyGoogleFontSourceEdits = ({ ast, updates, }) => {
|
|
455
|
+
const fonts = new Map();
|
|
456
|
+
let hasFontFamilyUpdate = false;
|
|
457
|
+
for (const update of updates) {
|
|
458
|
+
if (update.key === 'style.fontFamily') {
|
|
459
|
+
hasFontFamilyUpdate = true;
|
|
460
|
+
}
|
|
461
|
+
if (update.googleFont) {
|
|
462
|
+
fonts.set(update.googleFont.importName, update.googleFont);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const usedNames = collectTopLevelIdentifierNames(ast);
|
|
466
|
+
for (const font of fonts.values()) {
|
|
467
|
+
const localName = ensureLoadFontImport({ ast, font, usedNames });
|
|
468
|
+
insertLoadFontCall({ ast, font, localName });
|
|
469
|
+
}
|
|
470
|
+
if (hasFontFamilyUpdate) {
|
|
471
|
+
removeUnusedGoogleFontSourceEdits(ast);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
137
474
|
const updateSequencePropsNode = ({ jsxElement, updates, schema, }) => {
|
|
138
475
|
var _a, _b, _c;
|
|
139
476
|
var _d, _e;
|
|
@@ -152,8 +489,9 @@ const updateSequencePropsNode = ({ jsxElement, updates, schema, }) => {
|
|
|
152
489
|
oldValueStrings.push(oldValueString);
|
|
153
490
|
continue;
|
|
154
491
|
}
|
|
155
|
-
const isDefault = defaultValue
|
|
156
|
-
|
|
492
|
+
const isDefault = (defaultValue === null && value === undefined) ||
|
|
493
|
+
(defaultValue !== null &&
|
|
494
|
+
JSON.stringify(value) === JSON.stringify(defaultValue));
|
|
157
495
|
const dotIndex = key.indexOf('.');
|
|
158
496
|
const isNested = dotIndex !== -1;
|
|
159
497
|
const parentKey = isNested ? key.slice(0, dotIndex) : key;
|
|
@@ -261,6 +599,7 @@ const updateSequencePropsAst = ({ input, nodePath, updates, schema, }) => {
|
|
|
261
599
|
updates,
|
|
262
600
|
schema,
|
|
263
601
|
});
|
|
602
|
+
applyGoogleFontSourceEdits({ ast, updates });
|
|
264
603
|
return {
|
|
265
604
|
serialized: (0, parse_ast_1.serializeAst)(ast),
|
|
266
605
|
oldValueStrings,
|
|
@@ -271,13 +610,16 @@ const updateSequencePropsAst = ({ input, nodePath, updates, schema, }) => {
|
|
|
271
610
|
exports.updateSequencePropsAst = updateSequencePropsAst;
|
|
272
611
|
const updateMultipleSequenceProps = async ({ input, changes, prettierConfigOverride, }) => {
|
|
273
612
|
const ast = (0, parse_ast_1.parseAst)(input);
|
|
613
|
+
const allUpdates = [];
|
|
274
614
|
const results = changes.map(({ nodePath, updates, schema }) => {
|
|
275
615
|
const jsxElement = (0, can_update_sequence_props_1.findJsxElementNodeAtNodePath)(ast, nodePath);
|
|
276
616
|
if (!jsxElement) {
|
|
277
617
|
throw new Error('Could not find a JSX element at the specified line to update');
|
|
278
618
|
}
|
|
619
|
+
allUpdates.push(...updates);
|
|
279
620
|
return updateSequencePropsNode({ jsxElement, updates, schema });
|
|
280
621
|
});
|
|
622
|
+
applyGoogleFontSourceEdits({ ast, updates: allUpdates });
|
|
281
623
|
const { output, formatted } = await (0, format_file_content_1.formatFileContent)({
|
|
282
624
|
input: (0, parse_ast_1.serializeAst)(ast),
|
|
283
625
|
prettierConfigOverride,
|