@field/cascade 0.10.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 +20 -3
- package/CHANGELOG.md +187 -0
- package/README.md +1 -2
- package/dist/assets/{CodeEditor-DSZBpzdy.js → CodeEditor-4O7FQ__R.js} +4 -4
- package/dist/assets/{CodePanel-sY6sZa7B.js → CodePanel-BU-autmq.js} +2 -2
- package/dist/assets/{CustomNodeDialog-BRKnqZhp.js → CustomNodeDialog-BRpDfOV5.js} +1 -1
- package/dist/assets/{OpenGraphDialog-ZsgL-iwr.js → OpenGraphDialog-BwbRlC0X.js} +1 -1
- package/dist/assets/{ProjectSettings-Cw17nNe3.js → ProjectSettings-DLOXLxcA.js} +1 -1
- package/dist/assets/{SettingsDialog-p25h3tj0.js → SettingsDialog-Bz8JxISH.js} +1 -1
- package/dist/assets/{VersionHistoryDialog-DzFLUotB.js → VersionHistoryDialog-D5Z0nJ5-.js} +1 -1
- package/dist/assets/{core-BICrEt0y.js → core-Yqi0Ni61.js} +1 -1
- package/dist/assets/{cssMode-D7TXw57I.js → cssMode-CxUzc4vp.js} +1 -1
- package/dist/assets/{definitions-DUww2Rt-.js → definitions-GreEMXLV.js} +1 -1
- package/dist/assets/{editor.api-DvkCO3ko.js → editor.api-BNFb25CX.js} +1 -1
- package/dist/assets/{freemarker2-BA2lxsF1.js → freemarker2-0ZDnWxeM.js} +1 -1
- package/dist/assets/geo-u0n_jOsD.js +1 -0
- package/dist/assets/{handlebars-BcDiyPd8.js → handlebars-Cy18MoaD.js} +1 -1
- package/dist/assets/{html-C7O4TyhX.js → html-B-2Dx7nq.js} +1 -1
- package/dist/assets/{htmlMode-WaDGF3Mj.js → htmlMode-DUAc9B_d.js} +1 -1
- package/dist/assets/{image-DR4oeyJB.js → image-cAa-Nu9O.js} +1 -1
- package/dist/assets/{index-DyvDC6pS.js → index-5ZtDAuy2.js} +42 -41
- package/dist/assets/{javascript-CpIEK7tz.js → javascript-DeQsDqP5.js} +1 -1
- package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-BiFwJVSk.js} +1 -1
- package/dist/assets/{liquid-CN6FFHZE.js → liquid-BLoHH68a.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-CNFgm1Kv.js} +1 -1
- package/dist/assets/math-DioIQOPJ.js +1 -0
- package/dist/assets/{mdx-DE89pafV.js → mdx-CFXQVNfs.js} +1 -1
- package/dist/assets/pop-DC_TQG9t.js +1 -0
- package/dist/assets/{python-ByTllTB9.js → python-Cf7S7p2E.js} +1 -1
- package/dist/assets/{razor-USYlRRCE.js → razor-CMHBcG-g.js} +1 -1
- package/dist/assets/{tsMode-BXpN8iUz.js → tsMode-BEih0GDj.js} +1 -1
- package/dist/assets/{typescript-DInBjGQQ.js → typescript-COEKYD_a.js} +1 -1
- package/dist/assets/{workers-C7Ye5B7D.js → workers-BNhAhMNn.js} +1 -1
- package/dist/assets/{xml-U0Uocqyo.js → xml-CPiMpnPl.js} +1 -1
- package/dist/assets/{yaml-CBw_PVYf.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 +100 -30756
- 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 +4 -1
- 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/documentFormat.d.ts +18 -0
- package/dist/contracts/documentFormat.js +195 -0
- package/dist/contracts/editor.d.ts +17 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/image.d.ts +141 -0
- package/dist/image.js +127 -0
- package/dist/index.html +1 -1
- package/dist/player/editor.js +32 -7
- 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/stage.js +4 -1
- package/doc/CLI_REFERENCE.md +73 -2
- package/doc/EDITOR_EXPORT.md +11 -2
- package/doc/MCP.md +17 -1
- package/doc/NODE_REFERENCE.md +142 -0
- package/doc/PROJECT_AUTHORING.md +127 -7
- package/package.json +16 -4
- package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
- package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
- package/dist/assets/geo-C2RHOVHC.js +0 -1
- package/dist/assets/math-DaDEgWKt.js +0 -1
- package/dist/assets/pop-pLH7L0PW.js +0 -1
package/doc/PROJECT_AUTHORING.md
CHANGED
|
@@ -83,6 +83,13 @@ my-artwork/
|
|
|
83
83
|
|
|
84
84
|
Keep generated output in a project-specific directory such as `renders/`. Keep reusable algorithms in `nodes/` or a normal TypeScript package rather than in Studio components.
|
|
85
85
|
|
|
86
|
+
Before writing OKLCH conversions, a seeded generator, noise, paper, a pen plot,
|
|
87
|
+
an ffmpeg call, a Cairn or Observatory loader or a disk cache, use the sketch
|
|
88
|
+
kit, `@field/cascade-kit` (`packages/kit`, a `file:` dependency). It also
|
|
89
|
+
generates the stubs for using another project's nodes without a symlink
|
|
90
|
+
(`npx cascade-kit nodes <provider>`). Read [KIT.md](KIT.md). The kit is
|
|
91
|
+
outside the core: Cascade never imports it.
|
|
92
|
+
|
|
86
93
|
## Add a project panel
|
|
87
94
|
|
|
88
95
|
A project can provide an optional dockable Studio panel without adding UI code to Cascade. Create `panels/asset-browser/index.ts`:
|
|
@@ -191,6 +198,19 @@ image-api:
|
|
|
191
198
|
|
|
192
199
|
The proxy accepts HTTPS only, refuses undeclared hosts, strips caller-supplied authorization headers, and never returns the credential value to the page. Studio defers its initial cook when a required credential is missing and links to File → Project Settings.
|
|
193
200
|
|
|
201
|
+
## Design the graph, not one big node
|
|
202
|
+
|
|
203
|
+
A piece is several nodes, each doing one job: load the data, lay it out, colour it, draw it, export it. The usage survey of November 2026 found the opposite in most projects (1 to 5 nodes of 300+ lines each over `lib/`), which leaves the graph as a parameter panel around opaque code. The graph is the record Marcus inspects, so each node boundary should be a place worth looking at, with the parameters that matter on it.
|
|
204
|
+
|
|
205
|
+
Clean graph design is clean code design:
|
|
206
|
+
|
|
207
|
+
- **One responsibility per node**, named for what it does (`layout_rings`, not `main`).
|
|
208
|
+
- **Data flows through typed ports**, never through module-level state or files one node writes and another reads behind the graph's back.
|
|
209
|
+
- **What varies is a parameter**; seeds, counts and colours are on the node, not constants in the code.
|
|
210
|
+
- **Shared helpers go in `lib/` or the kit** (`KIT.md`); the node is the unit you compose and reuse.
|
|
211
|
+
|
|
212
|
+
Split a node when it does two of load, generate, style, render and export, or when its `execute` grows past about 150 lines.
|
|
213
|
+
|
|
194
214
|
## Add a custom node
|
|
195
215
|
|
|
196
216
|
Before writing one, check whether you need one. A parameter can hold an expression, so anything time-based is usually a parameter rather than a node. See [Animate a parameter](#animate-a-parameter). A node earns its place when it produces geometry or pixels, or when several parameters share a computation. The built-in library is listed in `NODE_REFERENCE.md`, generated from the definitions themselves; read that before searching for what a node does.
|
|
@@ -345,7 +365,7 @@ export async function execute(node: any) {
|
|
|
345
365
|
|
|
346
366
|
`param()` is a method on the node the engine hands to `execute`, not an import. It is re-declared on every cook, so it never resets a value, an expression, or a keyframe someone set: reading `.value` gives the *resolved* value for the current frame, whichever of the three bindings is in force. The loader imports the module as real JavaScript, so top-level code does run at import time. Keep dynamic modules free of top-level effects and put interface declaration and cooking inside `execute`.
|
|
347
367
|
|
|
348
|
-
Prefer `new OffscreenCanvas(width, height)` to `document.createElement('canvas')` when both hosts can run the node. A portable declaration still requires output parity checks; installing Skia does not override a browser-only declaration. Use Python for libraries or tools that require a server. Measure transport, serialization, and cold-start costs; persistent workers can avoid repeated process/model startup.
|
|
368
|
+
Prefer `new OffscreenCanvas(width, height)` to `document.createElement('canvas')` when both hosts can run the node; a definition-v1 node uses `cascade/image` (see "Draw an image in any host"). A portable declaration still requires output parity checks; installing Skia does not override a browser-only declaration. Use Python for libraries or tools that require a server. Measure transport, serialization, and cold-start costs; persistent workers can avoid repeated process/model startup.
|
|
349
369
|
|
|
350
370
|
Choose the narrowest execution locus:
|
|
351
371
|
|
|
@@ -367,6 +387,64 @@ export const definition = {
|
|
|
367
387
|
|
|
368
388
|
The runtime refuses unsupported host/capability combinations before executing the graph.
|
|
369
389
|
|
|
390
|
+
### Draw an image in any host
|
|
391
|
+
|
|
392
|
+
A node that draws, reads or writes an image uses `cascade/image`, and then one
|
|
393
|
+
body cooks in Studio, under `cascade run`/`render`/MCP, and in an exported
|
|
394
|
+
editor's worker. Declare `runsOn: 'portable'` and `capabilities: ['assets']`,
|
|
395
|
+
and import nothing host-specific: no `node:*`, no `@napi-rs/canvas`, no
|
|
396
|
+
`document`.
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
import type { NodeDefinition, NodeExecutionContext } from 'cascade/contracts';
|
|
400
|
+
import { readBytes, readImage, registerFont, surface, writeImage } from 'cascade/image';
|
|
401
|
+
|
|
402
|
+
export const definition = {
|
|
403
|
+
apiVersion: 1,
|
|
404
|
+
label: 'Poster',
|
|
405
|
+
runsOn: 'portable',
|
|
406
|
+
capabilities: ['assets'],
|
|
407
|
+
inputs: { photo: { kind: 'data', type: 'image' } },
|
|
408
|
+
outputs: { image: { kind: 'data', type: 'image' } },
|
|
409
|
+
props: { title: { type: 'string', default: 'Hello' }, font: { type: 'string', default: 'assets/fonts/Display.otf' } },
|
|
410
|
+
} as const satisfies NodeDefinition;
|
|
411
|
+
|
|
412
|
+
export async function execute(context: NodeExecutionContext<typeof definition>) {
|
|
413
|
+
const photo = await readImage(context, context.inputs.photo); // an image input, asset or project path
|
|
414
|
+
await registerFont('Display', await readBytes(context, context.props.font));
|
|
415
|
+
const { canvas, context: g } = surface(photo.width, photo.height); // OffscreenCanvas, CPU-backed 2D
|
|
416
|
+
g.drawImage(photo, 0, 0);
|
|
417
|
+
g.font = '96px Display';
|
|
418
|
+
g.fillText(context.props.title, 40, 140);
|
|
419
|
+
context.outputs.image.set(await writeImage(context, canvas, `${context.nodeId}.png`));
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
| | Browser (Studio, exported editor) | Headless (CLI, MCP) |
|
|
424
|
+
| --- | --- | --- |
|
|
425
|
+
| `createCanvas`, `surface` | the realm's `OffscreenCanvas` | Skia's, from `@napi-rs/canvas` |
|
|
426
|
+
| `decodeImage`, `readImage` | `createImageBitmap` | Skia's decoder |
|
|
427
|
+
| `writeImage` | an in-memory generated asset | `.cascade-cache/<name>.<hash>.png` |
|
|
428
|
+
| `registerFont` | `FontFace` added to the page's or worker's font set | Skia's `GlobalFonts` |
|
|
429
|
+
| `readBytes` | shipped files only (`editor.assets`, literal asset props, frozen files) | any project file |
|
|
430
|
+
|
|
431
|
+
- The host chooses where a written image lands; never name a path or read an
|
|
432
|
+
output file back by a path you built. `cachePath` and `saveImage` from
|
|
433
|
+
`cascade/io` remain for dynamic nodes.
|
|
434
|
+
- `surface(w, h)` asks for a `willReadFrequently` context, which keeps Chrome
|
|
435
|
+
on the same software Skia rasteriser the headless host uses. Expect the same
|
|
436
|
+
image, not the same bytes: on commons `plotter-social` the paper, foil and
|
|
437
|
+
wordmark match exactly and scanline marks differ at stroke edges (mean
|
|
438
|
+
channel delta under 0.5 of 255 with the same fonts).
|
|
439
|
+
- A font only matches across hosts when both register the same bytes. A file
|
|
440
|
+
that is not there (a licensed face the export never ships) should fall back
|
|
441
|
+
to a named system face, not fail; read it with `readBytes` in a `try`.
|
|
442
|
+
- What has no browser form is refused, not imitated: no canvas in the realm,
|
|
443
|
+
or `assets` not declared, throws a sentence saying which. A node that needs
|
|
444
|
+
the filesystem, a network fetch or ffmpeg stays `runsOn: 'server'`; to put
|
|
445
|
+
its output in an editor, freeze it (`cascade freeze <graph> <node> --fields
|
|
446
|
+
path,size,channels,depth,space` ships the image file with the snapshot).
|
|
447
|
+
|
|
370
448
|
### GPU nodes today
|
|
371
449
|
|
|
372
450
|
A definition-v1 node may declare `capabilities: ['gpu']`. Studio and the optional
|
|
@@ -606,12 +684,54 @@ in `cascade run --json` under `stages`, and in the run trace.
|
|
|
606
684
|
keeps running its old code in a live Studio. Its bytecode cache is keyed by
|
|
607
685
|
the code hash, because Python accepts a `.pyc` whose source kept its size
|
|
608
686
|
within the same second.
|
|
609
|
-
- `cache`
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
687
|
+
- `cache` controls Cascade's own result cache. A stored result is served only
|
|
688
|
+
while every file it names still has the bytes it had, so a stage that writes
|
|
689
|
+
a fixed path and was re-run with other inputs misses rather than serving the
|
|
690
|
+
overwritten file. See "The stage cache default" below.
|
|
691
|
+
|
|
692
|
+
#### The stage cache default
|
|
693
|
+
|
|
694
|
+
Since 6 Nov 2026 the cache is **on by default for the calls a node's cook
|
|
695
|
+
makes**, and off for everything else. A node is a pure function of its inputs,
|
|
696
|
+
so a stage it calls through `runStage` from `cascade/stage` is treated as pure:
|
|
697
|
+
same stage code, same arguments, same input file bytes, same answer. A second
|
|
698
|
+
identical cook is served from `.cascade/cache/stages/` without starting Python
|
|
699
|
+
at all; on `cloud-plots/relief.cascade` that takes a re-cook from about 4.2 s
|
|
700
|
+
to 0.45 s. A call from a panel (`api.runStage`) or a hand-rolled
|
|
701
|
+
`fetch('/api/exec')` is an action — `plot-start`, `plot-status` — and is not
|
|
702
|
+
cached unless you say so.
|
|
703
|
+
|
|
704
|
+
What is not pure, and must be opted out:
|
|
705
|
+
|
|
706
|
+
- a stage whose answer changes under an identical call: it reads the clock,
|
|
707
|
+
draws fresh randomness, queries a device or a job's progress, or calls a
|
|
708
|
+
network service whose answer you want fresh each time;
|
|
709
|
+
- a stage that reads a file its arguments do not name (a config file, a folder
|
|
710
|
+
it scans, a model downloaded on the side). Only files named in the arguments
|
|
711
|
+
are hashed into the key, so an edit to an unnamed file is invisible;
|
|
712
|
+
- a stage whose purpose is a side effect (copying to a named place, posting
|
|
713
|
+
somewhere). A result whose named files still have their bytes is served, so
|
|
714
|
+
a copy that already happened is not repeated; when the effect must happen
|
|
715
|
+
every time, opt the stage out.
|
|
716
|
+
|
|
717
|
+
How to opt out:
|
|
718
|
+
|
|
719
|
+
```json
|
|
720
|
+
"cache": { "exclude": ["plot-status", "camera-grab"], "outputs": ["out", "svg_out"] }
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
| Declaration | What is cached |
|
|
724
|
+
|---|---|
|
|
725
|
+
| absent | calls from node cooks, except `exclude` (none) |
|
|
726
|
+
| `{ "exclude": [...], "outputs": [...] }` | calls from node cooks, except the named stages |
|
|
727
|
+
| `{ "scope": "all", ... }` or `true` | every call, panel actions included, except `exclude` |
|
|
728
|
+
| `false` or `{ "enabled": false }` | nothing |
|
|
729
|
+
|
|
730
|
+
`CASCADE_STAGE_CACHE=off` in the environment still turns it off for one
|
|
731
|
+
process (`cascade parity` sets it). Every call's trace says what happened and
|
|
732
|
+
why: `hit`, `miss (no entry)`, `off (excluded)`, `off (not a cook call)`,
|
|
733
|
+
`off (not enabled)`. A project that keeps its own Python-side cache can keep
|
|
734
|
+
it; Cascade's sits in front, and on a hit the project's code is not called.
|
|
615
735
|
- A request carries `"cascade": {"key", "code", "cache"}` beside `args` (the
|
|
616
736
|
worker), or `CASCADE_STAGE_ENVELOPE` in the environment (one-shot). A
|
|
617
737
|
project that keeps its own cache should stand aside when `cache` is `"off"`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@field/cascade",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Visual programming for generative art",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "FIELD.IO <marcus@field.io>",
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
],
|
|
44
44
|
"workspaces": [
|
|
45
45
|
"packages/contracts",
|
|
46
|
-
"packages/runtime"
|
|
46
|
+
"packages/runtime",
|
|
47
|
+
"packages/kit"
|
|
47
48
|
],
|
|
48
49
|
"bin": {
|
|
49
50
|
"cascade": "./dist/cli/index.js"
|
|
@@ -105,6 +106,10 @@
|
|
|
105
106
|
"types": "./dist/io.d.ts",
|
|
106
107
|
"default": "./dist/io.js"
|
|
107
108
|
},
|
|
109
|
+
"./image": {
|
|
110
|
+
"types": "./dist/image.d.ts",
|
|
111
|
+
"default": "./dist/image.js"
|
|
112
|
+
},
|
|
108
113
|
"./gpu": {
|
|
109
114
|
"types": "./dist/gpu.d.ts",
|
|
110
115
|
"default": "./dist/gpu.js"
|
|
@@ -129,15 +134,22 @@
|
|
|
129
134
|
"dev": "vite",
|
|
130
135
|
"dev:client": "vite",
|
|
131
136
|
"build": "node scripts/build.mjs",
|
|
132
|
-
"build:workspaces": "npm run build --workspace @cascade/contracts && npm run build --workspace @cascade/runtime",
|
|
137
|
+
"build:workspaces": "npm run build --workspace @cascade/contracts && npm run build --workspace @cascade/runtime && npm run build --workspace @field/cascade-kit",
|
|
138
|
+
"build:kit": "npm run build --workspace @field/cascade-kit",
|
|
139
|
+
"test:kit": "npm run test --workspace @field/cascade-kit",
|
|
133
140
|
"build:packages": "npm run build:workspaces && node scripts/copy-package-builds.mjs",
|
|
134
141
|
"check:packages": "npm run check --workspace @cascade/contracts && npm run build --workspace @cascade/contracts && npm run check --workspace @cascade/runtime",
|
|
135
|
-
"test:packages": "npm run test --workspace @cascade/contracts && npm run test --workspace @cascade/runtime",
|
|
142
|
+
"test:packages": "npm run test --workspace @cascade/contracts && npm run test --workspace @cascade/runtime && npm run test --workspace @field/cascade-kit",
|
|
136
143
|
"build:cli": "node scripts/build.mjs --cli",
|
|
137
144
|
"build:server": "npm --prefix server run build",
|
|
138
145
|
"prepack": "npm run build",
|
|
139
146
|
"preview": "vite preview",
|
|
140
147
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
148
|
+
"verify": "node scripts/verify.mjs",
|
|
149
|
+
"verify:fast": "node scripts/verify.mjs --fast",
|
|
150
|
+
"check:architecture": "vitest run tests/architecture-",
|
|
151
|
+
"check:bundle": "node scripts/bundle-budget.mjs",
|
|
152
|
+
"check:node-reference": "tsx scripts/build-node-reference.mjs --check",
|
|
141
153
|
"test": "vitest",
|
|
142
154
|
"test:run": "vitest run",
|
|
143
155
|
"test:coverage": "vitest run --coverage",
|
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
import{At as e,Dt as t,G as n,Ot as r,Q as i,T as a,U as o,V as s,W as c,X as l,at as u,b as d,ct as f,d as p,dt as m,i as h,it as g,jt as _,l as v,lt as y,ot as b,pt as x,q as S,st as C,t as w,v as T,w as E,z as D}from"./legacy-CTEeXeqF.js";function O(e){let t=e.elements.filter(e=>!e.isAnnotation).map(e=>{let t=e;return{id:t.id,type:t.type,code:t.code,position:t.position,inputs:t.inputs.map(e=>({id:e.id,name:e.name,portType:e.portType,dataType:e.dataType,value:e.value,defaultValue:e.defaultValue})),outputs:t.outputs.map(e=>({id:e.id,name:e.name,portType:e.portType,dataType:e.dataType,value:e.value}))}}),n=e.connections.map(e=>({id:e.id,from:e.from,to:e.to,type:e.type})),r=e.elements.filter(e=>!e.isAnnotation).map(e=>e).filter(e=>e.inputs.every(e=>e.connections.length===0)).map(e=>e.id);return`
|
|
2
|
-
const graphData = {
|
|
3
|
-
version: '1.0.0',
|
|
4
|
-
nodes: ${JSON.stringify(t,null,2)},
|
|
5
|
-
connections: ${JSON.stringify(n,null,2)},
|
|
6
|
-
entryPoints: ${JSON.stringify(r)}
|
|
7
|
-
};
|
|
8
|
-
`.trim()}async function k(e){return new Promise((t,n)=>{let r=new FileReader;r.onloadend=()=>{let e=r.result.split(`,`)[1];t(e)},r.onerror=n,r.readAsDataURL(e)})}async function A(e){let t=[];for(let n of e)try{let e;if(n.type===`image`&&n.data instanceof HTMLImageElement){let t=document.createElement(`canvas`);t.width=n.data.width,t.height=n.data.height;let r=t.getContext(`2d`);r?(r.drawImage(n.data,0,0),e=t.toDataURL(`image/png`)):e=n.data.src}else if(n.type===`json`)e=`data:application/json;base64,${btoa(JSON.stringify(n.data))}`;else if(n.type===`text`)e=`data:text/plain;base64,${btoa(n.data)}`;else{let t=await(await fetch(n.path)).blob(),r=await k(t);e=`data:${t.type};base64,${r}`}t.push({id:n.id,path:n.path,dataUrl:e,type:n.type})}catch(e){console.warn(`Failed to embed asset ${n.path}:`,e)}return t}async function j(e,t=`Cascade Project`){let n=await A(e.assetManager.list()),r=O(e);return`<!DOCTYPE html>
|
|
9
|
-
<html lang="en">
|
|
10
|
-
<head>
|
|
11
|
-
<meta charset="UTF-8">
|
|
12
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
13
|
-
<title>${t}</title>
|
|
14
|
-
<style>
|
|
15
|
-
* {
|
|
16
|
-
margin: 0;
|
|
17
|
-
padding: 0;
|
|
18
|
-
box-sizing: border-box;
|
|
19
|
-
}
|
|
20
|
-
body {
|
|
21
|
-
width: 100vw;
|
|
22
|
-
height: 100vh;
|
|
23
|
-
overflow: hidden;
|
|
24
|
-
background: #0a0a0a;
|
|
25
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
26
|
-
}
|
|
27
|
-
#cascade-root {
|
|
28
|
-
width: 100%;
|
|
29
|
-
height: 100%;
|
|
30
|
-
}
|
|
31
|
-
</style>
|
|
32
|
-
</head>
|
|
33
|
-
<body>
|
|
34
|
-
<div id="cascade-root"></div>
|
|
35
|
-
|
|
36
|
-
<script>
|
|
37
|
-
// Minimal Cascade Runtime
|
|
38
|
-
${M()}
|
|
39
|
-
|
|
40
|
-
// Embedded assets
|
|
41
|
-
const ASSETS = ${JSON.stringify(n,null,2)};
|
|
42
|
-
|
|
43
|
-
// Override fetch for asset loading
|
|
44
|
-
const originalFetch = window.fetch;
|
|
45
|
-
window.fetch = function(url) {
|
|
46
|
-
const asset = ASSETS.find(a => url.includes(a.path) || url.includes(a.id));
|
|
47
|
-
if (asset) {
|
|
48
|
-
return Promise.resolve(new Response(
|
|
49
|
-
dataUrlToBlob(asset.dataUrl)
|
|
50
|
-
));
|
|
51
|
-
}
|
|
52
|
-
return originalFetch.apply(this, arguments);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// Helper function
|
|
56
|
-
function dataUrlToBlob(dataUrl) {
|
|
57
|
-
const arr = dataUrl.split(',');
|
|
58
|
-
const mime = arr[0].match(/:(.*?);/)?.[1] || 'application/octet-stream';
|
|
59
|
-
const bstr = atob(arr[1]);
|
|
60
|
-
let n = bstr.length;
|
|
61
|
-
const u8arr = new Uint8Array(n);
|
|
62
|
-
while (n--) {
|
|
63
|
-
u8arr[n] = bstr.charCodeAt(n);
|
|
64
|
-
}
|
|
65
|
-
return new Blob([u8arr], { type: mime });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Compiled graph
|
|
69
|
-
${r}
|
|
70
|
-
|
|
71
|
-
// Start execution
|
|
72
|
-
Cascade.run(graphData, document.getElementById('cascade-root'));
|
|
73
|
-
<\/script>
|
|
74
|
-
</body>
|
|
75
|
-
</html>`}function M(){return`(function() {
|
|
76
|
-
'use strict';
|
|
77
|
-
|
|
78
|
-
class CascadeRuntime {
|
|
79
|
-
static run(graphData, container) {
|
|
80
|
-
const graph = this.buildGraph(graphData);
|
|
81
|
-
|
|
82
|
-
// Find entry points and start execution
|
|
83
|
-
const entryNodes = graphData.entryPoints
|
|
84
|
-
.map(id => graph.nodes.find(e => e.id === id))
|
|
85
|
-
.filter(Boolean);
|
|
86
|
-
|
|
87
|
-
entryNodes.forEach(node => {
|
|
88
|
-
graph.execute(node);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// Mount scene container
|
|
92
|
-
if (container && graph.sceneContainer) {
|
|
93
|
-
container.appendChild(graph.sceneContainer);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
static buildGraph(graphData) {
|
|
98
|
-
// Minimal Node class
|
|
99
|
-
class Node {
|
|
100
|
-
constructor(id, type) {
|
|
101
|
-
this.id = id;
|
|
102
|
-
this.type = type;
|
|
103
|
-
this.inputs = [];
|
|
104
|
-
this.outputs = [];
|
|
105
|
-
this.position = { x: 0, y: 0 };
|
|
106
|
-
this.code = '';
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
in(name, defaultValue, options = {}) {
|
|
110
|
-
const port = {
|
|
111
|
-
id: this.id + '_in_' + name,
|
|
112
|
-
name,
|
|
113
|
-
portType: name === 'trigger' ? 'trigger' : 'param',
|
|
114
|
-
dataType: options.type || 'any',
|
|
115
|
-
value: defaultValue,
|
|
116
|
-
defaultValue,
|
|
117
|
-
options,
|
|
118
|
-
connections: []
|
|
119
|
-
};
|
|
120
|
-
this.inputs.push(port);
|
|
121
|
-
return port;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
out(name, portType = 'param') {
|
|
125
|
-
const port = {
|
|
126
|
-
id: this.id + '_out_' + name,
|
|
127
|
-
name,
|
|
128
|
-
portType,
|
|
129
|
-
dataType: 'any',
|
|
130
|
-
value: undefined,
|
|
131
|
-
connections: [],
|
|
132
|
-
setValue: (value) => {
|
|
133
|
-
port.value = value;
|
|
134
|
-
port.connections.forEach(conn => {
|
|
135
|
-
const targetNode = graph.nodes.find(e => e.id === conn.to.nodeId);
|
|
136
|
-
if (targetNode) {
|
|
137
|
-
const targetPort = targetNode.inputs.find(p => p.id === conn.to.portId);
|
|
138
|
-
if (targetPort) {
|
|
139
|
-
targetPort.value = value;
|
|
140
|
-
if (targetPort.onChange) targetPort.onChange(value);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
},
|
|
145
|
-
trigger: (props) => {
|
|
146
|
-
port.connections.forEach(conn => {
|
|
147
|
-
const targetNode = graph.nodes.find(e => e.id === conn.to.nodeId);
|
|
148
|
-
if (targetNode) {
|
|
149
|
-
const targetPort = targetNode.inputs.find(p => p.id === conn.to.portId);
|
|
150
|
-
if (targetPort && targetPort.onTrigger) {
|
|
151
|
-
targetPort.onTrigger(props);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
this.outputs.push(port);
|
|
158
|
-
return port;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
setFunction(fn) {
|
|
162
|
-
this.nodeFunction = fn;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async execute() {
|
|
166
|
-
if (this.nodeFunction) {
|
|
167
|
-
try {
|
|
168
|
-
await this.nodeFunction(this, graph);
|
|
169
|
-
} catch (err) {
|
|
170
|
-
console.error('Node execution error:', err);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
log(...args) {
|
|
176
|
-
console.log('[' + this.id + ']', ...args);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async require(packageName, version) {
|
|
180
|
-
return graph.packageManager.load(packageName, version);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Minimal PackageManager for runtime
|
|
185
|
-
class PackageManager {
|
|
186
|
-
constructor() {
|
|
187
|
-
this.cache = new Map();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async load(packageName, version = 'latest') {
|
|
191
|
-
const key = packageName + '@' + version;
|
|
192
|
-
if (this.cache.has(key)) {
|
|
193
|
-
return this.cache.get(key);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const versionPart = version === 'latest' ? '' : '@' + version;
|
|
197
|
-
const url = 'https://esm.sh/' + packageName + versionPart;
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
const module = await import(url);
|
|
201
|
-
const result = module.default || module;
|
|
202
|
-
this.cache.set(key, result);
|
|
203
|
-
return result;
|
|
204
|
-
} catch (error) {
|
|
205
|
-
throw new Error('Failed to load package ' + key + ': ' + error.message);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Minimal Graph class
|
|
211
|
-
class Graph {
|
|
212
|
-
constructor() {
|
|
213
|
-
this.nodes = [];
|
|
214
|
-
this.connections = [];
|
|
215
|
-
this.sceneContainer = document.createElement('div');
|
|
216
|
-
this.sceneContainer.id = 'cascade-scene';
|
|
217
|
-
this.sceneContainer.style.width = '100%';
|
|
218
|
-
this.sceneContainer.style.height = '100%';
|
|
219
|
-
this.packageManager = new PackageManager();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
addNode(type, position) {
|
|
223
|
-
const id = 'node_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
224
|
-
const node = new Node(id, type);
|
|
225
|
-
node.position = position;
|
|
226
|
-
this.nodes.push(node);
|
|
227
|
-
return node;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
getNode(nodeId) {
|
|
231
|
-
return this.nodes.find(n => n.id === nodeId) || null;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
connect(fromPort, toPort) {
|
|
235
|
-
// Parse port IDs to extract element IDs and indices
|
|
236
|
-
const fromParts = fromPort.id.split('_');
|
|
237
|
-
const toParts = toPort.id.split('_');
|
|
238
|
-
const fromElementId = fromParts.slice(0, -2).join('_');
|
|
239
|
-
const toElementId = toParts.slice(0, -2).join('_');
|
|
240
|
-
|
|
241
|
-
const connection = {
|
|
242
|
-
id: 'conn_' + Date.now(),
|
|
243
|
-
from: { nodeId: fromElementId, portId: fromPort.id },
|
|
244
|
-
to: { nodeId: toElementId, portId: toPort.id },
|
|
245
|
-
type: fromPort.portType
|
|
246
|
-
};
|
|
247
|
-
this.connections.push(connection);
|
|
248
|
-
fromPort.connections.push(connection);
|
|
249
|
-
toPort.connections.push(connection);
|
|
250
|
-
return connection;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
execute(entryNode) {
|
|
254
|
-
if (entryNode) {
|
|
255
|
-
entryNode.execute();
|
|
256
|
-
} else {
|
|
257
|
-
this.nodes.forEach(node => {
|
|
258
|
-
if (node.inputs.every(p => p.connections.length === 0)) {
|
|
259
|
-
node.execute();
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const graph = new Graph();
|
|
267
|
-
|
|
268
|
-
// Create nodes
|
|
269
|
-
graphData.nodes.forEach(nodeData => {
|
|
270
|
-
const node = graph.addNode(nodeData.type, nodeData.position);
|
|
271
|
-
node.id = nodeData.id;
|
|
272
|
-
node.code = nodeData.code || '';
|
|
273
|
-
|
|
274
|
-
// Restore ports
|
|
275
|
-
nodeData.inputs.forEach(portData => {
|
|
276
|
-
const port = node.in(portData.name, portData.defaultValue, { type: portData.dataType });
|
|
277
|
-
port.id = portData.id;
|
|
278
|
-
port.value = portData.value;
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
nodeData.outputs.forEach(portData => {
|
|
282
|
-
const port = node.out(portData.name, portData.portType);
|
|
283
|
-
port.id = portData.id;
|
|
284
|
-
port.value = portData.value;
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Compile and set function if code exists
|
|
288
|
-
if (node.code) {
|
|
289
|
-
try {
|
|
290
|
-
// Wrap code in async function to support top-level await
|
|
291
|
-
const wrappedCode = 'return (async function(node, graph) {
|
|
292
|
-
' + node.code + '
|
|
293
|
-
})(node, graph);';
|
|
294
|
-
const nodeFunction = new Function('node', 'graph', wrappedCode);
|
|
295
|
-
node.setFunction(nodeFunction);
|
|
296
|
-
} catch (err) {
|
|
297
|
-
console.warn('Failed to compile node ' + node.id + ':', err);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// Restore connections
|
|
303
|
-
graphData.connections.forEach(connData => {
|
|
304
|
-
const fromNode = graph.getNode(connData.from.nodeId);
|
|
305
|
-
const toNode = graph.getNode(connData.to.nodeId);
|
|
306
|
-
if (fromNode && toNode) {
|
|
307
|
-
const fromPort = fromNode.outputs.find(p => p.id === connData.from.portId);
|
|
308
|
-
const toPort = toNode.inputs.find(p => p.id === connData.to.portId);
|
|
309
|
-
if (fromPort && toPort) {
|
|
310
|
-
graph.connect(fromPort, toPort);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
return graph;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Export to global
|
|
320
|
-
window.Cascade = CascadeRuntime;
|
|
321
|
-
})();`}function N(e,t,n=`text/html`){let r=new Blob([e],{type:n}),i=URL.createObjectURL(r),a=document.createElement(`a`);a.href=i,a.download=t,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(i)}async function P(e,t=`Cascade Project`){let n=e.assetManager.list(),r=O(e),i=M(),a=`<!DOCTYPE html>
|
|
322
|
-
<html lang="en">
|
|
323
|
-
<head>
|
|
324
|
-
<meta charset="UTF-8">
|
|
325
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
326
|
-
<title>${t}</title>
|
|
327
|
-
<style>
|
|
328
|
-
* {
|
|
329
|
-
margin: 0;
|
|
330
|
-
padding: 0;
|
|
331
|
-
box-sizing: border-box;
|
|
332
|
-
}
|
|
333
|
-
body {
|
|
334
|
-
width: 100vw;
|
|
335
|
-
height: 100vh;
|
|
336
|
-
overflow: hidden;
|
|
337
|
-
background: #0a0a0a;
|
|
338
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
339
|
-
}
|
|
340
|
-
#cascade-root {
|
|
341
|
-
width: 100%;
|
|
342
|
-
height: 100%;
|
|
343
|
-
}
|
|
344
|
-
</style>
|
|
345
|
-
</head>
|
|
346
|
-
<body>
|
|
347
|
-
<div id="cascade-root"></div>
|
|
348
|
-
|
|
349
|
-
<script src="./runtime.js"><\/script>
|
|
350
|
-
<script src="./graph.js"><\/script>
|
|
351
|
-
<script>
|
|
352
|
-
// Start execution
|
|
353
|
-
Cascade.run(graphData, document.getElementById('cascade-root'));
|
|
354
|
-
<\/script>
|
|
355
|
-
</body>
|
|
356
|
-
</html>`,o=`// Minimal Cascade Runtime
|
|
357
|
-
${i}`,s=`// Compiled graph
|
|
358
|
-
${r}`,c=JSON.stringify(n.map(e=>({id:e.id,path:e.path,type:e.type,size:e.size})),null,2);N(a,`index.html`,`text/html`),setTimeout(()=>{N(o,`runtime.js`,`application/javascript`)},100),setTimeout(()=>{N(s,`graph.js`,`application/javascript`)},200),setTimeout(()=>{N(c,`assets.json`,`application/json`)},300),console.log(`Folder export initiated. Download the files and organize them in a folder structure:`),console.log(`- index.html`),console.log(`- runtime.js`),console.log(`- graph.js`),console.log(`- assets.json`),console.log(`- assets/ (create this folder and add your asset files)`)}var F=n(`<p class="svelte-12jesfv">This will export your project as a standalone HTML file with all assets embedded.</p> <ul class="svelte-12jesfv"><li class="svelte-12jesfv">✅ All nodes and connections</li> <li class="svelte-12jesfv">✅ All node code</li> <li class="svelte-12jesfv">✅ All assets (embedded as base64)</li> <li class="svelte-12jesfv">✅ Minimal runtime (~50KB)</li> <li class="svelte-12jesfv">✅ Single file - easy to share</li></ul>`,1),I=n(`<p class="svelte-12jesfv">This will export your project as a folder structure with separate files.</p> <ul class="svelte-12jesfv"><li class="svelte-12jesfv">✅ index.html - Main HTML file</li> <li class="svelte-12jesfv">✅ runtime.js - Cascade runtime</li> <li class="svelte-12jesfv">✅ graph.js - Compiled graph</li> <li class="svelte-12jesfv">✅ assets.json - Asset manifest</li> <li class="svelte-12jesfv">✅ assets/ - Asset files folder</li> <li class="svelte-12jesfv">⚠️ You'll need to manually organize asset files</li></ul>`,1),L=n(`<div class="error svelte-12jesfv"> </div>`),R=n(`<div class="overlay svelte-12jesfv"><div class="dialog svelte-12jesfv" role="dialog" aria-modal="true" aria-labelledby="export-title"><div class="header svelte-12jesfv"><h2 id="export-title" class="svelte-12jesfv">Export Project</h2> <button class="close-button svelte-12jesfv" aria-label="Close">×</button></div> <div class="content svelte-12jesfv"><div class="form-group svelte-12jesfv"><label for="project-name" class="svelte-12jesfv">Project Name</label> <input id="project-name" type="text" placeholder="My Project" class="svelte-12jesfv"/></div> <div class="form-group svelte-12jesfv"><label for="export-format" class="svelte-12jesfv">Export Format</label> <select id="export-format" class="svelte-12jesfv"><option>Single HTML File (all assets embedded)</option><option>Folder Structure (separate files)</option></select></div> <div class="info svelte-12jesfv"><!></div> <!></div> <div class="footer svelte-12jesfv"><button class="cancel-button svelte-12jesfv">Cancel</button> <button class="export-button svelte-12jesfv"><!></button></div></div></div>`);function z(n,O){r(O,!1);let k=h(O,`graph`,8),A=h(O,`open`,12,!1),M=w(),z=m(`Cascade Project`),B=m(!1),V=m(null),H=m(`html`);async function U(){if(!(!k()||i(B))){x(B,!0),x(V,null);try{i(H)===`html`?N(await j(k(),i(z)),`${i(z).replace(/[^a-z0-9]/gi,`_`).toLowerCase()}.html`,`text/html`):await P(k(),i(z)),setTimeout(()=>{A(!1),M(`close`)},500)}catch(e){x(V,e.message||`Failed to export project`),console.error(`Export error:`,e)}finally{x(B,!1)}}}function W(){A(!1),M(`close`)}function G(e){e.key===`Escape`&&W()}v();var K=c();l(`keydown`,u,G);var q=C(K),J=t=>{var n=R(),r=b(n),c=b(r),u=y(b(c),2);_(c);var m=y(c,2),h=b(m),v=y(b(h),2);d(v),_(h);var C=y(h,2),w=y(b(C),2),O=b(w);O.value=O.__value=`html`;var k=y(O);k.value=k.__value=`folder`,_(w),a(w),_(C);var A=y(C,2),j=b(A),M=t=>{var n=F();e(2),o(t,n)},N=t=>{var n=I();e(2),o(t,n)};D(j,e=>{i(H)===`html`?e(M):e(N,-1)}),_(A);var P=y(A,2),G=e=>{var t=L(),n=f(t,!0);g(()=>s(n,i(V))),o(e,t)};D(P,e=>{i(V)&&e(G)}),_(m);var K=y(m,2),q=b(K),J=y(q,2),Y=b(J),X=e=>{var t=S(`Exporting...`);o(e,t)},Z=e=>{var t=S();g(()=>s(t,`Export ${i(H)===`html`?`HTML`:`Folder`}`)),o(e,t)};D(Y,e=>{i(B)?e(X):e(Z,-1)}),_(J),_(K),_(r),_(n),g(()=>{v.disabled=i(B),w.disabled=i(B),q.disabled=i(B),J.disabled=i(B)}),l(`click`,u,W),T(v,()=>i(z),e=>x(z,e)),E(w,()=>i(H),e=>x(H,e)),l(`click`,q,W),l(`click`,J,U),l(`click`,n,p(W)),o(t,n)};D(q,e=>{A()&&e(J)}),o(n,K),t()}export{z as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.overlay.svelte-12jesfv{background:var(--shade-stronger);backdrop-filter:blur(4px);z-index:1000;justify-content:center;align-items:center;animation:.2s ease-out svelte-12jesfv-fadeIn;display:flex;position:fixed;inset:0}@keyframes svelte-12jesfv-fadeIn{0%{opacity:0}to{opacity:1}}.dialog.svelte-12jesfv{background:var(--surface-panel-alt);box-shadow:0 10px 40px var(--shadow);border-radius:8px;flex-direction:column;width:90%;max-width:500px;max-height:90vh;animation:.2s ease-out svelte-12jesfv-slideUp;display:flex}@keyframes svelte-12jesfv-slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.header.svelte-12jesfv{border-bottom:1px solid var(--tint);justify-content:space-between;align-items:center;padding:20px;display:flex}.header.svelte-12jesfv h2:where(.svelte-12jesfv){color:var(--text-bright);margin:0;font-size:20px;font-weight:600}.close-button.svelte-12jesfv{color:var(--text-muted);cursor:pointer;background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;width:32px;height:32px;padding:0;font-size:24px;transition:all .15s;display:flex}.close-button.svelte-12jesfv:hover{background:var(--tint);color:var(--text-bright)}.content.svelte-12jesfv{flex:1;padding:20px;overflow-y:auto}.form-group.svelte-12jesfv{margin-bottom:20px}.form-group.svelte-12jesfv label:where(.svelte-12jesfv){color:var(--text-muted);margin-bottom:8px;font-size:12px;font-weight:500;display:block}.form-group.svelte-12jesfv input:where(.svelte-12jesfv){background:var(--shade-weak);border:1px solid var(--tint);width:100%;color:var(--text-bright);box-sizing:border-box;border-radius:4px;padding:10px;font-size:14px}.form-group.svelte-12jesfv input:where(.svelte-12jesfv):focus{border-color:var(--accent);outline:none}.form-group.svelte-12jesfv input:where(.svelte-12jesfv):disabled,.form-group.svelte-12jesfv select:where(.svelte-12jesfv):disabled{opacity:.5;cursor:not-allowed}.form-group.svelte-12jesfv select:where(.svelte-12jesfv){background:var(--shade-weak);border:1px solid var(--tint);width:100%;color:var(--text-bright);box-sizing:border-box;cursor:pointer;border-radius:4px;padding:10px;font-size:14px}.form-group.svelte-12jesfv select:where(.svelte-12jesfv):focus{border-color:var(--accent);outline:none}.info.svelte-12jesfv{background:var(--accent-tint-weak);border:1px solid var(--accent-tint-medium);border-radius:4px;margin-top:20px;padding:16px}.info.svelte-12jesfv p:where(.svelte-12jesfv){color:var(--text-bright);margin:0 0 12px;font-size:14px}.info.svelte-12jesfv ul:where(.svelte-12jesfv){color:var(--text-muted);margin:0;padding-left:20px;font-size:13px}.info.svelte-12jesfv li:where(.svelte-12jesfv){margin:4px 0}.error.svelte-12jesfv{background:var(--status-error-tint-weak);border:1px solid var(--status-error-tint-strong);color:var(--status-error);border-radius:4px;margin-top:16px;padding:12px;font-size:13px}.footer.svelte-12jesfv{border-top:1px solid var(--tint);justify-content:flex-end;gap:12px;padding:20px;display:flex}.cancel-button.svelte-12jesfv,.export-button.svelte-12jesfv{cursor:pointer;border:none;border-radius:4px;padding:10px 20px;font-size:14px;font-weight:500;transition:all .15s}.cancel-button.svelte-12jesfv{color:var(--text-muted);background:0 0}.cancel-button.svelte-12jesfv:hover:not(:disabled){background:var(--tint);color:var(--text-bright)}.export-button.svelte-12jesfv{background:var(--accent);color:var(--text-on-accent)}.export-button.svelte-12jesfv:hover:not(:disabled){background:var(--accent-strong)}.export-button.svelte-12jesfv:disabled,.cancel-button.svelte-12jesfv:disabled{opacity:.5;cursor:not-allowed}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{M as e,N as t,c as n,w as r}from"./index-DyvDC6pS.js";t(n,{assets:e});export{r as geoLibrary};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{N as e,s as t}from"./index-DyvDC6pS.js";e(t,{});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{C as e,N as t,S as n}from"./index-DyvDC6pS.js";t(e,{});export{n as popLibrary};
|