@osmo.inc/cli-linux-arm64 0.1.21 → 0.1.22
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/studio.js +516 -366
- package/package.json +1 -1
- package/release.json +32 -32
- package/studio/src/InterDisplay-Bold-MWURHEXQ.ttf +0 -0
- package/studio/src/InterDisplay-Medium-HWBE5DYQ.ttf +0 -0
- package/studio/src/InterDisplay-Regular-H4SUAKN5.ttf +0 -0
- package/studio/src/InterDisplay-SemiBold-LUGMSURW.ttf +0 -0
- package/studio/src/app.css +1 -1
- package/studio/src/app.js +193 -160
- package/vendor/platform.json +1 -1
package/dist/studio.js
CHANGED
|
@@ -895,6 +895,94 @@ var init_comments = __esm({
|
|
|
895
895
|
}
|
|
896
896
|
});
|
|
897
897
|
|
|
898
|
+
// src/project-scenes.ts
|
|
899
|
+
import fs2 from "node:fs/promises";
|
|
900
|
+
import path14 from "node:path";
|
|
901
|
+
import ts4 from "typescript";
|
|
902
|
+
async function listProjectScenes(root) {
|
|
903
|
+
const scenes = [];
|
|
904
|
+
const errors = [];
|
|
905
|
+
const ignored = /* @__PURE__ */ new Set([
|
|
906
|
+
"node_modules",
|
|
907
|
+
"dist",
|
|
908
|
+
"build",
|
|
909
|
+
"target",
|
|
910
|
+
"coverage",
|
|
911
|
+
"references",
|
|
912
|
+
"video-references"
|
|
913
|
+
]);
|
|
914
|
+
const relative = (file) => path14.relative(root, file).split(path14.sep).join("/");
|
|
915
|
+
async function scan(folder, depth) {
|
|
916
|
+
if (depth > 16) {
|
|
917
|
+
errors.push(`${relative(folder)}: folder nesting exceeds the scan limit`);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
let entries;
|
|
921
|
+
try {
|
|
922
|
+
entries = await fs2.readdir(folder, { withFileTypes: true });
|
|
923
|
+
} catch (error) {
|
|
924
|
+
errors.push(`${relative(folder) || "."}: ${error.message}`);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
for (const item of entries) {
|
|
928
|
+
if (item.name.startsWith(".") || ignored.has(item.name) || item.isSymbolicLink())
|
|
929
|
+
continue;
|
|
930
|
+
const file = path14.join(folder, item.name);
|
|
931
|
+
if (item.isDirectory()) {
|
|
932
|
+
await scan(file, depth + 1);
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (!item.isFile() || !item.name.endsWith(".tsx")) continue;
|
|
936
|
+
const entry = relative(file);
|
|
937
|
+
try {
|
|
938
|
+
if ((await fs2.stat(file)).size > 1024 * 1024) {
|
|
939
|
+
errors.push(`${entry}: file exceeds the 1 MB scan limit`);
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
const text = await fs2.readFile(file, "utf8");
|
|
943
|
+
const ast = ts4.createSourceFile(
|
|
944
|
+
entry,
|
|
945
|
+
text,
|
|
946
|
+
ts4.ScriptTarget.Latest,
|
|
947
|
+
true,
|
|
948
|
+
ts4.ScriptKind.TSX
|
|
949
|
+
);
|
|
950
|
+
let node = ast.statements.find(
|
|
951
|
+
(statement) => ts4.isExportAssignment(statement) && !statement.isExportEquals
|
|
952
|
+
)?.expression;
|
|
953
|
+
while (node && ts4.isParenthesizedExpression(node))
|
|
954
|
+
node = node.expression;
|
|
955
|
+
const head = node && (ts4.isJsxElement(node) ? node.openingElement : ts4.isJsxSelfClosingElement(node) ? node : null);
|
|
956
|
+
if (head?.tagName.getText(ast) !== "Scene") continue;
|
|
957
|
+
const scene = { path: entry };
|
|
958
|
+
for (const attribute of head.attributes.properties) {
|
|
959
|
+
if (!ts4.isJsxAttribute(attribute) || !ts4.isIdentifier(attribute.name))
|
|
960
|
+
continue;
|
|
961
|
+
const name = attribute.name.text;
|
|
962
|
+
if (!["width", "height", "duration", "fps"].includes(name)) continue;
|
|
963
|
+
const value = attribute.initializer && ts4.isJsxExpression(attribute.initializer) ? attribute.initializer.expression : attribute.initializer;
|
|
964
|
+
if (value && ts4.isNumericLiteral(value) && Number.isFinite(Number(value.text)) && Number(value.text) > 0)
|
|
965
|
+
scene[name] = Number(
|
|
966
|
+
value.text
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
scenes.push(scene);
|
|
970
|
+
} catch (error) {
|
|
971
|
+
errors.push(`${entry}: ${error.message}`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
await scan(root, 0);
|
|
976
|
+
scenes.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
977
|
+
errors.sort();
|
|
978
|
+
return { scenes, errors };
|
|
979
|
+
}
|
|
980
|
+
var init_project_scenes = __esm({
|
|
981
|
+
"src/project-scenes.ts"() {
|
|
982
|
+
"use strict";
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
|
|
898
986
|
// src/asset-response.ts
|
|
899
987
|
import { pipeline } from "node:stream/promises";
|
|
900
988
|
async function streamAsset(req, res, asset, contentType) {
|
|
@@ -1005,29 +1093,29 @@ var init_assets = __esm({
|
|
|
1005
1093
|
});
|
|
1006
1094
|
|
|
1007
1095
|
// src/asset-import.ts
|
|
1008
|
-
import
|
|
1009
|
-
import
|
|
1096
|
+
import fs3 from "node:fs/promises";
|
|
1097
|
+
import path15 from "node:path";
|
|
1010
1098
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
1011
1099
|
async function importProjectAsset(root, stream, name, kind, limit = Infinity) {
|
|
1012
1100
|
if (!Object.hasOwn(assetExtensions, kind))
|
|
1013
1101
|
throw new Error("Unknown asset kind");
|
|
1014
1102
|
if (!name || name.length > 255 || /[/\\\x00-\x1f]/.test(name))
|
|
1015
1103
|
throw new Error("Invalid asset filename");
|
|
1016
|
-
const extension =
|
|
1104
|
+
const extension = path15.extname(name).slice(1).toLowerCase();
|
|
1017
1105
|
if (!assetExtensions[kind].includes(extension))
|
|
1018
1106
|
throw new Error(
|
|
1019
1107
|
`Expected a ${kind} file (${assetExtensions[kind].join(", ")})`
|
|
1020
1108
|
);
|
|
1021
|
-
const folder =
|
|
1022
|
-
await
|
|
1109
|
+
const folder = path15.join(root, "assets");
|
|
1110
|
+
await fs3.mkdir(folder).catch((error) => {
|
|
1023
1111
|
if (error.code !== "EEXIST") throw error;
|
|
1024
1112
|
});
|
|
1025
1113
|
const directory = sceneInProject(root, "assets").source;
|
|
1026
|
-
const stem =
|
|
1114
|
+
const stem = path15.basename(name, path15.extname(name)).normalize("NFKC").replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "asset";
|
|
1027
1115
|
const filename2 = `${stem}-${randomUUID5()}.${extension}`;
|
|
1028
|
-
const target =
|
|
1029
|
-
const temp =
|
|
1030
|
-
const file = await
|
|
1116
|
+
const target = path15.join(directory, filename2);
|
|
1117
|
+
const temp = path15.join(directory, `.${filename2}.upload`);
|
|
1118
|
+
const file = await fs3.open(temp, "wx", 384);
|
|
1031
1119
|
let size = 0;
|
|
1032
1120
|
try {
|
|
1033
1121
|
for await (const chunk of stream) {
|
|
@@ -1039,11 +1127,11 @@ async function importProjectAsset(root, stream, name, kind, limit = Infinity) {
|
|
|
1039
1127
|
}
|
|
1040
1128
|
if (!size) throw new Error("Asset file is empty");
|
|
1041
1129
|
await file.close();
|
|
1042
|
-
await
|
|
1043
|
-
return
|
|
1130
|
+
await fs3.link(temp, target);
|
|
1131
|
+
return path15.relative(await fs3.realpath(root), target).split(path15.sep).join("/");
|
|
1044
1132
|
} finally {
|
|
1045
1133
|
await file.close();
|
|
1046
|
-
await
|
|
1134
|
+
await fs3.rm(temp, { force: true });
|
|
1047
1135
|
}
|
|
1048
1136
|
}
|
|
1049
1137
|
var init_asset_import = __esm({
|
|
@@ -1271,49 +1359,49 @@ var init_storyboard = __esm({
|
|
|
1271
1359
|
});
|
|
1272
1360
|
|
|
1273
1361
|
// src/storyboard.ts
|
|
1274
|
-
import
|
|
1275
|
-
import
|
|
1362
|
+
import fs4 from "node:fs/promises";
|
|
1363
|
+
import path16 from "node:path";
|
|
1276
1364
|
import { createHash as createHash4, randomUUID as randomUUID6 } from "node:crypto";
|
|
1277
|
-
import
|
|
1365
|
+
import ts5 from "typescript";
|
|
1278
1366
|
function literal(n) {
|
|
1279
1367
|
if (!n) return void 0;
|
|
1280
|
-
if (
|
|
1281
|
-
return
|
|
1282
|
-
if (n.kind ===
|
|
1283
|
-
if (n.kind ===
|
|
1284
|
-
if (
|
|
1368
|
+
if (ts5.isStringLiteral(n) || ts5.isNumericLiteral(n))
|
|
1369
|
+
return ts5.isNumericLiteral(n) ? Number(n.text) : n.text;
|
|
1370
|
+
if (n.kind === ts5.SyntaxKind.TrueKeyword) return true;
|
|
1371
|
+
if (n.kind === ts5.SyntaxKind.FalseKeyword) return false;
|
|
1372
|
+
if (ts5.isPrefixUnaryExpression(n) && n.operator === ts5.SyntaxKind.MinusToken)
|
|
1285
1373
|
return -literal(n.operand);
|
|
1286
|
-
if (
|
|
1287
|
-
if (
|
|
1374
|
+
if (ts5.isArrayLiteralExpression(n)) return n.elements.map(literal);
|
|
1375
|
+
if (ts5.isObjectLiteralExpression(n))
|
|
1288
1376
|
return Object.fromEntries(
|
|
1289
|
-
n.properties.filter(
|
|
1377
|
+
n.properties.filter(ts5.isPropertyAssignment).map((p) => [p.name.text, literal(p.initializer)])
|
|
1290
1378
|
);
|
|
1291
1379
|
}
|
|
1292
1380
|
async function groupInfo(source, relative) {
|
|
1293
|
-
const text = await
|
|
1381
|
+
const text = await fs4.readFile(source, "utf8"), ast = ts5.createSourceFile(
|
|
1294
1382
|
source,
|
|
1295
1383
|
text,
|
|
1296
|
-
|
|
1384
|
+
ts5.ScriptTarget.Latest,
|
|
1297
1385
|
true,
|
|
1298
|
-
|
|
1386
|
+
ts5.ScriptKind.TSX
|
|
1299
1387
|
);
|
|
1300
|
-
let root = ast.statements.find(
|
|
1301
|
-
while (root &&
|
|
1302
|
-
const opening3 = root && (
|
|
1388
|
+
let root = ast.statements.find(ts5.isExportAssignment)?.expression;
|
|
1389
|
+
while (root && ts5.isParenthesizedExpression(root)) root = root.expression;
|
|
1390
|
+
const opening3 = root && (ts5.isJsxElement(root) ? root.openingElement : ts5.isJsxSelfClosingElement(root) ? root : null);
|
|
1303
1391
|
if (opening3?.tagName.getText(ast) !== "Group") return null;
|
|
1304
1392
|
const attrs = new Map(
|
|
1305
|
-
opening3.attributes.properties.filter(
|
|
1393
|
+
opening3.attributes.properties.filter(ts5.isJsxAttribute).map((p) => [
|
|
1306
1394
|
// A namespaced name (a:b) reads as undefined.
|
|
1307
1395
|
p.name.text,
|
|
1308
|
-
p.initializer && (
|
|
1396
|
+
p.initializer && (ts5.isJsxExpression(p.initializer) ? p.initializer.expression : p.initializer)
|
|
1309
1397
|
])
|
|
1310
1398
|
);
|
|
1311
1399
|
const knobName = attrs.get("knobs")?.getText(ast);
|
|
1312
1400
|
let knobs = {};
|
|
1313
1401
|
for (const statement of ast.statements)
|
|
1314
|
-
if (
|
|
1402
|
+
if (ts5.isVariableStatement(statement))
|
|
1315
1403
|
for (const d of statement.declarationList.declarations) {
|
|
1316
|
-
if (d.name.getText(ast) === knobName && d.initializer &&
|
|
1404
|
+
if (d.name.getText(ast) === knobName && d.initializer && ts5.isCallExpression(d.initializer) && d.initializer.expression.getText(ast) === "defineKnobs")
|
|
1317
1405
|
knobs = literal(d.initializer.arguments[0]) ?? {};
|
|
1318
1406
|
}
|
|
1319
1407
|
const defaults = literal(attrs.get("values")) ?? {};
|
|
@@ -1325,7 +1413,7 @@ async function groupInfo(source, relative) {
|
|
|
1325
1413
|
};
|
|
1326
1414
|
}
|
|
1327
1415
|
function createStoryboard(root) {
|
|
1328
|
-
const filename2 =
|
|
1416
|
+
const filename2 = path16.join(root, "storyboard.canvas.json"), cache2 = path16.join(root, ".osmo/cache/storyboard");
|
|
1329
1417
|
let queue = Promise.resolve(), undo = [], redo = [], knownRevision, activeRenderer, closed = false;
|
|
1330
1418
|
const serial = (fn) => {
|
|
1331
1419
|
const result = queue.then(fn);
|
|
@@ -1336,7 +1424,7 @@ function createStoryboard(root) {
|
|
|
1336
1424
|
async function read() {
|
|
1337
1425
|
let document;
|
|
1338
1426
|
try {
|
|
1339
|
-
document = validateBoard(JSON.parse(await
|
|
1427
|
+
document = validateBoard(JSON.parse(await fs4.readFile(filename2, "utf8")));
|
|
1340
1428
|
} catch (error) {
|
|
1341
1429
|
if (error.code !== "ENOENT") throw error;
|
|
1342
1430
|
document = structuredClone(empty);
|
|
@@ -1353,17 +1441,17 @@ function createStoryboard(root) {
|
|
|
1353
1441
|
const result = [];
|
|
1354
1442
|
async function scan(dir, depth = 0) {
|
|
1355
1443
|
if (depth > 8 || result.length >= 100) return;
|
|
1356
|
-
for (const e of await
|
|
1444
|
+
for (const e of await fs4.readdir(dir, { withFileTypes: true })) {
|
|
1357
1445
|
if (e.name.startsWith(".") || ["node_modules", "exports", "dist", "target", "assets"].includes(
|
|
1358
1446
|
e.name
|
|
1359
1447
|
))
|
|
1360
1448
|
continue;
|
|
1361
|
-
const absolute =
|
|
1449
|
+
const absolute = path16.join(dir, e.name);
|
|
1362
1450
|
if (e.isDirectory()) await scan(absolute, depth + 1);
|
|
1363
1451
|
else if (e.isFile() && e.name.endsWith(".tsx")) {
|
|
1364
1452
|
const info2 = await groupInfo(
|
|
1365
1453
|
absolute,
|
|
1366
|
-
|
|
1454
|
+
path16.relative(root, absolute).split(path16.sep).join("/")
|
|
1367
1455
|
);
|
|
1368
1456
|
if (info2) result.push(info2);
|
|
1369
1457
|
}
|
|
@@ -1397,10 +1485,10 @@ function createStoryboard(root) {
|
|
|
1397
1485
|
async function write(document) {
|
|
1398
1486
|
const temp = `${filename2}.${randomUUID6()}.tmp`;
|
|
1399
1487
|
try {
|
|
1400
|
-
await
|
|
1401
|
-
await
|
|
1488
|
+
await fs4.writeFile(temp, JSON.stringify(document, null, 2) + "\n");
|
|
1489
|
+
await fs4.rename(temp, filename2);
|
|
1402
1490
|
} finally {
|
|
1403
|
-
await
|
|
1491
|
+
await fs4.rm(temp, { force: true });
|
|
1404
1492
|
}
|
|
1405
1493
|
knownRevision = hash(JSON.stringify(document));
|
|
1406
1494
|
}
|
|
@@ -1452,8 +1540,8 @@ function createStoryboard(root) {
|
|
|
1452
1540
|
await info(card);
|
|
1453
1541
|
const { source } = sceneInProject(root, card.source), validation = await check(source);
|
|
1454
1542
|
if (!validation.ok) throw new Error(validation.errors.join("\n"));
|
|
1455
|
-
await
|
|
1456
|
-
const name = `${randomUUID6()}.mp4`, destination =
|
|
1543
|
+
await fs4.mkdir(cache2, { recursive: true });
|
|
1544
|
+
const name = `${randomUUID6()}.mp4`, destination = path16.join(cache2, name);
|
|
1457
1545
|
const renderer = new Renderer({
|
|
1458
1546
|
preview: {
|
|
1459
1547
|
width: card.width,
|
|
@@ -1469,7 +1557,7 @@ function createStoryboard(root) {
|
|
|
1469
1557
|
await renderer.request("export", { destination, supersample: 1 });
|
|
1470
1558
|
return { name, revision: current.revision };
|
|
1471
1559
|
} catch (error) {
|
|
1472
|
-
await
|
|
1560
|
+
await fs4.rm(destination, { force: true });
|
|
1473
1561
|
throw error;
|
|
1474
1562
|
} finally {
|
|
1475
1563
|
renderer.close();
|
|
@@ -1479,7 +1567,7 @@ function createStoryboard(root) {
|
|
|
1479
1567
|
video: async (name) => {
|
|
1480
1568
|
if (!/^[0-9a-f-]{36}\.mp4$/.test(name))
|
|
1481
1569
|
throw new Error("Invalid preview");
|
|
1482
|
-
return
|
|
1570
|
+
return fs4.readFile(path16.join(cache2, name));
|
|
1483
1571
|
},
|
|
1484
1572
|
close() {
|
|
1485
1573
|
closed = true;
|
|
@@ -1503,10 +1591,10 @@ var init_storyboard2 = __esm({
|
|
|
1503
1591
|
// src/asset-metadata.ts
|
|
1504
1592
|
import { execFile as execFile4 } from "node:child_process";
|
|
1505
1593
|
import { promisify as promisify4 } from "node:util";
|
|
1506
|
-
import
|
|
1594
|
+
import path17 from "node:path";
|
|
1507
1595
|
async function inspectAsset(file, size) {
|
|
1508
1596
|
const metadata = { size };
|
|
1509
|
-
const extension =
|
|
1597
|
+
const extension = path17.extname(file).slice(1).toLowerCase();
|
|
1510
1598
|
const image = ["png", "jpg", "jpeg", "webp", "gif"].includes(extension);
|
|
1511
1599
|
const video = ["mp4", "webm", "mov", "m4v"].includes(extension);
|
|
1512
1600
|
const audio = ["wav", "mp3", "m4a", "aac", "ogg", "flac"].includes(extension);
|
|
@@ -1566,9 +1654,9 @@ var init_asset_metadata = __esm({
|
|
|
1566
1654
|
|
|
1567
1655
|
// src/video-normalization.ts
|
|
1568
1656
|
import { execFile as execFile5 } from "node:child_process";
|
|
1569
|
-
import
|
|
1657
|
+
import fs5 from "node:fs/promises";
|
|
1570
1658
|
import os3 from "node:os";
|
|
1571
|
-
import
|
|
1659
|
+
import path18 from "node:path";
|
|
1572
1660
|
import { promisify as promisify5 } from "node:util";
|
|
1573
1661
|
function createVideoNormalizer() {
|
|
1574
1662
|
let directory;
|
|
@@ -1590,8 +1678,8 @@ function createVideoNormalizer() {
|
|
|
1590
1678
|
const task = remuxQueue.then(async () => {
|
|
1591
1679
|
if (closed) throw new Error("Video storage is closed");
|
|
1592
1680
|
if (cache2.has(identity2)) return;
|
|
1593
|
-
directory ??= await
|
|
1594
|
-
const file =
|
|
1681
|
+
directory ??= await fs5.mkdtemp(path18.join(os3.tmpdir(), "osmo-video-"));
|
|
1682
|
+
const file = path18.join(directory, `${sequence++}.mp4`);
|
|
1595
1683
|
try {
|
|
1596
1684
|
await exec5(
|
|
1597
1685
|
process.env.OSMO_FFMPEG ?? "ffmpeg",
|
|
@@ -1617,7 +1705,7 @@ function createVideoNormalizer() {
|
|
|
1617
1705
|
],
|
|
1618
1706
|
{ timeout: 6e5, maxBuffer: 1024 * 1024 }
|
|
1619
1707
|
);
|
|
1620
|
-
const size = (await
|
|
1708
|
+
const size = (await fs5.stat(file)).size;
|
|
1621
1709
|
if (!size) throw new Error("FFmpeg produced an empty playback copy");
|
|
1622
1710
|
await files(async () => {
|
|
1623
1711
|
cache2.set(identity2, { file, size });
|
|
@@ -1626,12 +1714,12 @@ function createVideoNormalizer() {
|
|
|
1626
1714
|
const [key, copy] = cache2.entries().next().value;
|
|
1627
1715
|
cache2.delete(key);
|
|
1628
1716
|
bytes -= copy.size;
|
|
1629
|
-
await
|
|
1717
|
+
await fs5.rm(copy.file, { force: true });
|
|
1630
1718
|
}
|
|
1631
1719
|
});
|
|
1632
1720
|
} catch (cause) {
|
|
1633
1721
|
cache2.delete(identity2);
|
|
1634
|
-
await
|
|
1722
|
+
await fs5.rm(file, { force: true });
|
|
1635
1723
|
const error = cause;
|
|
1636
1724
|
const detail = error.code === "ENOENT" ? "FFmpeg is not installed or cannot be found" : error.killed ? "FFmpeg exceeded the 10-minute preparation timeout" : error.stderr?.trim() || error.message || "Unknown FFmpeg error";
|
|
1637
1725
|
throw new Error(
|
|
@@ -1655,7 +1743,7 @@ function createVideoNormalizer() {
|
|
|
1655
1743
|
if (!cached) return null;
|
|
1656
1744
|
cache2.delete(identity2);
|
|
1657
1745
|
cache2.set(identity2, cached);
|
|
1658
|
-
return { handle: await
|
|
1746
|
+
return { handle: await fs5.open(cached.file, "r"), size: cached.size };
|
|
1659
1747
|
});
|
|
1660
1748
|
if (opened) return opened;
|
|
1661
1749
|
await prepare(source, identity2);
|
|
@@ -1673,7 +1761,7 @@ function createVideoNormalizer() {
|
|
|
1673
1761
|
closed = true;
|
|
1674
1762
|
await remuxQueue;
|
|
1675
1763
|
await fileQueue;
|
|
1676
|
-
if (directory) await
|
|
1764
|
+
if (directory) await fs5.rm(directory, { recursive: true, force: true });
|
|
1677
1765
|
cache2.clear();
|
|
1678
1766
|
}
|
|
1679
1767
|
return { open, read, close };
|
|
@@ -1819,17 +1907,17 @@ var init_document_assets = __esm({
|
|
|
1819
1907
|
|
|
1820
1908
|
// ../editor/src/runtime-paths.ts
|
|
1821
1909
|
function withRuntimePaths(root) {
|
|
1822
|
-
function visit(node,
|
|
1910
|
+
function visit(node, path26) {
|
|
1823
1911
|
if (!isSceneNode(node)) return node;
|
|
1824
|
-
if (node.props.hidden === true)
|
|
1912
|
+
if (node.props.hidden === true) path26 = null;
|
|
1825
1913
|
let index = 0;
|
|
1826
1914
|
return {
|
|
1827
1915
|
...node,
|
|
1828
|
-
runtimePath:
|
|
1916
|
+
runtimePath: path26,
|
|
1829
1917
|
children: node.children.map((child) => {
|
|
1830
1918
|
const hidden = isSceneNode(child) && (child.props.hidden === true || node.type === "Scene" && node.props.tracks?.[child.props.track]?.hidden === true);
|
|
1831
|
-
const omitted = !isSceneNode(child) || hidden ||
|
|
1832
|
-
return visit(child, omitted ? null : [
|
|
1919
|
+
const omitted = !isSceneNode(child) || hidden || path26 === null || node.type === "Model" || child.type === "Camera";
|
|
1920
|
+
return visit(child, omitted ? null : [path26, index++].filter((p) => p !== "").join("."));
|
|
1833
1921
|
})
|
|
1834
1922
|
};
|
|
1835
1923
|
}
|
|
@@ -1842,6 +1930,43 @@ var init_runtime_paths = __esm({
|
|
|
1842
1930
|
}
|
|
1843
1931
|
});
|
|
1844
1932
|
|
|
1933
|
+
// ../editor/src/module-parse.ts
|
|
1934
|
+
import ts6 from "typescript";
|
|
1935
|
+
function parseModule(file, text) {
|
|
1936
|
+
const last = holders ? held.get(file) : void 0;
|
|
1937
|
+
if (last) {
|
|
1938
|
+
held.delete(file);
|
|
1939
|
+
heldText -= last.text.length;
|
|
1940
|
+
}
|
|
1941
|
+
const tree = last?.text === text ? last.tree : ts6.createSourceFile(
|
|
1942
|
+
file,
|
|
1943
|
+
text,
|
|
1944
|
+
ts6.ScriptTarget.Latest,
|
|
1945
|
+
true,
|
|
1946
|
+
ts6.ScriptKind.TSX
|
|
1947
|
+
);
|
|
1948
|
+
if (holders) {
|
|
1949
|
+
held.set(file, { text, tree });
|
|
1950
|
+
heldText += text.length;
|
|
1951
|
+
for (const [path26, entry] of held) {
|
|
1952
|
+
if (heldText <= HELD_TEXT) break;
|
|
1953
|
+
held.delete(path26);
|
|
1954
|
+
heldText -= entry.text.length;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
return tree;
|
|
1958
|
+
}
|
|
1959
|
+
var held, holders, heldText, HELD_TEXT;
|
|
1960
|
+
var init_module_parse = __esm({
|
|
1961
|
+
"../editor/src/module-parse.ts"() {
|
|
1962
|
+
"use strict";
|
|
1963
|
+
held = /* @__PURE__ */ new Map();
|
|
1964
|
+
holders = 0;
|
|
1965
|
+
heldText = 0;
|
|
1966
|
+
HELD_TEXT = 16 * 1024 * 1024;
|
|
1967
|
+
}
|
|
1968
|
+
});
|
|
1969
|
+
|
|
1845
1970
|
// ../editor/src/source-path.ts
|
|
1846
1971
|
function normalize(value) {
|
|
1847
1972
|
const parts = [];
|
|
@@ -1862,24 +1987,24 @@ var init_source_path = __esm({
|
|
|
1862
1987
|
});
|
|
1863
1988
|
|
|
1864
1989
|
// ../editor/src/scene-document.ts
|
|
1865
|
-
import
|
|
1990
|
+
import ts7 from "typescript";
|
|
1866
1991
|
function decodeJsxLiteral(raw, attribute = false) {
|
|
1867
|
-
const code =
|
|
1992
|
+
const code = ts7.transpileModule(
|
|
1868
1993
|
attribute ? `const x=<Text value=${raw}/>;` : `const x=<Text>${raw}</Text>;`,
|
|
1869
|
-
{ compilerOptions: { jsx:
|
|
1994
|
+
{ compilerOptions: { jsx: ts7.JsxEmit.ReactJSX } }
|
|
1870
1995
|
).outputText;
|
|
1871
|
-
const ast =
|
|
1996
|
+
const ast = ts7.createSourceFile(
|
|
1872
1997
|
"literal.js",
|
|
1873
1998
|
code,
|
|
1874
|
-
|
|
1999
|
+
ts7.ScriptTarget.Latest,
|
|
1875
2000
|
true,
|
|
1876
|
-
|
|
2001
|
+
ts7.ScriptKind.JS
|
|
1877
2002
|
);
|
|
1878
2003
|
let result = "";
|
|
1879
2004
|
function visit(n) {
|
|
1880
|
-
if (
|
|
2005
|
+
if (ts7.isPropertyAssignment(n) && n.name.text === (attribute ? "value" : "children") && ts7.isStringLiteral(n.initializer))
|
|
1881
2006
|
result = n.initializer.text;
|
|
1882
|
-
|
|
2007
|
+
ts7.forEachChild(n, visit);
|
|
1883
2008
|
}
|
|
1884
2009
|
visit(ast);
|
|
1885
2010
|
return result;
|
|
@@ -1893,13 +2018,7 @@ function createSceneDocument(entry, files) {
|
|
|
1893
2018
|
const text = files[file];
|
|
1894
2019
|
if (typeof text !== "string")
|
|
1895
2020
|
throw new Error(`Missing animation source: ${file}`);
|
|
1896
|
-
const ast =
|
|
1897
|
-
file,
|
|
1898
|
-
text,
|
|
1899
|
-
ts5.ScriptTarget.Latest,
|
|
1900
|
-
true,
|
|
1901
|
-
ts5.ScriptKind.TSX
|
|
1902
|
-
);
|
|
2021
|
+
const ast = parseModule(file, text);
|
|
1903
2022
|
if (ast.parseDiagnostics.length) throw new Error(`Cannot parse ${file}`);
|
|
1904
2023
|
const imports = {}, locals = /* @__PURE__ */ new Set();
|
|
1905
2024
|
const location = (n) => ({
|
|
@@ -1914,32 +2033,32 @@ function createSceneDocument(entry, files) {
|
|
|
1914
2033
|
};
|
|
1915
2034
|
function read(n) {
|
|
1916
2035
|
if (!n) return true;
|
|
1917
|
-
if (
|
|
2036
|
+
if (ts7.isParenthesizedExpression(n) || ts7.isJsxExpression(n))
|
|
1918
2037
|
return n.expression ? read(n.expression) : void 0;
|
|
1919
|
-
if (
|
|
1920
|
-
return
|
|
1921
|
-
if (n.kind ===
|
|
1922
|
-
if (n.kind ===
|
|
1923
|
-
if (n.kind ===
|
|
1924
|
-
if (
|
|
2038
|
+
if (ts7.isStringLiteral(n) || ts7.isNumericLiteral(n))
|
|
2039
|
+
return ts7.isNumericLiteral(n) ? Number(n.text) : ts7.isJsxAttribute(n.parent) && n.text.includes("&") ? decodeJsxLiteral(n.getText(ast), true) : n.text;
|
|
2040
|
+
if (n.kind === ts7.SyntaxKind.TrueKeyword) return true;
|
|
2041
|
+
if (n.kind === ts7.SyntaxKind.FalseKeyword) return false;
|
|
2042
|
+
if (n.kind === ts7.SyntaxKind.NullKeyword) return null;
|
|
2043
|
+
if (ts7.isPrefixUnaryExpression(n)) {
|
|
1925
2044
|
const v = read(n.operand);
|
|
1926
2045
|
if (typeof v !== "number") return unsupported(n);
|
|
1927
|
-
return n.operator ===
|
|
2046
|
+
return n.operator === ts7.SyntaxKind.MinusToken ? -v : v;
|
|
1928
2047
|
}
|
|
1929
|
-
if (
|
|
1930
|
-
if (
|
|
2048
|
+
if (ts7.isArrayLiteralExpression(n)) return n.elements.map(read);
|
|
2049
|
+
if (ts7.isObjectLiteralExpression(n))
|
|
1931
2050
|
return Object.fromEntries(
|
|
1932
2051
|
n.properties.map((p) => {
|
|
1933
|
-
if (!
|
|
2052
|
+
if (!ts7.isPropertyAssignment(p) || ts7.isComputedPropertyName(p.name))
|
|
1934
2053
|
return unsupported(p);
|
|
1935
2054
|
return [p.name.text, read(p.initializer)];
|
|
1936
2055
|
})
|
|
1937
2056
|
);
|
|
1938
|
-
if (
|
|
2057
|
+
if (ts7.isIdentifier(n) && locals.has(n.text))
|
|
1939
2058
|
return { $ref: `${file}#${n.text}` };
|
|
1940
|
-
if (
|
|
2059
|
+
if (ts7.isPropertyAccessExpression(n) && ts7.isIdentifier(n.expression) && locals.has(n.expression.text))
|
|
1941
2060
|
return { $ref: `${file}#${n.expression.text}`, name: n.name.text };
|
|
1942
|
-
if (
|
|
2061
|
+
if (ts7.isCallExpression(n) && (["blur", "grain"].includes(n.expression.getText(ast)) || bindings[`${file}#${n.expression.getText(ast)}`]?.kind === "effect"))
|
|
1943
2062
|
return {
|
|
1944
2063
|
$effect: bindings[`${file}#${n.expression.getText(ast)}`]?.kind === "effect" ? `${file}#${n.expression.getText(ast)}` : n.expression.getText(ast),
|
|
1945
2064
|
values: n.arguments[0] ? read(n.arguments[0]) : {}
|
|
@@ -1948,7 +2067,7 @@ function createSceneDocument(entry, files) {
|
|
|
1948
2067
|
}
|
|
1949
2068
|
let exported;
|
|
1950
2069
|
for (const st of ast.statements) {
|
|
1951
|
-
if (
|
|
2070
|
+
if (ts7.isImportDeclaration(st) && st.importClause?.name)
|
|
1952
2071
|
imports[st.importClause.name.text] = module(
|
|
1953
2072
|
resolveSceneImport(
|
|
1954
2073
|
file,
|
|
@@ -1956,10 +2075,10 @@ function createSceneDocument(entry, files) {
|
|
|
1956
2075
|
),
|
|
1957
2076
|
[...stack, file]
|
|
1958
2077
|
);
|
|
1959
|
-
if (
|
|
2078
|
+
if (ts7.isVariableStatement(st))
|
|
1960
2079
|
for (const d of st.declarationList.declarations) {
|
|
1961
2080
|
const call2 = d.initializer;
|
|
1962
|
-
if (!call2 || !
|
|
2081
|
+
if (!call2 || !ts7.isCallExpression(call2) || !["defineMotion", "defineKnobs", "defineEffect"].includes(
|
|
1963
2082
|
call2.expression.getText(ast)
|
|
1964
2083
|
))
|
|
1965
2084
|
return unsupported(d);
|
|
@@ -1971,12 +2090,12 @@ function createSceneDocument(entry, files) {
|
|
|
1971
2090
|
source: location(d)
|
|
1972
2091
|
};
|
|
1973
2092
|
}
|
|
1974
|
-
if (
|
|
2093
|
+
if (ts7.isExportAssignment(st)) exported = st.expression;
|
|
1975
2094
|
}
|
|
1976
2095
|
function jsx2(n) {
|
|
1977
|
-
if (
|
|
2096
|
+
if (ts7.isParenthesizedExpression(n) || ts7.isJsxExpression(n))
|
|
1978
2097
|
return n.expression ? jsx2(n.expression) : [];
|
|
1979
|
-
if (
|
|
2098
|
+
if (ts7.isJsxText(n)) {
|
|
1980
2099
|
if (n.text.includes("&")) return [decodeJsxLiteral(n.getText(ast))];
|
|
1981
2100
|
const lines = n.text.replace(/\r/g, "").split("\n"), parts = [];
|
|
1982
2101
|
lines.forEach((line2, i) => {
|
|
@@ -1987,17 +2106,17 @@ function createSceneDocument(entry, files) {
|
|
|
1987
2106
|
});
|
|
1988
2107
|
return parts.length ? [parts.join(" ")] : [];
|
|
1989
2108
|
}
|
|
1990
|
-
if (
|
|
1991
|
-
if (!
|
|
2109
|
+
if (ts7.isJsxFragment(n)) return n.children.flatMap(jsx2);
|
|
2110
|
+
if (!ts7.isJsxElement(n) && !ts7.isJsxSelfClosingElement(n))
|
|
1992
2111
|
return [read(n)];
|
|
1993
|
-
const opening3 =
|
|
2112
|
+
const opening3 = ts7.isJsxElement(n) ? n.openingElement : n, tag = opening3.tagName.getText(ast);
|
|
1994
2113
|
const props = Object.fromEntries(
|
|
1995
2114
|
opening3.attributes.properties.map((a) => {
|
|
1996
|
-
if (!
|
|
2115
|
+
if (!ts7.isJsxAttribute(a)) return unsupported(a);
|
|
1997
2116
|
return [a.name.text, read(a.initializer)];
|
|
1998
2117
|
})
|
|
1999
2118
|
);
|
|
2000
|
-
const children =
|
|
2119
|
+
const children = ts7.isJsxElement(n) ? n.children.flatMap(jsx2) : [];
|
|
2001
2120
|
if ("label" in props) {
|
|
2002
2121
|
if (tag !== "Group" && !imports[tag])
|
|
2003
2122
|
throw new Error("label belongs to Groups only");
|
|
@@ -2138,6 +2257,7 @@ var init_scene_document = __esm({
|
|
|
2138
2257
|
"../editor/src/scene-document.ts"() {
|
|
2139
2258
|
"use strict";
|
|
2140
2259
|
init_runtime_paths();
|
|
2260
|
+
init_module_parse();
|
|
2141
2261
|
init_source_path();
|
|
2142
2262
|
init_metadata();
|
|
2143
2263
|
init_types();
|
|
@@ -2374,10 +2494,10 @@ var init_document_outline = __esm({
|
|
|
2374
2494
|
});
|
|
2375
2495
|
|
|
2376
2496
|
// src/scene-document.ts
|
|
2377
|
-
import
|
|
2378
|
-
import
|
|
2497
|
+
import fs6 from "node:fs/promises";
|
|
2498
|
+
import path19 from "node:path";
|
|
2379
2499
|
import { createHash as createHash5 } from "node:crypto";
|
|
2380
|
-
import
|
|
2500
|
+
import ts8 from "typescript";
|
|
2381
2501
|
function createDocumentLoader(root) {
|
|
2382
2502
|
const videos = createVideoNormalizer();
|
|
2383
2503
|
const assets = /* @__PURE__ */ new Map();
|
|
@@ -2389,16 +2509,16 @@ function createDocumentLoader(root) {
|
|
|
2389
2509
|
throw new Error("Cyclic animation imports");
|
|
2390
2510
|
if (Object.hasOwn(files, file)) return;
|
|
2391
2511
|
const located = sceneInProject(root, file);
|
|
2392
|
-
files[file] = body3 ?? inputs.get(located.source) ?? await
|
|
2393
|
-
const ast =
|
|
2512
|
+
files[file] = body3 ?? inputs.get(located.source) ?? await fs6.readFile(located.source, "utf8");
|
|
2513
|
+
const ast = ts8.createSourceFile(
|
|
2394
2514
|
file,
|
|
2395
2515
|
files[file],
|
|
2396
|
-
|
|
2516
|
+
ts8.ScriptTarget.Latest,
|
|
2397
2517
|
true,
|
|
2398
|
-
|
|
2518
|
+
ts8.ScriptKind.TSX
|
|
2399
2519
|
);
|
|
2400
2520
|
for (const st of ast.statements)
|
|
2401
|
-
if (
|
|
2521
|
+
if (ts8.isImportDeclaration(st) && st.importClause?.name)
|
|
2402
2522
|
await read(
|
|
2403
2523
|
// A module specifier is a string literal.
|
|
2404
2524
|
resolveSceneImport(
|
|
@@ -2415,7 +2535,7 @@ function createDocumentLoader(root) {
|
|
|
2415
2535
|
document.assets = {};
|
|
2416
2536
|
try {
|
|
2417
2537
|
for (const ref of documentAssetReferences(document)) {
|
|
2418
|
-
const relative =
|
|
2538
|
+
const relative = path19.posix.normalize(path19.posix.join(path19.posix.dirname(ref.file), ref.value));
|
|
2419
2539
|
if (document.assets[relative]) continue;
|
|
2420
2540
|
try {
|
|
2421
2541
|
document.assets[relative] = await registerAsset(relative, ref.kind);
|
|
@@ -2430,7 +2550,7 @@ function createDocumentLoader(root) {
|
|
|
2430
2550
|
return { ...document, ...includeSources ? { sourceFiles: files } : {} };
|
|
2431
2551
|
}
|
|
2432
2552
|
async function registerAsset(relative, kind) {
|
|
2433
|
-
const located = sceneInProject(root, relative), info = await
|
|
2553
|
+
const located = sceneInProject(root, relative), info = await fs6.stat(located.source);
|
|
2434
2554
|
if (!info.isFile()) throw new Error(`Asset is not a file: ${relative}`);
|
|
2435
2555
|
const id = createHash5("sha256").update(located.source + "\0" + info.mtimeMs + "\0" + info.size).digest("hex");
|
|
2436
2556
|
assets.set(id, {
|
|
@@ -2441,7 +2561,7 @@ function createDocumentLoader(root) {
|
|
|
2441
2561
|
const asset = { id, kind };
|
|
2442
2562
|
if (kind === "shader") {
|
|
2443
2563
|
if (info.size > 1024 * 1024) throw new Error("Shader exceeds 1 MB");
|
|
2444
|
-
asset.code = await
|
|
2564
|
+
asset.code = await fs6.readFile(located.source, "utf8");
|
|
2445
2565
|
}
|
|
2446
2566
|
return asset;
|
|
2447
2567
|
}
|
|
@@ -2490,7 +2610,7 @@ function createDocumentLoader(root) {
|
|
|
2490
2610
|
const asset = assets.get(id);
|
|
2491
2611
|
if (!asset)
|
|
2492
2612
|
throw Object.assign(new Error("Unknown scene asset"), { status: 404 });
|
|
2493
|
-
const current = sceneInProject(root, asset.file), info = await
|
|
2613
|
+
const current = sceneInProject(root, asset.file), info = await fs6.stat(current.source);
|
|
2494
2614
|
if (current.source !== asset.file || info.size !== asset.size || info.mtimeMs !== asset.mtime)
|
|
2495
2615
|
throw Object.assign(
|
|
2496
2616
|
new Error("Scene asset changed. Refresh the scene."),
|
|
@@ -2500,9 +2620,9 @@ function createDocumentLoader(root) {
|
|
|
2500
2620
|
}
|
|
2501
2621
|
async function openAsset(id) {
|
|
2502
2622
|
const current = await locateAsset(id);
|
|
2503
|
-
const extension =
|
|
2623
|
+
const extension = path19.extname(current.file).toLowerCase();
|
|
2504
2624
|
const normalized = [".mp4", ".mov", ".m4v"].includes(extension);
|
|
2505
|
-
const opened = normalized ? await videos.open(current.file, id) : { handle: await
|
|
2625
|
+
const opened = normalized ? await videos.open(current.file, id) : { handle: await fs6.open(current.file, "r"), size: current.size };
|
|
2506
2626
|
try {
|
|
2507
2627
|
await locateAsset(id);
|
|
2508
2628
|
return { ...opened, extension: normalized ? ".mp4" : extension };
|
|
@@ -2542,30 +2662,29 @@ var init_scene_document2 = __esm({
|
|
|
2542
2662
|
});
|
|
2543
2663
|
|
|
2544
2664
|
// src/project-resources.ts
|
|
2545
|
-
import
|
|
2546
|
-
import
|
|
2665
|
+
import fs7 from "node:fs/promises";
|
|
2666
|
+
import path20 from "node:path";
|
|
2547
2667
|
import { execFile as execFile6 } from "node:child_process";
|
|
2548
2668
|
import { promisify as promisify6 } from "node:util";
|
|
2549
|
-
import ts7 from "typescript";
|
|
2550
2669
|
async function resourceFile(root, relative) {
|
|
2551
|
-
if (typeof relative !== "string" ||
|
|
2670
|
+
if (typeof relative !== "string" || path20.isAbsolute(relative))
|
|
2552
2671
|
throw new Error("Resource paths must be relative.");
|
|
2553
|
-
const base = await
|
|
2554
|
-
if (!inside(
|
|
2672
|
+
const base = await fs7.realpath(root), file = await fs7.realpath(path20.resolve(base, relative));
|
|
2673
|
+
if (!inside(path20.join(base, "resources"), file) || (await fs7.stat(file)).isDirectory())
|
|
2555
2674
|
throw new Error("Resource files must stay inside resources/.");
|
|
2556
2675
|
return file;
|
|
2557
2676
|
}
|
|
2558
2677
|
async function resourceFolder(root) {
|
|
2559
|
-
const base = await
|
|
2560
|
-
await
|
|
2561
|
-
if ((await
|
|
2678
|
+
const base = await fs7.realpath(root), folder = path20.join(base, "resources");
|
|
2679
|
+
await fs7.mkdir(folder, { recursive: true });
|
|
2680
|
+
if ((await fs7.lstat(folder)).isSymbolicLink())
|
|
2562
2681
|
throw new Error("resources/ must be a real directory.");
|
|
2563
2682
|
return folder;
|
|
2564
2683
|
}
|
|
2565
2684
|
async function listProjectResources(root) {
|
|
2566
2685
|
let entries;
|
|
2567
2686
|
try {
|
|
2568
|
-
entries = await
|
|
2687
|
+
entries = await fs7.readdir(path20.join(root, "resources"), {
|
|
2569
2688
|
withFileTypes: true
|
|
2570
2689
|
});
|
|
2571
2690
|
} catch (e) {
|
|
@@ -2577,12 +2696,12 @@ async function listProjectResources(root) {
|
|
|
2577
2696
|
if (item.name.startsWith(".") || !item.isDirectory()) continue;
|
|
2578
2697
|
try {
|
|
2579
2698
|
const relative = `resources/${item.name}/resource.json`, manifestFile = await resourceFile(root, relative);
|
|
2580
|
-
if ((await
|
|
2699
|
+
if ((await fs7.stat(manifestFile)).size > 1024 * 1024)
|
|
2581
2700
|
throw new Error("Manifest exceeds 1 MB");
|
|
2582
|
-
const meta = JSON.parse(await
|
|
2701
|
+
const meta = JSON.parse(await fs7.readFile(manifestFile, "utf8"));
|
|
2583
2702
|
const entry = `resources/${item.name}/${meta.entry}`, file = await resourceFile(root, entry);
|
|
2584
|
-
const kind = meta.kind ?? (meta.category === "3d" ? "model" : meta.category) ?? kinds[
|
|
2585
|
-
if (!["shader", "image", "video", "model", "scene"].includes(kind) || kinds[
|
|
2703
|
+
const kind = meta.kind ?? (meta.category === "3d" ? "model" : meta.category) ?? kinds[path20.extname(file).toLowerCase()];
|
|
2704
|
+
if (!["shader", "image", "video", "model", "scene"].includes(kind) || kinds[path20.extname(file).toLowerCase()] !== kind)
|
|
2586
2705
|
throw new Error("Unsupported resource type");
|
|
2587
2706
|
let effect;
|
|
2588
2707
|
if (kind === "shader" && meta.effect) {
|
|
@@ -2600,7 +2719,7 @@ async function listProjectResources(root) {
|
|
|
2600
2719
|
const dimensions = Object.fromEntries(
|
|
2601
2720
|
["width", "height", "duration"].filter((k) => Number.isFinite(meta[k]) && meta[k] > 0).map((k) => [k, meta[k]])
|
|
2602
2721
|
);
|
|
2603
|
-
const info = await
|
|
2722
|
+
const info = await fs7.stat(file), preview = meta.preview ? `resources/${item.name}/${meta.preview}` : ["image", "video"].includes(kind) ? entry : null;
|
|
2604
2723
|
if (preview) await resourceFile(root, preview);
|
|
2605
2724
|
resources.push({
|
|
2606
2725
|
id: item.name,
|
|
@@ -2615,83 +2734,34 @@ async function listProjectResources(root) {
|
|
|
2615
2734
|
} : {},
|
|
2616
2735
|
preview: preview && (meta.preview || info.size < 12 * 1024 * 1024) ? preview : null,
|
|
2617
2736
|
previewType: meta.preview ? "image" : kind,
|
|
2618
|
-
stamp: `${info.mtimeMs}:${info.size}:${(await
|
|
2737
|
+
stamp: `${info.mtimeMs}:${info.size}:${(await fs7.stat(manifestFile)).mtimeMs}`
|
|
2619
2738
|
});
|
|
2620
2739
|
} catch (error) {
|
|
2621
2740
|
if (error.code !== "ENOENT")
|
|
2622
2741
|
errors.push(`${item.name}: ${error.message}`);
|
|
2623
2742
|
}
|
|
2624
2743
|
}
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
try {
|
|
2645
|
-
const text = await fs6.readFile(file, "utf8"), ast = ts7.createSourceFile(
|
|
2646
|
-
entry,
|
|
2647
|
-
text,
|
|
2648
|
-
ts7.ScriptTarget.Latest,
|
|
2649
|
-
true,
|
|
2650
|
-
ts7.ScriptKind.TSX
|
|
2651
|
-
);
|
|
2652
|
-
let node = ast.statements.find(ts7.isExportAssignment)?.expression;
|
|
2653
|
-
while (node && ts7.isParenthesizedExpression(node))
|
|
2654
|
-
node = node.expression;
|
|
2655
|
-
const head = node && (ts7.isJsxElement(node) ? node.openingElement : ts7.isJsxSelfClosingElement(node) ? node : null);
|
|
2656
|
-
if (head?.tagName.getText(ast) !== "Scene") continue;
|
|
2657
|
-
const attrs = Object.fromEntries(
|
|
2658
|
-
head.attributes.properties.filter(ts7.isJsxAttribute).map((a) => {
|
|
2659
|
-
const value = ts7.isJsxExpression(a.initializer ?? {}) ? a.initializer.expression : a.initializer;
|
|
2660
|
-
return [
|
|
2661
|
-
// A namespaced name (a:b) has no text; nothing reads it.
|
|
2662
|
-
a.name.text,
|
|
2663
|
-
value && ts7.isNumericLiteral(value) ? Number(value.text) : void 0
|
|
2664
|
-
];
|
|
2665
|
-
})
|
|
2666
|
-
);
|
|
2667
|
-
if (![attrs.width, attrs.height, attrs.duration].every(
|
|
2668
|
-
(v) => Number.isFinite(v) && v > 0
|
|
2669
|
-
))
|
|
2670
|
-
continue;
|
|
2671
|
-
const existing = resources.find((r) => r.entry === entry);
|
|
2672
|
-
const info = {
|
|
2673
|
-
width: attrs.width,
|
|
2674
|
-
height: attrs.height,
|
|
2675
|
-
duration: attrs.duration,
|
|
2676
|
-
fps: attrs.fps ?? 30
|
|
2677
|
-
};
|
|
2678
|
-
if (existing) Object.assign(existing, info);
|
|
2679
|
-
else
|
|
2680
|
-
resources.push({
|
|
2681
|
-
id: `scene:${entry}`,
|
|
2682
|
-
title: item.name === "scene.tsx" ? path19.basename(folder) : item.name.slice(0, -4),
|
|
2683
|
-
kind: "scene",
|
|
2684
|
-
entry,
|
|
2685
|
-
...info,
|
|
2686
|
-
preview: null,
|
|
2687
|
-
stamp: String((await fs6.stat(file)).mtimeMs)
|
|
2688
|
-
});
|
|
2689
|
-
} catch (error) {
|
|
2690
|
-
errors.push(`${entry}: ${error.message}`);
|
|
2691
|
-
}
|
|
2744
|
+
const catalog = await listProjectScenes(root);
|
|
2745
|
+
errors.push(...catalog.errors);
|
|
2746
|
+
for (const scene of catalog.scenes) {
|
|
2747
|
+
const { path: entry, width, height, duration, fps } = scene;
|
|
2748
|
+
if (!width || !height || !duration) continue;
|
|
2749
|
+
const existing = resources.find((resource) => resource.entry === entry);
|
|
2750
|
+
const info = { width, height, duration, fps: fps ?? 30 };
|
|
2751
|
+
if (existing) Object.assign(existing, info);
|
|
2752
|
+
else {
|
|
2753
|
+
const name = path20.basename(entry);
|
|
2754
|
+
resources.push({
|
|
2755
|
+
id: `scene:${entry}`,
|
|
2756
|
+
title: name === "scene.tsx" ? path20.basename(path20.dirname(path20.join(root, entry))) : name.slice(0, -4),
|
|
2757
|
+
kind: "scene",
|
|
2758
|
+
entry,
|
|
2759
|
+
...info,
|
|
2760
|
+
preview: null,
|
|
2761
|
+
stamp: String((await fs7.stat(path20.join(root, entry))).mtimeMs)
|
|
2762
|
+
});
|
|
2692
2763
|
}
|
|
2693
2764
|
}
|
|
2694
|
-
await scenes(root);
|
|
2695
2765
|
return { resources, errors };
|
|
2696
2766
|
}
|
|
2697
2767
|
async function getProjectResource(root, id) {
|
|
@@ -2703,33 +2773,33 @@ async function getProjectResource(root, id) {
|
|
|
2703
2773
|
async function resourcePreview(root, id) {
|
|
2704
2774
|
const resource = await getProjectResource(root, id);
|
|
2705
2775
|
if (!resource.preview) throw new Error("No preview for this resource");
|
|
2706
|
-
const file = await resourceFile(root, resource.preview), type = mime[
|
|
2776
|
+
const file = await resourceFile(root, resource.preview), type = mime[path20.extname(file).toLowerCase()];
|
|
2707
2777
|
if (!type) throw new Error("Unsupported preview type");
|
|
2708
|
-
if ((await
|
|
2778
|
+
if ((await fs7.stat(file)).size > 12 * 1024 * 1024)
|
|
2709
2779
|
throw new Error("Preview is too large");
|
|
2710
|
-
return { bytes: await
|
|
2780
|
+
return { bytes: await fs7.readFile(file), type };
|
|
2711
2781
|
}
|
|
2712
2782
|
async function installResourceExamples(root) {
|
|
2713
|
-
const folder = await resourceFolder(root), source =
|
|
2714
|
-
for (const item of await
|
|
2783
|
+
const folder = await resourceFolder(root), source = path20.join(packageRoot, "src/resource-examples/");
|
|
2784
|
+
for (const item of await fs7.readdir(source, { withFileTypes: true })) {
|
|
2715
2785
|
if (!item.isDirectory()) continue;
|
|
2716
|
-
const destination =
|
|
2786
|
+
const destination = path20.join(folder, item.name);
|
|
2717
2787
|
try {
|
|
2718
|
-
await
|
|
2788
|
+
await fs7.mkdir(destination);
|
|
2719
2789
|
} catch (e) {
|
|
2720
2790
|
if (e.code === "EEXIST") continue;
|
|
2721
2791
|
throw e;
|
|
2722
2792
|
}
|
|
2723
2793
|
try {
|
|
2724
|
-
for (const file of (await
|
|
2794
|
+
for (const file of (await fs7.readdir(path20.join(source, item.name))).sort(
|
|
2725
2795
|
(a, b) => Number(a === "resource.json") - Number(b === "resource.json")
|
|
2726
2796
|
))
|
|
2727
|
-
await
|
|
2728
|
-
|
|
2729
|
-
|
|
2797
|
+
await fs7.copyFile(
|
|
2798
|
+
path20.join(source, item.name, file),
|
|
2799
|
+
path20.join(destination, file)
|
|
2730
2800
|
);
|
|
2731
2801
|
} catch (error) {
|
|
2732
|
-
await
|
|
2802
|
+
await fs7.rm(destination, { recursive: true, force: true });
|
|
2733
2803
|
throw error;
|
|
2734
2804
|
}
|
|
2735
2805
|
}
|
|
@@ -2739,6 +2809,7 @@ var exec6, kinds, mime, inside;
|
|
|
2739
2809
|
var init_project_resources = __esm({
|
|
2740
2810
|
"src/project-resources.ts"() {
|
|
2741
2811
|
"use strict";
|
|
2812
|
+
init_project_scenes();
|
|
2742
2813
|
init_runtime();
|
|
2743
2814
|
exec6 = promisify6(execFile6);
|
|
2744
2815
|
kinds = {
|
|
@@ -2765,8 +2836,8 @@ var init_project_resources = __esm({
|
|
|
2765
2836
|
".mov": "video/quicktime"
|
|
2766
2837
|
};
|
|
2767
2838
|
inside = (root, file) => {
|
|
2768
|
-
const rel =
|
|
2769
|
-
return rel !== ".." && !rel.startsWith(".." +
|
|
2839
|
+
const rel = path20.relative(root, file);
|
|
2840
|
+
return rel !== ".." && !rel.startsWith(".." + path20.sep) && !path20.isAbsolute(rel);
|
|
2770
2841
|
};
|
|
2771
2842
|
}
|
|
2772
2843
|
});
|
|
@@ -3005,6 +3076,86 @@ var init_resource_drop = __esm({
|
|
|
3005
3076
|
}
|
|
3006
3077
|
});
|
|
3007
3078
|
|
|
3079
|
+
// ../editor/src/duplicate-motion.ts
|
|
3080
|
+
import ts9 from "typescript";
|
|
3081
|
+
function duplicateMotionBindings(source) {
|
|
3082
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
3083
|
+
const names = /* @__PURE__ */ new Set();
|
|
3084
|
+
function collect(node) {
|
|
3085
|
+
if (ts9.isIdentifier(node)) names.add(node.text);
|
|
3086
|
+
ts9.forEachChild(node, collect);
|
|
3087
|
+
}
|
|
3088
|
+
collect(source);
|
|
3089
|
+
for (const statement of source.statements) {
|
|
3090
|
+
if (!ts9.isVariableStatement(statement)) continue;
|
|
3091
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
3092
|
+
const call2 = declaration.initializer;
|
|
3093
|
+
if (ts9.isIdentifier(declaration.name) && call2 && ts9.isCallExpression(call2) && ts9.isIdentifier(call2.expression) && call2.expression.text === "defineMotion")
|
|
3094
|
+
definitions.set(declaration.name.text, call2.getText(source));
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
const copies = /* @__PURE__ */ new Map();
|
|
3098
|
+
let index = 1;
|
|
3099
|
+
return {
|
|
3100
|
+
copy(text) {
|
|
3101
|
+
const ast = ts9.createSourceFile(
|
|
3102
|
+
"copy.tsx",
|
|
3103
|
+
text,
|
|
3104
|
+
ts9.ScriptTarget.Latest,
|
|
3105
|
+
true,
|
|
3106
|
+
ts9.ScriptKind.TSX
|
|
3107
|
+
);
|
|
3108
|
+
const edits = [];
|
|
3109
|
+
function visit(node) {
|
|
3110
|
+
if (ts9.isPropertyAccessExpression(node) && ts9.isIdentifier(node.expression) && definitions.has(node.expression.text)) {
|
|
3111
|
+
const original = node.expression.text;
|
|
3112
|
+
let name = copies.get(original);
|
|
3113
|
+
if (!name) {
|
|
3114
|
+
do {
|
|
3115
|
+
name = `motionCopy${index++}`;
|
|
3116
|
+
} while (names.has(name));
|
|
3117
|
+
names.add(name);
|
|
3118
|
+
copies.set(original, name);
|
|
3119
|
+
}
|
|
3120
|
+
edits.push({
|
|
3121
|
+
start: node.expression.getStart(ast),
|
|
3122
|
+
end: node.expression.end,
|
|
3123
|
+
text: name
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
ts9.forEachChild(node, visit);
|
|
3127
|
+
}
|
|
3128
|
+
visit(ast);
|
|
3129
|
+
for (const edit of edits.sort((a, b) => b.start - a.start))
|
|
3130
|
+
text = text.slice(0, edit.start) + edit.text + text.slice(edit.end);
|
|
3131
|
+
return text;
|
|
3132
|
+
},
|
|
3133
|
+
finish(text) {
|
|
3134
|
+
if (!copies.size) return text;
|
|
3135
|
+
const ast = ts9.createSourceFile(
|
|
3136
|
+
source.fileName,
|
|
3137
|
+
text,
|
|
3138
|
+
ts9.ScriptTarget.Latest,
|
|
3139
|
+
true,
|
|
3140
|
+
ts9.ScriptKind.TSX
|
|
3141
|
+
);
|
|
3142
|
+
const exported = ast.statements.find(ts9.isExportAssignment);
|
|
3143
|
+
if (!exported) throw new Error("Scene export no longer exists");
|
|
3144
|
+
const at = exported.getStart(ast);
|
|
3145
|
+
const declarations2 = [...copies].map(
|
|
3146
|
+
([original, name]) => `const ${name} = ${definitions.get(original)};
|
|
3147
|
+
`
|
|
3148
|
+
).join("");
|
|
3149
|
+
return text.slice(0, at) + declarations2 + text.slice(at);
|
|
3150
|
+
}
|
|
3151
|
+
};
|
|
3152
|
+
}
|
|
3153
|
+
var init_duplicate_motion = __esm({
|
|
3154
|
+
"../editor/src/duplicate-motion.ts"() {
|
|
3155
|
+
"use strict";
|
|
3156
|
+
}
|
|
3157
|
+
});
|
|
3158
|
+
|
|
3008
3159
|
// ../editor/src/property-validation.ts
|
|
3009
3160
|
function validProperty(name, value, spec) {
|
|
3010
3161
|
if (name === "mask") {
|
|
@@ -3168,29 +3319,29 @@ var init_timing_model = __esm({
|
|
|
3168
3319
|
});
|
|
3169
3320
|
|
|
3170
3321
|
// ../editor/src/timeline.ts
|
|
3171
|
-
import
|
|
3322
|
+
import ts10 from "typescript";
|
|
3172
3323
|
function literal2(node) {
|
|
3173
3324
|
const n = unwrap(node);
|
|
3174
3325
|
if (!n) return { known: false };
|
|
3175
|
-
if (
|
|
3176
|
-
if (
|
|
3177
|
-
if (n.kind ===
|
|
3178
|
-
return { known: true, value: n.kind ===
|
|
3179
|
-
if (
|
|
3326
|
+
if (ts10.isStringLiteral(n)) return { known: true, value: n.text };
|
|
3327
|
+
if (ts10.isNumericLiteral(n)) return { known: true, value: Number(n.text) };
|
|
3328
|
+
if (n.kind === ts10.SyntaxKind.TrueKeyword || n.kind === ts10.SyntaxKind.FalseKeyword)
|
|
3329
|
+
return { known: true, value: n.kind === ts10.SyntaxKind.TrueKeyword };
|
|
3330
|
+
if (ts10.isPrefixUnaryExpression(n) && [ts10.SyntaxKind.MinusToken, ts10.SyntaxKind.PlusToken].includes(n.operator)) {
|
|
3180
3331
|
const v = literal2(n.operand);
|
|
3181
3332
|
return typeof v.value === "number" ? {
|
|
3182
3333
|
known: true,
|
|
3183
|
-
value: v.value * (n.operator ===
|
|
3334
|
+
value: v.value * (n.operator === ts10.SyntaxKind.MinusToken ? -1 : 1)
|
|
3184
3335
|
} : { known: false };
|
|
3185
3336
|
}
|
|
3186
|
-
if (
|
|
3337
|
+
if (ts10.isArrayLiteralExpression(n)) {
|
|
3187
3338
|
const items = n.elements.map(literal2);
|
|
3188
3339
|
return items.every((v) => v.known) ? { known: true, value: items.map((v) => v.value) } : { known: false };
|
|
3189
3340
|
}
|
|
3190
|
-
if (
|
|
3341
|
+
if (ts10.isObjectLiteralExpression(n)) {
|
|
3191
3342
|
const entries = [];
|
|
3192
3343
|
for (const p of n.properties) {
|
|
3193
|
-
if (!
|
|
3344
|
+
if (!ts10.isPropertyAssignment(p) || ts10.isComputedPropertyName(p.name))
|
|
3194
3345
|
return { known: false };
|
|
3195
3346
|
const v = literal2(p.initializer);
|
|
3196
3347
|
if (!v.known) return { known: false };
|
|
@@ -3201,25 +3352,25 @@ function literal2(node) {
|
|
|
3201
3352
|
return { known: false };
|
|
3202
3353
|
}
|
|
3203
3354
|
function opening(n) {
|
|
3204
|
-
return
|
|
3355
|
+
return ts10.isJsxElement(n) ? n.openingElement : ts10.isJsxSelfClosingElement(n) ? n : null;
|
|
3205
3356
|
}
|
|
3206
3357
|
function childCount(n) {
|
|
3207
3358
|
return (n.children ?? []).reduce(
|
|
3208
|
-
(count, child) => count + (
|
|
3359
|
+
(count, child) => count + (ts10.isJsxFragment(child) || opening(child)?.tagName.getText() === "Fragment" ? childCount(child) : opening(child) ? 1 : 0),
|
|
3209
3360
|
0
|
|
3210
3361
|
);
|
|
3211
3362
|
}
|
|
3212
3363
|
function attributes(n) {
|
|
3213
3364
|
return new Map(
|
|
3214
|
-
opening(n).attributes.properties.filter(
|
|
3365
|
+
opening(n).attributes.properties.filter(ts10.isJsxAttribute).map((p) => [p.name.text, p])
|
|
3215
3366
|
);
|
|
3216
3367
|
}
|
|
3217
3368
|
function declarations(ast) {
|
|
3218
3369
|
const result = {};
|
|
3219
3370
|
for (const st of ast.statements)
|
|
3220
|
-
if (
|
|
3371
|
+
if (ts10.isVariableStatement(st))
|
|
3221
3372
|
for (const d of st.declarationList.declarations) {
|
|
3222
|
-
if (d.initializer &&
|
|
3373
|
+
if (d.initializer && ts10.isCallExpression(d.initializer) && d.initializer.expression.getText(ast) === "defineKnobs")
|
|
3223
3374
|
result[d.name.getText(ast)] = literal2(d.initializer.arguments[0]).value ?? {};
|
|
3224
3375
|
}
|
|
3225
3376
|
return result;
|
|
@@ -3276,13 +3427,7 @@ function propertiesFor(type) {
|
|
|
3276
3427
|
};
|
|
3277
3428
|
}
|
|
3278
3429
|
function describeGroup(text, filename2 = "animation.tsx") {
|
|
3279
|
-
const ast =
|
|
3280
|
-
filename2,
|
|
3281
|
-
text,
|
|
3282
|
-
ts8.ScriptTarget.Latest,
|
|
3283
|
-
true,
|
|
3284
|
-
ts8.ScriptKind.TSX
|
|
3285
|
-
), n = unwrap(ast.statements.find(ts8.isExportAssignment)?.expression);
|
|
3430
|
+
const ast = parseModule(filename2, text), n = unwrap(ast.statements.find(ts10.isExportAssignment)?.expression);
|
|
3286
3431
|
if (!n || !opening(n) || !["Scene", "Group"].includes(opening(n).tagName.getText(ast)))
|
|
3287
3432
|
throw new Error("Reusable file must default-export a Scene or Group");
|
|
3288
3433
|
const attrs = attributes(n), props = Object.fromEntries(
|
|
@@ -3294,7 +3439,7 @@ function describeGroup(text, filename2 = "animation.tsx") {
|
|
|
3294
3439
|
const name = unwrap(attrs.get("knobs")?.initializer)?.getText(ast);
|
|
3295
3440
|
const valueNode = unwrap(attrs.get("values")?.initializer);
|
|
3296
3441
|
const values = Object.fromEntries(
|
|
3297
|
-
valueNode &&
|
|
3442
|
+
valueNode && ts10.isObjectLiteralExpression(valueNode) ? valueNode.properties.filter(ts10.isPropertyAssignment).map((p) => [
|
|
3298
3443
|
p.name.text,
|
|
3299
3444
|
{
|
|
3300
3445
|
...literal2(p.initializer),
|
|
@@ -3324,7 +3469,7 @@ function describeClip(n, ast, definitions, knobs) {
|
|
|
3324
3469
|
const track = attrLiteral(attrs.get("track")).value;
|
|
3325
3470
|
const schema = definition?.knobs ?? knobs[unwrap(attrs.get("knobs")?.initializer)?.getText(ast)] ?? {};
|
|
3326
3471
|
const valueNode = unwrap(attrs.get("values")?.initializer), overrides = new Map(
|
|
3327
|
-
valueNode &&
|
|
3472
|
+
valueNode && ts10.isObjectLiteralExpression(valueNode) ? valueNode.properties.filter(ts10.isPropertyAssignment).map((p) => [
|
|
3328
3473
|
p.name.text,
|
|
3329
3474
|
p
|
|
3330
3475
|
]) : []
|
|
@@ -3357,8 +3502,8 @@ function describeClip(n, ast, definitions, knobs) {
|
|
|
3357
3502
|
expression: v.expression
|
|
3358
3503
|
};
|
|
3359
3504
|
});
|
|
3360
|
-
const words2 = tag === "Text" &&
|
|
3361
|
-
(c) =>
|
|
3505
|
+
const words2 = tag === "Text" && ts10.isJsxElement(n) ? n.children.map(
|
|
3506
|
+
(c) => ts10.isJsxText(c) ? c.text : ts10.isJsxExpression(c) ? literal2(c.expression).value ?? "" : ""
|
|
3362
3507
|
).join("").trim().replace(/\s+/g, " ").slice(0, 64) : "";
|
|
3363
3508
|
return {
|
|
3364
3509
|
id: id ?? `draft:${n.getStart(ast)}`,
|
|
@@ -3383,13 +3528,7 @@ function describeClip(n, ast, definitions, knobs) {
|
|
|
3383
3528
|
};
|
|
3384
3529
|
}
|
|
3385
3530
|
function parse(text, definitions = {}, filename2 = "scene.tsx") {
|
|
3386
|
-
const ast =
|
|
3387
|
-
filename2,
|
|
3388
|
-
text,
|
|
3389
|
-
ts8.ScriptTarget.Latest,
|
|
3390
|
-
true,
|
|
3391
|
-
ts8.ScriptKind.TSX
|
|
3392
|
-
), root = unwrap(ast.statements.find(ts8.isExportAssignment)?.expression);
|
|
3531
|
+
const ast = parseModule(filename2, text), root = unwrap(ast.statements.find(ts10.isExportAssignment)?.expression);
|
|
3393
3532
|
if (ast.parseDiagnostics.length)
|
|
3394
3533
|
throw new Error("Fix the TSX syntax before editing");
|
|
3395
3534
|
if (!root || !opening(root) || opening(root).tagName.getText(ast) !== "Scene")
|
|
@@ -3409,7 +3548,7 @@ function parse(text, definitions = {}, filename2 = "scene.tsx") {
|
|
|
3409
3548
|
throw new Error("Scene duration and fps must be literal numbers");
|
|
3410
3549
|
const knobs = declarations(ast), clips = [];
|
|
3411
3550
|
function visit(n) {
|
|
3412
|
-
if (
|
|
3551
|
+
if (ts10.isJsxFragment(n) || opening(n)?.tagName.getText(ast) === "Fragment" && ts10.isJsxElement(n)) {
|
|
3413
3552
|
n.children.forEach(visit);
|
|
3414
3553
|
return;
|
|
3415
3554
|
}
|
|
@@ -3432,7 +3571,7 @@ function parse(text, definitions = {}, filename2 = "scene.tsx") {
|
|
|
3432
3571
|
throw new Error("Clips must stay inside the scene.");
|
|
3433
3572
|
clips.push({ ...clip, ...timing });
|
|
3434
3573
|
}
|
|
3435
|
-
if (
|
|
3574
|
+
if (ts10.isJsxElement(root)) root.children.forEach(visit);
|
|
3436
3575
|
const assigned = clips.filter((c) => c.track !== void 0);
|
|
3437
3576
|
for (const clip of clips.filter((c) => c.track === void 0)) {
|
|
3438
3577
|
clip.track = 0;
|
|
@@ -3497,28 +3636,28 @@ function setAttrs(text, node, values, ast) {
|
|
|
3497
3636
|
}
|
|
3498
3637
|
function copyClip(clip, ast, values, fresh) {
|
|
3499
3638
|
let text = clip.node.getText(ast);
|
|
3500
|
-
const subtree =
|
|
3639
|
+
const subtree = ts10.createSourceFile(
|
|
3501
3640
|
"copy.tsx",
|
|
3502
3641
|
text,
|
|
3503
|
-
|
|
3642
|
+
ts10.ScriptTarget.Latest,
|
|
3504
3643
|
true,
|
|
3505
|
-
|
|
3644
|
+
ts10.ScriptKind.TSX
|
|
3506
3645
|
), edits = [];
|
|
3507
3646
|
const ids = /* @__PURE__ */ new Map();
|
|
3508
3647
|
function collect(node2) {
|
|
3509
|
-
if (
|
|
3648
|
+
if (ts10.isJsxAttribute(node2) && node2.name.text === "id")
|
|
3510
3649
|
ids.set(literal2(node2.initializer).value, fresh());
|
|
3511
|
-
|
|
3650
|
+
ts10.forEachChild(node2, collect);
|
|
3512
3651
|
}
|
|
3513
3652
|
collect(subtree);
|
|
3514
3653
|
function replace(node2) {
|
|
3515
|
-
if (
|
|
3654
|
+
if (ts10.isJsxAttribute(node2) && node2.name.text === "id")
|
|
3516
3655
|
edits.push({
|
|
3517
3656
|
start: node2.getStart(subtree),
|
|
3518
3657
|
end: node2.end,
|
|
3519
3658
|
text: `id={${JSON.stringify(ids.get(literal2(node2.initializer).value))}}`
|
|
3520
3659
|
});
|
|
3521
|
-
if (
|
|
3660
|
+
if (ts10.isJsxAttribute(node2) && node2.name.text === "mask") {
|
|
3522
3661
|
const raw = literal2(node2.initializer).value, mask = readMask(raw);
|
|
3523
3662
|
if (mask && ids.has(mask.source)) {
|
|
3524
3663
|
const value = typeof raw === "string" ? ids.get(mask.source) : { ...raw, source: ids.get(mask.source) };
|
|
@@ -3529,16 +3668,16 @@ function copyClip(clip, ast, values, fresh) {
|
|
|
3529
3668
|
});
|
|
3530
3669
|
}
|
|
3531
3670
|
}
|
|
3532
|
-
|
|
3671
|
+
ts10.forEachChild(node2, replace);
|
|
3533
3672
|
}
|
|
3534
3673
|
replace(subtree);
|
|
3535
3674
|
text = patches(text, edits);
|
|
3536
|
-
const copyAst =
|
|
3675
|
+
const copyAst = ts10.createSourceFile(
|
|
3537
3676
|
"copy.tsx",
|
|
3538
3677
|
text,
|
|
3539
|
-
|
|
3678
|
+
ts10.ScriptTarget.Latest,
|
|
3540
3679
|
true,
|
|
3541
|
-
|
|
3680
|
+
ts10.ScriptKind.TSX
|
|
3542
3681
|
), node = copyAst.statements[0].expression;
|
|
3543
3682
|
return {
|
|
3544
3683
|
text: setAttrs(text, node, values, copyAst),
|
|
@@ -3567,11 +3706,11 @@ function editClipValue(text, clip, command, ast) {
|
|
|
3567
3706
|
if (!validKnob(knob.schema, command.value))
|
|
3568
3707
|
throw new Error(`Invalid ${command.name}`);
|
|
3569
3708
|
const attr = clip.attrs.get("values"), object = unwrap(attr?.initializer), rendered = JSON.stringify(command.value);
|
|
3570
|
-
if (object && !
|
|
3709
|
+
if (object && !ts10.isObjectLiteralExpression(object))
|
|
3571
3710
|
throw new Error("Edit the values binding in source.");
|
|
3572
3711
|
if (object) {
|
|
3573
3712
|
const prop = object.properties.find(
|
|
3574
|
-
(p) =>
|
|
3713
|
+
(p) => ts10.isPropertyAssignment(p) && p.name.text === command.name
|
|
3575
3714
|
);
|
|
3576
3715
|
if (prop)
|
|
3577
3716
|
text = patches(text, [
|
|
@@ -3603,17 +3742,17 @@ function editClipValue(text, clip, command, ast) {
|
|
|
3603
3742
|
}
|
|
3604
3743
|
function editGroupSource(text, sourceStart, command, filename2 = "scene.tsx") {
|
|
3605
3744
|
const changes = groupChanges(command);
|
|
3606
|
-
const ast =
|
|
3745
|
+
const ast = ts10.createSourceFile(
|
|
3607
3746
|
filename2,
|
|
3608
3747
|
text,
|
|
3609
|
-
|
|
3748
|
+
ts10.ScriptTarget.Latest,
|
|
3610
3749
|
true,
|
|
3611
|
-
|
|
3750
|
+
ts10.ScriptKind.TSX
|
|
3612
3751
|
);
|
|
3613
3752
|
let target;
|
|
3614
3753
|
function visit(node) {
|
|
3615
3754
|
if (opening(node)?.getStart(ast) === sourceStart) target = node;
|
|
3616
|
-
else
|
|
3755
|
+
else ts10.forEachChild(node, visit);
|
|
3617
3756
|
}
|
|
3618
3757
|
visit(ast);
|
|
3619
3758
|
if (!target) throw new Error("Group source no longer exists");
|
|
@@ -3638,7 +3777,7 @@ function applyTimelineCommand(text, command, {
|
|
|
3638
3777
|
const v = literal2(attributes(n).get("id")?.initializer).value;
|
|
3639
3778
|
if (v) ids.add(v);
|
|
3640
3779
|
}
|
|
3641
|
-
|
|
3780
|
+
ts10.forEachChild(n, collect);
|
|
3642
3781
|
}
|
|
3643
3782
|
collect(model.ast);
|
|
3644
3783
|
const fresh = () => {
|
|
@@ -3688,6 +3827,7 @@ function applyTimelineCommand(text, command, {
|
|
|
3688
3827
|
if (duplicate)
|
|
3689
3828
|
next.tracks[command.track + 1] = { ...model.tracks[command.track] };
|
|
3690
3829
|
const edits = [];
|
|
3830
|
+
const motions = duplicateMotionBindings(model.ast);
|
|
3691
3831
|
for (const clip2 of model.clips) {
|
|
3692
3832
|
const updated = next.clips.find((c) => c.id === clip2.id);
|
|
3693
3833
|
if (!updated)
|
|
@@ -3714,12 +3854,13 @@ function applyTimelineCommand(text, command, {
|
|
|
3714
3854
|
edits.push({
|
|
3715
3855
|
start: clip2.node.end,
|
|
3716
3856
|
end: clip2.node.end,
|
|
3717
|
-
text: "\n " + copy.text
|
|
3857
|
+
text: "\n " + motions.copy(copy.text)
|
|
3718
3858
|
});
|
|
3719
3859
|
}
|
|
3720
3860
|
}
|
|
3721
3861
|
text = patches(text, edits);
|
|
3722
3862
|
text = setAttrs(text, model.root, { tracks: next.tracks }, model.ast);
|
|
3863
|
+
text = motions.finish(text);
|
|
3723
3864
|
return { text, selection: null, idMap: Object.fromEntries(idMap) };
|
|
3724
3865
|
}
|
|
3725
3866
|
if (command.type === "move" || command.type === "trim") {
|
|
@@ -3788,10 +3929,13 @@ function applyTimelineCommand(text, command, {
|
|
|
3788
3929
|
}
|
|
3789
3930
|
if (track >= 100) throw new Error("Maximum 100 tracks");
|
|
3790
3931
|
const copy = copyClip(clip, model.ast, { start, track }, fresh);
|
|
3932
|
+
const motions = duplicateMotionBindings(model.ast);
|
|
3933
|
+
copy.text = motions.copy(copy.text);
|
|
3791
3934
|
id = copy.id;
|
|
3792
3935
|
text = patches(text, [
|
|
3793
3936
|
{ start: clip.node.end, end: clip.node.end, text: "\n " + copy.text }
|
|
3794
3937
|
]);
|
|
3938
|
+
text = motions.finish(text);
|
|
3795
3939
|
} else if (command.type === "property") {
|
|
3796
3940
|
text = editClipValue(text, clip, command, model.ast);
|
|
3797
3941
|
} else if (command.type === "knob") {
|
|
@@ -3816,7 +3960,7 @@ function applyTimelineCommand(text, command, {
|
|
|
3816
3960
|
))
|
|
3817
3961
|
track++;
|
|
3818
3962
|
if (track >= 100) throw new Error("Maximum 100 tracks");
|
|
3819
|
-
const imports = model.ast.statements.filter(
|
|
3963
|
+
const imports = model.ast.statements.filter(ts10.isImportDeclaration), existing = imports.find(
|
|
3820
3964
|
(st) => st.importClause?.name && (st.moduleSpecifier.text === importPath || definition.source && definitions[st.importClause.name.text]?.source === definition.source)
|
|
3821
3965
|
);
|
|
3822
3966
|
let tag = existing?.importClause.name.text;
|
|
@@ -3836,7 +3980,7 @@ function applyTimelineCommand(text, command, {
|
|
|
3836
3980
|
model = parse(text, definitions, filename2);
|
|
3837
3981
|
}
|
|
3838
3982
|
const root = model.root;
|
|
3839
|
-
if (
|
|
3983
|
+
if (ts10.isJsxSelfClosingElement(root))
|
|
3840
3984
|
text = patches(text, [
|
|
3841
3985
|
{
|
|
3842
3986
|
start: root.end - 2,
|
|
@@ -3875,18 +4019,20 @@ var unwrap, attrLiteral, propertySpecs, pick, transformNames, numberProp, mediaS
|
|
|
3875
4019
|
var init_timeline = __esm({
|
|
3876
4020
|
"../editor/src/timeline.ts"() {
|
|
3877
4021
|
"use strict";
|
|
4022
|
+
init_duplicate_motion();
|
|
3878
4023
|
init_mask();
|
|
3879
4024
|
init_property_validation();
|
|
3880
4025
|
init_clip();
|
|
3881
4026
|
init_metadata();
|
|
3882
4027
|
init_labels();
|
|
4028
|
+
init_module_parse();
|
|
3883
4029
|
init_track_model();
|
|
3884
4030
|
init_group_model();
|
|
3885
4031
|
init_timing_model();
|
|
3886
4032
|
init_clip_timing();
|
|
3887
4033
|
init_clip_timing();
|
|
3888
4034
|
unwrap = (n) => {
|
|
3889
|
-
while (n && (
|
|
4035
|
+
while (n && (ts10.isParenthesizedExpression(n) || ts10.isJsxExpression(n)))
|
|
3890
4036
|
n = n.expression;
|
|
3891
4037
|
return n;
|
|
3892
4038
|
};
|
|
@@ -3967,20 +4113,20 @@ var init_timeline = __esm({
|
|
|
3967
4113
|
});
|
|
3968
4114
|
|
|
3969
4115
|
// ../editor/src/resource-source.ts
|
|
3970
|
-
import
|
|
4116
|
+
import ts11 from "typescript";
|
|
3971
4117
|
function addImports(text, names, declaration = "") {
|
|
3972
|
-
const ast =
|
|
4118
|
+
const ast = ts11.createSourceFile(
|
|
3973
4119
|
"scene.tsx",
|
|
3974
4120
|
text,
|
|
3975
|
-
|
|
4121
|
+
ts11.ScriptTarget.Latest,
|
|
3976
4122
|
true,
|
|
3977
|
-
|
|
4123
|
+
ts11.ScriptKind.TSX
|
|
3978
4124
|
);
|
|
3979
4125
|
const existing = ast.statements.find(
|
|
3980
|
-
(s) =>
|
|
4126
|
+
(s) => ts11.isImportDeclaration(s) && s.moduleSpecifier.text === "@osmo/scene" && !s.importClause?.isTypeOnly
|
|
3981
4127
|
);
|
|
3982
4128
|
const bindings = existing?.importClause?.namedBindings;
|
|
3983
|
-
if (bindings &&
|
|
4129
|
+
if (bindings && ts11.isNamedImports(bindings)) {
|
|
3984
4130
|
const missing = names.filter(
|
|
3985
4131
|
(name) => !bindings.elements.some((e) => e.name.text === name)
|
|
3986
4132
|
);
|
|
@@ -3995,19 +4141,19 @@ function addImports(text, names, declaration = "") {
|
|
|
3995
4141
|
} else text = `import {${names.join(", ")}} from '@osmo/scene';
|
|
3996
4142
|
${text}`;
|
|
3997
4143
|
if (declaration) {
|
|
3998
|
-
const next =
|
|
4144
|
+
const next = ts11.createSourceFile(
|
|
3999
4145
|
"scene.tsx",
|
|
4000
4146
|
text,
|
|
4001
|
-
|
|
4147
|
+
ts11.ScriptTarget.Latest,
|
|
4002
4148
|
true,
|
|
4003
|
-
|
|
4004
|
-
), at = next.statements.find(
|
|
4149
|
+
ts11.ScriptKind.TSX
|
|
4150
|
+
), at = next.statements.find(ts11.isExportAssignment).getStart(next);
|
|
4005
4151
|
text = patches2(text, [{ start: at, end: at, text: declaration + "\n" }]);
|
|
4006
4152
|
}
|
|
4007
4153
|
return text;
|
|
4008
4154
|
}
|
|
4009
4155
|
function insertChild(text, node, child, ast, first = false) {
|
|
4010
|
-
if (
|
|
4156
|
+
if (ts11.isJsxSelfClosingElement(node))
|
|
4011
4157
|
return patches2(text, [
|
|
4012
4158
|
{
|
|
4013
4159
|
start: node.end - 2,
|
|
@@ -4026,7 +4172,7 @@ function findOpening(ast, start) {
|
|
|
4026
4172
|
let found;
|
|
4027
4173
|
function walk(n) {
|
|
4028
4174
|
if (opening2(n)?.getStart(ast) === start) found = n;
|
|
4029
|
-
else
|
|
4175
|
+
else ts11.forEachChild(n, walk);
|
|
4030
4176
|
}
|
|
4031
4177
|
walk(ast);
|
|
4032
4178
|
return found;
|
|
@@ -4058,7 +4204,7 @@ function applyResourceSource(text, plan, {
|
|
|
4058
4204
|
}
|
|
4059
4205
|
selection = newId();
|
|
4060
4206
|
if (plan.resource.kind === "scene") {
|
|
4061
|
-
const imports = model.ast.statements.filter(
|
|
4207
|
+
const imports = model.ast.statements.filter(ts11.isImportDeclaration);
|
|
4062
4208
|
const existing = imports.find(
|
|
4063
4209
|
(st) => st.importClause?.name && (st.moduleSpecifier.text === src || definitions[st.importClause.name.text]?.source === plan.resource.entry)
|
|
4064
4210
|
);
|
|
@@ -4092,12 +4238,12 @@ ${text}`;
|
|
|
4092
4238
|
text = addImports(text, [plan.tag]);
|
|
4093
4239
|
}
|
|
4094
4240
|
} else {
|
|
4095
|
-
const ast =
|
|
4241
|
+
const ast = ts11.createSourceFile(
|
|
4096
4242
|
filename2,
|
|
4097
4243
|
text,
|
|
4098
|
-
|
|
4244
|
+
ts11.ScriptTarget.Latest,
|
|
4099
4245
|
true,
|
|
4100
|
-
|
|
4246
|
+
ts11.ScriptKind.TSX
|
|
4101
4247
|
);
|
|
4102
4248
|
const node = findOpening(ast, plan.node.source.start);
|
|
4103
4249
|
if (!node) throw new Error("The target layer changed. Try again.");
|
|
@@ -4107,7 +4253,7 @@ ${text}`;
|
|
|
4107
4253
|
);
|
|
4108
4254
|
const id = first && attributes2(first).get("id")?.initializer;
|
|
4109
4255
|
const value = unwrap2(id);
|
|
4110
|
-
const replace = first && ["Image", "Video"].includes(opening2(first).tagName.getText(ast)) && value &&
|
|
4256
|
+
const replace = first && ["Image", "Video"].includes(opening2(first).tagName.getText(ast)) && value && ts11.isStringLiteral(value) && value.text.startsWith("fill-");
|
|
4111
4257
|
const element = jsx(plan.tag, {
|
|
4112
4258
|
id: replace ? value.text : `fill-${newId()}`,
|
|
4113
4259
|
...plan.props,
|
|
@@ -4127,7 +4273,7 @@ ${text}`;
|
|
|
4127
4273
|
const attrs = attributes2(node), attr = attrs.get("effects"), array = unwrap2(attr?.initializer);
|
|
4128
4274
|
const call2 = `${name}(${JSON.stringify(effect.values ?? {})})`;
|
|
4129
4275
|
if (attr) {
|
|
4130
|
-
if (!array || !
|
|
4276
|
+
if (!array || !ts11.isArrayLiteralExpression(array))
|
|
4131
4277
|
throw new Error("Edit this effect stack in source.");
|
|
4132
4278
|
const last = array.elements.at(-1), at = last ? last.end : array.getStart(ast) + 1;
|
|
4133
4279
|
text = patches2(text, [
|
|
@@ -4167,11 +4313,11 @@ var init_resource_source = __esm({
|
|
|
4167
4313
|
});
|
|
4168
4314
|
|
|
4169
4315
|
// src/timeline-editor.ts
|
|
4170
|
-
import
|
|
4316
|
+
import fs8 from "node:fs/promises";
|
|
4171
4317
|
import { realpathSync } from "node:fs";
|
|
4172
|
-
import
|
|
4318
|
+
import path21 from "node:path";
|
|
4173
4319
|
import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
|
|
4174
|
-
import
|
|
4320
|
+
import ts12 from "typescript";
|
|
4175
4321
|
function createTimelineEditor(root, context, {
|
|
4176
4322
|
nativeValidation = false,
|
|
4177
4323
|
prepareCandidate,
|
|
@@ -4188,19 +4334,19 @@ function createTimelineEditor(root, context, {
|
|
|
4188
4334
|
};
|
|
4189
4335
|
async function read() {
|
|
4190
4336
|
const { source: inputSource, scene } = context();
|
|
4191
|
-
const source = await
|
|
4192
|
-
const text = await
|
|
4337
|
+
const source = await fs8.realpath(inputSource);
|
|
4338
|
+
const text = await fs8.readFile(source, "utf8"), definitions = {}, inputs = /* @__PURE__ */ new Map([[source, text]]);
|
|
4193
4339
|
async function dependencies(file, body3, depth = 0) {
|
|
4194
4340
|
if (depth > 32) throw new Error("Too many imported animations");
|
|
4195
|
-
const ast =
|
|
4341
|
+
const ast = ts12.createSourceFile(
|
|
4196
4342
|
file,
|
|
4197
4343
|
body3,
|
|
4198
|
-
|
|
4344
|
+
ts12.ScriptTarget.Latest,
|
|
4199
4345
|
true,
|
|
4200
|
-
|
|
4346
|
+
ts12.ScriptKind.TSX
|
|
4201
4347
|
);
|
|
4202
4348
|
for (const st of ast.statements)
|
|
4203
|
-
if (
|
|
4349
|
+
if (ts12.isImportDeclaration(st) && st.importClause?.name && // A module specifier is a string literal.
|
|
4204
4350
|
st.moduleSpecifier.text.startsWith(".")) {
|
|
4205
4351
|
const next = sceneInProject(
|
|
4206
4352
|
root,
|
|
@@ -4208,7 +4354,7 @@ function createTimelineEditor(root, context, {
|
|
|
4208
4354
|
);
|
|
4209
4355
|
let code = inputs.get(next.source);
|
|
4210
4356
|
if (code === void 0) {
|
|
4211
|
-
code = await
|
|
4357
|
+
code = await fs8.readFile(next.source, "utf8");
|
|
4212
4358
|
inputs.set(next.source, code);
|
|
4213
4359
|
await dependencies(next.source, code, depth + 1);
|
|
4214
4360
|
}
|
|
@@ -4257,7 +4403,7 @@ function createTimelineEditor(root, context, {
|
|
|
4257
4403
|
...data,
|
|
4258
4404
|
sourceFiles: Object.fromEntries(
|
|
4259
4405
|
[...inputs].map(([file, body3]) => [
|
|
4260
|
-
|
|
4406
|
+
path21.relative(root, file).split(path21.sep).join("/"),
|
|
4261
4407
|
body3
|
|
4262
4408
|
])
|
|
4263
4409
|
),
|
|
@@ -4268,16 +4414,16 @@ function createTimelineEditor(root, context, {
|
|
|
4268
4414
|
};
|
|
4269
4415
|
}
|
|
4270
4416
|
async function write(file, text) {
|
|
4271
|
-
const temp =
|
|
4272
|
-
|
|
4417
|
+
const temp = path21.join(
|
|
4418
|
+
path21.dirname(file),
|
|
4273
4419
|
`.osmo-edit-${randomUUID7()}.tmp`
|
|
4274
4420
|
);
|
|
4275
4421
|
try {
|
|
4276
|
-
const mode = (await
|
|
4277
|
-
await
|
|
4278
|
-
await
|
|
4422
|
+
const mode = (await fs8.stat(file)).mode;
|
|
4423
|
+
await fs8.writeFile(temp, text, { mode });
|
|
4424
|
+
await fs8.rename(temp, file);
|
|
4279
4425
|
} finally {
|
|
4280
|
-
await
|
|
4426
|
+
await fs8.rm(temp, { force: true });
|
|
4281
4427
|
}
|
|
4282
4428
|
}
|
|
4283
4429
|
return {
|
|
@@ -4290,27 +4436,27 @@ function createTimelineEditor(root, context, {
|
|
|
4290
4436
|
resolveAdd: (cardId) => serial(async () => {
|
|
4291
4437
|
const board = validateBoard(
|
|
4292
4438
|
JSON.parse(
|
|
4293
|
-
await
|
|
4294
|
-
|
|
4439
|
+
await fs8.readFile(
|
|
4440
|
+
path21.join(root, "storyboard.canvas.json"),
|
|
4295
4441
|
"utf8"
|
|
4296
4442
|
)
|
|
4297
4443
|
)
|
|
4298
4444
|
);
|
|
4299
4445
|
const card = board.cards.find((c) => c.id === cardId);
|
|
4300
4446
|
if (!card) throw new Error("Storyboard card no longer exists");
|
|
4301
|
-
const located = sceneInProject(root, card.source), code = await
|
|
4447
|
+
const located = sceneInProject(root, card.source), code = await fs8.readFile(located.source, "utf8");
|
|
4302
4448
|
const document = await documentLoader.load(located.source, code);
|
|
4303
4449
|
const sourceFiles = Object.fromEntries(
|
|
4304
4450
|
await Promise.all(
|
|
4305
4451
|
document.files.map(
|
|
4306
4452
|
async (file) => [
|
|
4307
4453
|
file,
|
|
4308
|
-
await
|
|
4454
|
+
await fs8.readFile(sceneInProject(root, file).source, "utf8")
|
|
4309
4455
|
]
|
|
4310
4456
|
)
|
|
4311
4457
|
)
|
|
4312
4458
|
);
|
|
4313
|
-
let importPath =
|
|
4459
|
+
let importPath = path21.relative(path21.dirname(context().source), located.source).split(path21.sep).join("/");
|
|
4314
4460
|
if (!importPath.startsWith(".")) importPath = "./" + importPath;
|
|
4315
4461
|
return {
|
|
4316
4462
|
type: "add",
|
|
@@ -4350,7 +4496,7 @@ function createTimelineEditor(root, context, {
|
|
|
4350
4496
|
if (typeof change.file !== "string" || !change.file.endsWith(".tsx") || typeof change.after !== "string" || change.after.length > 1024 * 1024)
|
|
4351
4497
|
throw new Error("Invalid source patch");
|
|
4352
4498
|
const file = sceneInProject(root, change.file).source;
|
|
4353
|
-
const current2 = await
|
|
4499
|
+
const current2 = await fs8.readFile(file, "utf8");
|
|
4354
4500
|
if (current2 !== change.before)
|
|
4355
4501
|
throw Object.assign(new Error(`Source changed: ${change.file}`), {
|
|
4356
4502
|
status: 409
|
|
@@ -4384,7 +4530,7 @@ function createTimelineEditor(root, context, {
|
|
|
4384
4530
|
status: 409
|
|
4385
4531
|
});
|
|
4386
4532
|
for (const change of changed)
|
|
4387
|
-
if (await
|
|
4533
|
+
if (await fs8.readFile(change.file, "utf8") !== change.before)
|
|
4388
4534
|
throw Object.assign(new Error("Source changed while validating"), {
|
|
4389
4535
|
status: 409
|
|
4390
4536
|
});
|
|
@@ -4396,7 +4542,7 @@ function createTimelineEditor(root, context, {
|
|
|
4396
4542
|
}
|
|
4397
4543
|
} catch (error) {
|
|
4398
4544
|
for (const change of written.reverse())
|
|
4399
|
-
if (await
|
|
4545
|
+
if (await fs8.readFile(change.file, "utf8") === change.after)
|
|
4400
4546
|
await write(change.file, change.before);
|
|
4401
4547
|
throw error;
|
|
4402
4548
|
}
|
|
@@ -4459,11 +4605,11 @@ function createTimelineEditor(root, context, {
|
|
|
4459
4605
|
if (!before.inputs.has(file))
|
|
4460
4606
|
throw new Error("Target source is not part of this scene");
|
|
4461
4607
|
const relative = (value) => {
|
|
4462
|
-
let p =
|
|
4608
|
+
let p = path21.relative(path21.dirname(file), sceneInProject(root, value).source).split(path21.sep).join("/");
|
|
4463
4609
|
return p.startsWith(".") ? p : "./" + p;
|
|
4464
4610
|
};
|
|
4465
4611
|
result = applyResourceSource(before.inputs.get(file), plan, {
|
|
4466
|
-
filename:
|
|
4612
|
+
filename: path21.relative(root, file),
|
|
4467
4613
|
definitions: before.definitions,
|
|
4468
4614
|
src: relative(resource.entry),
|
|
4469
4615
|
effectSrc: resource.effect ? relative(resource.effect.src) : void 0,
|
|
@@ -4487,8 +4633,8 @@ function createTimelineEditor(root, context, {
|
|
|
4487
4633
|
if (command.type === "add") {
|
|
4488
4634
|
const board = validateBoard(
|
|
4489
4635
|
JSON.parse(
|
|
4490
|
-
await
|
|
4491
|
-
|
|
4636
|
+
await fs8.readFile(
|
|
4637
|
+
path21.join(root, "storyboard.canvas.json"),
|
|
4492
4638
|
"utf8"
|
|
4493
4639
|
)
|
|
4494
4640
|
)
|
|
@@ -4497,12 +4643,12 @@ function createTimelineEditor(root, context, {
|
|
|
4497
4643
|
const { source: groupSource, scene: groupScene } = sceneInProject(
|
|
4498
4644
|
root,
|
|
4499
4645
|
card.source
|
|
4500
|
-
), code = await
|
|
4646
|
+
), code = await fs8.readFile(groupSource, "utf8");
|
|
4501
4647
|
const definition = {
|
|
4502
4648
|
...describeGroup(code, groupScene),
|
|
4503
4649
|
source: groupScene
|
|
4504
4650
|
};
|
|
4505
|
-
let importPath =
|
|
4651
|
+
let importPath = path21.relative(path21.dirname(before.source), groupSource).split(path21.sep).join("/");
|
|
4506
4652
|
if (!importPath.startsWith(".")) importPath = "./" + importPath;
|
|
4507
4653
|
resolved = {
|
|
4508
4654
|
type: "add",
|
|
@@ -4614,8 +4760,8 @@ var init_timeline_editor = __esm({
|
|
|
4614
4760
|
init_resource_source();
|
|
4615
4761
|
init_timeline();
|
|
4616
4762
|
init_group_model();
|
|
4617
|
-
resolveImport = (source, spec) =>
|
|
4618
|
-
|
|
4763
|
+
resolveImport = (source, spec) => path21.resolve(
|
|
4764
|
+
path21.dirname(source),
|
|
4619
4765
|
spec.endsWith(".tsx") ? spec : spec.endsWith(".js") ? spec.slice(0, -3) + ".tsx" : spec + ".tsx"
|
|
4620
4766
|
);
|
|
4621
4767
|
hash2 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
@@ -4624,12 +4770,12 @@ var init_timeline_editor = __esm({
|
|
|
4624
4770
|
|
|
4625
4771
|
// src/studio-settings.ts
|
|
4626
4772
|
import { mkdir as mkdir8, readFile as readFile8, writeFile as writeFile8, rename as rename3, rm as rm7 } from "node:fs/promises";
|
|
4627
|
-
import
|
|
4773
|
+
import path22 from "node:path";
|
|
4628
4774
|
import { randomBytes } from "node:crypto";
|
|
4629
4775
|
async function studioSettings(root) {
|
|
4630
|
-
const directory =
|
|
4776
|
+
const directory = path22.join(root, ".osmo");
|
|
4631
4777
|
await mkdir8(directory, { recursive: true });
|
|
4632
|
-
const lock =
|
|
4778
|
+
const lock = path22.join(directory, "studio.lock");
|
|
4633
4779
|
const deadline = Date.now() + 5e3;
|
|
4634
4780
|
while (true) {
|
|
4635
4781
|
try {
|
|
@@ -4646,7 +4792,7 @@ async function studioSettings(root) {
|
|
|
4646
4792
|
}
|
|
4647
4793
|
const release = () => rm7(lock, { recursive: true, force: true });
|
|
4648
4794
|
try {
|
|
4649
|
-
const file =
|
|
4795
|
+
const file = path22.join(directory, "studio.json");
|
|
4650
4796
|
let settings, existing = false;
|
|
4651
4797
|
try {
|
|
4652
4798
|
settings = JSON.parse(await readFile8(file, "utf8"));
|
|
@@ -4658,7 +4804,7 @@ async function studioSettings(root) {
|
|
|
4658
4804
|
throw new Error(
|
|
4659
4805
|
"Cannot read .osmo/studio.json. Fix or remove it to reset the Studio address."
|
|
4660
4806
|
);
|
|
4661
|
-
const name =
|
|
4807
|
+
const name = path22.basename(root).normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/['’]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 48).replace(/-$/, "") || "project";
|
|
4662
4808
|
settings = {
|
|
4663
4809
|
version: 1,
|
|
4664
4810
|
id: `${name}-${randomBytes(2).toString("hex")}`,
|
|
@@ -4666,7 +4812,7 @@ async function studioSettings(root) {
|
|
|
4666
4812
|
token: randomBytes(24).toString("hex")
|
|
4667
4813
|
};
|
|
4668
4814
|
}
|
|
4669
|
-
const ignorePath =
|
|
4815
|
+
const ignorePath = path22.join(root, ".gitignore");
|
|
4670
4816
|
let ignore = "";
|
|
4671
4817
|
try {
|
|
4672
4818
|
ignore = await readFile8(ignorePath, "utf8");
|
|
@@ -4714,11 +4860,11 @@ var init_studio_settings = __esm({
|
|
|
4714
4860
|
import { readFile as readFile9, writeFile as writeFile9, rename as rename4, mkdir as mkdir9 } from "node:fs/promises";
|
|
4715
4861
|
import { userInfo } from "node:os";
|
|
4716
4862
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
4717
|
-
import
|
|
4863
|
+
import path23 from "node:path";
|
|
4718
4864
|
async function commentProfile(root) {
|
|
4719
4865
|
try {
|
|
4720
4866
|
const profile = JSON.parse(
|
|
4721
|
-
await readFile9(
|
|
4867
|
+
await readFile9(path23.join(root, ".osmo/comment-profile.json"), "utf8")
|
|
4722
4868
|
);
|
|
4723
4869
|
if (!validAuthor(profile)) throw new Error("Invalid comment profile");
|
|
4724
4870
|
return profile;
|
|
@@ -4737,11 +4883,11 @@ async function saveCommentProfile(root, input) {
|
|
|
4737
4883
|
throw new Error(
|
|
4738
4884
|
"Provide a name and a PNG, JPEG, or WebP avatar under 110 KB."
|
|
4739
4885
|
);
|
|
4740
|
-
const folder =
|
|
4886
|
+
const folder = path23.join(root, ".osmo");
|
|
4741
4887
|
await mkdir9(folder, { recursive: true });
|
|
4742
|
-
const temp =
|
|
4888
|
+
const temp = path23.join(folder, `comment-profile.${randomUUID8()}.tmp`);
|
|
4743
4889
|
await writeFile9(temp, JSON.stringify(profile) + "\n");
|
|
4744
|
-
await rename4(temp,
|
|
4890
|
+
await rename4(temp, path23.join(folder, "comment-profile.json"));
|
|
4745
4891
|
return profile;
|
|
4746
4892
|
}
|
|
4747
4893
|
var init_comment_profile = __esm({
|
|
@@ -4757,16 +4903,16 @@ __export(server_exports, {
|
|
|
4757
4903
|
startStudio: () => startStudio
|
|
4758
4904
|
});
|
|
4759
4905
|
import http from "node:http";
|
|
4760
|
-
import
|
|
4906
|
+
import fs9 from "node:fs";
|
|
4761
4907
|
import { readFile as readFile10, stat as stat4, realpath as realpath3, readdir as readdir3 } from "node:fs/promises";
|
|
4762
|
-
import
|
|
4908
|
+
import path24 from "node:path";
|
|
4763
4909
|
import { createHash as createHash7, randomUUID as randomUUID9 } from "node:crypto";
|
|
4764
4910
|
import { spawn as spawn3 } from "node:child_process";
|
|
4765
4911
|
async function revision(paths2) {
|
|
4766
4912
|
const hash3 = createHash7("sha256");
|
|
4767
4913
|
for (const file of [...new Set(paths2)].sort()) {
|
|
4768
4914
|
hash3.update(file);
|
|
4769
|
-
for await (const chunk of
|
|
4915
|
+
for await (const chunk of fs9.createReadStream(file)) hash3.update(chunk);
|
|
4770
4916
|
}
|
|
4771
4917
|
return `sha256:${hash3.digest("hex")}`;
|
|
4772
4918
|
}
|
|
@@ -4783,7 +4929,7 @@ async function fingerprint(paths2) {
|
|
|
4783
4929
|
).then((v) => v.join("|"));
|
|
4784
4930
|
}
|
|
4785
4931
|
async function studioRevision() {
|
|
4786
|
-
const files = (await readdir3(studioRoot, { recursive: true })).filter((file) => /\.(html|js|css|svg|woff2|wasm)$/.test(file)).sort().map((file) =>
|
|
4932
|
+
const files = (await readdir3(studioRoot, { recursive: true })).filter((file) => /\.(html|js|css|svg|woff2|ttf|wasm)$/.test(file)).sort().map((file) => path24.join(studioRoot, file));
|
|
4787
4933
|
const stamp = await fingerprint(files);
|
|
4788
4934
|
if (stamp !== studioStamp) {
|
|
4789
4935
|
const digest = await revision(files);
|
|
@@ -4821,8 +4967,8 @@ async function startStudio({
|
|
|
4821
4967
|
preview = {},
|
|
4822
4968
|
open = true
|
|
4823
4969
|
}) {
|
|
4824
|
-
const studioBuild =
|
|
4825
|
-
if (
|
|
4970
|
+
const studioBuild = path24.join(runtimeRoot, "packages/studio/tools/build.mjs");
|
|
4971
|
+
if (fs9.existsSync(studioBuild))
|
|
4826
4972
|
await run(process.execPath, [studioBuild], { stdio: ["ignore", 2, 2] });
|
|
4827
4973
|
root = await realpath3(root);
|
|
4828
4974
|
source = await realpath3(source);
|
|
@@ -4859,7 +5005,7 @@ async function startStudio({
|
|
|
4859
5005
|
scene,
|
|
4860
5006
|
preview,
|
|
4861
5007
|
version,
|
|
4862
|
-
projectName:
|
|
5008
|
+
projectName: path24.basename(root),
|
|
4863
5009
|
projectId: settings.id,
|
|
4864
5010
|
loading: true,
|
|
4865
5011
|
error: null,
|
|
@@ -5056,7 +5202,7 @@ async function startStudio({
|
|
|
5056
5202
|
"Content-Type": "application/octet-stream",
|
|
5057
5203
|
"X-Content-Type-Options": "nosniff"
|
|
5058
5204
|
});
|
|
5059
|
-
const stream =
|
|
5205
|
+
const stream = fs9.createReadStream(file2.sourcePath);
|
|
5060
5206
|
res.once("close", () => stream.destroy());
|
|
5061
5207
|
stream.on("error", () => res.destroy()).pipe(res);
|
|
5062
5208
|
return;
|
|
@@ -5112,6 +5258,8 @@ async function startStudio({
|
|
|
5112
5258
|
types3[asset.extension] ?? "application/octet-stream"
|
|
5113
5259
|
);
|
|
5114
5260
|
}
|
|
5261
|
+
if (req.method === "GET" && url2.pathname === "/api/scenes")
|
|
5262
|
+
return json(res, 200, await listProjectScenes(root));
|
|
5115
5263
|
if (req.method === "GET" && url2.pathname === "/api/resources")
|
|
5116
5264
|
return json(
|
|
5117
5265
|
res,
|
|
@@ -5301,14 +5449,14 @@ async function startStudio({
|
|
|
5301
5449
|
geometry = {
|
|
5302
5450
|
revision: rev,
|
|
5303
5451
|
frame,
|
|
5304
|
-
targets: result.targets.filter((t) => rows.has(t.path)).map(({ path:
|
|
5452
|
+
targets: result.targets.filter((t) => rows.has(t.path)).map(({ path: path26, ...t }, order) => ({
|
|
5305
5453
|
...t,
|
|
5306
5454
|
// Filtered to rows above.
|
|
5307
|
-
id: rows.get(
|
|
5455
|
+
id: rows.get(path26),
|
|
5308
5456
|
order
|
|
5309
5457
|
})),
|
|
5310
5458
|
quads: Object.fromEntries(
|
|
5311
|
-
Object.entries(result.quads).filter(([
|
|
5459
|
+
Object.entries(result.quads).filter(([path26]) => rows.has(path26)).map(([path26, quad]) => [rows.get(path26), quad])
|
|
5312
5460
|
)
|
|
5313
5461
|
};
|
|
5314
5462
|
geometryCache.set(key, geometry);
|
|
@@ -5451,7 +5599,7 @@ async function startStudio({
|
|
|
5451
5599
|
"Content-Disposition": `attachment; filename*=UTF-8''${encodeURIComponent(file2.name)}`,
|
|
5452
5600
|
"X-Content-Type-Options": "nosniff"
|
|
5453
5601
|
});
|
|
5454
|
-
const stream =
|
|
5602
|
+
const stream = fs9.createReadStream(filename2);
|
|
5455
5603
|
res.once("close", () => stream.destroy());
|
|
5456
5604
|
stream.on("error", () => res.destroy()).pipe(res);
|
|
5457
5605
|
return;
|
|
@@ -5550,15 +5698,15 @@ async function startStudio({
|
|
|
5550
5698
|
if (!state.meta) throw new Error("Load a scene before exporting");
|
|
5551
5699
|
const settings2 = validateExportSettings(input, state.meta);
|
|
5552
5700
|
const format2 = exportFormats[settings2.format];
|
|
5553
|
-
const filename2 = input.filename ?? `${
|
|
5701
|
+
const filename2 = input.filename ?? `${path24.basename(scene, ".tsx")}-${Date.now()}.${format2.extension}`;
|
|
5554
5702
|
if (typeof filename2 !== "string" || !/^[\w .-]+$/.test(filename2) || !filename2.toLowerCase().endsWith(`.${format2.extension}`) || filename2.startsWith("."))
|
|
5555
5703
|
throw new Error(`Use a filename ending in .${format2.extension}`);
|
|
5556
|
-
const folder =
|
|
5557
|
-
await
|
|
5704
|
+
const folder = path24.join(root, "exports");
|
|
5705
|
+
await fs9.promises.mkdir(folder, { recursive: true });
|
|
5558
5706
|
const canonicalFolder = await realpath3(folder);
|
|
5559
|
-
if (
|
|
5707
|
+
if (path24.relative(root, canonicalFolder).startsWith(".."))
|
|
5560
5708
|
throw new Error("Export folder must stay inside the project");
|
|
5561
|
-
const destination =
|
|
5709
|
+
const destination = path24.join(canonicalFolder, filename2);
|
|
5562
5710
|
if (closed || project !== snapshot || state.loading || editing || dirty)
|
|
5563
5711
|
throw Object.assign(
|
|
5564
5712
|
new Error("The scene changed before export. Try again."),
|
|
@@ -5634,7 +5782,7 @@ async function startStudio({
|
|
|
5634
5782
|
`${cookieName}=${token}; HttpOnly; SameSite=Strict; Path=${projectPath}; Max-Age=31536000`
|
|
5635
5783
|
);
|
|
5636
5784
|
const file = isPage ? "index.html" : url2.pathname.slice(1);
|
|
5637
|
-
if (!/^(index\.html|public\/(osmo-studio-logo|pencil-simple|check-circle-filled)\.svg|src\/[\w/-]+\.(js|css|wasm|svg|woff2)|public\/comment-cursor\/(default|active-outer)\.svg|public\/fonts\/[\w-]+\.woff2)$/.test(
|
|
5785
|
+
if (!/^(index\.html|public\/(osmo-studio-logo|pencil-simple|check-circle-filled)\.svg|src\/[\w/-]+\.(js|css|wasm|svg|woff2|ttf)|public\/comment-cursor\/(default|active-outer)\.svg|public\/fonts\/[\w-]+\.woff2)$/.test(
|
|
5638
5786
|
file
|
|
5639
5787
|
))
|
|
5640
5788
|
return json(res, 404, { error: "Not found" });
|
|
@@ -5644,15 +5792,16 @@ async function startStudio({
|
|
|
5644
5792
|
".js": "text/javascript",
|
|
5645
5793
|
".css": "text/css",
|
|
5646
5794
|
".woff2": "font/woff2",
|
|
5795
|
+
".ttf": "font/ttf",
|
|
5647
5796
|
".svg": "image/svg+xml"
|
|
5648
5797
|
};
|
|
5649
|
-
const bytes = await readFile10(
|
|
5798
|
+
const bytes = await readFile10(path24.join(studioRoot, file));
|
|
5650
5799
|
res.setHeader(
|
|
5651
5800
|
"Content-Security-Policy",
|
|
5652
5801
|
"default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:; media-src 'self' blob:; connect-src 'self' blob:; font-src 'self'; frame-ancestors 'none'"
|
|
5653
5802
|
);
|
|
5654
5803
|
res.writeHead(200, {
|
|
5655
|
-
"Content-Type": types2[
|
|
5804
|
+
"Content-Type": types2[path24.extname(file)],
|
|
5656
5805
|
"Cache-Control": "no-cache"
|
|
5657
5806
|
});
|
|
5658
5807
|
res.end(bytes);
|
|
@@ -5768,16 +5917,16 @@ async function startStudio({
|
|
|
5768
5917
|
const url = `http://127.0.0.1:${settings.port}${projectPath}`;
|
|
5769
5918
|
openBrowser(url);
|
|
5770
5919
|
try {
|
|
5771
|
-
watcher =
|
|
5920
|
+
watcher = fs9.watch(root, { recursive: true }, async (event, filename2) => {
|
|
5772
5921
|
const name = String(filename2 ?? "");
|
|
5773
5922
|
if (/(^|[/\\])\.osmo-edit-/.test(name)) return;
|
|
5774
5923
|
if (name === "storyboard.canvas.json" || name.startsWith("storyboard.canvas.json."))
|
|
5775
5924
|
return;
|
|
5776
5925
|
if (/(^|[/\\])(node_modules|\.git|\.osmo|exports)([/\\]|$)/.test(name))
|
|
5777
5926
|
return;
|
|
5778
|
-
if ((name.startsWith("resources/") || name === "assets" || name.startsWith("assets/")) && !dependencies.includes(
|
|
5927
|
+
if ((name.startsWith("resources/") || name === "assets" || name.startsWith("assets/")) && !dependencies.includes(path24.join(root, name)))
|
|
5779
5928
|
return;
|
|
5780
|
-
const file =
|
|
5929
|
+
const file = path24.join(root, name), before = watchedStamps.get(file);
|
|
5781
5930
|
const next = await fingerprint([file]);
|
|
5782
5931
|
watchedStamps.set(file, next);
|
|
5783
5932
|
if (closed || editing) return;
|
|
@@ -5804,6 +5953,7 @@ var studioStamp, studioDigest;
|
|
|
5804
5953
|
var init_server = __esm({
|
|
5805
5954
|
"src/server.ts"() {
|
|
5806
5955
|
"use strict";
|
|
5956
|
+
init_project_scenes();
|
|
5807
5957
|
init_asset_response();
|
|
5808
5958
|
init_attachment_thumbnail();
|
|
5809
5959
|
init_local_attachment_picker();
|
|
@@ -5827,7 +5977,7 @@ var init_server = __esm({
|
|
|
5827
5977
|
init_check();
|
|
5828
5978
|
import { existsSync as existsSync3, statSync } from "node:fs";
|
|
5829
5979
|
import { mkdir as mkdir10, readFile as readFile11, rename as rename5, rm as rm8 } from "node:fs/promises";
|
|
5830
|
-
import
|
|
5980
|
+
import path25 from "node:path";
|
|
5831
5981
|
|
|
5832
5982
|
// src/cloud.ts
|
|
5833
5983
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
@@ -8714,7 +8864,7 @@ async function checked(root, source) {
|
|
|
8714
8864
|
const seen = /* @__PURE__ */ new Set();
|
|
8715
8865
|
return issues.map((issue) => ({
|
|
8716
8866
|
...issue,
|
|
8717
|
-
...issue.file &&
|
|
8867
|
+
...issue.file && path25.isAbsolute(issue.file) ? { file: path25.relative(root, issue.file) } : {}
|
|
8718
8868
|
})).filter((issue) => {
|
|
8719
8869
|
const key = JSON.stringify(issue);
|
|
8720
8870
|
return !seen.has(key) && seen.add(key);
|
|
@@ -8800,7 +8950,7 @@ var STUDIO = {
|
|
|
8800
8950
|
run: async ([project, folder]) => {
|
|
8801
8951
|
const done = await clone(
|
|
8802
8952
|
parseProject(project),
|
|
8803
|
-
folder === void 0 ? void 0 :
|
|
8953
|
+
folder === void 0 ? void 0 : path25.resolve(folder)
|
|
8804
8954
|
);
|
|
8805
8955
|
return {
|
|
8806
8956
|
status: "cloned",
|
|
@@ -8885,15 +9035,15 @@ ${r.status === "running" ? "Ctrl+C stops the server and renderer." : "Using the
|
|
|
8885
9035
|
);
|
|
8886
9036
|
const still = at !== void 0;
|
|
8887
9037
|
const extension = still ? ".png" : ".mp4";
|
|
8888
|
-
const name =
|
|
8889
|
-
const out =
|
|
8890
|
-
flags.out ??
|
|
9038
|
+
const name = path25.basename(scene, path25.extname(scene));
|
|
9039
|
+
const out = path25.resolve(
|
|
9040
|
+
flags.out ?? path25.join(
|
|
8891
9041
|
root,
|
|
8892
9042
|
"exports",
|
|
8893
9043
|
`${name}${still ? `-${at}s` : ""}${extension}`
|
|
8894
9044
|
)
|
|
8895
9045
|
);
|
|
8896
|
-
if (
|
|
9046
|
+
if (path25.extname(out).toLowerCase() !== extension)
|
|
8897
9047
|
throw new UsageError(
|
|
8898
9048
|
still ? "With --at, --out names a .png file." : "--out names an .mp4 file. For one frame as a .png, add --at SECONDS.",
|
|
8899
9049
|
"osmo studio render --help"
|
|
@@ -8927,10 +9077,10 @@ ${r.status === "running" ? "Ctrl+C stops the server and renderer." : "Using the
|
|
|
8927
9077
|
"osmo studio render --help"
|
|
8928
9078
|
);
|
|
8929
9079
|
const frame = still ? Math.min(Math.round(at * fps), frames - 1) : void 0;
|
|
8930
|
-
await mkdir10(
|
|
8931
|
-
const partial =
|
|
8932
|
-
|
|
8933
|
-
`.${
|
|
9080
|
+
await mkdir10(path25.dirname(out), { recursive: true });
|
|
9081
|
+
const partial = path25.join(
|
|
9082
|
+
path25.dirname(out),
|
|
9083
|
+
`.${path25.basename(out, extension)}.${process.pid}${extension}`
|
|
8934
9084
|
);
|
|
8935
9085
|
try {
|
|
8936
9086
|
await runRenderer(
|