@field/cascade 0.11.0 → 0.12.0
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/AGENTS.md +7 -4
- package/ARCHITECTURE.md +5 -4
- package/CHANGELOG.md +195 -0
- package/README.md +1 -1
- package/dist/assets/AgentPanel-1JugFjE4.css +1 -0
- package/dist/assets/AgentPanel-BDL5MddA.js +5 -0
- package/dist/assets/{CodeEditor-4O7FQ__R.js → CodeEditor-C-xYG-Wn.js} +120 -120
- package/dist/assets/CodePanel-CtXd50m6.js +2 -0
- package/dist/assets/CookInfoPanel-B3NMhKJF.css +1 -0
- package/dist/assets/CookInfoPanel-DG4_TRsX.js +2 -0
- package/dist/assets/CustomNodeDialog-CHfZUs-r.js +1 -0
- package/dist/assets/DefinitionNode-CaRTXQ_b.js +1 -0
- package/dist/assets/DefinitionPanel-C3CxZY9r.js +1 -0
- package/dist/assets/DefinitionPanel-VNLnAiQD.css +1 -0
- package/dist/assets/OpenGraphDialog-BLdr6k_q.js +1 -0
- package/dist/assets/ProjectSettings-ZnvmIAir.js +2 -0
- package/dist/assets/{SettingsDialog-Bz8JxISH.js → SettingsDialog-CBX3Wa87.js} +1 -1
- package/dist/assets/TimelinePanel-CDeR0arY.css +1 -0
- package/dist/assets/TimelinePanel-CWgnFH1e.js +1 -0
- package/dist/assets/{VersionHistoryDialog-D5Z0nJ5-.js → VersionHistoryDialog-BOSoW1R-.js} +1 -1
- package/dist/assets/assert-BO2_erPD.js +1 -0
- package/dist/assets/assert-hvykGCnP.js +1 -0
- package/dist/assets/builder-BmBKcDix.js +1 -0
- package/dist/assets/camera-DFF7YA3F.js +1 -0
- package/dist/assets/checks-DAvyO9Gf.js +1 -0
- package/dist/assets/contactSheet-N1NcaWxw.js +2 -0
- package/dist/assets/{core-Yqi0Ni61.js → core-DB-MkGjj.js} +3 -4
- package/dist/assets/coreTypes-DtnDATZ7.js +1 -0
- package/dist/assets/{cssMode-CxUzc4vp.js → cssMode-DPVckSOc.js} +1 -1
- package/dist/assets/definitions-B227e7f_.js +1 -0
- package/dist/assets/definitions-D0WOUJd7.js +1 -0
- package/dist/assets/{editor.api-BNFb25CX.js → editor.api-Dkutg_Z1.js} +1 -1
- package/dist/assets/expressions-X-Z-GrgW.js +5 -0
- package/dist/assets/file-DAL6IfjV.js +1 -0
- package/dist/assets/filters-D3k1LRMa.js +1 -0
- package/dist/assets/{freemarker2-0ZDnWxeM.js → freemarker2-CLiLKk3B.js} +1 -1
- package/dist/assets/generators-BhDxRAFC.js +1 -0
- package/dist/assets/geo-BsMImJIJ.js +1 -0
- package/dist/assets/geo-C4YngDob.js +2 -0
- package/dist/assets/{handlebars-Cy18MoaD.js → handlebars-CcD3aMeL.js} +1 -1
- package/dist/assets/{html-B-2Dx7nq.js → html-DvZWGfqb.js} +1 -1
- package/dist/assets/{htmlMode-DUAc9B_d.js → htmlMode-DUD3VaoN.js} +1 -1
- package/dist/assets/image-0SfH-snh.js +1 -0
- package/dist/assets/image-Ck-RdGU4.js +2 -0
- package/dist/assets/index-BWnrmIB9.js +137 -0
- package/dist/assets/index-DIaxEONI.css +1 -0
- package/dist/assets/io-BGmt2g0D.js +1 -0
- package/dist/assets/{javascript-DeQsDqP5.js → javascript-Cap2ahgh.js} +1 -1
- package/dist/assets/{jsonMode-BiFwJVSk.js → jsonMode-B0uLjp9C.js} +1 -1
- package/dist/assets/{liquid-BLoHH68a.js → liquid-CnxwhfLn.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-CNFgm1Kv.js → lspLanguageFeatures-Dn9hlKX6.js} +1 -1
- package/dist/assets/math-GeULS-sk.js +1 -0
- package/dist/assets/{mdx-CFXQVNfs.js → mdx-BV3jbrVa.js} +1 -1
- package/dist/assets/pop-KMF_qloi.js +1 -0
- package/dist/assets/preload-helper-Czpn1I53.js +1 -0
- package/dist/assets/{python-Cf7S7p2E.js → python-CGyFk4XK.js} +1 -1
- package/dist/assets/{razor-CMHBcG-g.js → razor-CQfzX3-N.js} +1 -1
- package/dist/assets/remap-5SjXQU-q.js +1 -0
- package/dist/assets/renderMarkdown-ork0Oir7.js +67 -0
- package/dist/assets/studioImages-CAyH-0Pl.js +1 -0
- package/dist/assets/text-ZpX7qNkx.js +2 -0
- package/dist/assets/time-P_OiBhcf.js +1 -0
- package/dist/assets/{tsMode-BEih0GDj.js → tsMode-BOeXzO9H.js} +1 -1
- package/dist/assets/{typescript-COEKYD_a.js → typescript-J1SnE_Tu.js} +1 -1
- package/dist/assets/{workers-BNhAhMNn.js → workers-CYwwTiVF.js} +1 -1
- package/dist/assets/{xml-CPiMpnPl.js → xml-DnPRNzK9.js} +1 -1
- package/dist/assets/{yaml-Bgp1ITLR.js → yaml-BmmYBNa9.js} +1 -1
- package/dist/cli/DefinitionNode-P7AA4TAU.js +15 -0
- package/dist/cli/{Graph-ZJJTRQTM.js → Graph-OB6EWSGL.js} +12 -12
- package/dist/cli/assert-GROGQNCQ.js +25 -0
- package/dist/cli/checks-3WB44XBL.js +189 -0
- package/dist/cli/{chunk-RQZ4SX5R.js → chunk-2JEU3MGB.js} +2 -2
- package/dist/cli/{chunk-WKURDVYP.js → chunk-3RXL5GHB.js} +28 -331
- package/dist/cli/chunk-53LGFPWX.js +138 -0
- package/dist/cli/chunk-5ORUQ6ES.js +267 -0
- package/dist/cli/chunk-73S4JZXK.js +336 -0
- package/dist/cli/{chunk-YAT5Y25U.js → chunk-7QE2ZNSN.js} +3 -3
- package/dist/cli/{chunk-SGJAET2L.js → chunk-BPODRH52.js} +8 -4
- package/dist/cli/{chunk-XWDSKG7N.js → chunk-BZ63XQ3I.js} +2 -2
- package/dist/cli/{chunk-FCDXJ6EK.js → chunk-CTPLRSWF.js} +2 -13
- package/dist/cli/chunk-EU4ZFZ7Y.js +122 -0
- package/dist/cli/{chunk-WIH4TEIS.js → chunk-EZRWJ4G2.js} +1 -1
- package/dist/cli/{chunk-BIJO5OFS.js → chunk-FWMSGKEH.js} +1 -1
- package/dist/cli/{chunk-POMMNKZZ.js → chunk-G23A2BZZ.js} +38 -5
- package/dist/cli/{chunk-35D343DC.js → chunk-GRIN2DDP.js} +19 -1
- package/dist/cli/chunk-JVOCK5W4.js +83 -0
- package/dist/cli/chunk-KBGUO27Q.js +15 -0
- package/dist/cli/{chunk-GFS6W3C5.js → chunk-L6B6FUQP.js} +54 -32
- package/dist/cli/chunk-LTXWBXOM.js +438 -0
- package/dist/cli/chunk-MI5JLQYA.js +220 -0
- package/dist/cli/{chunk-EUIAC3U3.js → chunk-MP653A5O.js} +884 -124
- package/dist/cli/{chunk-A4OAMOZH.js → chunk-NJFMDXHR.js} +13 -3
- package/dist/cli/{chunk-3JTJHF7V.js → chunk-OM6BEME6.js} +4 -4
- package/dist/cli/{chunk-DUIUFVU2.js → chunk-ST6LROUG.js} +49 -23
- package/dist/cli/{chunk-RLCAJBVR.js → chunk-TJW67QSI.js} +8 -7
- package/dist/cli/chunk-WGKPJZDY.js +364 -0
- package/dist/cli/{chunk-D6BUOHUG.js → chunk-X4WKAOQP.js} +4 -4
- package/dist/cli/{chunk-VVGSLVSF.js → chunk-X566FWBV.js} +6 -1
- package/dist/cli/chunk-YH637PDL.js +317 -0
- package/dist/cli/chunk-ZSCUZE6I.js +112 -0
- package/dist/cli/{cliCommands-UQSFGEHF.js → cliCommands-JJN5Z3ZC.js} +23 -17
- package/dist/cli/{commands-Q23M26QM.js → commands-PHYFYA3F.js} +129 -47
- package/dist/cli/contactSheet-2PQVRUFW.js +11 -0
- package/dist/cli/{definitions-HF2FTHYU.js → definitions-BPOPW52L.js} +7 -7
- package/dist/cli/{doctorCommand-B7BPQMN6.js → doctorCommand-ORWQRNYP.js} +36 -6
- package/dist/cli/{edit-JHCPTQFT.js → edit-S5KEQ5RA.js} +3 -3
- package/dist/cli/editorCommands-NFR32JOY.js +41 -0
- package/dist/cli/{encode-HAUZTYPT.js → encode-ICPEPMH5.js} +2 -2
- package/dist/cli/file-3HTQHPYQ.js +41 -0
- package/dist/cli/filters-6ODFWYK4.js +379 -0
- package/dist/cli/generators-YU7GB4MD.js +210 -0
- package/dist/cli/{geo-4RNK3QVN.js → geo-6ZPGKZRU.js} +7 -7
- package/dist/cli/{graphCommands-6BMAKGQE.js → graphCommands-EOUOBOIJ.js} +4 -4
- package/dist/cli/{graphFile-VPSQ6OF7.js → graphFile-YTGJB5F5.js} +25 -19
- package/dist/cli/{headlessCanvas-5XLDZT7M.js → headlessCanvas-ENRHC7GS.js} +3 -2
- package/dist/cli/{helpCommand-3VRE3KVO.js → helpCommand-PULH6ZYB.js} +9 -7
- package/dist/cli/image-TXELZZK5.js +55 -0
- package/dist/cli/index.js +14 -14
- package/dist/cli/{math-ZRMLKU2D.js → math-RSYRQ3C6.js} +5 -5
- package/dist/cli/{mcp-ZLEEMYGF.js → mcp-JVZFQOEX.js} +4 -4
- package/dist/cli/{parity-KINARCP2.js → parity-X6DEH2DV.js} +42 -36
- package/dist/cli/{pop-USDFXB7H.js → pop-B7SIIYKN.js} +5 -5
- package/dist/cli/project-VYGIA7JX.js +12 -0
- package/dist/cli/{projectDefinition-DMTZN2NE.js → projectDefinition-F3D7UIHJ.js} +8 -8
- package/dist/cli/{projectRuntime-Z5PJOH47.js → projectRuntime-2Q4JKEH3.js} +19 -12
- package/dist/cli/registerStandardNodes-BXJCB6E4.js +744 -0
- package/dist/cli/{render-YURAPPUI.js → render-PT4MWI6U.js} +24 -22
- package/dist/cli/{runCommand-PI764LEG.js → runCommand-HNPQVZMS.js} +3 -3
- package/dist/cli/runner-5XPXVW7J.js +43 -0
- package/dist/cli/runtime/image.ts +27 -2
- package/dist/cli/runtime/io.ts +78 -0
- package/dist/cli/share-MHF6TNRK.js +166 -0
- package/dist/cli/stageCache-TY2BFZJR.js +15 -0
- package/dist/cli/{stageRunner-KUNIMQKJ.js → stageRunner-KY2EJP7X.js} +2 -2
- package/dist/cli/text-MB3EKR4H.js +126 -0
- package/dist/cli/{verbs-RCE3K4VQ.js → verbs-PI2JH5FI.js} +2 -2
- package/dist/cli/webBuild-WPPMGGW7.js +44 -0
- package/dist/contracts/capabilities.d.ts +18 -1
- package/dist/contracts/color.d.ts +8 -0
- package/dist/contracts/color.js +23 -0
- package/dist/contracts/definition.d.ts +10 -0
- package/dist/contracts/schema.d.ts +1 -1
- package/dist/contracts/schema.js +1 -0
- package/dist/contracts/validate.js +11 -1
- package/dist/contracts/values.d.ts +24 -0
- package/dist/image.d.ts +15 -0
- package/dist/image.js +36 -2
- package/dist/index.html +8 -2
- package/dist/io.d.ts +29 -0
- package/dist/io.js +40 -0
- package/dist/player/app.js +3 -2
- package/dist/player/editor.js +3 -2
- package/dist/player/editorEngine.js +3 -2
- package/dist/runtime/builtins/assert/checks.d.ts +18 -0
- package/dist/runtime/builtins/assert/checks.js +207 -0
- package/dist/runtime/builtins/assert/definitions.d.ts +130 -0
- package/dist/runtime/builtins/assert/definitions.js +54 -0
- package/dist/runtime/builtins/assert/index.d.ts +152 -0
- package/dist/runtime/builtins/assert/index.js +36 -0
- package/dist/runtime/builtins/image/contactSheet.d.ts +3 -165
- package/dist/runtime/builtins/image/contactSheet.js +0 -49
- package/dist/runtime/builtins/image/definitions.d.ts +950 -0
- package/dist/runtime/builtins/image/definitions.js +343 -0
- package/dist/runtime/builtins/image/file.d.ts +8 -0
- package/dist/runtime/builtins/image/file.js +37 -0
- package/dist/runtime/builtins/image/filters.d.ts +8 -0
- package/dist/runtime/builtins/image/filters.js +394 -0
- package/dist/runtime/builtins/image/generators.d.ts +7 -0
- package/dist/runtime/builtins/image/generators.js +144 -0
- package/dist/runtime/builtins/image/index.d.ts +947 -4
- package/dist/runtime/builtins/image/index.js +41 -6
- package/dist/runtime/builtins/image/io.d.ts +100 -0
- package/dist/runtime/builtins/image/io.js +142 -0
- package/dist/runtime/builtins/image/planar.d.ts +55 -0
- package/dist/runtime/builtins/image/planar.js +274 -0
- package/dist/runtime/builtins/image/simplex.d.ts +22 -0
- package/dist/runtime/builtins/image/simplex.js +92 -0
- package/dist/runtime/builtins/image/text.d.ts +4 -0
- package/dist/runtime/builtins/image/text.js +141 -0
- package/dist/runtime/builtins/index.d.ts +3 -0
- package/dist/runtime/builtins/index.js +7 -0
- package/dist/runtime/gpu.d.ts +25 -0
- package/dist/runtime/gpu.js +40 -0
- package/dist/runtime/runtime.d.ts +2 -0
- package/dist/runtime/runtime.js +268 -44
- package/dist/runtime/types.d.ts +30 -0
- package/doc/CLI_REFERENCE.md +21 -3
- package/doc/EDITOR_EXPORT.md +26 -0
- package/doc/MCP.md +2 -1
- package/doc/NODE_REFERENCE.md +286 -14
- package/doc/PROJECT_AUTHORING.md +98 -3
- package/package.json +4 -1
- package/dist/assets/CodePanel-BU-autmq.js +0 -2
- package/dist/assets/CustomNodeDialog-BRpDfOV5.js +0 -1
- package/dist/assets/OpenGraphDialog-BwbRlC0X.js +0 -1
- package/dist/assets/ProjectSettings-DLOXLxcA.js +0 -2
- package/dist/assets/definitions-GreEMXLV.js +0 -1
- package/dist/assets/geo-u0n_jOsD.js +0 -1
- package/dist/assets/image-cAa-Nu9O.js +0 -2062
- package/dist/assets/index-5ZtDAuy2.js +0 -220
- package/dist/assets/index-7vae6YFC.css +0 -1
- package/dist/assets/math-DioIQOPJ.js +0 -1
- package/dist/assets/pop-DC_TQG9t.js +0 -1
- package/dist/cli/chunk-36P5K3SD.js +0 -631
- package/dist/cli/chunk-4UDFYF5G.js +0 -132
- package/dist/cli/chunk-53NMBJAO.js +0 -35
- package/dist/cli/chunk-MIIEFUGG.js +0 -623
- package/dist/cli/chunk-QBBKRNGY.js +0 -90
- package/dist/cli/chunk-WPUDGEFY.js +0 -20
- package/dist/cli/editorCommands-UIF5YLUZ.js +0 -33
- package/dist/cli/project-K3QIPGBB.js +0 -12
- package/dist/cli/registerStandardNodes-S2VLMF6J.js +0 -3081
- package/dist/cli/runner-RA6UKEPD.js +0 -37
- package/dist/cli/webBuild-GT4PZT5D.js +0 -37
- package/dist/runtime/builtins/headless.d.ts +0 -19
- package/dist/runtime/builtins/headless.js +0 -25
|
@@ -5,30 +5,21 @@ import {
|
|
|
5
5
|
inside,
|
|
6
6
|
playerFile,
|
|
7
7
|
staticRuntimePlugin
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OM6BEME6.js";
|
|
9
9
|
import {
|
|
10
10
|
PREFLIGHT_WARNING_CODES,
|
|
11
|
-
cookProjectGraph,
|
|
12
11
|
createRuntime,
|
|
13
|
-
installProjectIo,
|
|
14
12
|
moduleId,
|
|
15
13
|
projectModuleFile,
|
|
16
14
|
validateNodeModuleArchitecture
|
|
17
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-MP653A5O.js";
|
|
18
16
|
import {
|
|
19
17
|
builtinNodeRegistration
|
|
20
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-MI5JLQYA.js";
|
|
21
19
|
import {
|
|
22
20
|
ProjectRoot,
|
|
23
21
|
extractNodeDefinition
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import {
|
|
26
|
-
installHeadlessCanvas
|
|
27
|
-
} from "./chunk-WIH4TEIS.js";
|
|
28
|
-
|
|
29
|
-
// src/cli/editorCommands.ts
|
|
30
|
-
import fs2 from "node:fs/promises";
|
|
31
|
-
import path2 from "node:path";
|
|
22
|
+
} from "./chunk-2JEU3MGB.js";
|
|
32
23
|
|
|
33
24
|
// packages/runtime/src/editor/index.ts
|
|
34
25
|
var SCENE_FORMAT = "cascade.scene";
|
|
@@ -335,7 +326,8 @@ async function exportEditor(file, options = {}) {
|
|
|
335
326
|
const directory = await buildSite(prepared, options.out);
|
|
336
327
|
const plan = JSON.parse(await fs.readFile(path.join(directory, "export-manifest.json"), "utf8"));
|
|
337
328
|
let url;
|
|
338
|
-
if (options.deploy === "vercel")
|
|
329
|
+
if (options.deploy === "vercel")
|
|
330
|
+
url = (await deployVercel(directory, { project: options.project ?? prepared.plan.editor.id, scope: options.scope, public: options.public === true, log })).deployment;
|
|
339
331
|
return { plan, directory, ...url ? { url } : {} };
|
|
340
332
|
}
|
|
341
333
|
async function prepareExport(file) {
|
|
@@ -659,7 +651,7 @@ async function secretValues(projectRoot) {
|
|
|
659
651
|
values.set("your home directory", os.homedir());
|
|
660
652
|
return [...values];
|
|
661
653
|
}
|
|
662
|
-
async
|
|
654
|
+
var deployVercel = async (directory, { project: projectName, scope, public: intendedPublic, log }) => {
|
|
663
655
|
const team = scope ?? "field-io";
|
|
664
656
|
const scratch = await fs.mkdtemp(path.join(os.tmpdir(), "cascade-vercel-"));
|
|
665
657
|
try {
|
|
@@ -671,16 +663,21 @@ async function deployVercel(directory, projectName, scope, log, intendedPublic)
|
|
|
671
663
|
await fs.rm(scratch, { recursive: true, force: true });
|
|
672
664
|
}
|
|
673
665
|
log(`Deploying ${directory} to Vercel project ${team}/${projectName}\u2026`);
|
|
674
|
-
const { stdout } = await run("vercel", ["deploy", "--prod", "--yes", "--scope", team], { cwd: directory, maxBuffer: 16 * 1024 * 1024 });
|
|
675
|
-
const
|
|
676
|
-
if (!
|
|
666
|
+
const { stdout, stderr } = await run("vercel", ["deploy", "--prod", "--yes", "--scope", team], { cwd: directory, maxBuffer: 16 * 1024 * 1024 });
|
|
667
|
+
const deployment = stdout.trim().split("\n").reverse().find((line) => /^https:\/\//.test(line.trim()))?.trim();
|
|
668
|
+
if (!deployment) throw new Error(`Vercel did not report a deployment URL:
|
|
677
669
|
${stdout}`);
|
|
678
|
-
const
|
|
670
|
+
const url = productionAlias(`${stdout}
|
|
671
|
+
${stderr}`) ?? `https://${projectName}.vercel.app`;
|
|
672
|
+
const response = await fetch(`${url}/`, { redirect: "manual" });
|
|
679
673
|
const verdict = publicVerdict(response.status, intendedPublic);
|
|
680
|
-
if (verdict === "public-as-intended") log(`Public, as intended
|
|
674
|
+
if (verdict === "public-as-intended") log(`Public, as intended: ${url}`);
|
|
681
675
|
else if (verdict === "public-by-accident")
|
|
682
|
-
throw new Error(`Deployed, but
|
|
683
|
-
return url;
|
|
676
|
+
throw new Error(`Deployed, but ${url} is publicly readable. Turn on Vercel Authentication (Settings \u2192 Deployment Protection) for ${team}/${projectName}.`);
|
|
677
|
+
return { deployment, url };
|
|
678
|
+
};
|
|
679
|
+
function productionAlias(output) {
|
|
680
|
+
return /Aliased:?\s+(https:\/\/[^\s[\]]+)/.exec(output)?.[1]?.replace(/\/$/, "");
|
|
684
681
|
}
|
|
685
682
|
function publicVerdict(status, intendedPublic) {
|
|
686
683
|
if (status !== 200) return "protected";
|
|
@@ -806,314 +803,14 @@ function formatSize(bytes) {
|
|
|
806
803
|
return bytes >= 1048576 ? `${(bytes / 1048576).toFixed(1)} MB` : bytes >= 1024 ? `${Math.round(bytes / 1024)} KB` : `${bytes} B`;
|
|
807
804
|
}
|
|
808
805
|
|
|
809
|
-
// src/cli/editorCommands.ts
|
|
810
|
-
var EDITOR_COMMANDS = ["export", "freeze", "publish", "scene"];
|
|
811
|
-
var EDITOR_HELP = `
|
|
812
|
-
Shareable editors (see doc/EDITOR_EXPORT.md):
|
|
813
|
-
cascade export <graph> --plan Print what would ship, build nothing
|
|
814
|
-
cascade export <graph> --out <dir> [--deploy vercel [--project <name>] [--scope <team>] [--public]]
|
|
815
|
-
cascade publish <graph> <node>.<prop> [--id <id>] [--label <text>] [--group <name>]
|
|
816
|
-
[--control <kind>] [--min n] [--max n] [--step n] [--help <text>] [--remove]
|
|
817
|
-
cascade freeze <graph> <node> --fields <a,b.c> [--output <port>] [--personal <a,..>]
|
|
818
|
-
cascade scene <graph> <scene.json> --out <new-graph.cascade>
|
|
819
|
-
cascade run <graph> --scene <scene.json> [--write <node>.<port>=<file>]...
|
|
820
|
-
`;
|
|
821
|
-
async function runEditorCommand(args) {
|
|
822
|
-
const [command, graph] = args;
|
|
823
|
-
if (!graph || graph.startsWith("-")) throw new Error(`cascade ${command} requires a graph file`);
|
|
824
|
-
const flags = parseFlags(args.slice(2));
|
|
825
|
-
switch (command) {
|
|
826
|
-
case "export":
|
|
827
|
-
return exportCommand(graph, flags);
|
|
828
|
-
case "publish":
|
|
829
|
-
return publishCommand(graph, flags);
|
|
830
|
-
case "freeze":
|
|
831
|
-
return freezeCommand(graph, flags);
|
|
832
|
-
case "scene":
|
|
833
|
-
return sceneCommand(graph, flags);
|
|
834
|
-
case "run":
|
|
835
|
-
return runWithScene(graph, flags);
|
|
836
|
-
default:
|
|
837
|
-
throw new Error(`Unknown command ${command}`);
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
var SWITCHES = /* @__PURE__ */ new Set(["--plan", "--remove", "--json", "--public"]);
|
|
841
|
-
function parseFlags(args) {
|
|
842
|
-
const positional = [];
|
|
843
|
-
const values = /* @__PURE__ */ new Map();
|
|
844
|
-
const switches = /* @__PURE__ */ new Set();
|
|
845
|
-
for (let index = 0; index < args.length; index++) {
|
|
846
|
-
const arg = args[index];
|
|
847
|
-
if (!arg.startsWith("--")) {
|
|
848
|
-
positional.push(arg);
|
|
849
|
-
continue;
|
|
850
|
-
}
|
|
851
|
-
if (SWITCHES.has(arg)) {
|
|
852
|
-
switches.add(arg);
|
|
853
|
-
continue;
|
|
854
|
-
}
|
|
855
|
-
const value = args[index + 1];
|
|
856
|
-
if (value === void 0 || value.startsWith("--") && !/^--?\d/.test(value)) throw new Error(`${arg} needs a value`);
|
|
857
|
-
values.set(arg, [...values.get(arg) ?? [], value]);
|
|
858
|
-
index++;
|
|
859
|
-
}
|
|
860
|
-
return { positional, values, switches };
|
|
861
|
-
}
|
|
862
|
-
var one = (flags, name) => {
|
|
863
|
-
const list = flags.values.get(name);
|
|
864
|
-
return list?.[list.length - 1];
|
|
865
|
-
};
|
|
866
|
-
async function exportCommand(graph, flags) {
|
|
867
|
-
const deploy = one(flags, "--deploy");
|
|
868
|
-
if (deploy && deploy !== "vercel") throw new Error("--deploy supports vercel only");
|
|
869
|
-
const result = await exportEditor(graph, {
|
|
870
|
-
plan: flags.switches.has("--plan"),
|
|
871
|
-
out: one(flags, "--out"),
|
|
872
|
-
...deploy ? { deploy: "vercel" } : {},
|
|
873
|
-
project: one(flags, "--project"),
|
|
874
|
-
scope: one(flags, "--scope"),
|
|
875
|
-
...flags.switches.has("--public") ? { public: true } : {}
|
|
876
|
-
});
|
|
877
|
-
if (flags.switches.has("--json")) console.log(JSON.stringify(result.plan, null, 2));
|
|
878
|
-
else console.log(formatPlan(result.plan));
|
|
879
|
-
if (result.directory) console.log(`
|
|
880
|
-
Editor exported: ${result.directory}
|
|
881
|
-
Manifest: ${path2.join(result.directory, "export-manifest.json")}`);
|
|
882
|
-
if (result.url) console.log(`Deployed: ${result.url}`);
|
|
883
|
-
}
|
|
884
|
-
async function readGraph(file) {
|
|
885
|
-
return JSON.parse(await fs2.readFile(file, "utf8"));
|
|
886
|
-
}
|
|
887
|
-
async function writeEditor(file, document, editor) {
|
|
888
|
-
const next = { ...document, editor };
|
|
889
|
-
await fs2.writeFile(file, `${JSON.stringify(next, null, 2)}
|
|
890
|
-
`);
|
|
891
|
-
}
|
|
892
|
-
async function publishCommand(graph, flags) {
|
|
893
|
-
const target = flags.positional[0];
|
|
894
|
-
const match = target ? /^([^.]+)\.(.+)$/.exec(target) : null;
|
|
895
|
-
if (!match) throw new Error("cascade publish needs <node>.<prop>");
|
|
896
|
-
const [, node, prop] = match;
|
|
897
|
-
const document = await readGraph(graph);
|
|
898
|
-
if (!document.nodes.some((item) => item.id === node)) throw new Error(`No node ${node} in ${graph}`);
|
|
899
|
-
const editor = document.editor ?? { id: path2.basename(graph).replace(/\.cascade$/, "") };
|
|
900
|
-
const parameters = [...editor.parameters ?? []];
|
|
901
|
-
const id = one(flags, "--id") ?? prop;
|
|
902
|
-
const existing = parameters.findIndex((item) => item.node === node && item.prop === prop);
|
|
903
|
-
if (flags.switches.has("--remove")) {
|
|
904
|
-
if (existing < 0) throw new Error(`${node}.${prop} is not published`);
|
|
905
|
-
parameters.splice(existing, 1);
|
|
906
|
-
await writeEditor(graph, document, { ...editor, parameters });
|
|
907
|
-
console.log(`Unpublished ${node}.${prop}`);
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
const number = (name) => {
|
|
911
|
-
const value = one(flags, name);
|
|
912
|
-
if (value === void 0) return void 0;
|
|
913
|
-
const parsed = Number(value);
|
|
914
|
-
if (!Number.isFinite(parsed)) throw new Error(`${name} must be a number`);
|
|
915
|
-
return parsed;
|
|
916
|
-
};
|
|
917
|
-
const previous = existing >= 0 ? parameters[existing] : void 0;
|
|
918
|
-
const parameter = {
|
|
919
|
-
...previous,
|
|
920
|
-
id: previous?.id ?? id,
|
|
921
|
-
node,
|
|
922
|
-
prop,
|
|
923
|
-
...one(flags, "--label") ? { label: one(flags, "--label") } : {},
|
|
924
|
-
...one(flags, "--group") ? { group: one(flags, "--group") } : {},
|
|
925
|
-
...one(flags, "--control") ? { control: one(flags, "--control") } : {},
|
|
926
|
-
...number("--min") !== void 0 ? { min: number("--min") } : {},
|
|
927
|
-
...number("--max") !== void 0 ? { max: number("--max") } : {},
|
|
928
|
-
...number("--step") !== void 0 ? { step: number("--step") } : {},
|
|
929
|
-
...number("--order") !== void 0 ? { order: number("--order") } : {},
|
|
930
|
-
...one(flags, "--help") ? { help: one(flags, "--help") } : {},
|
|
931
|
-
...one(flags, "--unit") ? { unit: one(flags, "--unit") } : {}
|
|
932
|
-
};
|
|
933
|
-
if (existing >= 0) parameters[existing] = parameter;
|
|
934
|
-
else {
|
|
935
|
-
if (parameters.some((item) => item.id === parameter.id)) throw new Error(`Parameter id ${parameter.id} is taken; pass --id`);
|
|
936
|
-
parameters.push(parameter);
|
|
937
|
-
}
|
|
938
|
-
await writeEditor(graph, document, { ...editor, parameters });
|
|
939
|
-
console.log(`${existing >= 0 ? "Updated" : "Published"} ${node}.${prop} as "${parameter.id}"`);
|
|
940
|
-
}
|
|
941
|
-
async function freezeCommand(graph, flags) {
|
|
942
|
-
const node = flags.positional[0];
|
|
943
|
-
if (!node) throw new Error("cascade freeze needs a node id");
|
|
944
|
-
const fields = (one(flags, "--fields") ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
945
|
-
if (!fields.length) throw new Error("cascade freeze needs --fields: name exactly the values to keep");
|
|
946
|
-
const personal = (one(flags, "--personal") ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
947
|
-
const document = await readGraph(graph);
|
|
948
|
-
if (!document.nodes.some((item) => item.id === node)) throw new Error(`No node ${node} in ${graph}`);
|
|
949
|
-
const graphFile = path2.resolve(graph);
|
|
950
|
-
const project = new ProjectRoot(path2.dirname(graphFile));
|
|
951
|
-
await installHeadlessCanvas().catch(() => void 0);
|
|
952
|
-
const removeIo = installProjectIo(project);
|
|
953
|
-
const { outputs } = await cookProjectGraph(graphFile, document, node).finally(removeIo);
|
|
954
|
-
const produced = outputs.get(node);
|
|
955
|
-
if (!produced) throw new Error(`${node} produced nothing`);
|
|
956
|
-
const output = one(flags, "--output") ?? [...produced.keys()][0];
|
|
957
|
-
if (!produced.has(output)) throw new Error(`${node} has no output ${output}; it has ${[...produced.keys()].join(", ")}`);
|
|
958
|
-
const full = toJson(produced.get(output));
|
|
959
|
-
const directory = path2.join("frozen", node);
|
|
960
|
-
await fs2.mkdir(project.resolve(directory), { recursive: true });
|
|
961
|
-
const files = [];
|
|
962
|
-
const pick = async (value) => {
|
|
963
|
-
if (value && typeof value === "object" && !Array.isArray(value) && typeof value.path === "string") {
|
|
964
|
-
const from = value.path;
|
|
965
|
-
const source = project.resolve(from.replace(/^\.\//, ""));
|
|
966
|
-
const name = `${directory}/${path2.basename(from)}`;
|
|
967
|
-
await fs2.copyFile(source, project.resolve(name));
|
|
968
|
-
files.push(name);
|
|
969
|
-
return { ...value, path: name };
|
|
970
|
-
}
|
|
971
|
-
if (Array.isArray(value)) return Promise.all(value.map(pick));
|
|
972
|
-
if (value && typeof value === "object")
|
|
973
|
-
return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await pick(item)])));
|
|
974
|
-
return value;
|
|
975
|
-
};
|
|
976
|
-
const chosen = {};
|
|
977
|
-
for (const field of fields) {
|
|
978
|
-
const value = getPath(full, field);
|
|
979
|
-
if (value === void 0) throw new Error(`${node}.${output} has no field ${field}`);
|
|
980
|
-
setPath(chosen, field, await pick(value));
|
|
981
|
-
}
|
|
982
|
-
if (fields.includes("path") && typeof chosen.path === "string" && full && typeof full === "object" && !Array.isArray(full)) {
|
|
983
|
-
const { path: copied } = await pick({ path: chosen.path });
|
|
984
|
-
chosen.path = copied;
|
|
985
|
-
}
|
|
986
|
-
const snapshot = {
|
|
987
|
-
format: "cascade.frozen",
|
|
988
|
-
version: 1,
|
|
989
|
-
node,
|
|
990
|
-
output,
|
|
991
|
-
fields,
|
|
992
|
-
...personal.length ? { personal } : {},
|
|
993
|
-
value: chosen,
|
|
994
|
-
...files.length ? { files } : {},
|
|
995
|
-
frozenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
996
|
-
};
|
|
997
|
-
const snapshotFile = `${directory}.json`;
|
|
998
|
-
await fs2.writeFile(project.resolve(snapshotFile), `${JSON.stringify(snapshot, null, 2)}
|
|
999
|
-
`);
|
|
1000
|
-
const editor = document.editor ?? { id: path2.basename(graph).replace(/\.cascade$/, "") };
|
|
1001
|
-
const freeze = (editor.freeze ?? []).filter((item) => item.node !== node);
|
|
1002
|
-
freeze.push({ node, output, snapshot: snapshotFile });
|
|
1003
|
-
await writeEditor(graph, document, { ...editor, freeze });
|
|
1004
|
-
console.log(`Froze ${node}.${output}: ${fields.join(", ")} \u2192 ${snapshotFile}${files.length ? ` (+${files.length} file${files.length > 1 ? "s" : ""})` : ""}`);
|
|
1005
|
-
}
|
|
1006
|
-
async function sceneCommand(graph, flags) {
|
|
1007
|
-
const sceneFile = flags.positional[0];
|
|
1008
|
-
const out = one(flags, "--out");
|
|
1009
|
-
if (!sceneFile || !out) throw new Error("cascade scene needs <scene.json> --out <new-graph.cascade>");
|
|
1010
|
-
if (path2.resolve(out) === path2.resolve(graph)) throw new Error("--out must be a new file; the source graph is never overwritten");
|
|
1011
|
-
const { document, warnings } = await sceneDocument(graph, sceneFile);
|
|
1012
|
-
await fs2.writeFile(out, `${JSON.stringify(document, null, 2)}
|
|
1013
|
-
`);
|
|
1014
|
-
for (const warning of warnings) console.warn(`Warning: ${warning}`);
|
|
1015
|
-
console.log(`Scene applied: ${out}`);
|
|
1016
|
-
}
|
|
1017
|
-
async function sceneDocument(graph, sceneFile) {
|
|
1018
|
-
const document = await readGraph(graph);
|
|
1019
|
-
const scene = parseScene(await fs2.readFile(sceneFile, "utf8"));
|
|
1020
|
-
const project = new ProjectRoot(path2.dirname(path2.resolve(graph)));
|
|
1021
|
-
const located = /* @__PURE__ */ new Map();
|
|
1022
|
-
for (const [id, file] of Object.entries(scene.files ?? {})) {
|
|
1023
|
-
const found = await locateSceneFile(project, file);
|
|
1024
|
-
if (found) located.set(id, { path: found, ...file.mediaType ? { mediaType: file.mediaType } : {} });
|
|
1025
|
-
}
|
|
1026
|
-
return applyScene(document, scene, { resolveFile: (parameter) => located.get(parameter.id) });
|
|
1027
|
-
}
|
|
1028
|
-
async function locateSceneFile(project, file) {
|
|
1029
|
-
const { createHash } = await import("node:crypto");
|
|
1030
|
-
const safe = path2.basename(file.name).replace(/[^A-Za-z0-9._-]/g, "_");
|
|
1031
|
-
if (file.data) {
|
|
1032
|
-
const bytes = fromBase64(file.data);
|
|
1033
|
-
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
1034
|
-
if (digest !== file.sha256) throw new Error(`Scene file ${file.name} does not match its hash`);
|
|
1035
|
-
const name = `assets/scenes/${digest.slice(0, 12)}-${safe}`;
|
|
1036
|
-
await fs2.mkdir(path2.dirname(project.resolve(name)), { recursive: true });
|
|
1037
|
-
await fs2.writeFile(project.resolve(name), bytes);
|
|
1038
|
-
return name;
|
|
1039
|
-
}
|
|
1040
|
-
for (const candidate of await filesNamed(project.root, path2.basename(file.name))) {
|
|
1041
|
-
const digest = createHash("sha256").update(await fs2.readFile(candidate)).digest("hex");
|
|
1042
|
-
if (digest === file.sha256) return path2.relative(project.root, candidate).split(path2.sep).join("/");
|
|
1043
|
-
}
|
|
1044
|
-
return void 0;
|
|
1045
|
-
}
|
|
1046
|
-
async function filesNamed(root, name) {
|
|
1047
|
-
const found = [];
|
|
1048
|
-
const walk = async (directory, depth) => {
|
|
1049
|
-
if (depth > 6) return;
|
|
1050
|
-
for (const entry of await fs2.readdir(directory, { withFileTypes: true }).catch(() => [])) {
|
|
1051
|
-
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".cascade-cache") continue;
|
|
1052
|
-
const file = path2.join(directory, entry.name);
|
|
1053
|
-
if (entry.isDirectory()) await walk(file, depth + 1);
|
|
1054
|
-
else if (entry.name === name) found.push(file);
|
|
1055
|
-
}
|
|
1056
|
-
};
|
|
1057
|
-
await walk(path2.join(root, "assets"), 0);
|
|
1058
|
-
await walk(path2.join(root, "licensed"), 0);
|
|
1059
|
-
return found;
|
|
1060
|
-
}
|
|
1061
|
-
async function runWithScene(graph, flags) {
|
|
1062
|
-
const graphFile = path2.resolve(graph);
|
|
1063
|
-
let document = await readGraph(graph);
|
|
1064
|
-
const scene = one(flags, "--scene");
|
|
1065
|
-
if (scene) {
|
|
1066
|
-
const applied = await sceneDocument(graph, scene);
|
|
1067
|
-
document = applied.document;
|
|
1068
|
-
for (const warning of applied.warnings) console.warn(`Warning: ${warning}`);
|
|
1069
|
-
}
|
|
1070
|
-
await installHeadlessCanvas().catch(() => void 0);
|
|
1071
|
-
const project = new ProjectRoot(path2.dirname(graphFile));
|
|
1072
|
-
const removeIo = installProjectIo(project);
|
|
1073
|
-
const { outputs } = await cookProjectGraph(graphFile, document).finally(removeIo);
|
|
1074
|
-
for (const spec of flags.values.get("--write") ?? []) {
|
|
1075
|
-
const match = /^([^.=]+)\.([^=]+)=(.+)$/.exec(spec);
|
|
1076
|
-
if (!match) throw new Error(`--write expects <node>.<port>=<file>, got ${spec}`);
|
|
1077
|
-
const [, node, port, file] = match;
|
|
1078
|
-
const value = outputs.get(node)?.get(port);
|
|
1079
|
-
if (value === void 0) throw new Error(`${node}.${port} produced nothing`);
|
|
1080
|
-
if (typeof value === "string") await fs2.writeFile(file, value);
|
|
1081
|
-
else if (value && typeof value === "object" && typeof value.path === "string")
|
|
1082
|
-
await fs2.copyFile(project.resolve(value.path.replace(/^\.\//, "")), file);
|
|
1083
|
-
else await fs2.writeFile(file, JSON.stringify(toJson(value)));
|
|
1084
|
-
console.log(`Wrote ${node}.${port} \u2192 ${file}`);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
function toJson(value) {
|
|
1088
|
-
return JSON.parse(JSON.stringify(value, (_key, item) => ArrayBuffer.isView(item) ? Array.from(item) : item) ?? "null");
|
|
1089
|
-
}
|
|
1090
|
-
function getPath(value, field) {
|
|
1091
|
-
let current = value;
|
|
1092
|
-
for (const part of field.split(".").flatMap((item) => item.split(/\[(\d+)\]/).filter(Boolean))) {
|
|
1093
|
-
if (current === null || typeof current !== "object") return void 0;
|
|
1094
|
-
current = current[part];
|
|
1095
|
-
}
|
|
1096
|
-
return current;
|
|
1097
|
-
}
|
|
1098
|
-
function setPath(target, field, value) {
|
|
1099
|
-
const parts = field.split(".").flatMap((item) => item.split(/\[(\d+)\]/).filter(Boolean));
|
|
1100
|
-
let current = target;
|
|
1101
|
-
parts.forEach((part, index) => {
|
|
1102
|
-
const last = index === parts.length - 1;
|
|
1103
|
-
const container = current;
|
|
1104
|
-
if (last) {
|
|
1105
|
-
container[part] = value;
|
|
1106
|
-
return;
|
|
1107
|
-
}
|
|
1108
|
-
if (container[part] === void 0) container[part] = /^\d+$/.test(parts[index + 1]) ? [] : {};
|
|
1109
|
-
current = container[part];
|
|
1110
|
-
});
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
806
|
export {
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
807
|
+
parseScene,
|
|
808
|
+
applyScene,
|
|
809
|
+
fromBase64,
|
|
810
|
+
exportEditor,
|
|
811
|
+
formatPlan,
|
|
812
|
+
vercelConfig,
|
|
813
|
+
scanForLeaks,
|
|
814
|
+
deployVercel
|
|
1118
815
|
};
|
|
1119
|
-
//# sourceMappingURL=chunk-
|
|
816
|
+
//# sourceMappingURL=chunk-3RXL5GHB.js.map
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// packages/runtime/src/builtins/image/contactSheet.ts
|
|
2
|
+
function contactSheetLayout(sizes, options = {}) {
|
|
3
|
+
const count = sizes.length;
|
|
4
|
+
if (!count) throw new Error("cascade.image.ContactSheet needs at least one image");
|
|
5
|
+
for (const [width, height] of sizes) {
|
|
6
|
+
if (!(width > 0 && height > 0)) throw new Error(`cascade.image.ContactSheet: an image is ${width}x${height}`);
|
|
7
|
+
}
|
|
8
|
+
const gap = Math.max(0, Math.round(options.gap ?? 16));
|
|
9
|
+
const columns = Math.min(count, options.columns && options.columns > 0 ? Math.round(options.columns) : Math.ceil(Math.sqrt(count)));
|
|
10
|
+
const rows = Math.ceil(count / columns);
|
|
11
|
+
const scaled = Boolean(options.cellWidth && options.cellWidth > 0);
|
|
12
|
+
const cellWidth = scaled ? Math.round(options.cellWidth) : Math.max(...sizes.map(([width]) => width));
|
|
13
|
+
const cellHeight = scaled ? Math.max(...sizes.map(([width, height]) => Math.round(height * cellWidth / width))) : Math.max(...sizes.map(([, height]) => height));
|
|
14
|
+
const labelHeight = Math.max(0, Math.round(options.labelHeight ?? 0));
|
|
15
|
+
const cells = sizes.map(([width, height], index) => {
|
|
16
|
+
const column = index % columns;
|
|
17
|
+
const row = Math.floor(index / columns);
|
|
18
|
+
const fit = Math.min(cellWidth / width, cellHeight / height);
|
|
19
|
+
const scale = scaled ? fit : Math.min(1, fit);
|
|
20
|
+
const drawnWidth = Math.max(1, Math.round(width * scale));
|
|
21
|
+
const drawnHeight = Math.max(1, Math.round(height * scale));
|
|
22
|
+
const left = gap + column * (cellWidth + gap);
|
|
23
|
+
const top = gap + row * (cellHeight + labelHeight + gap);
|
|
24
|
+
return {
|
|
25
|
+
x: left + Math.floor((cellWidth - drawnWidth) / 2),
|
|
26
|
+
y: top + Math.floor((cellHeight - drawnHeight) / 2),
|
|
27
|
+
width: drawnWidth,
|
|
28
|
+
height: drawnHeight,
|
|
29
|
+
labelX: left,
|
|
30
|
+
labelY: top + cellHeight
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
width: gap + columns * (cellWidth + gap),
|
|
35
|
+
height: gap + rows * (cellHeight + labelHeight + gap),
|
|
36
|
+
columns,
|
|
37
|
+
rows,
|
|
38
|
+
cell: [cellWidth, cellHeight],
|
|
39
|
+
labelHeight,
|
|
40
|
+
cells
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function host() {
|
|
44
|
+
const globals = globalThis;
|
|
45
|
+
const Canvas = globals.OffscreenCanvas;
|
|
46
|
+
const createBitmap = globals.createImageBitmap;
|
|
47
|
+
if (!Canvas || !createBitmap || !globals.Blob) {
|
|
48
|
+
throw new Error("cascade.image.ContactSheet needs OffscreenCanvas and createImageBitmap, which this host does not provide");
|
|
49
|
+
}
|
|
50
|
+
const BlobType = globals.Blob;
|
|
51
|
+
return {
|
|
52
|
+
surface: (width, height) => new Canvas(width, height),
|
|
53
|
+
decode: (bytes) => createBitmap(new BlobType([bytes]))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function css(colour, fallback) {
|
|
57
|
+
const [r = 0, g = 0, b = 0, a = 1] = colour ?? fallback;
|
|
58
|
+
return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`;
|
|
59
|
+
}
|
|
60
|
+
async function composeContactSheet(images, options = {}) {
|
|
61
|
+
const canvas = host();
|
|
62
|
+
const bitmaps = [];
|
|
63
|
+
try {
|
|
64
|
+
for (const bytes of images) bitmaps.push(await canvas.decode(bytes));
|
|
65
|
+
const labels = options.labels ?? [];
|
|
66
|
+
const labelSize = Math.round(options.labelSize ?? 14);
|
|
67
|
+
const labelled = labels.some((label) => label.trim().length > 0);
|
|
68
|
+
const layout = contactSheetLayout(bitmaps.map((bitmap) => [bitmap.width, bitmap.height]), {
|
|
69
|
+
columns: options.columns,
|
|
70
|
+
gap: options.gap,
|
|
71
|
+
cellWidth: options.cellWidth,
|
|
72
|
+
labelHeight: labelled ? Math.round(labelSize * 1.8) : 0
|
|
73
|
+
});
|
|
74
|
+
const surface = canvas.surface(layout.width, layout.height);
|
|
75
|
+
const context = surface.getContext("2d");
|
|
76
|
+
if (!context) throw new Error("cascade.image.ContactSheet: no 2D context");
|
|
77
|
+
context.fillStyle = css(options.background, [1, 1, 1, 1]);
|
|
78
|
+
context.fillRect(0, 0, layout.width, layout.height);
|
|
79
|
+
context.imageSmoothingEnabled = true;
|
|
80
|
+
context.imageSmoothingQuality = "high";
|
|
81
|
+
layout.cells.forEach((cell, index) => context.drawImage(bitmaps[index], cell.x, cell.y, cell.width, cell.height));
|
|
82
|
+
if (labelled) {
|
|
83
|
+
context.fillStyle = css(options.labelColor, [0.1, 0.1, 0.1, 1]);
|
|
84
|
+
context.font = `${labelSize}px sans-serif`;
|
|
85
|
+
context.textBaseline = "top";
|
|
86
|
+
layout.cells.forEach((cell, index) => {
|
|
87
|
+
const label = labels[index];
|
|
88
|
+
if (label) context.fillText(label, cell.labelX, cell.labelY + Math.round((layout.labelHeight - labelSize) / 2), layout.cell[0]);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const blob = await surface.convertToBlob({ type: "image/png" });
|
|
92
|
+
return { bytes: new Uint8Array(await blob.arrayBuffer()), layout };
|
|
93
|
+
} finally {
|
|
94
|
+
for (const bitmap of bitmaps) bitmap.close?.();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function imageReference(value) {
|
|
98
|
+
if (typeof value === "string" && value) return { path: value };
|
|
99
|
+
const candidate = value?.path;
|
|
100
|
+
return typeof candidate === "string" && candidate ? { path: candidate } : void 0;
|
|
101
|
+
}
|
|
102
|
+
async function executeContactSheet(context) {
|
|
103
|
+
const { props } = context;
|
|
104
|
+
const references = [...context.inputs.images, ...context.inputs.list ?? []].map(imageReference).filter((reference) => reference !== void 0);
|
|
105
|
+
const bytes = [];
|
|
106
|
+
for (const reference of references) {
|
|
107
|
+
bytes.push(await context.capabilities.assets.read(reference, { signal: context.signal }));
|
|
108
|
+
}
|
|
109
|
+
const { bytes: sheet, layout } = await composeContactSheet(bytes, {
|
|
110
|
+
columns: props.columns,
|
|
111
|
+
gap: props.gap,
|
|
112
|
+
cellWidth: props.cellWidth,
|
|
113
|
+
labels: props.labels ? props.labels.split("\n") : [],
|
|
114
|
+
labelSize: props.labelSize,
|
|
115
|
+
background: props.background,
|
|
116
|
+
labelColor: props.labelColor
|
|
117
|
+
});
|
|
118
|
+
const asset = await context.capabilities.assets.write(
|
|
119
|
+
sheet,
|
|
120
|
+
{ mediaType: "image/png", suggestedName: props.filename },
|
|
121
|
+
{ signal: context.signal }
|
|
122
|
+
);
|
|
123
|
+
context.outputs.asset.set(asset);
|
|
124
|
+
context.outputs.image.set({
|
|
125
|
+
path: asset.path ?? props.filename,
|
|
126
|
+
size: [layout.width, layout.height],
|
|
127
|
+
channels: "rgba",
|
|
128
|
+
depth: "u8",
|
|
129
|
+
space: "srgb"
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export {
|
|
134
|
+
contactSheetLayout,
|
|
135
|
+
composeContactSheet,
|
|
136
|
+
executeContactSheet
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=chunk-53LGFPWX.js.map
|