@field/cascade 0.8.0 → 0.11.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 +36 -3
- package/CHANGELOG.md +401 -0
- package/DESIGN.md +14 -0
- package/README.md +41 -26
- package/dist/assets/CodeEditor-4O7FQ__R.js +623 -0
- package/dist/assets/CodePanel-BU-autmq.js +2 -0
- package/dist/assets/{CustomNodeDialog-BQbEhIwL.js → CustomNodeDialog-BRpDfOV5.js} +1 -1
- package/dist/assets/OpenGraphDialog-BwbRlC0X.js +1 -0
- package/dist/assets/ProjectSettings-DLOXLxcA.js +2 -0
- package/dist/assets/SettingsDialog-Bz8JxISH.js +1 -0
- package/dist/assets/{VersionHistoryDialog-D6VBzSQ0.js → VersionHistoryDialog-D5Z0nJ5-.js} +2 -2
- package/dist/assets/{core-BInLG4gw.js → core-Yqi0Ni61.js} +8 -8
- package/dist/assets/{cssMode-DfR2qmYG.js → cssMode-CxUzc4vp.js} +1 -1
- package/dist/assets/definitions-GreEMXLV.js +1 -0
- package/dist/assets/{editor.api-Ck8_vgQV.js → editor.api-BNFb25CX.js} +1 -1
- package/dist/assets/{freemarker2-fVn10Wn3.js → freemarker2-0ZDnWxeM.js} +1 -1
- package/dist/assets/geo-u0n_jOsD.js +1 -0
- package/dist/assets/{handlebars-BCSchLT2.js → handlebars-Cy18MoaD.js} +1 -1
- package/dist/assets/{html-CVpSv_k9.js → html-B-2Dx7nq.js} +1 -1
- package/dist/assets/{htmlMode-Da4ESTv0.js → htmlMode-DUAc9B_d.js} +1 -1
- package/dist/assets/{image-BLn--oSF.js → image-cAa-Nu9O.js} +3 -3
- package/dist/assets/index-5ZtDAuy2.js +220 -0
- package/dist/assets/index-7vae6YFC.css +1 -0
- package/dist/assets/index-client-DqYmKstU.js +1 -0
- package/dist/assets/{javascript-BpX0Gvcf.js → javascript-DeQsDqP5.js} +1 -1
- package/dist/assets/{jsonMode-CsLqaou7.js → jsonMode-BiFwJVSk.js} +1 -1
- package/dist/assets/legacy-CTEeXeqF.js +2 -0
- package/dist/assets/{liquid-efUz51Wf.js → liquid-BLoHH68a.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-DCi5-K0n.js → lspLanguageFeatures-CNFgm1Kv.js} +1 -1
- package/dist/assets/math-DioIQOPJ.js +1 -0
- package/dist/assets/{mdx-DP_sjGVy.js → mdx-CFXQVNfs.js} +1 -1
- package/dist/assets/pop-DC_TQG9t.js +1 -0
- package/dist/assets/{python-aAuP3LUi.js → python-Cf7S7p2E.js} +1 -1
- package/dist/assets/{razor-7M6sMmC5.js → razor-CMHBcG-g.js} +1 -1
- package/dist/assets/studioScenes-8sq9FfFg.js +1 -0
- package/dist/assets/{tsMode-Bf4CnoYK.js → tsMode-BEih0GDj.js} +1 -1
- package/dist/assets/{typescript-ddhYBGyT.js → typescript-COEKYD_a.js} +1 -1
- package/dist/assets/{workers-X5la8DDT.js → workers-BNhAhMNn.js} +1 -1
- package/dist/assets/{xml-BzbXa3Ps.js → xml-CPiMpnPl.js} +1 -1
- package/dist/assets/{yaml-CXs-VzxL.js → yaml-Bgp1ITLR.js} +1 -1
- package/dist/cli/Graph-ZJJTRQTM.js +2975 -0
- package/dist/cli/chunk-2KXB47FP.js +115 -0
- package/dist/cli/chunk-35D343DC.js +1447 -0
- package/dist/cli/chunk-36P5K3SD.js +631 -0
- package/dist/cli/chunk-3JTJHF7V.js +267 -0
- package/dist/cli/chunk-4UDFYF5G.js +132 -0
- package/dist/cli/chunk-53NMBJAO.js +35 -0
- package/dist/cli/chunk-A4OAMOZH.js +310 -0
- package/dist/cli/chunk-BIJO5OFS.js +71 -0
- package/dist/cli/chunk-BP7UKF36.js +376 -0
- package/dist/cli/chunk-CKS2FQWR.js +89 -0
- package/dist/cli/chunk-CO22XHNG.js +593 -0
- package/dist/cli/chunk-D6BUOHUG.js +634 -0
- package/dist/cli/chunk-DEQSEFFH.js +153 -0
- package/dist/cli/chunk-DUIUFVU2.js +241 -0
- package/dist/cli/chunk-EUIAC3U3.js +3006 -0
- package/dist/cli/chunk-FCDXJ6EK.js +2208 -0
- package/dist/cli/chunk-GFS6W3C5.js +308 -0
- package/dist/cli/chunk-HFHWYL55.js +139 -0
- package/dist/cli/chunk-HSNOBIVT.js +263 -0
- package/dist/cli/chunk-ICH7N5PN.js +26 -0
- package/dist/cli/chunk-J7VK7MNF.js +418 -0
- package/dist/cli/chunk-KH4MUBKF.js +1551 -0
- package/dist/cli/chunk-MIIEFUGG.js +623 -0
- package/dist/cli/chunk-OWWBYLHC.js +136 -0
- package/dist/cli/chunk-POMMNKZZ.js +672 -0
- package/dist/cli/chunk-QBBKRNGY.js +90 -0
- package/dist/cli/chunk-QQPA2ZHH.js +273 -0
- package/dist/cli/chunk-RLCAJBVR.js +224 -0
- package/dist/cli/chunk-RQZ4SX5R.js +1125 -0
- package/dist/cli/chunk-RUU3YWXU.js +99 -0
- package/dist/cli/chunk-RXDIMXN5.js +98 -0
- package/dist/cli/chunk-SGJAET2L.js +197 -0
- package/dist/cli/chunk-VVGSLVSF.js +151 -0
- package/dist/cli/chunk-WD62JIP6.js +118 -0
- package/dist/cli/chunk-WIH4TEIS.js +104 -0
- package/dist/cli/chunk-WKURDVYP.js +1119 -0
- package/dist/cli/chunk-WPUDGEFY.js +20 -0
- package/dist/cli/chunk-XWDSKG7N.js +1301 -0
- package/dist/cli/chunk-YAT5Y25U.js +121 -0
- package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
- package/dist/cli/cliCommands-UQSFGEHF.js +3464 -0
- package/dist/cli/commands-Q23M26QM.js +1292 -0
- package/dist/cli/definitions-HF2FTHYU.js +32 -0
- package/dist/cli/doctorCommand-B7BPQMN6.js +76 -0
- package/dist/cli/edit-JHCPTQFT.js +28 -0
- package/dist/cli/editorCommands-UIF5YLUZ.js +33 -0
- package/dist/cli/encode-HAUZTYPT.js +17 -0
- package/dist/cli/frames-LSAUBZ7P.js +16 -0
- package/dist/cli/geo-4RNK3QVN.js +43 -0
- package/dist/cli/graphCommands-6BMAKGQE.js +184 -0
- package/dist/cli/graphFile-VPSQ6OF7.js +286 -0
- package/dist/cli/headlessCanvas-5XLDZT7M.js +15 -0
- package/dist/cli/helpCommand-3VRE3KVO.js +155 -0
- package/dist/cli/index.js +106 -14207
- package/dist/cli/math-ZRMLKU2D.js +16 -0
- package/dist/cli/mcp-ZLEEMYGF.js +164 -0
- package/dist/cli/parity-KINARCP2.js +328 -0
- package/dist/cli/pop-USDFXB7H.js +39 -0
- package/dist/cli/project-K3QIPGBB.js +12 -0
- package/dist/cli/projectDefinition-DMTZN2NE.js +27 -0
- package/dist/cli/projectRuntime-Z5PJOH47.js +48 -0
- package/dist/cli/registerStandardNodes-S2VLMF6J.js +3081 -0
- package/dist/cli/render-YURAPPUI.js +436 -0
- package/dist/cli/runCommand-PI764LEG.js +198 -0
- package/dist/cli/runner-RA6UKEPD.js +37 -0
- package/dist/cli/runtime/image.ts +277 -0
- package/dist/cli/runtime/stage.ts +74 -11
- package/dist/cli/stageRunner-KUNIMQKJ.js +10 -0
- package/dist/cli/verbs-RCE3K4VQ.js +19 -0
- package/dist/cli/webBuild-GT4PZT5D.js +37 -0
- package/dist/contracts/bundle.d.ts +75 -0
- package/dist/contracts/bundle.js +112 -0
- package/dist/contracts/definition.d.ts +67 -4
- package/dist/contracts/document.d.ts +29 -0
- package/dist/contracts/documentFormat.d.ts +18 -0
- package/dist/contracts/documentFormat.js +195 -0
- package/dist/contracts/editor.d.ts +189 -0
- package/dist/contracts/editor.js +2 -0
- package/dist/contracts/index.d.ts +3 -0
- package/dist/contracts/index.js +3 -0
- package/dist/contracts/validate.js +59 -3
- package/dist/contracts/values.d.ts +3 -3
- package/dist/contracts/values.js +1 -0
- package/dist/image.d.ts +141 -0
- package/dist/image.js +127 -0
- package/dist/index.html +4 -4
- package/dist/player/app.js +17 -5
- package/dist/player/editor.js +1919 -0
- package/dist/player/editorEngine.js +477 -0
- package/dist/runtime/builtins/core/bundle.d.ts +70 -0
- package/dist/runtime/builtins/core/bundle.js +81 -0
- package/dist/runtime/builtins/core/index.d.ts +3 -2
- package/dist/runtime/builtins/core/index.js +6 -2
- package/dist/runtime/builtins/core/routing.d.ts +27 -0
- package/dist/runtime/builtins/core/routing.js +28 -0
- package/dist/runtime/builtins/headless.d.ts +19 -0
- package/dist/runtime/builtins/headless.js +25 -0
- package/dist/runtime/builtins/image/contactSheet.d.ts +226 -0
- package/dist/runtime/builtins/image/contactSheet.js +199 -0
- package/dist/runtime/builtins/image/index.d.ts +9 -0
- package/dist/runtime/builtins/image/index.js +9 -0
- package/dist/runtime/edit/document.d.ts +94 -0
- package/dist/runtime/edit/document.js +194 -0
- package/dist/runtime/edit/graphEditor.d.ts +223 -0
- package/dist/runtime/edit/graphEditor.js +893 -0
- package/dist/runtime/edit/index.d.ts +12 -0
- package/dist/runtime/edit/index.js +6 -0
- package/dist/runtime/edit/layout.d.ts +50 -0
- package/dist/runtime/edit/layout.js +126 -0
- package/dist/runtime/edit/operations.d.ts +65 -0
- package/dist/runtime/edit/operations.js +57 -0
- package/dist/runtime/edit/parameters.d.ts +75 -0
- package/dist/runtime/edit/parameters.js +284 -0
- package/dist/runtime/edit/read.d.ts +35 -0
- package/dist/runtime/edit/read.js +65 -0
- package/dist/runtime/editor/index.d.ts +104 -0
- package/dist/runtime/editor/index.js +439 -0
- package/dist/runtime/expressions/bundlePaths.d.ts +37 -0
- package/dist/runtime/expressions/bundlePaths.js +55 -0
- package/dist/runtime/expressions/index.d.ts +2 -0
- package/dist/runtime/expressions/index.js +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/params/animator.d.ts +5 -0
- package/dist/runtime/params/animator.js +16 -0
- package/dist/runtime/runtime.js +238 -12
- package/dist/runtime/seed.d.ts +29 -0
- package/dist/runtime/seed.js +52 -0
- package/dist/runtime/types.d.ts +12 -0
- package/dist/stage.js +43 -9
- package/dist/studio/panel.d.ts +2 -0
- package/doc/CLI_REFERENCE.md +171 -0
- package/doc/EDITOR_EXPORT.md +235 -0
- package/doc/HEADLESS_GPU.md +27 -0
- package/doc/MCP.md +69 -0
- package/doc/NODE_REFERENCE.md +208 -0
- package/doc/PROJECT_AUTHORING.md +258 -1
- package/package.json +29 -8
- package/dist/assets/CodeEditor-BEk-kIBd.js +0 -623
- package/dist/assets/CodePanel-CeSdjXkg.js +0 -2
- package/dist/assets/ExportDialog-CjTbbTZJ.js +0 -358
- package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
- package/dist/assets/OpenGraphDialog-DCwH9itM.js +0 -1
- package/dist/assets/ProjectSettings-Dg7M5xt9.js +0 -2
- package/dist/assets/SettingsDialog-e7GgsjB9.js +0 -1
- package/dist/assets/definitions-DZpOjWKR.js +0 -1
- package/dist/assets/geo-D2VPjAdt.js +0 -1
- package/dist/assets/index-CMiBdEuT.js +0 -212
- package/dist/assets/index-DMIuml9a.css +0 -1
- package/dist/assets/index-client-BI2H82FJ.js +0 -1
- package/dist/assets/legacy-ZA7textG.js +0 -2
- package/dist/assets/math-B6hpxPJO.js +0 -1
- package/dist/assets/pop-CYUIjT6x.js +0 -1
- package/dist/assets/projectSettingsApi-C1WvIwfP.js +0 -1
|
@@ -109,6 +109,33 @@ declare const nullDefinition: {
|
|
|
109
109
|
};
|
|
110
110
|
};
|
|
111
111
|
};
|
|
112
|
+
/**
|
|
113
|
+
* `cascade.core.Frozen` — a snapshot standing in for a live data source.
|
|
114
|
+
*
|
|
115
|
+
* `cascade export` writes one in place of a node the author froze with
|
|
116
|
+
* `cascade freeze`: the chosen fields of that node's output, and nothing else.
|
|
117
|
+
* It has no inputs, so nothing upstream of a frozen source ships.
|
|
118
|
+
*/
|
|
119
|
+
declare const frozenDefinition: {
|
|
120
|
+
readonly apiVersion: 1;
|
|
121
|
+
readonly runsOn: "portable";
|
|
122
|
+
readonly label: "Frozen";
|
|
123
|
+
readonly description: "Emits a frozen snapshot of a data source's chosen values";
|
|
124
|
+
readonly icon: "Snowflake";
|
|
125
|
+
readonly outputs: {
|
|
126
|
+
readonly value: {
|
|
127
|
+
readonly kind: "data";
|
|
128
|
+
readonly type: "any";
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
readonly props: {
|
|
132
|
+
readonly value: {
|
|
133
|
+
readonly type: "any";
|
|
134
|
+
readonly default: null;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
export declare const frozenRegistration: DefinitionNodeRegistration<typeof frozenDefinition>;
|
|
112
139
|
export declare const switchRegistration: DefinitionNodeRegistration<typeof switchDefinition>;
|
|
113
140
|
export declare const mergeRegistration: DefinitionNodeRegistration<typeof mergeDefinition>;
|
|
114
141
|
export declare const selectRegistration: DefinitionNodeRegistration<typeof selectDefinition>;
|
|
@@ -61,6 +61,34 @@ const nullDefinition = {
|
|
|
61
61
|
output: { kind: "data", type: "any" },
|
|
62
62
|
},
|
|
63
63
|
};
|
|
64
|
+
/**
|
|
65
|
+
* `cascade.core.Frozen` — a snapshot standing in for a live data source.
|
|
66
|
+
*
|
|
67
|
+
* `cascade export` writes one in place of a node the author froze with
|
|
68
|
+
* `cascade freeze`: the chosen fields of that node's output, and nothing else.
|
|
69
|
+
* It has no inputs, so nothing upstream of a frozen source ships.
|
|
70
|
+
*/
|
|
71
|
+
const frozenDefinition = {
|
|
72
|
+
apiVersion: 1,
|
|
73
|
+
runsOn: "portable",
|
|
74
|
+
label: "Frozen",
|
|
75
|
+
description: "Emits a frozen snapshot of a data source's chosen values",
|
|
76
|
+
icon: "Snowflake",
|
|
77
|
+
outputs: {
|
|
78
|
+
value: { kind: "data", type: "any" },
|
|
79
|
+
},
|
|
80
|
+
props: {
|
|
81
|
+
value: { type: "any", default: null },
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
export const frozenRegistration = {
|
|
85
|
+
kind: "definition-v1",
|
|
86
|
+
moduleId: "cascade.core.Frozen",
|
|
87
|
+
definition: frozenDefinition,
|
|
88
|
+
loadExecute: async () => ({ outputs, props }) => {
|
|
89
|
+
outputs.value.set(props.value);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
64
92
|
export const switchRegistration = {
|
|
65
93
|
kind: "definition-v1",
|
|
66
94
|
moduleId: "cascade.core.Switch",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DefinitionNodeRegistration } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Built-ins the headless host knows and Studio does not yet: today only
|
|
4
|
+
* `cascade.image.ContactSheet`.
|
|
5
|
+
*
|
|
6
|
+
* A separate list rather than a line in `builtinNodeRegistrations` because
|
|
7
|
+
* that list is in Studio's first-paint bundle (the runtime and the Definition
|
|
8
|
+
* panel both import it), and the bundle budget has no room for a node Studio
|
|
9
|
+
* does not register. The CLI passes these to the runtime as ordinary
|
|
10
|
+
* registrations. When Studio registers the image nodes, they move into
|
|
11
|
+
* `index.ts` and this file goes.
|
|
12
|
+
*/
|
|
13
|
+
export declare const headlessOnlyNodeRegistrations: readonly DefinitionNodeRegistration[];
|
|
14
|
+
/** Every built-in the headless host can run. */
|
|
15
|
+
export declare const headlessNodeRegistrations: readonly DefinitionNodeRegistration[];
|
|
16
|
+
/** A headless-only built-in, which the runtime does not know unless it is passed in. */
|
|
17
|
+
export declare function headlessOnlyNodeRegistration(moduleId: string): DefinitionNodeRegistration | undefined;
|
|
18
|
+
export declare function headlessNodeRegistration(moduleId: string): DefinitionNodeRegistration | undefined;
|
|
19
|
+
//# sourceMappingURL=headless.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { builtinNodeRegistration, builtinNodeRegistrations } from "./index.js";
|
|
2
|
+
import { imageNodeRegistrations } from "./image/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Built-ins the headless host knows and Studio does not yet: today only
|
|
5
|
+
* `cascade.image.ContactSheet`.
|
|
6
|
+
*
|
|
7
|
+
* A separate list rather than a line in `builtinNodeRegistrations` because
|
|
8
|
+
* that list is in Studio's first-paint bundle (the runtime and the Definition
|
|
9
|
+
* panel both import it), and the bundle budget has no room for a node Studio
|
|
10
|
+
* does not register. The CLI passes these to the runtime as ordinary
|
|
11
|
+
* registrations. When Studio registers the image nodes, they move into
|
|
12
|
+
* `index.ts` and this file goes.
|
|
13
|
+
*/
|
|
14
|
+
export const headlessOnlyNodeRegistrations = Object.freeze([...imageNodeRegistrations]);
|
|
15
|
+
/** Every built-in the headless host can run. */
|
|
16
|
+
export const headlessNodeRegistrations = Object.freeze([...builtinNodeRegistrations, ...headlessOnlyNodeRegistrations]);
|
|
17
|
+
const headlessOnlyById = new Map(headlessOnlyNodeRegistrations.map((registration) => [registration.moduleId, registration]));
|
|
18
|
+
/** A headless-only built-in, which the runtime does not know unless it is passed in. */
|
|
19
|
+
export function headlessOnlyNodeRegistration(moduleId) {
|
|
20
|
+
return headlessOnlyById.get(moduleId);
|
|
21
|
+
}
|
|
22
|
+
export function headlessNodeRegistration(moduleId) {
|
|
23
|
+
return builtinNodeRegistration(moduleId) ?? headlessOnlyById.get(moduleId);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=headless.js.map
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { NodeExecutionContext } from "@field/cascade/contracts";
|
|
2
|
+
/**
|
|
3
|
+
* `cascade.image.ContactSheet` — several images as one labelled grid.
|
|
4
|
+
*
|
|
5
|
+
* The selection loop is a ladder of three to five options and a pick, and the
|
|
6
|
+
* pick is made by looking at them side by side. So the sheet is a node rather
|
|
7
|
+
* than a script beside the graph: a graph can end in one, and `cascade ladder`
|
|
8
|
+
* lays out its variants with the same layout and drawing code below.
|
|
9
|
+
*
|
|
10
|
+
* `portable` with `assets`, like `cascade.geo.Render`: it reads each image
|
|
11
|
+
* through the asset capability, decodes it with `createImageBitmap` and draws
|
|
12
|
+
* on an `OffscreenCanvas`, which both hosts supply (the headless one through
|
|
13
|
+
* @napi-rs/canvas). It lives in `cascade.image.*` beside Studio's image
|
|
14
|
+
* library, as the first definition-v1 node there; Studio does not register it
|
|
15
|
+
* yet, so today it runs headless and is listed in `../headless.ts` rather
|
|
16
|
+
* than in the built-ins Studio bundles.
|
|
17
|
+
*/
|
|
18
|
+
export declare const contactSheetDefinition: {
|
|
19
|
+
readonly apiVersion: 1;
|
|
20
|
+
readonly label: "Contact Sheet";
|
|
21
|
+
readonly description: "Lay images out as one labelled grid.";
|
|
22
|
+
readonly icon: "LayoutGrid";
|
|
23
|
+
readonly runsOn: "portable";
|
|
24
|
+
readonly capabilities: readonly ["assets"];
|
|
25
|
+
readonly inputs: {
|
|
26
|
+
readonly images: {
|
|
27
|
+
readonly kind: "data";
|
|
28
|
+
readonly type: "image";
|
|
29
|
+
readonly variadic: true;
|
|
30
|
+
};
|
|
31
|
+
/** More images after the wired ones: image values or project-relative paths. */
|
|
32
|
+
readonly list: {
|
|
33
|
+
readonly kind: "data";
|
|
34
|
+
readonly type: "array";
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
readonly outputs: {
|
|
38
|
+
readonly image: {
|
|
39
|
+
readonly kind: "data";
|
|
40
|
+
readonly type: "image";
|
|
41
|
+
};
|
|
42
|
+
readonly asset: {
|
|
43
|
+
readonly kind: "data";
|
|
44
|
+
readonly type: "asset";
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
readonly props: {
|
|
48
|
+
readonly columns: {
|
|
49
|
+
readonly type: "int";
|
|
50
|
+
readonly default: 0;
|
|
51
|
+
readonly min: 0;
|
|
52
|
+
readonly max: 64;
|
|
53
|
+
readonly description: "0: as square as the count allows.";
|
|
54
|
+
};
|
|
55
|
+
readonly gap: {
|
|
56
|
+
readonly type: "int";
|
|
57
|
+
readonly default: 16;
|
|
58
|
+
readonly min: 0;
|
|
59
|
+
readonly max: 512;
|
|
60
|
+
};
|
|
61
|
+
readonly background: {
|
|
62
|
+
readonly type: "color";
|
|
63
|
+
readonly default: readonly [1, 1, 1, 1];
|
|
64
|
+
readonly label: "Background";
|
|
65
|
+
};
|
|
66
|
+
readonly cellWidth: {
|
|
67
|
+
readonly type: "int";
|
|
68
|
+
readonly default: 0;
|
|
69
|
+
readonly min: 0;
|
|
70
|
+
readonly max: 8192;
|
|
71
|
+
readonly description: "0: images at their own size. Otherwise each is scaled to fit this width.";
|
|
72
|
+
};
|
|
73
|
+
readonly labels: {
|
|
74
|
+
readonly type: "string";
|
|
75
|
+
readonly default: "";
|
|
76
|
+
readonly description: "One label per line, in image order.";
|
|
77
|
+
};
|
|
78
|
+
readonly labelSize: {
|
|
79
|
+
readonly type: "int";
|
|
80
|
+
readonly default: 14;
|
|
81
|
+
readonly min: 6;
|
|
82
|
+
readonly max: 128;
|
|
83
|
+
};
|
|
84
|
+
readonly labelColor: {
|
|
85
|
+
readonly type: "color";
|
|
86
|
+
readonly default: readonly [0.1, 0.1, 0.1, 1];
|
|
87
|
+
readonly label: "Label Colour";
|
|
88
|
+
};
|
|
89
|
+
readonly filename: {
|
|
90
|
+
readonly type: "string";
|
|
91
|
+
readonly default: "contact-sheet.png";
|
|
92
|
+
readonly label: "Filename";
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
export interface ContactSheetOptions {
|
|
97
|
+
readonly columns?: number;
|
|
98
|
+
readonly gap?: number;
|
|
99
|
+
readonly cellWidth?: number;
|
|
100
|
+
readonly labels?: readonly string[];
|
|
101
|
+
readonly labelSize?: number;
|
|
102
|
+
/** RGBA, 0 to 1. */
|
|
103
|
+
readonly background?: readonly number[];
|
|
104
|
+
readonly labelColor?: readonly number[];
|
|
105
|
+
}
|
|
106
|
+
export interface SheetCell {
|
|
107
|
+
readonly x: number;
|
|
108
|
+
readonly y: number;
|
|
109
|
+
readonly width: number;
|
|
110
|
+
readonly height: number;
|
|
111
|
+
/** Top-left of the label line. */
|
|
112
|
+
readonly labelX: number;
|
|
113
|
+
readonly labelY: number;
|
|
114
|
+
}
|
|
115
|
+
export interface SheetLayout {
|
|
116
|
+
readonly width: number;
|
|
117
|
+
readonly height: number;
|
|
118
|
+
readonly columns: number;
|
|
119
|
+
readonly rows: number;
|
|
120
|
+
readonly cell: readonly [number, number];
|
|
121
|
+
readonly labelHeight: number;
|
|
122
|
+
readonly cells: readonly SheetCell[];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Where each image goes. By default every cell is as wide as the widest image
|
|
126
|
+
* and as tall as the tallest, and images keep their size. With `cellWidth`,
|
|
127
|
+
* cells are that wide and as tall as the tallest image needs at that width,
|
|
128
|
+
* and each image is scaled to fit. Either way an image is centred in its cell
|
|
129
|
+
* and never cropped, in whole pixels, so a sheet is the same bytes every run.
|
|
130
|
+
*/
|
|
131
|
+
export declare function contactSheetLayout(sizes: readonly (readonly [number, number])[], options?: {
|
|
132
|
+
columns?: number;
|
|
133
|
+
gap?: number;
|
|
134
|
+
cellWidth?: number;
|
|
135
|
+
labelHeight?: number;
|
|
136
|
+
}): SheetLayout;
|
|
137
|
+
/** Encoded images (PNG, JPEG, WebP) in, one PNG sheet out. */
|
|
138
|
+
export declare function composeContactSheet(images: readonly Uint8Array[], options?: ContactSheetOptions): Promise<{
|
|
139
|
+
bytes: Uint8Array;
|
|
140
|
+
layout: SheetLayout;
|
|
141
|
+
}>;
|
|
142
|
+
export declare function executeContactSheet(context: NodeExecutionContext<typeof contactSheetDefinition>): Promise<void>;
|
|
143
|
+
export declare const contactSheetRegistration: {
|
|
144
|
+
kind: "definition-v1";
|
|
145
|
+
moduleId: string;
|
|
146
|
+
definition: {
|
|
147
|
+
readonly apiVersion: 1;
|
|
148
|
+
readonly label: "Contact Sheet";
|
|
149
|
+
readonly description: "Lay images out as one labelled grid.";
|
|
150
|
+
readonly icon: "LayoutGrid";
|
|
151
|
+
readonly runsOn: "portable";
|
|
152
|
+
readonly capabilities: readonly ["assets"];
|
|
153
|
+
readonly inputs: {
|
|
154
|
+
readonly images: {
|
|
155
|
+
readonly kind: "data";
|
|
156
|
+
readonly type: "image";
|
|
157
|
+
readonly variadic: true;
|
|
158
|
+
};
|
|
159
|
+
/** More images after the wired ones: image values or project-relative paths. */
|
|
160
|
+
readonly list: {
|
|
161
|
+
readonly kind: "data";
|
|
162
|
+
readonly type: "array";
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
readonly outputs: {
|
|
166
|
+
readonly image: {
|
|
167
|
+
readonly kind: "data";
|
|
168
|
+
readonly type: "image";
|
|
169
|
+
};
|
|
170
|
+
readonly asset: {
|
|
171
|
+
readonly kind: "data";
|
|
172
|
+
readonly type: "asset";
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
readonly props: {
|
|
176
|
+
readonly columns: {
|
|
177
|
+
readonly type: "int";
|
|
178
|
+
readonly default: 0;
|
|
179
|
+
readonly min: 0;
|
|
180
|
+
readonly max: 64;
|
|
181
|
+
readonly description: "0: as square as the count allows.";
|
|
182
|
+
};
|
|
183
|
+
readonly gap: {
|
|
184
|
+
readonly type: "int";
|
|
185
|
+
readonly default: 16;
|
|
186
|
+
readonly min: 0;
|
|
187
|
+
readonly max: 512;
|
|
188
|
+
};
|
|
189
|
+
readonly background: {
|
|
190
|
+
readonly type: "color";
|
|
191
|
+
readonly default: readonly [1, 1, 1, 1];
|
|
192
|
+
readonly label: "Background";
|
|
193
|
+
};
|
|
194
|
+
readonly cellWidth: {
|
|
195
|
+
readonly type: "int";
|
|
196
|
+
readonly default: 0;
|
|
197
|
+
readonly min: 0;
|
|
198
|
+
readonly max: 8192;
|
|
199
|
+
readonly description: "0: images at their own size. Otherwise each is scaled to fit this width.";
|
|
200
|
+
};
|
|
201
|
+
readonly labels: {
|
|
202
|
+
readonly type: "string";
|
|
203
|
+
readonly default: "";
|
|
204
|
+
readonly description: "One label per line, in image order.";
|
|
205
|
+
};
|
|
206
|
+
readonly labelSize: {
|
|
207
|
+
readonly type: "int";
|
|
208
|
+
readonly default: 14;
|
|
209
|
+
readonly min: 6;
|
|
210
|
+
readonly max: 128;
|
|
211
|
+
};
|
|
212
|
+
readonly labelColor: {
|
|
213
|
+
readonly type: "color";
|
|
214
|
+
readonly default: readonly [0.1, 0.1, 0.1, 1];
|
|
215
|
+
readonly label: "Label Colour";
|
|
216
|
+
};
|
|
217
|
+
readonly filename: {
|
|
218
|
+
readonly type: "string";
|
|
219
|
+
readonly default: "contact-sheet.png";
|
|
220
|
+
readonly label: "Filename";
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
loadExecute: () => Promise<typeof executeContactSheet>;
|
|
225
|
+
};
|
|
226
|
+
//# sourceMappingURL=contactSheet.d.ts.map
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cascade.image.ContactSheet` — several images as one labelled grid.
|
|
3
|
+
*
|
|
4
|
+
* The selection loop is a ladder of three to five options and a pick, and the
|
|
5
|
+
* pick is made by looking at them side by side. So the sheet is a node rather
|
|
6
|
+
* than a script beside the graph: a graph can end in one, and `cascade ladder`
|
|
7
|
+
* lays out its variants with the same layout and drawing code below.
|
|
8
|
+
*
|
|
9
|
+
* `portable` with `assets`, like `cascade.geo.Render`: it reads each image
|
|
10
|
+
* through the asset capability, decodes it with `createImageBitmap` and draws
|
|
11
|
+
* on an `OffscreenCanvas`, which both hosts supply (the headless one through
|
|
12
|
+
* @napi-rs/canvas). It lives in `cascade.image.*` beside Studio's image
|
|
13
|
+
* library, as the first definition-v1 node there; Studio does not register it
|
|
14
|
+
* yet, so today it runs headless and is listed in `../headless.ts` rather
|
|
15
|
+
* than in the built-ins Studio bundles.
|
|
16
|
+
*/
|
|
17
|
+
export const contactSheetDefinition = {
|
|
18
|
+
apiVersion: 1,
|
|
19
|
+
label: "Contact Sheet",
|
|
20
|
+
description: "Lay images out as one labelled grid.",
|
|
21
|
+
icon: "LayoutGrid",
|
|
22
|
+
runsOn: "portable",
|
|
23
|
+
capabilities: ["assets"],
|
|
24
|
+
inputs: {
|
|
25
|
+
images: { kind: "data", type: "image", variadic: true },
|
|
26
|
+
/** More images after the wired ones: image values or project-relative paths. */
|
|
27
|
+
list: { kind: "data", type: "array" },
|
|
28
|
+
},
|
|
29
|
+
outputs: {
|
|
30
|
+
image: { kind: "data", type: "image" },
|
|
31
|
+
asset: { kind: "data", type: "asset" },
|
|
32
|
+
},
|
|
33
|
+
props: {
|
|
34
|
+
columns: { type: "int", default: 0, min: 0, max: 64, description: "0: as square as the count allows." },
|
|
35
|
+
gap: { type: "int", default: 16, min: 0, max: 512 },
|
|
36
|
+
background: { type: "color", default: [1, 1, 1, 1], label: "Background" },
|
|
37
|
+
cellWidth: { type: "int", default: 0, min: 0, max: 8192, description: "0: images at their own size. Otherwise each is scaled to fit this width." },
|
|
38
|
+
labels: { type: "string", default: "", description: "One label per line, in image order." },
|
|
39
|
+
labelSize: { type: "int", default: 14, min: 6, max: 128 },
|
|
40
|
+
labelColor: { type: "color", default: [0.1, 0.1, 0.1, 1], label: "Label Colour" },
|
|
41
|
+
filename: { type: "string", default: "contact-sheet.png", label: "Filename" },
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Where each image goes. By default every cell is as wide as the widest image
|
|
46
|
+
* and as tall as the tallest, and images keep their size. With `cellWidth`,
|
|
47
|
+
* cells are that wide and as tall as the tallest image needs at that width,
|
|
48
|
+
* and each image is scaled to fit. Either way an image is centred in its cell
|
|
49
|
+
* and never cropped, in whole pixels, so a sheet is the same bytes every run.
|
|
50
|
+
*/
|
|
51
|
+
export function contactSheetLayout(sizes, options = {}) {
|
|
52
|
+
const count = sizes.length;
|
|
53
|
+
if (!count)
|
|
54
|
+
throw new Error("cascade.image.ContactSheet needs at least one image");
|
|
55
|
+
for (const [width, height] of sizes) {
|
|
56
|
+
if (!(width > 0 && height > 0))
|
|
57
|
+
throw new Error(`cascade.image.ContactSheet: an image is ${width}x${height}`);
|
|
58
|
+
}
|
|
59
|
+
const gap = Math.max(0, Math.round(options.gap ?? 16));
|
|
60
|
+
const columns = Math.min(count, options.columns && options.columns > 0 ? Math.round(options.columns) : Math.ceil(Math.sqrt(count)));
|
|
61
|
+
const rows = Math.ceil(count / columns);
|
|
62
|
+
const scaled = Boolean(options.cellWidth && options.cellWidth > 0);
|
|
63
|
+
const cellWidth = scaled ? Math.round(options.cellWidth) : Math.max(...sizes.map(([width]) => width));
|
|
64
|
+
const cellHeight = scaled
|
|
65
|
+
? Math.max(...sizes.map(([width, height]) => Math.round((height * cellWidth) / width)))
|
|
66
|
+
: Math.max(...sizes.map(([, height]) => height));
|
|
67
|
+
const labelHeight = Math.max(0, Math.round(options.labelHeight ?? 0));
|
|
68
|
+
const cells = sizes.map(([width, height], index) => {
|
|
69
|
+
const column = index % columns;
|
|
70
|
+
const row = Math.floor(index / columns);
|
|
71
|
+
// Native size unless a cell width was asked for; never cropped.
|
|
72
|
+
const fit = Math.min(cellWidth / width, cellHeight / height);
|
|
73
|
+
const scale = scaled ? fit : Math.min(1, fit);
|
|
74
|
+
const drawnWidth = Math.max(1, Math.round(width * scale));
|
|
75
|
+
const drawnHeight = Math.max(1, Math.round(height * scale));
|
|
76
|
+
const left = gap + column * (cellWidth + gap);
|
|
77
|
+
const top = gap + row * (cellHeight + labelHeight + gap);
|
|
78
|
+
return {
|
|
79
|
+
x: left + Math.floor((cellWidth - drawnWidth) / 2),
|
|
80
|
+
y: top + Math.floor((cellHeight - drawnHeight) / 2),
|
|
81
|
+
width: drawnWidth,
|
|
82
|
+
height: drawnHeight,
|
|
83
|
+
labelX: left,
|
|
84
|
+
labelY: top + cellHeight,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
width: gap + columns * (cellWidth + gap),
|
|
89
|
+
height: gap + rows * (cellHeight + labelHeight + gap),
|
|
90
|
+
columns,
|
|
91
|
+
rows,
|
|
92
|
+
cell: [cellWidth, cellHeight],
|
|
93
|
+
labelHeight,
|
|
94
|
+
cells,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** The host's canvas, read here and nowhere else (see `cascade.geo.Render`). */
|
|
98
|
+
function host() {
|
|
99
|
+
const globals = globalThis;
|
|
100
|
+
const Canvas = globals.OffscreenCanvas;
|
|
101
|
+
const createBitmap = globals.createImageBitmap;
|
|
102
|
+
if (!Canvas || !createBitmap || !globals.Blob) {
|
|
103
|
+
throw new Error("cascade.image.ContactSheet needs OffscreenCanvas and createImageBitmap, which this host does not provide");
|
|
104
|
+
}
|
|
105
|
+
const BlobType = globals.Blob;
|
|
106
|
+
return {
|
|
107
|
+
surface: (width, height) => new Canvas(width, height),
|
|
108
|
+
decode: (bytes) => createBitmap(new BlobType([bytes])),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function css(colour, fallback) {
|
|
112
|
+
const [r = 0, g = 0, b = 0, a = 1] = colour ?? fallback;
|
|
113
|
+
return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`;
|
|
114
|
+
}
|
|
115
|
+
/** Encoded images (PNG, JPEG, WebP) in, one PNG sheet out. */
|
|
116
|
+
export async function composeContactSheet(images, options = {}) {
|
|
117
|
+
const canvas = host();
|
|
118
|
+
const bitmaps = [];
|
|
119
|
+
try {
|
|
120
|
+
for (const bytes of images)
|
|
121
|
+
bitmaps.push(await canvas.decode(bytes));
|
|
122
|
+
const labels = options.labels ?? [];
|
|
123
|
+
const labelSize = Math.round(options.labelSize ?? 14);
|
|
124
|
+
const labelled = labels.some((label) => label.trim().length > 0);
|
|
125
|
+
const layout = contactSheetLayout(bitmaps.map((bitmap) => [bitmap.width, bitmap.height]), {
|
|
126
|
+
columns: options.columns,
|
|
127
|
+
gap: options.gap,
|
|
128
|
+
cellWidth: options.cellWidth,
|
|
129
|
+
labelHeight: labelled ? Math.round(labelSize * 1.8) : 0,
|
|
130
|
+
});
|
|
131
|
+
const surface = canvas.surface(layout.width, layout.height);
|
|
132
|
+
const context = surface.getContext("2d");
|
|
133
|
+
if (!context)
|
|
134
|
+
throw new Error("cascade.image.ContactSheet: no 2D context");
|
|
135
|
+
context.fillStyle = css(options.background, [1, 1, 1, 1]);
|
|
136
|
+
context.fillRect(0, 0, layout.width, layout.height);
|
|
137
|
+
context.imageSmoothingEnabled = true;
|
|
138
|
+
context.imageSmoothingQuality = "high";
|
|
139
|
+
layout.cells.forEach((cell, index) => context.drawImage(bitmaps[index], cell.x, cell.y, cell.width, cell.height));
|
|
140
|
+
if (labelled) {
|
|
141
|
+
context.fillStyle = css(options.labelColor, [0.1, 0.1, 0.1, 1]);
|
|
142
|
+
context.font = `${labelSize}px sans-serif`;
|
|
143
|
+
context.textBaseline = "top";
|
|
144
|
+
layout.cells.forEach((cell, index) => {
|
|
145
|
+
const label = labels[index];
|
|
146
|
+
if (label)
|
|
147
|
+
context.fillText(label, cell.labelX, cell.labelY + Math.round((layout.labelHeight - labelSize) / 2), layout.cell[0]);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const blob = await surface.convertToBlob({ type: "image/png" });
|
|
151
|
+
return { bytes: new Uint8Array(await blob.arrayBuffer()), layout };
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
for (const bitmap of bitmaps)
|
|
155
|
+
bitmap.close?.();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** An image value or a path, as the asset reference to read it by. */
|
|
159
|
+
function imageReference(value) {
|
|
160
|
+
if (typeof value === "string" && value)
|
|
161
|
+
return { path: value };
|
|
162
|
+
const candidate = value?.path;
|
|
163
|
+
return typeof candidate === "string" && candidate ? { path: candidate } : undefined;
|
|
164
|
+
}
|
|
165
|
+
export async function executeContactSheet(context) {
|
|
166
|
+
const { props } = context;
|
|
167
|
+
const references = [...context.inputs.images, ...(context.inputs.list ?? [])]
|
|
168
|
+
.map(imageReference)
|
|
169
|
+
.filter((reference) => reference !== undefined);
|
|
170
|
+
const bytes = [];
|
|
171
|
+
for (const reference of references) {
|
|
172
|
+
bytes.push(await context.capabilities.assets.read(reference, { signal: context.signal }));
|
|
173
|
+
}
|
|
174
|
+
const { bytes: sheet, layout } = await composeContactSheet(bytes, {
|
|
175
|
+
columns: props.columns,
|
|
176
|
+
gap: props.gap,
|
|
177
|
+
cellWidth: props.cellWidth,
|
|
178
|
+
labels: props.labels ? props.labels.split("\n") : [],
|
|
179
|
+
labelSize: props.labelSize,
|
|
180
|
+
background: props.background,
|
|
181
|
+
labelColor: props.labelColor,
|
|
182
|
+
});
|
|
183
|
+
const asset = await context.capabilities.assets.write(sheet, { mediaType: "image/png", suggestedName: props.filename }, { signal: context.signal });
|
|
184
|
+
context.outputs.asset.set(asset);
|
|
185
|
+
context.outputs.image.set({
|
|
186
|
+
path: asset.path ?? props.filename,
|
|
187
|
+
size: [layout.width, layout.height],
|
|
188
|
+
channels: "rgba",
|
|
189
|
+
depth: "u8",
|
|
190
|
+
space: "srgb",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
export const contactSheetRegistration = {
|
|
194
|
+
kind: "definition-v1",
|
|
195
|
+
moduleId: "cascade.image.ContactSheet",
|
|
196
|
+
definition: contactSheetDefinition,
|
|
197
|
+
loadExecute: async () => executeContactSheet,
|
|
198
|
+
};
|
|
199
|
+
//# sourceMappingURL=contactSheet.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DefinitionNodeRegistration } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The definition-v1 image nodes. One so far: the contact sheet a ladder of
|
|
4
|
+
* variants is judged on. Studio's `cascade.image.*` library (src/nodes/image)
|
|
5
|
+
* is the compatibility engine's and is not listed here.
|
|
6
|
+
*/
|
|
7
|
+
export { composeContactSheet, contactSheetDefinition, contactSheetLayout, contactSheetRegistration, executeContactSheet, type ContactSheetOptions, type SheetLayout, } from "./contactSheet.js";
|
|
8
|
+
export declare const imageNodeRegistrations: readonly DefinitionNodeRegistration[];
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { contactSheetRegistration } from "./contactSheet.js";
|
|
2
|
+
/**
|
|
3
|
+
* The definition-v1 image nodes. One so far: the contact sheet a ladder of
|
|
4
|
+
* variants is judged on. Studio's `cascade.image.*` library (src/nodes/image)
|
|
5
|
+
* is the compatibility engine's and is not listed here.
|
|
6
|
+
*/
|
|
7
|
+
export { composeContactSheet, contactSheetDefinition, contactSheetLayout, contactSheetRegistration, executeContactSheet, } from "./contactSheet.js";
|
|
8
|
+
export const imageNodeRegistrations = Object.freeze([contactSheetRegistration]);
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `.cascade` document as plain, mutable JSON — the shape the editor works
|
|
3
|
+
* on — and the small vocabulary for reading it: wires, ports, positions and
|
|
4
|
+
* key order.
|
|
5
|
+
*
|
|
6
|
+
* The editor deliberately edits the parsed JSON rather than a model built from
|
|
7
|
+
* it. A model would have to re-serialise every node on save, and every field it
|
|
8
|
+
* did not know about (a colour, a promotion, a key some other tool wrote) would
|
|
9
|
+
* either be dropped or reordered. Editing in place means a command touches the
|
|
10
|
+
* keys it is about and nothing else, which is what makes the diff minimal.
|
|
11
|
+
*/
|
|
12
|
+
import type { NodeDefinition } from "@field/cascade/contracts";
|
|
13
|
+
import type { Point } from "./layout.js";
|
|
14
|
+
export type JsonObject = Record<string, unknown>;
|
|
15
|
+
export interface EditableNode extends JsonObject {
|
|
16
|
+
id: string;
|
|
17
|
+
module?: string;
|
|
18
|
+
type?: string;
|
|
19
|
+
parent?: string;
|
|
20
|
+
props?: JsonObject;
|
|
21
|
+
}
|
|
22
|
+
export interface EditableDocument extends JsonObject {
|
|
23
|
+
nodes: EditableNode[];
|
|
24
|
+
connections?: unknown[];
|
|
25
|
+
annotations?: JsonObject[];
|
|
26
|
+
}
|
|
27
|
+
export declare const SUBNET = "cascade.core.Subnet";
|
|
28
|
+
export declare const INPUT = "cascade.core.Input";
|
|
29
|
+
export declare const OUTPUT = "cascade.core.Output";
|
|
30
|
+
/** One end of a wire: a node, a port name, and the port's index on that node. */
|
|
31
|
+
export interface Endpoint {
|
|
32
|
+
readonly node: string;
|
|
33
|
+
readonly port: string;
|
|
34
|
+
readonly index: number;
|
|
35
|
+
}
|
|
36
|
+
export interface Wire {
|
|
37
|
+
readonly from: Endpoint;
|
|
38
|
+
readonly to: Endpoint;
|
|
39
|
+
}
|
|
40
|
+
export type WireStyle = "tuple" | "object";
|
|
41
|
+
/**
|
|
42
|
+
* Read either connection form. Studio writes `[[id, index, name], [id, index,
|
|
43
|
+
* name]]`; the contracts also allow `{ source, target }`. Anything else is
|
|
44
|
+
* reported as `null`, never guessed at.
|
|
45
|
+
*/
|
|
46
|
+
export declare function readWire(raw: unknown): Wire | null;
|
|
47
|
+
export declare function writeWire(wire: Wire, style: WireStyle): unknown;
|
|
48
|
+
/** Whichever form the file already uses, so a new wire looks like its neighbours. */
|
|
49
|
+
export declare function wireStyleOf(document: EditableDocument): WireStyle;
|
|
50
|
+
/** `a.out -> b.in`: the same spelling the CLI takes. */
|
|
51
|
+
export declare function wireLabel(wire: Wire): string;
|
|
52
|
+
export declare function sameEndpoint(a: Endpoint, b: Endpoint): boolean;
|
|
53
|
+
export declare function moduleOf(node: EditableNode): string;
|
|
54
|
+
export declare function parentOf(node: EditableNode): string | null;
|
|
55
|
+
export declare function positionOf(node: EditableNode): Point;
|
|
56
|
+
/** Keep whichever shape the node already uses; Studio's `[x, y]` otherwise. */
|
|
57
|
+
export declare function setPosition(node: EditableNode, point: Point): void;
|
|
58
|
+
/**
|
|
59
|
+
* The key order Studio writes a node in (`Graph.nodeToJSON`). A key the editor
|
|
60
|
+
* adds goes where Studio would have put it, so a node edited by the CLI and
|
|
61
|
+
* then saved by Studio does not reorder.
|
|
62
|
+
*/
|
|
63
|
+
export declare const NODE_KEY_ORDER: readonly ["id", "module", "type", "position", "source", "file", "code", "comment", "color", "parent", "params", "promotions", "inputs", "props", "bypass", "cook", "state"];
|
|
64
|
+
/**
|
|
65
|
+
* Set a key without moving any other. An existing key keeps its place; a new
|
|
66
|
+
* one is inserted before the first key that Studio writes after it, or at the
|
|
67
|
+
* end when it is not one Studio knows.
|
|
68
|
+
*/
|
|
69
|
+
export declare function setOrderedKey(target: JsonObject, key: string, value: unknown, order: readonly string[]): void;
|
|
70
|
+
export interface PortInfo {
|
|
71
|
+
readonly name: string;
|
|
72
|
+
readonly index: number;
|
|
73
|
+
readonly kind: "data" | "trigger";
|
|
74
|
+
readonly type: string;
|
|
75
|
+
readonly variadic: boolean;
|
|
76
|
+
}
|
|
77
|
+
export interface NodePorts {
|
|
78
|
+
readonly inputs: readonly PortInfo[];
|
|
79
|
+
readonly outputs: readonly PortInfo[];
|
|
80
|
+
}
|
|
81
|
+
/** Ports in declaration order, which is the order Studio indexes them in. */
|
|
82
|
+
export declare function portsFromDefinition(definition: NodeDefinition): NodePorts;
|
|
83
|
+
/**
|
|
84
|
+
* A subnet's ports are not declared anywhere: they are its `Input` and
|
|
85
|
+
* `Output` children, named `input_<n>` and `output_<n>`. The runtime derives
|
|
86
|
+
* them the same way (`indexedBoundaryPorts` in runtime.ts); this mirrors it so
|
|
87
|
+
* the editor and the runtime agree on what a subnet exposes.
|
|
88
|
+
*/
|
|
89
|
+
export declare function subnetPorts(children: readonly EditableNode[]): NodePorts;
|
|
90
|
+
export declare function typesConnect(from: PortInfo | undefined, to: PortInfo | undefined): boolean;
|
|
91
|
+
export declare function integer(value: unknown): number;
|
|
92
|
+
/** Deep equality for JSON values, insensitive to key order. */
|
|
93
|
+
export declare function jsonEqual(a: unknown, b: unknown): boolean;
|
|
94
|
+
//# sourceMappingURL=document.d.ts.map
|