Compare commits
67 Commits
feat/svelt
...
1126cf8f9f
| Author | SHA1 | Date | |
|---|---|---|---|
|
1126cf8f9f
|
|||
|
|
ef479d0557
|
||
|
|
a1c926c3cf
|
||
|
ca8b1e15ac
|
|||
|
4878d02705
|
|||
|
2b4c81f557
|
|||
|
d178f812fb
|
|||
|
669a2c7991
|
|||
|
becd7a1eb3
|
|||
|
d140f42468
|
|||
|
be835e5cff
|
|||
|
|
6229becfd8
|
||
|
|
af944cefaa
|
||
|
|
a1ea56093c
|
||
|
|
1850e21810
|
||
|
|
7e51cc5ea1
|
||
|
|
1ea544e765
|
||
|
e5658b8a7e
|
|||
|
d3a9b3f056
|
|||
|
0894141d3e
|
|||
|
925167d9f2
|
|||
|
|
9c4554a1f0
|
||
|
|
67a104ff84
|
||
|
|
1212c28152
|
||
|
|
cfcb447784
|
||
|
|
d64877666b
|
||
|
|
0fa1b64d49
|
||
|
|
6ca1ff2a34
|
||
|
|
716df245ab
|
||
|
|
2e76202c63
|
||
|
|
7818148b12
|
||
|
|
566b287550
|
||
|
|
62d3f58d86
|
||
|
|
c868818ba2
|
||
|
|
64ea7ac349
|
||
|
|
2dcd797762
|
||
| 05b192e7ab | |||
|
edcaab4bd4
|
|||
|
a99040f42e
|
|||
|
fca59e87e5
|
|||
|
05e8970475
|
|||
|
385d1dd831
|
|||
|
dc46c4b64c
|
|||
| 15ff1cc52d | |||
| a70e8195a2 | |||
| 4ca36b324b | |||
| 221817fc16 | |||
| 7060b37df5 | |||
| ec037a3bbd | |||
| 2814165ee6 | |||
| c6badff1ee | |||
| a0d420517c | |||
| eadd37bfa4 | |||
| 9d698be86f | |||
| 540d0549d7 | |||
| a740da1099 | |||
| 33d5ed14dd | |||
| 53f400a4f6 | |||
| 74b7cc4232 | |||
| 972fd39da2 | |||
| 00a9d5b532 | |||
| c7d1c28c83 | |||
| 43d525f1d9 | |||
| 48175aade0 | |||
| 6d6ca6f888 | |||
| 0c9a9269c4 | |||
|
9d4d67f086
|
6
.github/workflows/deploy.yaml
vendored
6
.github/workflows/deploy.yaml
vendored
@@ -2,7 +2,7 @@ name: Deploy to GitHub Pages
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: 'main'
|
branches: "main"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_site:
|
build_site:
|
||||||
@@ -13,7 +13,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
run: pnpm run build:deploy
|
run: pnpm run build:deploy
|
||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
echo "$SSH_PRIVATE_KEY" > /tmp/id_rsa
|
echo "$SSH_PRIVATE_KEY" > /tmp/id_rsa
|
||||||
chmod 600 /tmp/id_rsa
|
chmod 600 /tmp/id_rsa
|
||||||
mkdir -p ~/.config/rclone
|
mkdir -p ~/.config/rclone
|
||||||
echo "[sftp-remote]\ntype = sftp\nhost = ${SSH_HOST}\nuser = ${SSH_USER}\nport = ${SSH_PORT}\nkey_file = /tmp/id_rsa" > ~/.config/rclone/rclone.conf
|
echo -e "[sftp-remote]\ntype = sftp\nhost = ${SSH_HOST}\nuser = ${SSH_USER}\nport = ${SSH_PORT}\nkey_file = /tmp/id_rsa" > ~/.config/rclone/rclone.conf
|
||||||
env:
|
env:
|
||||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
SSH_HOST: ${{ vars.SSH_HOST }}
|
SSH_HOST: ${{ vars.SSH_HOST }}
|
||||||
|
|||||||
23
Dockerfile
23
Dockerfile
@@ -1,18 +1,21 @@
|
|||||||
FROM node:21
|
FROM node:24-alpine
|
||||||
|
|
||||||
# IMAGE CUSTOMISATIONS
|
RUN apk add --no-cache --update curl rclone g++
|
||||||
|
|
||||||
# Install rust
|
|
||||||
# https://github.com/rust-lang/rustup/issues/1085
|
|
||||||
RUN RUSTUP_URL="https://sh.rustup.rs" \
|
RUN RUSTUP_URL="https://sh.rustup.rs" \
|
||||||
&& curl --silent --show-error --location --fail --retry 3 --proto '=https' --tlsv1.2 --output /tmp/rustup-linux-install.sh $RUSTUP_URL \
|
&& curl --silent --show-error --location --fail --retry 3 --proto '=https' --tlsv1.2 --output /tmp/rustup-linux-install.sh $RUSTUP_URL \
|
||||||
&& bash /tmp/rustup-linux-install.sh -y \
|
&& sh /tmp/rustup-linux-install.sh -y
|
||||||
&& curl https://rclone.org/install.sh --output /tmp/rclone-install.sh \
|
|
||||||
&& bash /tmp/rclone-install.sh
|
|
||||||
|
|
||||||
ENV PATH=/root/.cargo/bin:$PATH
|
ENV RUSTUP_HOME=/usr/local/rustup \
|
||||||
|
CARGO_HOME=/usr/local/cargo \
|
||||||
|
PATH=/usr/local/cargo/bin:$PATH
|
||||||
|
|
||||||
RUN rustup target add wasm32-unknown-unknown \
|
RUN curl --silent --show-error --location --fail --retry 3 \
|
||||||
|
--proto '=https' --tlsv1.2 \
|
||||||
|
--output /tmp/rustup-init.sh https://sh.rustup.rs \
|
||||||
|
&& sh /tmp/rustup-init.sh -y --no-modify-path --profile minimal \
|
||||||
|
&& rm /tmp/rustup-init.sh \
|
||||||
|
&& rustup default stable \
|
||||||
|
&& rustup target add wasm32-unknown-unknown \
|
||||||
&& cargo install wasm-pack \
|
&& cargo install wasm-pack \
|
||||||
&& npm i -g pnpm
|
&& npm i -g pnpm
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1 @@
|
|||||||
# Tauri + Svelte + Typescript
|
# Nodarium App
|
||||||
|
|
||||||
This template should help get you started developing with Tauri, Svelte and TypeScript in Vite.
|
|
||||||
|
|
||||||
## Recommended IDE Setup
|
|
||||||
|
|
||||||
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer).
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@nodes/app",
|
"name": "@nodarium/app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -10,37 +10,37 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodes/registry": "link:../packages/registry",
|
"@nodarium/registry": "link:../packages/registry",
|
||||||
"@nodes/ui": "link:../packages/ui",
|
"@nodarium/ui": "link:../packages/ui",
|
||||||
"@nodes/utils": "link:../packages/utils",
|
"@nodarium/utils": "link:../packages/utils",
|
||||||
"@sveltejs/kit": "^2.7.4",
|
"@sveltejs/kit": "^2.49.0",
|
||||||
"@threlte/core": "8.0.0-next.23",
|
"@threlte/core": "8.3.0",
|
||||||
"@threlte/extras": "9.0.0-next.33",
|
"@threlte/extras": "9.7.0",
|
||||||
"@types/three": "^0.169.0",
|
"@types/three": "^0.181.0",
|
||||||
"@unocss/reset": "^0.63.6",
|
"@unocss/reset": "^66.5.9",
|
||||||
"comlink": "^4.4.1",
|
"comlink": "^4.4.2",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"idb": "^8.0.0",
|
"idb": "^8.0.3",
|
||||||
"jsondiffpatch": "^0.6.0",
|
"jsondiffpatch": "^0.7.3",
|
||||||
"three": "^0.170.0"
|
"three": "^0.181.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/tabler": "^1.2.7",
|
"@iconify-json/tabler": "^1.2.23",
|
||||||
"@nodes/types": "link:../packages/types",
|
"@nodarium/types": "link:../packages/types",
|
||||||
"@sveltejs/adapter-static": "^3.0.6",
|
"@sveltejs/adapter-static": "^3.0.10",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.6",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@unocss/preset-icons": "^0.63.6",
|
"@unocss/preset-icons": "^66.5.9",
|
||||||
"svelte": "^5.1.9",
|
"svelte": "^5.43.14",
|
||||||
"svelte-check": "^4.0.5",
|
"svelte-check": "^4.3.4",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.9.3",
|
||||||
"unocss": "^0.63.6",
|
"unocss": "^66.5.9",
|
||||||
"vite": "^5.4.10",
|
"vite": "^7.2.4",
|
||||||
"vite-plugin-comlink": "^5.1.0",
|
"vite-plugin-comlink": "^5.3.0",
|
||||||
"vite-plugin-glsl": "^1.3.0",
|
"vite-plugin-glsl": "^1.5.4",
|
||||||
"vite-plugin-wasm": "^3.3.0",
|
"vite-plugin-wasm": "^3.5.0",
|
||||||
"vitest": "^2.1.4"
|
"vitest": "^4.0.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/svelte.svg" />
|
<link rel="icon" href="%sveltekit.assets%/svelte.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<script defer src="https://umami.max-richter.dev/script.js" data-website-id="585c442b-0524-4874-8955-f9853b44b17e"></script>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
<title>Nodes</title>
|
<title>Nodes</title>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
precision highp float;
|
precision highp float;
|
||||||
|
// For WebGL1 make sure this extension is enabled in your material:
|
||||||
|
// #extension GL_OES_standard_derivatives : enable
|
||||||
|
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
|
|
||||||
@@ -10,33 +12,45 @@ uniform vec2 zoomLimits;
|
|||||||
uniform vec3 backgroundColor;
|
uniform vec3 backgroundColor;
|
||||||
uniform vec3 lineColor;
|
uniform vec3 lineColor;
|
||||||
|
|
||||||
|
// Anti-aliased step: threshold in the same units as `value`
|
||||||
|
float aaStep(float threshold, float value, float deriv) {
|
||||||
|
float w = deriv * 0.5; // ~one pixel
|
||||||
|
return smoothstep(threshold - w, threshold + w, value);
|
||||||
|
}
|
||||||
|
|
||||||
float grid(float x, float y, float divisions, float thickness) {
|
float grid(float x, float y, float divisions, float thickness) {
|
||||||
x = fract(x * divisions);
|
// Continuous grid coordinates
|
||||||
x = min(x, 1.0 - x);
|
float gx = x * divisions;
|
||||||
|
float gy = y * divisions;
|
||||||
|
|
||||||
float xdelta = fwidth(x);
|
// Distance to nearest grid line (0 at the line)
|
||||||
x = smoothstep(x - xdelta, x + xdelta, thickness);
|
float fx = fract(gx);
|
||||||
|
fx = min(fx, 1.0 - fx);
|
||||||
|
float fy = fract(gy);
|
||||||
|
fy = min(fy, 1.0 - fy);
|
||||||
|
|
||||||
y = fract(y * divisions);
|
// Derivatives in screen space – use the continuous coords here
|
||||||
y = min(y, 1.0 - y);
|
float dx = fwidth(gx);
|
||||||
|
float dy = fwidth(gy);
|
||||||
|
|
||||||
float ydelta = fwidth(y);
|
// Keep the original semantics: thickness is the threshold in the [0, 0.5] distance domain
|
||||||
y = smoothstep(y - ydelta, y + ydelta, thickness);
|
float lineX = 1.0 - aaStep(thickness, fx, dx);
|
||||||
|
float lineY = 1.0 - aaStep(thickness, fy, dy);
|
||||||
|
|
||||||
return clamp(x + y, 0.0, 1.0);
|
return clamp(lineX + lineY, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
float circle_grid(float x, float y, float divisions, float circleRadius) {
|
float circle_grid(float x, float y, float divisions, float circleRadius) {
|
||||||
|
float gridX = mod(x + divisions * 0.5, divisions) - divisions * 0.5;
|
||||||
|
float gridY = mod(y + divisions * 0.5, divisions) - divisions * 0.5;
|
||||||
|
|
||||||
float gridX = mod(x + divisions/2.0, divisions) - divisions / 2.0;
|
vec2 g = vec2(gridX, gridY);
|
||||||
float gridY = mod(y + divisions/2.0, divisions) - divisions / 2.0;
|
float d = length(g);
|
||||||
|
|
||||||
// Calculate the distance from the center of the grid
|
// Screen-space derivative for AA on the circle edge
|
||||||
float gridDistance = length(vec2(gridX, gridY));
|
float w = fwidth(d);
|
||||||
|
|
||||||
// Use smoothstep to create a smooth transition at the edges of the circle
|
|
||||||
float circle = 1.0 - smoothstep(circleRadius - 0.5, circleRadius + 0.5, gridDistance);
|
|
||||||
|
|
||||||
|
float circle = 1.0 - smoothstep(circleRadius - w, circleRadius + w, d);
|
||||||
return circle;
|
return circle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,44 +70,43 @@ void main(void) {
|
|||||||
float minZ = zoomLimits.x;
|
float minZ = zoomLimits.x;
|
||||||
float maxZ = zoomLimits.y;
|
float maxZ = zoomLimits.y;
|
||||||
|
|
||||||
float divisions = 0.1/cz;
|
float divisions = 0.1 / cz;
|
||||||
float thickness = 0.05/cz;
|
float thickness = 0.05 / cz;
|
||||||
float delta = 0.1 / 2.0;
|
|
||||||
|
|
||||||
float nz = (cz - minZ) / (maxZ - minZ);
|
float nz = (cz - minZ) / (maxZ - minZ);
|
||||||
|
|
||||||
float ux = (vUv.x-0.5) * width + cx*cz;
|
float ux = (vUv.x - 0.5) * width + cx * cz;
|
||||||
float uy = (vUv.y-0.5) * height - cy*cz;
|
float uy = (vUv.y - 0.5) * height - cy * cz;
|
||||||
|
|
||||||
|
// extra small grid
|
||||||
//extra small grid
|
float m1 = grid(ux, uy, divisions * 4.0, thickness * 4.0) * 0.9;
|
||||||
float m1 = grid(ux, uy, divisions*4.0, thickness*4.0) * 0.9;
|
float m2 = grid(ux, uy, divisions * 16.0, thickness * 16.0) * 0.5;
|
||||||
float m2 = grid(ux, uy, divisions*16.0, thickness*16.0) * 0.5;
|
|
||||||
float xsmall = max(m1, m2);
|
float xsmall = max(m1, m2);
|
||||||
|
|
||||||
float s3 = circle_grid(ux, uy, cz/1.6, 1.0) * 0.5;
|
float s3 = circle_grid(ux, uy, cz / 1.6, 1.0) * 0.5;
|
||||||
xsmall = max(xsmall, s3);
|
xsmall = max(xsmall, s3);
|
||||||
|
|
||||||
// small grid
|
// small grid
|
||||||
float c1 = grid(ux, uy, divisions, thickness) * 0.6;
|
float c1 = grid(ux, uy, divisions, thickness) * 0.6;
|
||||||
float c2 = grid(ux, uy, divisions*2.0, thickness) * 0.5;
|
float c2 = grid(ux, uy, divisions * 2.0, thickness * 2.0) * 0.5;
|
||||||
float small = max(c1, c2);
|
float small = max(c1, c2);
|
||||||
|
|
||||||
float s1 = circle_grid(ux, uy, cz*10.0, 2.0) * 0.5;
|
float s1 = circle_grid(ux, uy, cz * 10.0, 2.0) * 0.5;
|
||||||
small = max(small, s1);
|
small = max(small, s1);
|
||||||
|
|
||||||
// large grid
|
// large grid
|
||||||
float c3 = grid(ux, uy, divisions/8.0, thickness/8.0) * 0.5;
|
float c3 = grid(ux, uy, divisions / 8.0, thickness / 8.0) * 0.5;
|
||||||
float c4 = grid(ux, uy, divisions/2.0, thickness/4.0) * 0.4;
|
float c4 = grid(ux, uy, divisions / 2.0, thickness / 4.0) * 0.4;
|
||||||
float large = max(c3, c4);
|
float large = max(c3, c4);
|
||||||
|
|
||||||
float s2 = circle_grid(ux, uy, cz*20.0, 1.0) * 0.4;
|
float s2 = circle_grid(ux, uy, cz * 20.0, 1.0) * 0.4;
|
||||||
large = max(large, s2);
|
large = max(large, s2);
|
||||||
|
|
||||||
float c = mix(large, small, min(nz*2.0+0.05, 1.0));
|
float c = mix(large, small, min(nz * 2.0 + 0.05, 1.0));
|
||||||
c = mix(c, xsmall, max(min((nz-0.3)/0.7, 1.0), 0.0));
|
c = mix(c, xsmall, clamp((nz - 0.3) / 0.7, 0.0, 1.0));
|
||||||
|
|
||||||
vec3 color = mix(backgroundColor, lineColor, c);
|
vec3 color = mix(backgroundColor, lineColor, c);
|
||||||
|
|
||||||
gl_FragColor = vec4(color, 1.0);
|
gl_FragColor = vec4(color, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Hst } from "@histoire/plugin-svelte";
|
|
||||||
export let Hst: Hst;
|
|
||||||
import Background from "./Background.svelte";
|
|
||||||
import { Canvas } from "@threlte/core";
|
|
||||||
import Camera from "../Camera.svelte";
|
|
||||||
let width = globalThis.innerWidth || 100;
|
|
||||||
let height = globalThis.innerHeight || 100;
|
|
||||||
|
|
||||||
let cameraPosition: [number, number, number] = [0, 1, 0];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:window bind:innerWidth={width} bind:innerHeight={height} />
|
|
||||||
|
|
||||||
<Hst.Story>
|
|
||||||
<Canvas shadows={false}>
|
|
||||||
<Camera bind:position={cameraPosition} />
|
|
||||||
|
|
||||||
<Background {cameraPosition} {width} {height} />
|
|
||||||
</Canvas>
|
|
||||||
</Hst.Story>
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
import BackgroundVert from "./Background.vert";
|
import BackgroundVert from "./Background.vert";
|
||||||
import BackgroundFrag from "./Background.frag";
|
import BackgroundFrag from "./Background.frag";
|
||||||
import { colors } from "../graph/colors.svelte";
|
import { colors } from "../graph/colors.svelte";
|
||||||
import { Color } from "three";
|
|
||||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -42,10 +41,10 @@
|
|||||||
value: [0, 1, 0],
|
value: [0, 1, 0],
|
||||||
},
|
},
|
||||||
backgroundColor: {
|
backgroundColor: {
|
||||||
value: colors["layer-0"].clone(),
|
value: colors["layer-0"],
|
||||||
},
|
},
|
||||||
lineColor: {
|
lineColor: {
|
||||||
value: colors["outline"].clone(),
|
value: colors["outline"],
|
||||||
},
|
},
|
||||||
zoomLimits: {
|
zoomLimits: {
|
||||||
value: [2, 50],
|
value: [2, 50],
|
||||||
@@ -55,9 +54,9 @@
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
uniforms.camPos.value={cameraPosition}
|
uniforms.camPos.value={cameraPosition}
|
||||||
uniforms.backgroundColor.value={appSettings.theme &&
|
uniforms.backgroundColor.value={appSettings.value.theme &&
|
||||||
colors["layer-0"].clone()}
|
colors["layer-0"]}
|
||||||
uniforms.lineColor.value={appSettings.theme && colors["outline"].clone()}
|
uniforms.lineColor.value={appSettings.value.theme && colors["outline"]}
|
||||||
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
||||||
uniforms.dimensions.value={[width, height]}
|
uniforms.dimensions.value={[width, height]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,39 +1,59 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { GraphManager } from "./graph-manager.js";
|
|
||||||
import { HTML } from "@threlte/extras";
|
import { HTML } from "@threlte/extras";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import type { Node, NodeType } from "@nodarium/types";
|
||||||
|
import { getGraphManager, getGraphState } from "../graph/state.svelte";
|
||||||
|
|
||||||
export let position: [x: number, y: number] | null;
|
type Props = {
|
||||||
|
onnode: (n: Node) => void;
|
||||||
|
};
|
||||||
|
|
||||||
export let graph: GraphManager;
|
const { onnode }: Props = $props();
|
||||||
|
|
||||||
|
const graph = getGraphManager();
|
||||||
|
const graphState = getGraphState();
|
||||||
|
|
||||||
let input: HTMLInputElement;
|
let input: HTMLInputElement;
|
||||||
let value: string = "";
|
let value = $state<string>();
|
||||||
let activeNodeId: string = "";
|
let activeNodeId = $state<NodeType>();
|
||||||
|
|
||||||
const allNodes = graph.getNodeDefinitions();
|
const allNodes = graphState.activeSocket
|
||||||
|
? graph.getPossibleNodes(graphState.activeSocket)
|
||||||
|
: graph.getNodeDefinitions();
|
||||||
|
|
||||||
function filterNodes() {
|
function filterNodes() {
|
||||||
return allNodes.filter((node) => node.id.includes(value));
|
return allNodes.filter((node) => node.id.includes(value ?? ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
$: nodes = value === "" ? allNodes : filterNodes();
|
const nodes = $derived(value === "" ? allNodes : filterNodes());
|
||||||
$: if (nodes) {
|
$effect(() => {
|
||||||
if (activeNodeId === "") {
|
if (nodes) {
|
||||||
activeNodeId = nodes[0].id;
|
if (activeNodeId === undefined) {
|
||||||
} else if (nodes.length) {
|
activeNodeId = nodes?.[0]?.id;
|
||||||
const node = nodes.find((node) => node.id === activeNodeId);
|
} else if (nodes.length) {
|
||||||
if (!node) {
|
const node = nodes.find((node) => node.id === activeNodeId);
|
||||||
activeNodeId = nodes[0].id;
|
if (!node) {
|
||||||
|
activeNodeId = nodes[0].id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleNodeCreation(nodeType: Node["type"]) {
|
||||||
|
if (!graphState.addMenuPosition) return;
|
||||||
|
onnode?.({
|
||||||
|
id: -1,
|
||||||
|
type: nodeType,
|
||||||
|
position: [...graphState.addMenuPosition],
|
||||||
|
props: {},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
position = null;
|
graphState.addMenuPosition = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,9 +70,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
if (activeNodeId && position) {
|
if (activeNodeId && graphState.addMenuPosition) {
|
||||||
graph.createNode({ type: activeNodeId, position });
|
handleNodeCreation(activeNodeId);
|
||||||
position = null;
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,7 +83,11 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<HTML position.x={position?.[0]} position.z={position?.[1]} transform={false}>
|
<HTML
|
||||||
|
position.x={graphState.addMenuPosition?.[0]}
|
||||||
|
position.z={graphState.addMenuPosition?.[1]}
|
||||||
|
transform={false}
|
||||||
|
>
|
||||||
<div class="add-menu-wrapper">
|
<div class="add-menu-wrapper">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<input
|
<input
|
||||||
@@ -74,7 +97,7 @@
|
|||||||
role="searchbox"
|
role="searchbox"
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
disabled={false}
|
disabled={false}
|
||||||
on:keydown={handleKeyDown}
|
onkeydown={handleKeyDown}
|
||||||
bind:value
|
bind:value
|
||||||
bind:this={input}
|
bind:this={input}
|
||||||
/>
|
/>
|
||||||
@@ -87,25 +110,17 @@
|
|||||||
role="treeitem"
|
role="treeitem"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
aria-selected={node.id === activeNodeId}
|
aria-selected={node.id === activeNodeId}
|
||||||
on:keydown={(event) => {
|
onkeydown={(event) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
if (position) {
|
handleNodeCreation(node.id);
|
||||||
graph.createNode({ type: node.id, position, props: {} });
|
|
||||||
position = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
on:mousedown={() => {
|
onmousedown={() => handleNodeCreation(node.id)}
|
||||||
if (position) {
|
onfocus={() => {
|
||||||
graph.createNode({ type: node.id, position, props: {} });
|
|
||||||
position = null;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
on:focus={() => {
|
|
||||||
activeNodeId = node.id;
|
activeNodeId = node.id;
|
||||||
}}
|
}}
|
||||||
class:selected={node.id === activeNodeId}
|
class:selected={node.id === activeNodeId}
|
||||||
on:mouseover={() => {
|
onmouseover={() => {
|
||||||
activeNodeId = node.id;
|
activeNodeId = node.id;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { NodeDefinition, NodeRegistry } from "@nodes/types";
|
import type { NodeDefinition, NodeRegistry } from "@nodarium/types";
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
let mx = $state(0);
|
let mx = $state(0);
|
||||||
let my = $state(0);
|
let my = $state(0);
|
||||||
@@ -5,15 +5,17 @@
|
|||||||
color: colors.edge.clone(),
|
color: colors.edge.clone(),
|
||||||
toneMapped: false,
|
toneMapped: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let lineColor = $state(colors.edge.clone().convertSRGBToLinear());
|
||||||
|
|
||||||
$effect.root(() => {
|
$effect.root(() => {
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
appSettings.theme;
|
appSettings.value.theme;
|
||||||
circleMaterial.color = colors.edge.clone().convertSRGBToLinear();
|
circleMaterial.color = colors.edge.clone().convertSRGBToLinear();
|
||||||
})
|
lineColor = colors.edge.clone().convertSRGBToLinear();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const lineCache = new Map<number, BufferGeometry>();
|
|
||||||
|
|
||||||
const curve = new CubicBezierCurve(
|
const curve = new CubicBezierCurve(
|
||||||
new Vector2(0, 0),
|
new Vector2(0, 0),
|
||||||
new Vector2(0, 0),
|
new Vector2(0, 0),
|
||||||
@@ -24,44 +26,36 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { T } from "@threlte/core";
|
import { T } from "@threlte/core";
|
||||||
import { MeshLineMaterial } from "@threlte/extras";
|
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
|
||||||
import { BufferGeometry, MeshBasicMaterial, Vector3 } from "three";
|
import { Mesh, MeshBasicMaterial, Vector3 } from "three";
|
||||||
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
|
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
|
||||||
import { Vector2 } from "three/src/math/Vector2.js";
|
import { Vector2 } from "three/src/math/Vector2.js";
|
||||||
import { createEdgeGeometry } from "./createEdgeGeometry.js";
|
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
from: { x: number; y: number };
|
x1: number;
|
||||||
to: { x: number; y: number };
|
y1: number;
|
||||||
z:number;
|
x2: number;
|
||||||
|
y2: number;
|
||||||
|
z: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { from, to, z }: Props = $props();
|
const { x1, y1, x2, y2, z }: Props = $props();
|
||||||
|
|
||||||
let geometry: BufferGeometry|null = $state(null);
|
const thickness = $derived(Math.max(0.001, 0.00082 * Math.exp(0.055 * z)));
|
||||||
|
|
||||||
const lineColor = $derived(appSettings.theme && colors.edge.clone().convertSRGBToLinear());
|
let points = $state<Vector3[]>([]);
|
||||||
|
|
||||||
let lastId: number | null = null;
|
let lastId: string | null = null;
|
||||||
|
|
||||||
const primeA = 31;
|
|
||||||
const primeB = 37;
|
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
const new_x = to.x - from.x;
|
const new_x = x2 - x1;
|
||||||
const new_y = to.y - from.y;
|
const new_y = y2 - y1;
|
||||||
const curveId = new_x * primeA + new_y * primeB;
|
const curveId = `${x1}-${y1}-${x2}-${y2}`;
|
||||||
if (lastId === curveId) {
|
if (lastId === curveId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
lastId = curveId;
|
||||||
const mid = new Vector2(new_x / 2, new_y / 2);
|
|
||||||
|
|
||||||
if (lineCache.has(curveId)) {
|
|
||||||
geometry = lineCache.get(curveId)!;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const length = Math.floor(
|
const length = Math.floor(
|
||||||
Math.sqrt(Math.pow(new_x, 2) + Math.pow(new_y, 2)) / 4,
|
Math.sqrt(Math.pow(new_x, 2) + Math.pow(new_y, 2)) / 4,
|
||||||
@@ -70,30 +64,26 @@
|
|||||||
const samples = Math.max(length * 16, 10);
|
const samples = Math.max(length * 16, 10);
|
||||||
|
|
||||||
curve.v0.set(0, 0);
|
curve.v0.set(0, 0);
|
||||||
curve.v1.set(mid.x, 0);
|
curve.v1.set(new_x / 2, 0);
|
||||||
curve.v2.set(mid.x, new_y);
|
curve.v2.set(new_x / 2, new_y);
|
||||||
curve.v3.set(new_x, new_y);
|
curve.v3.set(new_x, new_y);
|
||||||
|
|
||||||
const points = curve
|
points = curve
|
||||||
.getPoints(samples)
|
.getPoints(samples)
|
||||||
.map((p) => new Vector3(p.x, 0, p.y))
|
.map((p) => new Vector3(p.x, 0, p.y))
|
||||||
.flat();
|
.flat();
|
||||||
|
}
|
||||||
geometry = createEdgeGeometry(points);
|
|
||||||
lineCache.set(curveId, geometry);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (from || to) {
|
if (x1 || x2 || y1 || y2) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<T.Mesh
|
<T.Mesh
|
||||||
position.x={from.x}
|
position.x={x1}
|
||||||
position.z={from.y}
|
position.z={y1}
|
||||||
position.y={0.8}
|
position.y={0.8}
|
||||||
rotation.x={-Math.PI / 2}
|
rotation.x={-Math.PI / 2}
|
||||||
material={circleMaterial}
|
material={circleMaterial}
|
||||||
@@ -102,8 +92,8 @@
|
|||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
|
|
||||||
<T.Mesh
|
<T.Mesh
|
||||||
position.x={to.x}
|
position.x={x2}
|
||||||
position.z={to.y}
|
position.z={y2}
|
||||||
position.y={0.8}
|
position.y={0.8}
|
||||||
rotation.x={-Math.PI / 2}
|
rotation.x={-Math.PI / 2}
|
||||||
material={circleMaterial}
|
material={circleMaterial}
|
||||||
@@ -111,8 +101,7 @@
|
|||||||
<T.CircleGeometry args={[0.5, 16]} />
|
<T.CircleGeometry args={[0.5, 16]} />
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
|
|
||||||
{#if geometry}
|
<T.Mesh position.x={x1} position.z={y1} position.y={0.1}>
|
||||||
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}>
|
<MeshLineGeometry {points} />
|
||||||
<MeshLineMaterial width={Math.max(z*0.0001,0.00001)} color={lineColor} />
|
<MeshLineMaterial width={thickness} color={lineColor} />
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
{/if}
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Edge from "./Edge.svelte";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
from: { x: number; y: number };
|
|
||||||
to: { x: number; y: number };
|
|
||||||
z: number;
|
|
||||||
};
|
|
||||||
const { from, to, z }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Edge {from} {to} {z} />
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import { BufferAttribute, BufferGeometry, Vector3 } from 'three';
|
|
||||||
import { setXY, setXYZ, setXYZW, setXYZXYZ } from './utils.js';
|
|
||||||
|
|
||||||
|
|
||||||
export function createEdgeGeometry(points: Vector3[]) {
|
|
||||||
|
|
||||||
const length = points[0].distanceTo(points[points.length - 1]);
|
|
||||||
|
|
||||||
const startRadius = 10.5;
|
|
||||||
const constantWidth = 2;
|
|
||||||
const taperFraction = 0.8 / length;
|
|
||||||
|
|
||||||
function ease(t: number) {
|
|
||||||
return t * t * (3 - 2 * t);
|
|
||||||
}
|
|
||||||
let shapeFunction = (alpha: number) => {
|
|
||||||
if (alpha < taperFraction) {
|
|
||||||
const easedAlpha = ease(alpha / taperFraction);
|
|
||||||
return startRadius + (constantWidth - startRadius) * easedAlpha;
|
|
||||||
} else if (alpha > 1 - taperFraction) {
|
|
||||||
const easedAlpha = ease((alpha - (1 - taperFraction)) / taperFraction);
|
|
||||||
return constantWidth + (startRadius - constantWidth) * easedAlpha;
|
|
||||||
} else {
|
|
||||||
return constantWidth;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// When the component first runs we create the buffer geometry and allocate the buffer attributes
|
|
||||||
let pointCount = points.length
|
|
||||||
let counters: number[] = []
|
|
||||||
let counterIndex = 0
|
|
||||||
let side: number[] = []
|
|
||||||
let widthArray: number[] = []
|
|
||||||
let doubleIndex = 0
|
|
||||||
let uvArray: number[] = []
|
|
||||||
let uvIndex = 0
|
|
||||||
let indices: number[] = []
|
|
||||||
let indicesIndex = 0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for (let j = 0; j < pointCount; j++) {
|
|
||||||
const c = j / points.length
|
|
||||||
counters[counterIndex + 0] = c
|
|
||||||
counters[counterIndex + 1] = c
|
|
||||||
counterIndex += 2
|
|
||||||
|
|
||||||
setXY(side, doubleIndex, 1, -1)
|
|
||||||
let width = shapeFunction((j / (pointCount - 1)))
|
|
||||||
setXY(widthArray, doubleIndex, width, width)
|
|
||||||
doubleIndex += 2
|
|
||||||
|
|
||||||
setXYZW(uvArray, uvIndex, j / (pointCount - 1), 0, j / (pointCount - 1), 1)
|
|
||||||
uvIndex += 4
|
|
||||||
|
|
||||||
if (j < pointCount - 1) {
|
|
||||||
const n = j * 2
|
|
||||||
setXYZ(indices, indicesIndex, n + 0, n + 1, n + 2)
|
|
||||||
setXYZ(indices, indicesIndex + 3, n + 2, n + 1, n + 3)
|
|
||||||
indicesIndex += 6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const geometry = new BufferGeometry()
|
|
||||||
// create these buffer attributes at the correct length but leave them empty for now
|
|
||||||
geometry.setAttribute('position', new BufferAttribute(new Float32Array(pointCount * 6), 3))
|
|
||||||
geometry.setAttribute('previous', new BufferAttribute(new Float32Array(pointCount * 6), 3))
|
|
||||||
geometry.setAttribute('next', new BufferAttribute(new Float32Array(pointCount * 6), 3))
|
|
||||||
// create and populate these buffer attributes
|
|
||||||
geometry.setAttribute('counters', new BufferAttribute(new Float32Array(counters), 1))
|
|
||||||
geometry.setAttribute('side', new BufferAttribute(new Float32Array(side), 1))
|
|
||||||
geometry.setAttribute('width', new BufferAttribute(new Float32Array(widthArray), 1))
|
|
||||||
geometry.setAttribute('uv', new BufferAttribute(new Float32Array(uvArray), 2))
|
|
||||||
geometry.setIndex(new BufferAttribute(new Uint16Array(indices), 1))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let positions: number[] = []
|
|
||||||
let previous: number[] = []
|
|
||||||
let next: number[] = []
|
|
||||||
let positionIndex = 0
|
|
||||||
let previousIndex = 0
|
|
||||||
let nextIndex = 0
|
|
||||||
setXYZXYZ(previous, previousIndex, points[0].x, points[0].y, points[0].z)
|
|
||||||
previousIndex += 6
|
|
||||||
for (let j = 0; j < pointCount; j++) {
|
|
||||||
const p = points[j]
|
|
||||||
setXYZXYZ(positions, positionIndex, p.x, p.y, p.z)
|
|
||||||
positionIndex += 6
|
|
||||||
if (j < pointCount - 1) {
|
|
||||||
setXYZXYZ(previous, previousIndex, p.x, p.y, p.z)
|
|
||||||
previousIndex += 6
|
|
||||||
}
|
|
||||||
if (j > 0 && j + 1 <= pointCount) {
|
|
||||||
setXYZXYZ(next, nextIndex, p.x, p.y, p.z)
|
|
||||||
nextIndex += 6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setXYZXYZ(
|
|
||||||
next,
|
|
||||||
nextIndex,
|
|
||||||
points[pointCount - 1].x,
|
|
||||||
points[pointCount - 1].y,
|
|
||||||
points[pointCount - 1].z
|
|
||||||
)
|
|
||||||
const positionAttribute = (geometry.getAttribute('position') as BufferAttribute).set(positions)
|
|
||||||
const previousAttribute = (geometry.getAttribute('previous') as BufferAttribute).set(previous)
|
|
||||||
const nextAttribute = (geometry.getAttribute('next') as BufferAttribute).set(next)
|
|
||||||
positionAttribute.needsUpdate = true
|
|
||||||
previousAttribute.needsUpdate = true
|
|
||||||
nextAttribute.needsUpdate = true
|
|
||||||
geometry.computeBoundingSphere()
|
|
||||||
|
|
||||||
return geometry;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,86 +1,125 @@
|
|||||||
import type { Edge, Graph, Node, NodeInput, NodeRegistry, Socket, } from "@nodes/types";
|
import type {
|
||||||
import { fastHashString } from "@nodes/utils";
|
Edge,
|
||||||
import { get, writable, type Writable } from "svelte/store";
|
Graph,
|
||||||
import EventEmitter from "./helpers/EventEmitter.js";
|
Node,
|
||||||
import { createLogger } from "./helpers/index.js";
|
NodeDefinition,
|
||||||
import throttle from "./helpers/throttle.js";
|
NodeInput,
|
||||||
import { HistoryManager } from "./history-manager.js";
|
NodeRegistry,
|
||||||
|
NodeType,
|
||||||
|
Socket,
|
||||||
|
} from "@nodarium/types";
|
||||||
|
import { fastHashString } from "@nodarium/utils";
|
||||||
|
import { SvelteMap } from "svelte/reactivity";
|
||||||
|
import EventEmitter from "./helpers/EventEmitter";
|
||||||
|
import { createLogger } from "@nodarium/utils";
|
||||||
|
import throttle from "$lib/helpers/throttle";
|
||||||
|
import { HistoryManager } from "./history-manager";
|
||||||
|
|
||||||
const logger = createLogger("graph-manager");
|
const logger = createLogger("graph-manager");
|
||||||
|
|
||||||
logger.mute();
|
logger.mute();
|
||||||
|
|
||||||
const clone = "structuredClone" in self ? self.structuredClone : (args: any) => JSON.parse(JSON.stringify(args));
|
const clone =
|
||||||
|
"structuredClone" in self
|
||||||
|
? self.structuredClone
|
||||||
|
: (args: any) => JSON.parse(JSON.stringify(args));
|
||||||
|
|
||||||
function areSocketsCompatible(output: string | undefined, inputs: string | string[] | undefined) {
|
function areSocketsCompatible(
|
||||||
|
output: string | undefined,
|
||||||
|
inputs: string | (string | undefined)[] | undefined,
|
||||||
|
) {
|
||||||
if (Array.isArray(inputs) && output) {
|
if (Array.isArray(inputs) && output) {
|
||||||
return inputs.includes(output);
|
return inputs.includes(output);
|
||||||
}
|
}
|
||||||
return inputs === output;
|
return inputs === output;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "settings": { types: Record<string, NodeInput>, values: Record<string, unknown> } }> {
|
function areEdgesEqual(firstEdge: Edge, secondEdge: Edge) {
|
||||||
|
|
||||||
status: Writable<"loading" | "idle" | "error"> = writable("loading");
|
if (firstEdge[0].id !== secondEdge[0].id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstEdge[1] !== secondEdge[1]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstEdge[2].id !== secondEdge[2].id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstEdge[3] !== secondEdge[3]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GraphManager extends EventEmitter<{
|
||||||
|
save: Graph;
|
||||||
|
result: any;
|
||||||
|
settings: {
|
||||||
|
types: Record<string, NodeInput>;
|
||||||
|
values: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
}> {
|
||||||
|
status = $state<"loading" | "idle" | "error">();
|
||||||
loaded = false;
|
loaded = false;
|
||||||
|
|
||||||
graph: Graph = { id: 0, nodes: [], edges: [] };
|
graph: Graph = { id: 0, nodes: [], edges: [] };
|
||||||
id = writable(0);
|
id = $state(0);
|
||||||
|
|
||||||
private _nodes: Map<number, Node> = new Map();
|
nodes = new SvelteMap<number, Node>();
|
||||||
nodes: Writable<Map<number, Node>> = writable(new Map());
|
|
||||||
|
|
||||||
private _edges: Edge[] = [];
|
edges = $state<Edge[]>([]);
|
||||||
edges: Writable<Edge[]> = writable([]);
|
|
||||||
|
|
||||||
settingTypes: Record<string, NodeInput> = {};
|
settingTypes: Record<string, NodeInput> = {};
|
||||||
settings: Record<string, unknown> = {};
|
settings = $state<Record<string, unknown>>();
|
||||||
|
|
||||||
currentUndoGroup: number | null = null;
|
currentUndoGroup: number | null = null;
|
||||||
|
|
||||||
inputSockets: Writable<Set<string>> = writable(new Set());
|
inputSockets = $derived.by(() => {
|
||||||
|
const s = new Set<string>();
|
||||||
|
for (const edge of this.edges) {
|
||||||
|
s.add(`${edge[2].id}-${edge[3]}`);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
|
||||||
history: HistoryManager = new HistoryManager();
|
history: HistoryManager = new HistoryManager();
|
||||||
execute = throttle(() => {
|
execute = throttle(() => {
|
||||||
console.log("Props", get(this.nodes).values().find(n => n.type === "max/plantarium/gravity")?.props);
|
|
||||||
if (this.loaded === false) return;
|
if (this.loaded === false) return;
|
||||||
this.emit("result", this.serialize());
|
this.emit("result", this.serialize());
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
constructor(public registry: NodeRegistry) {
|
constructor(public registry: NodeRegistry) {
|
||||||
super();
|
super();
|
||||||
this.nodes.subscribe((nodes) => {
|
|
||||||
this._nodes = nodes;
|
|
||||||
});
|
|
||||||
this.edges.subscribe((edges) => {
|
|
||||||
this._edges = edges;
|
|
||||||
const s = new Set<string>();
|
|
||||||
for (const edge of edges) {
|
|
||||||
s.add(`${edge[2].id}-${edge[3]}`);
|
|
||||||
}
|
|
||||||
this.inputSockets.set(s);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(): Graph {
|
serialize(): Graph {
|
||||||
logger.group("serializing graph")
|
const nodes = Array.from(this.nodes.values()).map((node) => ({
|
||||||
const nodes = Array.from(this._nodes.values()).map(node => ({
|
|
||||||
id: node.id,
|
id: node.id,
|
||||||
position: [...node.position],
|
position: [...node.position],
|
||||||
type: node.type,
|
type: node.type,
|
||||||
props: node.props,
|
props: node.props,
|
||||||
})) as Node[];
|
})) as Node[];
|
||||||
const edges = this._edges.map(edge => [edge[0].id, edge[1], edge[2].id, edge[3]]) as Graph["edges"];
|
const edges = this.edges.map((edge) => [
|
||||||
const serialized = { id: this.graph.id, settings: this.settings, nodes, edges };
|
edge[0].id,
|
||||||
logger.groupEnd();
|
edge[1],
|
||||||
|
edge[2].id,
|
||||||
return clone(serialized);
|
edge[3],
|
||||||
|
]) as Graph["edges"];
|
||||||
|
const serialized = {
|
||||||
|
id: this.graph.id,
|
||||||
|
settings: $state.snapshot(this.settings),
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
};
|
||||||
|
logger.log("serializing graph", serialized);
|
||||||
|
return clone($state.snapshot(serialized));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private lastSettingsHash = 0;
|
private lastSettingsHash = 0;
|
||||||
setSettings(settings: Record<string, unknown>) {
|
setSettings(settings: Record<string, unknown>) {
|
||||||
|
|
||||||
let hash = fastHashString(JSON.stringify(settings));
|
let hash = fastHashString(JSON.stringify(settings));
|
||||||
if (hash === this.lastSettingsHash) return;
|
if (hash === this.lastSettingsHash) return;
|
||||||
this.lastSettingsHash = hash;
|
this.lastSettingsHash = hash;
|
||||||
@@ -90,8 +129,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
this.execute();
|
this.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
getNodeDefinitions() {
|
getNodeDefinitions() {
|
||||||
return this.registry.getAllNodes();
|
return this.registry.getAllNodes();
|
||||||
}
|
}
|
||||||
@@ -103,9 +140,9 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
const n = stack.pop();
|
const n = stack.pop();
|
||||||
if (!n) continue;
|
if (!n) continue;
|
||||||
nodes.add(n);
|
nodes.add(n);
|
||||||
const children = this.getChildrenOfNode(n);
|
const children = this.getChildren(n);
|
||||||
const parents = this.getParentsOfNode(n);
|
const parents = this.getParentsOfNode(n);
|
||||||
const newNodes = [...children, ...parents].filter(n => !nodes.has(n));
|
const newNodes = [...children, ...parents].filter((n) => !nodes.has(n));
|
||||||
stack.push(...newNodes);
|
stack.push(...newNodes);
|
||||||
}
|
}
|
||||||
return [...nodes.values()];
|
return [...nodes.values()];
|
||||||
@@ -117,9 +154,16 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
const children = node.tmp?.children || [];
|
const children = node.tmp?.children || [];
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (nodes.includes(child)) {
|
if (nodes.includes(child)) {
|
||||||
const edge = this._edges.find(e => e[0].id === node.id && e[2].id === child.id);
|
const edge = this.edges.find(
|
||||||
|
(e) => e[0].id === node.id && e[2].id === child.id,
|
||||||
|
);
|
||||||
if (edge) {
|
if (edge) {
|
||||||
edges.push([edge[0].id, edge[1], edge[2].id, edge[3]] as [number, number, number, string]);
|
edges.push([edge[0].id, edge[1], edge[2].id, edge[3]] as [
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
string,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,25 +172,25 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
return edges;
|
return edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private _init(graph: Graph) {
|
private _init(graph: Graph) {
|
||||||
const nodes = new Map(graph.nodes.map(node => {
|
const nodes = new Map(
|
||||||
const nodeType = this.registry.getNode(node.type);
|
graph.nodes.map((node: Node) => {
|
||||||
if (nodeType) {
|
const nodeType = this.registry.getNode(node.type);
|
||||||
node.tmp = {
|
if (nodeType) {
|
||||||
random: (Math.random() - 0.5) * 2,
|
node.tmp = {
|
||||||
type: nodeType
|
type: nodeType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return [node.id, node]
|
return [node.id, node];
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const edges = graph.edges.map((edge) => {
|
const edges = graph.edges.map((edge) => {
|
||||||
const from = nodes.get(edge[0]);
|
const from = nodes.get(edge[0]);
|
||||||
const to = nodes.get(edge[2]);
|
const to = nodes.get(edge[2]);
|
||||||
if (!from || !to) {
|
if (!from || !to) {
|
||||||
throw new Error("Edge references non-existing node");
|
throw new Error("Edge references non-existing node");
|
||||||
};
|
}
|
||||||
from.tmp = from.tmp || {};
|
from.tmp = from.tmp || {};
|
||||||
from.tmp.children = from.tmp.children || [];
|
from.tmp.children = from.tmp.children || [];
|
||||||
from.tmp.children.push(to);
|
from.tmp.children.push(to);
|
||||||
@@ -154,42 +198,50 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
to.tmp.parents = to.tmp.parents || [];
|
to.tmp.parents = to.tmp.parents || [];
|
||||||
to.tmp.parents.push(from);
|
to.tmp.parents.push(from);
|
||||||
return [from, edge[1], to, edge[3]] as Edge;
|
return [from, edge[1], to, edge[3]] as Edge;
|
||||||
})
|
});
|
||||||
|
|
||||||
this.edges.set(edges);
|
this.edges = [...edges];
|
||||||
this.nodes.set(nodes);
|
|
||||||
|
this.nodes.clear();
|
||||||
|
for (const [id, node] of nodes) {
|
||||||
|
this.nodes.set(id, node);
|
||||||
|
}
|
||||||
|
|
||||||
this.execute();
|
this.execute();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async load(graph: Graph) {
|
async load(graph: Graph) {
|
||||||
|
|
||||||
const a = performance.now();
|
const a = performance.now();
|
||||||
|
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.graph = graph;
|
this.graph = graph;
|
||||||
this.status.set("loading");
|
this.status = "loading";
|
||||||
this.id.set(graph.id);
|
this.id = graph.id;
|
||||||
|
|
||||||
const nodeIds = Array.from(new Set([...graph.nodes.map(n => n.type)]));
|
logger.info("loading graph", $state.snapshot(graph));
|
||||||
|
|
||||||
|
const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)]));
|
||||||
await this.registry.load(nodeIds);
|
await this.registry.load(nodeIds);
|
||||||
|
|
||||||
|
logger.info("loaded node types", this.registry.getAllNodes());
|
||||||
|
|
||||||
for (const node of this.graph.nodes) {
|
for (const node of this.graph.nodes) {
|
||||||
const nodeType = this.registry.getNode(node.type);
|
const nodeType = this.registry.getNode(node.type);
|
||||||
if (!nodeType) {
|
if (!nodeType) {
|
||||||
logger.error(`Node type not found: ${node.type}`);
|
logger.error(`Node type not found: ${node.type}`);
|
||||||
this.status.set("error");
|
this.status = "error";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
node.tmp.random = (Math.random() - 0.5) * 2;
|
|
||||||
node.tmp.type = nodeType;
|
node.tmp.type = nodeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// load settings
|
// load settings
|
||||||
const settingTypes: Record<string, NodeInput> = {};
|
const settingTypes: Record<
|
||||||
|
string,
|
||||||
|
// Optional metadata to map settings to specific nodes
|
||||||
|
NodeInput & { __node_type: string; __node_input: string }
|
||||||
|
> = {};
|
||||||
const settingValues = graph.settings || {};
|
const settingValues = graph.settings || {};
|
||||||
const types = this.getNodeDefinitions();
|
const types = this.getNodeDefinitions();
|
||||||
for (const type of types) {
|
for (const type of types) {
|
||||||
@@ -197,8 +249,15 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
for (const key in type.inputs) {
|
for (const key in type.inputs) {
|
||||||
let settingId = type.inputs[key].setting;
|
let settingId = type.inputs[key].setting;
|
||||||
if (settingId) {
|
if (settingId) {
|
||||||
settingTypes[settingId] = { __node_type: type.id, __node_input: key, ...type.inputs[key] };
|
settingTypes[settingId] = {
|
||||||
if (settingValues[settingId] === undefined && "value" in type.inputs[key]) {
|
__node_type: type.id,
|
||||||
|
__node_input: key,
|
||||||
|
...type.inputs[key],
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
settingValues[settingId] === undefined &&
|
||||||
|
"value" in type.inputs[key]
|
||||||
|
) {
|
||||||
settingValues[settingId] = type.inputs[key].value;
|
settingValues[settingId] = type.inputs[key].value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,28 +273,26 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
|
|
||||||
this.status.set("idle");
|
this.status = "idle";
|
||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
logger.log(`Graph loaded in ${performance.now() - a}ms`);
|
logger.log(`Graph loaded in ${performance.now() - a}ms`);
|
||||||
setTimeout(() => this.execute(), 100);
|
setTimeout(() => this.execute(), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getAllNodes() {
|
getAllNodes() {
|
||||||
return Array.from(this._nodes.values());
|
return Array.from(this.nodes.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
getNode(id: number) {
|
getNode(id: number) {
|
||||||
return this._nodes.get(id);
|
return this.nodes.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeType(id: string) {
|
getNodeType(id: string) {
|
||||||
return this.registry.getNode(id);
|
return this.registry.getNode(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadNode(id: string) {
|
async loadNodeType(id: NodeType) {
|
||||||
|
|
||||||
await this.registry.load([id]);
|
await this.registry.load([id]);
|
||||||
const nodeType = this.registry.getNode(id);
|
const nodeType = this.registry.getNode(id);
|
||||||
|
|
||||||
@@ -248,7 +305,11 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
let settingId = nodeType.inputs[key].setting;
|
let settingId = nodeType.inputs[key].setting;
|
||||||
if (settingId) {
|
if (settingId) {
|
||||||
settingTypes[settingId] = nodeType.inputs[key];
|
settingTypes[settingId] = nodeType.inputs[key];
|
||||||
if (settingValues[settingId] === undefined && "value" in nodeType.inputs[key]) {
|
if (
|
||||||
|
settingValues &&
|
||||||
|
settingValues?.[settingId] === undefined &&
|
||||||
|
"value" in nodeType.inputs[key]
|
||||||
|
) {
|
||||||
settingValues[settingId] = nodeType.inputs[key].value;
|
settingValues[settingId] = nodeType.inputs[key].value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,14 +321,14 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
this.emit("settings", { types: settingTypes, values: settingValues });
|
this.emit("settings", { types: settingTypes, values: settingValues });
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildrenOfNode(node: Node) {
|
getChildren(node: Node) {
|
||||||
const children = [];
|
const children = [];
|
||||||
const stack = node.tmp?.children?.slice(0);
|
const stack = node.tmp?.children?.slice(0);
|
||||||
while (stack?.length) {
|
while (stack?.length) {
|
||||||
const child = stack.pop();
|
const child = stack.pop();
|
||||||
if (!child) continue;
|
if (!child) continue;
|
||||||
children.push(child);
|
children.push(child);
|
||||||
stack.push(...child.tmp?.children || []);
|
stack.push(...(child.tmp?.children || []));
|
||||||
}
|
}
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
@@ -278,11 +339,11 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
// < - - - - from - - - - to
|
// < - - - - from - - - - to
|
||||||
const fromParents = this.getParentsOfNode(from);
|
const fromParents = this.getParentsOfNode(from);
|
||||||
if (toParents.includes(from)) {
|
if (toParents.includes(from)) {
|
||||||
const fromChildren = this.getChildrenOfNode(from);
|
const fromChildren = this.getChildren(from);
|
||||||
return toParents.filter(n => fromChildren.includes(n));
|
return toParents.filter((n) => fromChildren.includes(n));
|
||||||
} else if (fromParents.includes(to)) {
|
} else if (fromParents.includes(to)) {
|
||||||
const toChildren = this.getChildrenOfNode(to);
|
const toChildren = this.getChildren(to);
|
||||||
return fromParents.filter(n => toChildren.includes(n));
|
return fromParents.filter((n) => toChildren.includes(n));
|
||||||
} else {
|
} else {
|
||||||
// these two nodes are not connected
|
// these two nodes are not connected
|
||||||
return;
|
return;
|
||||||
@@ -290,53 +351,61 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeNode(node: Node, { restoreEdges = false } = {}) {
|
removeNode(node: Node, { restoreEdges = false } = {}) {
|
||||||
|
const edgesToNode = this.edges.filter((edge) => edge[2].id === node.id);
|
||||||
const edgesToNode = this._edges.filter((edge) => edge[2].id === node.id);
|
const edgesFromNode = this.edges.filter((edge) => edge[0].id === node.id);
|
||||||
const edgesFromNode = this._edges.filter((edge) => edge[0].id === node.id);
|
|
||||||
for (const edge of [...edgesToNode, ...edgesFromNode]) {
|
for (const edge of [...edgesToNode, ...edgesFromNode]) {
|
||||||
this.removeEdge(edge, { applyDeletion: false });
|
this.removeEdge(edge, { applyDeletion: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restoreEdges) {
|
if (restoreEdges) {
|
||||||
const outputSockets = edgesToNode.map(e => [e[0], e[1]] as const);
|
const outputSockets = edgesToNode.map((e) => [e[0], e[1]] as const);
|
||||||
const inputSockets = edgesFromNode.map(e => [e[2], e[3]] as const);
|
const inputSockets = edgesFromNode.map((e) => [e[2], e[3]] as const);
|
||||||
|
|
||||||
for (const [to, toSocket] of inputSockets) {
|
for (const [to, toSocket] of inputSockets) {
|
||||||
for (const [from, fromSocket] of outputSockets) {
|
for (const [from, fromSocket] of outputSockets) {
|
||||||
const outputType = from.tmp?.type?.outputs?.[fromSocket];
|
const outputType = from.tmp?.type?.outputs?.[fromSocket];
|
||||||
const inputType = to?.tmp?.type?.inputs?.[toSocket]?.type;
|
const inputType = to?.tmp?.type?.inputs?.[toSocket]?.type;
|
||||||
if (outputType === inputType) {
|
if (outputType === inputType) {
|
||||||
this.createEdge(from, fromSocket, to, toSocket, { applyUpdate: false });
|
this.createEdge(from, fromSocket, to, toSocket, {
|
||||||
|
applyUpdate: false,
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.edges.set(this._edges);
|
this.nodes.delete(node.id);
|
||||||
|
this.execute();
|
||||||
this.nodes.update((nodes) => {
|
|
||||||
nodes.delete(node.id);
|
|
||||||
return nodes;
|
|
||||||
});
|
|
||||||
this.execute()
|
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smartConnect(from: Node, to: Node): Edge | undefined {
|
||||||
|
const inputs = Object.entries(to.tmp?.type?.inputs ?? {});
|
||||||
|
const outputs = from.tmp?.type?.outputs ?? [];
|
||||||
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
|
const [inputName, input] = inputs[0];
|
||||||
|
for (let o = 0; o < outputs.length; o++) {
|
||||||
|
const output = outputs[0];
|
||||||
|
if (input.type === output) {
|
||||||
|
return this.createEdge(from, o, to, inputName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createNodeId() {
|
createNodeId() {
|
||||||
const max = Math.max(...this._nodes.keys());
|
return Math.max(0, ...this.nodes.keys()) + 1;
|
||||||
return max + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createGraph(nodes: Node[], edges: [number, number, number, string][]) {
|
createGraph(nodes: Node[], edges: [number, number, number, string][]) {
|
||||||
|
|
||||||
// map old ids to new ids
|
// map old ids to new ids
|
||||||
const idMap = new Map<number, number>();
|
const idMap = new Map<number, number>();
|
||||||
|
|
||||||
const startId = this.createNodeId();
|
let startId = this.createNodeId()
|
||||||
|
|
||||||
nodes = nodes.map((node, i) => {
|
nodes = nodes.map((node) => {
|
||||||
const id = startId + i;
|
const id = startId++;
|
||||||
idMap.set(node.id, id);
|
idMap.set(node.id, id);
|
||||||
const type = this.registry.getNode(node.type);
|
const type = this.registry.getNode(node.type);
|
||||||
if (!type) {
|
if (!type) {
|
||||||
@@ -345,9 +414,9 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
return { ...node, id, tmp: { type } };
|
return { ...node, id, tmp: { type } };
|
||||||
});
|
});
|
||||||
|
|
||||||
const _edges = edges.map(edge => {
|
const _edges = edges.map((edge) => {
|
||||||
const from = nodes.find(n => n.id === idMap.get(edge[0]));
|
const from = nodes.find((n) => n.id === idMap.get(edge[0]));
|
||||||
const to = nodes.find(n => n.id === idMap.get(edge[2]));
|
const to = nodes.find((n) => n.id === idMap.get(edge[2]));
|
||||||
|
|
||||||
if (!from || !to) {
|
if (!from || !to) {
|
||||||
throw new Error("Edge references non-existing node");
|
throw new Error("Edge references non-existing node");
|
||||||
@@ -365,45 +434,63 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
this._nodes.set(node.id, node);
|
this.nodes.set(node.id, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._edges.push(..._edges);
|
this.edges.push(..._edges);
|
||||||
|
|
||||||
this.nodes.set(this._nodes);
|
|
||||||
this.edges.set(this._edges);
|
|
||||||
this.save();
|
this.save();
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
createNode({ type, position, props = {} }: { type: Node["type"], position: Node["position"], props: Node["props"] }) {
|
createNode({
|
||||||
|
type,
|
||||||
|
position,
|
||||||
|
props = {},
|
||||||
|
}: {
|
||||||
|
type: Node["type"];
|
||||||
|
position: Node["position"];
|
||||||
|
props: Node["props"];
|
||||||
|
}) {
|
||||||
const nodeType = this.registry.getNode(type);
|
const nodeType = this.registry.getNode(type);
|
||||||
if (!nodeType) {
|
if (!nodeType) {
|
||||||
logger.error(`Node type not found: ${type}`);
|
logger.error(`Node type not found: ${type}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const node: Node = { id: this.createNodeId(), type, position, tmp: { type: nodeType }, props };
|
const node: Node = $state({
|
||||||
|
id: this.createNodeId(),
|
||||||
this.nodes.update((nodes) => {
|
type,
|
||||||
nodes.set(node.id, node);
|
position,
|
||||||
return nodes;
|
tmp: { type: nodeType },
|
||||||
|
props,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.nodes.set(node.id, node);
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
|
|
||||||
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
createEdge(from: Node, fromSocket: number, to: Node, toSocket: string, { applyUpdate = true } = {}) {
|
createEdge(
|
||||||
|
from: Node,
|
||||||
|
fromSocket: number,
|
||||||
|
to: Node,
|
||||||
|
toSocket: string,
|
||||||
|
{ applyUpdate = true } = {},
|
||||||
|
): Edge | undefined {
|
||||||
|
|
||||||
const existingEdges = this.getEdgesToNode(to);
|
const existingEdges = this.getEdgesToNode(to);
|
||||||
|
|
||||||
// check if this exact edge already exists
|
// check if this exact edge already exists
|
||||||
const existingEdge = existingEdges.find(e => e[0].id === from.id && e[1] === fromSocket && e[3] === toSocket);
|
const existingEdge = existingEdges.find(
|
||||||
|
(e) => e[0].id === from.id && e[1] === fromSocket && e[3] === toSocket,
|
||||||
|
);
|
||||||
if (existingEdge) {
|
if (existingEdge) {
|
||||||
logger.error("Edge already exists", existingEdge);
|
logger.error("Edge already exists", existingEdge);
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
// check if socket types match
|
// check if socket types match
|
||||||
const fromSocketType = from.tmp?.type?.outputs?.[fromSocket];
|
const fromSocketType = from.tmp?.type?.outputs?.[fromSocket];
|
||||||
@@ -413,20 +500,22 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!areSocketsCompatible(fromSocketType, toSocketType)) {
|
if (!areSocketsCompatible(fromSocketType, toSocketType)) {
|
||||||
logger.error(`Socket types do not match: ${fromSocketType} !== ${toSocketType}`);
|
logger.error(
|
||||||
|
`Socket types do not match: ${fromSocketType} !== ${toSocketType}`,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const edgeToBeReplaced = this._edges.find(e => e[2].id === to.id && e[3] === toSocket);
|
const edgeToBeReplaced = this.edges.find(
|
||||||
|
(e) => e[2].id === to.id && e[3] === toSocket,
|
||||||
|
);
|
||||||
if (edgeToBeReplaced) {
|
if (edgeToBeReplaced) {
|
||||||
this.removeEdge(edgeToBeReplaced, { applyDeletion: false });
|
this.removeEdge(edgeToBeReplaced, { applyDeletion: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applyUpdate) {
|
const edge = [from, fromSocket, to, toSocket] as Edge;
|
||||||
this._edges.push([from, fromSocket, to, toSocket]);
|
|
||||||
} else {
|
this.edges.push(edge);
|
||||||
this._edges.push([from, fromSocket, to, toSocket]);
|
|
||||||
}
|
|
||||||
|
|
||||||
from.tmp = from.tmp || {};
|
from.tmp = from.tmp || {};
|
||||||
from.tmp.children = from.tmp.children || [];
|
from.tmp.children = from.tmp.children || [];
|
||||||
@@ -437,10 +526,11 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
to.tmp.parents.push(from);
|
to.tmp.parents.push(from);
|
||||||
|
|
||||||
if (applyUpdate) {
|
if (applyUpdate) {
|
||||||
this.edges.set(this._edges);
|
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
this.execute();
|
this.execute();
|
||||||
|
|
||||||
|
return edge;
|
||||||
}
|
}
|
||||||
|
|
||||||
undo() {
|
undo() {
|
||||||
@@ -451,14 +541,12 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
redo() {
|
redo() {
|
||||||
const nextState = this.history.redo();
|
const nextState = this.history.redo();
|
||||||
if (nextState) {
|
if (nextState) {
|
||||||
this._init(nextState);
|
this._init(nextState);
|
||||||
this.emit("save", this.serialize());
|
this.emit("save", this.serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startUndoGroup() {
|
startUndoGroup() {
|
||||||
@@ -483,30 +571,60 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
const stack = node.tmp?.parents?.slice(0);
|
const stack = node.tmp?.parents?.slice(0);
|
||||||
while (stack?.length) {
|
while (stack?.length) {
|
||||||
if (parents.length > 1000000) {
|
if (parents.length > 1000000) {
|
||||||
logger.warn("Infinite loop detected")
|
logger.warn("Infinite loop detected");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const parent = stack.pop();
|
const parent = stack.pop();
|
||||||
if (!parent) continue;
|
if (!parent) continue;
|
||||||
parents.push(parent);
|
parents.push(parent);
|
||||||
stack.push(...parent.tmp?.parents || []);
|
stack.push(...(parent.tmp?.parents || []));
|
||||||
}
|
}
|
||||||
return parents.reverse();
|
return parents.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
getPossibleSockets({ node, index }: Socket): [Node, string | number][] {
|
getPossibleNodes(socket: Socket): NodeDefinition[] {
|
||||||
|
const allDefinitions = this.getNodeDefinitions();
|
||||||
|
|
||||||
|
const nodeType = socket.node.tmp?.type;
|
||||||
|
if (!nodeType) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const definitions = typeof socket.index === "string"
|
||||||
|
? allDefinitions.filter(s => {
|
||||||
|
return s.outputs?.find(_s => Object
|
||||||
|
.values(nodeType?.inputs || {})
|
||||||
|
.map(s => s.type)
|
||||||
|
.includes(_s as NodeInput["type"])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
: allDefinitions.filter(s => Object
|
||||||
|
.values(s.inputs ?? {})
|
||||||
|
.find(s => {
|
||||||
|
if (s.hidden) return false;
|
||||||
|
if (nodeType.outputs?.includes(s.type)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return s.accepts?.find(a => nodeType.outputs?.includes(a))
|
||||||
|
}))
|
||||||
|
|
||||||
|
return definitions
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getPossibleSockets({ node, index }: Socket): [Node, string | number][] {
|
||||||
const nodeType = node?.tmp?.type;
|
const nodeType = node?.tmp?.type;
|
||||||
if (!nodeType) return [];
|
if (!nodeType) return [];
|
||||||
|
|
||||||
const sockets: [Node, string | number][] = []
|
const sockets: [Node, string | number][] = [];
|
||||||
|
|
||||||
// if index is a string, we are an input looking for outputs
|
// if index is a string, we are an input looking for outputs
|
||||||
if (typeof index === "string") {
|
if (typeof index === "string") {
|
||||||
|
|
||||||
// filter out self and child nodes
|
// filter out self and child nodes
|
||||||
const children = new Set(this.getChildrenOfNode(node).map(n => n.id));
|
const children = new Set(this.getChildren(node).map((n) => n.id));
|
||||||
const nodes = this.getAllNodes().filter(n => n.id !== node.id && !children.has(n.id));
|
const nodes = this.getAllNodes().filter(
|
||||||
|
(n) => n.id !== node.id && !children.has(n.id),
|
||||||
|
);
|
||||||
|
|
||||||
const ownType = nodeType?.inputs?.[index].type;
|
const ownType = nodeType?.inputs?.[index].type;
|
||||||
|
|
||||||
@@ -520,16 +638,21 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (typeof index === "number") {
|
} else if (typeof index === "number") {
|
||||||
// if index is a number, we are an output looking for inputs
|
// if index is a number, we are an output looking for inputs
|
||||||
|
|
||||||
// filter out self and parent nodes
|
// filter out self and parent nodes
|
||||||
const parents = new Set(this.getParentsOfNode(node).map(n => n.id));
|
const parents = new Set(this.getParentsOfNode(node).map((n) => n.id));
|
||||||
const nodes = this.getAllNodes().filter(n => n.id !== node.id && !parents.has(n.id));
|
const nodes = this.getAllNodes().filter(
|
||||||
|
(n) => n.id !== node.id && !parents.has(n.id),
|
||||||
|
);
|
||||||
|
|
||||||
// get edges from this socket
|
// get edges from this socket
|
||||||
const edges = new Map(this.getEdgesFromNode(node).filter(e => e[1] === index).map(e => [e[2].id, e[3]]));
|
const edges = new Map(
|
||||||
|
this.getEdgesFromNode(node)
|
||||||
|
.filter((e) => e[1] === index)
|
||||||
|
.map((e) => [e[2].id, e[3]]),
|
||||||
|
);
|
||||||
|
|
||||||
const ownType = nodeType.outputs?.[index];
|
const ownType = nodeType.outputs?.[index];
|
||||||
|
|
||||||
@@ -537,11 +660,13 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
const inputs = node?.tmp?.type?.inputs;
|
const inputs = node?.tmp?.type?.inputs;
|
||||||
if (!inputs) continue;
|
if (!inputs) continue;
|
||||||
for (const key in inputs) {
|
for (const key in inputs) {
|
||||||
|
|
||||||
const otherType = [inputs[key].type];
|
const otherType = [inputs[key].type];
|
||||||
otherType.push(...(inputs[key].accepts || []));
|
otherType.push(...(inputs[key].accepts || []));
|
||||||
|
|
||||||
if (areSocketsCompatible(ownType, otherType) && edges.get(node.id) !== key) {
|
if (
|
||||||
|
areSocketsCompatible(ownType, otherType) &&
|
||||||
|
edges.get(node.id) !== key
|
||||||
|
) {
|
||||||
sockets.push([node, key]);
|
sockets.push([node, key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -549,42 +674,48 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
}
|
}
|
||||||
|
|
||||||
return sockets;
|
return sockets;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeEdge(edge: Edge, { applyDeletion = true }: { applyDeletion?: boolean } = {}) {
|
removeEdge(
|
||||||
|
edge: Edge,
|
||||||
|
{ applyDeletion = true }: { applyDeletion?: boolean } = {},
|
||||||
|
) {
|
||||||
const id0 = edge[0].id;
|
const id0 = edge[0].id;
|
||||||
const sid0 = edge[1];
|
const sid0 = edge[1];
|
||||||
const id2 = edge[2].id;
|
const id2 = edge[2].id;
|
||||||
const sid2 = edge[3];
|
const sid2 = edge[3];
|
||||||
|
|
||||||
const _edge = this._edges.find((e) => e[0].id === id0 && e[1] === sid0 && e[2].id === id2 && e[3] === sid2);
|
const _edge = this.edges.find(
|
||||||
|
(e) =>
|
||||||
|
e[0].id === id0 && e[1] === sid0 && e[2].id === id2 && e[3] === sid2,
|
||||||
|
);
|
||||||
|
|
||||||
if (!_edge) return;
|
if (!_edge) return;
|
||||||
|
|
||||||
edge[0].tmp = edge[0].tmp || {};
|
edge[0].tmp = edge[0].tmp || {};
|
||||||
if (edge[0].tmp.children) {
|
if (edge[0].tmp.children) {
|
||||||
edge[0].tmp.children = edge[0].tmp.children.filter(n => n.id !== id2);
|
edge[0].tmp.children = edge[0].tmp.children.filter(
|
||||||
|
(n: Node) => n.id !== id2,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
edge[2].tmp = edge[2].tmp || {};
|
edge[2].tmp = edge[2].tmp || {};
|
||||||
if (edge[2].tmp.parents) {
|
if (edge[2].tmp.parents) {
|
||||||
edge[2].tmp.parents = edge[2].tmp.parents.filter(n => n.id !== id0);
|
edge[2].tmp.parents = edge[2].tmp.parents.filter(
|
||||||
|
(n: Node) => n.id !== id0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.edges = this.edges.filter((e) => !areEdgesEqual(e, edge));
|
||||||
if (applyDeletion) {
|
if (applyDeletion) {
|
||||||
this.edges.update((edges) => {
|
|
||||||
return edges.filter(e => e !== _edge);
|
|
||||||
});
|
|
||||||
this.execute();
|
this.execute();
|
||||||
this.save();
|
this.save();
|
||||||
} else {
|
|
||||||
this._edges = this._edges.filter(e => e !== _edge);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdgesToNode(node: Node) {
|
getEdgesToNode(node: Node) {
|
||||||
return this._edges
|
return this.edges
|
||||||
.filter((edge) => edge[2].id === node.id)
|
.filter((edge) => edge[2].id === node.id)
|
||||||
.map((edge) => {
|
.map((edge) => {
|
||||||
const from = this.getNode(edge[0].id);
|
const from = this.getNode(edge[0].id);
|
||||||
@@ -596,7 +727,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
}
|
}
|
||||||
|
|
||||||
getEdgesFromNode(node: Node) {
|
getEdgesFromNode(node: Node) {
|
||||||
return this._edges
|
return this.edges
|
||||||
.filter((edge) => edge[0].id === node.id)
|
.filter((edge) => edge[0].id === node.id)
|
||||||
.map((edge) => {
|
.map((edge) => {
|
||||||
const from = this.getNode(edge[0].id);
|
const from = this.getNode(edge[0].id);
|
||||||
@@ -606,7 +737,4 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
})
|
})
|
||||||
.filter(Boolean) as unknown as [Node, number, Node, string][];
|
.filter(Boolean) as unknown as [Node, number, Node, string][];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,94 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Edge as EdgeType, Node as NodeType } from "@nodes/types";
|
|
||||||
import { HTML } from "@threlte/extras";
|
|
||||||
import Edge from "../edges/Edge.svelte";
|
|
||||||
import Node from "../node/Node.svelte";
|
|
||||||
import { getContext, onMount } from "svelte";
|
|
||||||
import type { Writable } from "svelte/store";
|
|
||||||
import { getGraphState } from "./state.svelte";
|
|
||||||
import { useThrelte } from "@threlte/core";
|
|
||||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
nodes: Writable<Map<number, NodeType>>;
|
|
||||||
edges: Writable<EdgeType[]>;
|
|
||||||
cameraPosition: [number, number, number];
|
|
||||||
};
|
|
||||||
|
|
||||||
const { nodes, edges, cameraPosition = [0, 0, 4] }: Props = $props();
|
|
||||||
|
|
||||||
const { invalidate } = useThrelte();
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
appSettings.theme;
|
|
||||||
invalidate();
|
|
||||||
});
|
|
||||||
|
|
||||||
const graphState = getGraphState();
|
|
||||||
|
|
||||||
const isNodeInView = getContext<(n: NodeType) => boolean>("isNodeInView");
|
|
||||||
|
|
||||||
const getSocketPosition =
|
|
||||||
getContext<(node: NodeType, index: string | number) => [number, number]>(
|
|
||||||
"getSocketPosition",
|
|
||||||
);
|
|
||||||
|
|
||||||
function getEdgePosition(edge: EdgeType) {
|
|
||||||
const pos1 = getSocketPosition(edge[0], edge[1]);
|
|
||||||
const pos2 = getSocketPosition(edge[2], edge[3]);
|
|
||||||
return [pos1[0], pos1[1], pos2[0], pos2[1]];
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
for (const node of $nodes.values()) {
|
|
||||||
if (node?.tmp?.ref) {
|
|
||||||
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
|
|
||||||
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#each $edges as edge (`${edge[0].id}-${edge[1]}-${edge[2].id}-${edge[3]}`)}
|
|
||||||
{@const pos = getEdgePosition(edge)}
|
|
||||||
{@const [x1, y1, x2, y2] = pos}
|
|
||||||
<Edge
|
|
||||||
z={cameraPosition[2]}
|
|
||||||
from={{
|
|
||||||
x: x1,
|
|
||||||
y: y1,
|
|
||||||
}}
|
|
||||||
to={{
|
|
||||||
x: x2,
|
|
||||||
y: y2,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<HTML transform={false}>
|
|
||||||
<div
|
|
||||||
role="tree"
|
|
||||||
id="graph"
|
|
||||||
tabindex="0"
|
|
||||||
class="wrapper"
|
|
||||||
style:transform={`scale(${cameraPosition[2] * 0.1})`}
|
|
||||||
class:hovering-sockets={graphState.activeSocket}
|
|
||||||
>
|
|
||||||
{#each $nodes.values() as node (node.id)}
|
|
||||||
<Node
|
|
||||||
{node}
|
|
||||||
inView={cameraPosition && isNodeInView(node)}
|
|
||||||
z={cameraPosition[2]}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</HTML>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.wrapper {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 100;
|
|
||||||
width: 0px;
|
|
||||||
height: 0px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,14 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Graph, Node, NodeRegistry } from "@nodes/types";
|
import type { Graph, Node, NodeRegistry } from "@nodarium/types";
|
||||||
import GraphEl from "./Graph.svelte";
|
import GraphEl from "./Graph.svelte";
|
||||||
import { GraphManager } from "../graph-manager.js";
|
import { GraphManager } from "../graph-manager.svelte";
|
||||||
import { setContext } from "svelte";
|
|
||||||
import { debounce } from "$lib/helpers";
|
|
||||||
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
import { GraphState } from "./state.svelte";
|
import { GraphState, setGraphManager, setGraphState } from "./state.svelte";
|
||||||
|
import { setupKeymaps } from "../keymaps";
|
||||||
const graphState = new GraphState();
|
|
||||||
setContext("graphState", graphState);
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
graph: Graph;
|
graph: Graph;
|
||||||
@@ -31,8 +27,8 @@
|
|||||||
registry,
|
registry,
|
||||||
settings = $bindable(),
|
settings = $bindable(),
|
||||||
activeNode = $bindable(),
|
activeNode = $bindable(),
|
||||||
showGrid,
|
showGrid = $bindable(true),
|
||||||
snapToGrid,
|
snapToGrid = $bindable(true),
|
||||||
showHelp = $bindable(false),
|
showHelp = $bindable(false),
|
||||||
settingTypes = $bindable(),
|
settingTypes = $bindable(),
|
||||||
onsave,
|
onsave,
|
||||||
@@ -40,31 +36,44 @@
|
|||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
export const keymap = createKeyMap([]);
|
export const keymap = createKeyMap([]);
|
||||||
setContext("keymap", keymap);
|
|
||||||
|
|
||||||
export const manager = new GraphManager(registry);
|
export const manager = new GraphManager(registry);
|
||||||
setContext("graphManager", manager);
|
setGraphManager(manager);
|
||||||
|
|
||||||
|
const graphState = new GraphState(manager);
|
||||||
|
$effect(() => {
|
||||||
|
graphState.showGrid = showGrid;
|
||||||
|
graphState.snapToGrid = snapToGrid;
|
||||||
|
graphState.showHelp = showHelp;
|
||||||
|
});
|
||||||
|
|
||||||
|
setGraphState(graphState);
|
||||||
|
|
||||||
|
setupKeymaps(keymap, manager, graphState);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (graphState.activeNodeId !== -1) {
|
if (graphState.activeNodeId !== -1) {
|
||||||
activeNode = manager.getNode(graphState.activeNodeId);
|
activeNode = manager.getNode(graphState.activeNodeId);
|
||||||
} else {
|
} else if (activeNode) {
|
||||||
activeNode = undefined;
|
activeNode = undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateSettings = debounce((s) => {
|
$effect(() => {
|
||||||
manager.setSettings(s);
|
if (!graphState.addMenuPosition) {
|
||||||
}, 200);
|
graphState.edgeEndPosition = null;
|
||||||
|
graphState.activeSocket = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (settingTypes && settings) {
|
if (settingTypes && settings) {
|
||||||
updateSettings($state.snapshot(settings));
|
manager.setSettings(settings);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.on("settings", (_settings) => {
|
manager.on("settings", (_settings) => {
|
||||||
settingTypes = _settings.types;
|
settingTypes = { ...settingTypes, ..._settings.types };
|
||||||
settings = _settings.values;
|
settings = _settings.values;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -75,4 +84,4 @@
|
|||||||
manager.load(graph);
|
manager.load(graph);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<GraphEl bind:showGrid bind:snapToGrid bind:showHelp />
|
<GraphEl {keymap} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||||
import { Color } from "three";
|
import { Color, LinearSRGBColorSpace } from "three";
|
||||||
|
|
||||||
const variables = [
|
const variables = [
|
||||||
"layer-0",
|
"layer-0",
|
||||||
@@ -12,20 +12,23 @@ const variables = [
|
|||||||
"edge",
|
"edge",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
function getColor(variable: typeof variables[number]) {
|
function getColor(variable: (typeof variables)[number]) {
|
||||||
const style = getComputedStyle(document.body.parentElement!);
|
const style = getComputedStyle(document.body.parentElement!);
|
||||||
let color = style.getPropertyValue(`--${variable}`);
|
let color = style.getPropertyValue(`--${variable}`);
|
||||||
return new Color().setStyle(color);
|
return new Color().setStyle(color, LinearSRGBColorSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const colors = Object.fromEntries(variables.map(v => [v, getColor(v)])) as Record<typeof variables[number], Color>;
|
export const colors = Object.fromEntries(
|
||||||
|
variables.map((v) => [v, getColor(v)]),
|
||||||
|
) as Record<(typeof variables)[number], Color>;
|
||||||
|
|
||||||
$effect.root(() => {
|
$effect.root(() => {
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!appSettings.theme || !("getComputedStyle" in globalThis)) return;
|
if (!appSettings.value.theme || !("getComputedStyle" in globalThis)) return;
|
||||||
const style = getComputedStyle(document.body.parentElement!);
|
const style = getComputedStyle(document.body.parentElement!);
|
||||||
for (const v of variables) {
|
for (const v of variables) {
|
||||||
colors[v].setStyle(style.getPropertyValue(`--${v}`));
|
const hex = style.getPropertyValue(`--${v}`);
|
||||||
|
colors[v].setStyle(hex, LinearSRGBColorSpace);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
3
app/src/lib/graph-interface/graph/constants.ts
Normal file
3
app/src/lib/graph-interface/graph/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const minZoom = 1;
|
||||||
|
export const maxZoom = 40;
|
||||||
|
export const zoomSpeed = 2;
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import type { GraphManager } from "../graph-manager.js";
|
|
||||||
import { getContext } from "svelte";
|
|
||||||
|
|
||||||
export function getGraphManager(): GraphManager {
|
|
||||||
return getContext("graphManager");
|
|
||||||
}
|
|
||||||
500
app/src/lib/graph-interface/graph/events.ts
Normal file
500
app/src/lib/graph-interface/graph/events.ts
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
import { GraphSchema, type NodeType, type Node } from "@nodarium/types";
|
||||||
|
import type { GraphManager } from "../graph-manager.svelte";
|
||||||
|
import type { GraphState } from "./state.svelte";
|
||||||
|
import { animate, lerp } from "$lib/helpers";
|
||||||
|
import { snapToGrid as snapPointToGrid } from "../helpers";
|
||||||
|
import { maxZoom, minZoom, zoomSpeed } from "./constants";
|
||||||
|
|
||||||
|
|
||||||
|
export class FileDropEventManager {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private graph: GraphManager,
|
||||||
|
private state: GraphState
|
||||||
|
) { }
|
||||||
|
|
||||||
|
handleFileDrop(event: DragEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.state.isDragging = false;
|
||||||
|
if (!event.dataTransfer) return;
|
||||||
|
const nodeId = event.dataTransfer.getData("data/node-id") as NodeType;
|
||||||
|
let mx = event.clientX - this.state.rect.x;
|
||||||
|
let my = event.clientY - this.state.rect.y;
|
||||||
|
|
||||||
|
if (nodeId) {
|
||||||
|
let nodeOffsetX = event.dataTransfer.getData("data/node-offset-x");
|
||||||
|
let nodeOffsetY = event.dataTransfer.getData("data/node-offset-y");
|
||||||
|
if (nodeOffsetX && nodeOffsetY) {
|
||||||
|
mx += parseInt(nodeOffsetX);
|
||||||
|
my += parseInt(nodeOffsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
let props = {};
|
||||||
|
let rawNodeProps = event.dataTransfer.getData("data/node-props");
|
||||||
|
if (rawNodeProps) {
|
||||||
|
try {
|
||||||
|
props = JSON.parse(rawNodeProps);
|
||||||
|
} catch (e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
const pos = this.state.projectScreenToWorld(mx, my);
|
||||||
|
this.graph.registry.load([nodeId]).then(() => {
|
||||||
|
this.graph.createNode({
|
||||||
|
type: nodeId,
|
||||||
|
props,
|
||||||
|
position: pos,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (event.dataTransfer.files.length) {
|
||||||
|
const file = event.dataTransfer.files[0];
|
||||||
|
|
||||||
|
if (file.type === "application/wasm") {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async (e) => {
|
||||||
|
const buffer = e.target?.result;
|
||||||
|
if (buffer?.constructor === ArrayBuffer) {
|
||||||
|
const nodeType = await this.graph.registry.register(buffer);
|
||||||
|
|
||||||
|
this.graph.createNode({
|
||||||
|
type: nodeType.id,
|
||||||
|
props: {},
|
||||||
|
position: this.state.projectScreenToWorld(mx, my),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
} else if (file.type === "application/json") {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const buffer = e.target?.result as ArrayBuffer;
|
||||||
|
if (buffer) {
|
||||||
|
const state = GraphSchema.parse(JSON.parse(buffer.toString()));
|
||||||
|
this.graph.load(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave() {
|
||||||
|
this.state.isDragging = false;
|
||||||
|
this.state.isPanning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragEnter(e: DragEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.state.isDragging = true;
|
||||||
|
this.state.isPanning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragOver(e: DragEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.state.isDragging = true;
|
||||||
|
this.state.isPanning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragEnd(e: DragEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.state.isDragging = true;
|
||||||
|
this.state.isPanning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEventListenerProps() {
|
||||||
|
return {
|
||||||
|
ondragenter: (ev: DragEvent) => this.handleDragEnter(ev),
|
||||||
|
ondragover: (ev: DragEvent) => this.handleDragOver(ev),
|
||||||
|
ondragexit: (ev: DragEvent) => this.handleDragEnd(ev),
|
||||||
|
ondrop: (ev: DragEvent) => this.handleFileDrop(ev),
|
||||||
|
onmouseleave: () => this.handleMouseLeave(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class MouseEventManager {
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private graph: GraphManager,
|
||||||
|
private state: GraphState
|
||||||
|
) { }
|
||||||
|
|
||||||
|
|
||||||
|
handleMouseUp(event: MouseEvent) {
|
||||||
|
this.state.isPanning = false;
|
||||||
|
if (!this.state.mouseDown) return;
|
||||||
|
|
||||||
|
const activeNode = this.graph.getNode(this.state.activeNodeId);
|
||||||
|
|
||||||
|
const clickedNodeId = this.state.getNodeIdFromEvent(event);
|
||||||
|
|
||||||
|
if (clickedNodeId !== -1) {
|
||||||
|
if (activeNode) {
|
||||||
|
if (!activeNode?.tmp?.isMoving && !event.ctrlKey && !event.shiftKey) {
|
||||||
|
this.state.activeNodeId = clickedNodeId;
|
||||||
|
this.state.clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeNode?.tmp?.isMoving) {
|
||||||
|
activeNode.tmp = activeNode.tmp || {};
|
||||||
|
activeNode.tmp.isMoving = false;
|
||||||
|
if (this.state.snapToGrid) {
|
||||||
|
const snapLevel = this.state.getSnapLevel();
|
||||||
|
activeNode.position[0] = snapPointToGrid(
|
||||||
|
activeNode?.tmp?.x ?? activeNode.position[0],
|
||||||
|
5 / snapLevel,
|
||||||
|
);
|
||||||
|
activeNode.position[1] = snapPointToGrid(
|
||||||
|
activeNode?.tmp?.y ?? activeNode.position[1],
|
||||||
|
5 / snapLevel,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
activeNode.position[0] = activeNode?.tmp?.x ?? activeNode.position[0];
|
||||||
|
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1];
|
||||||
|
}
|
||||||
|
const nodes = [
|
||||||
|
...[...(this.state.selectedNodes?.values() || [])].map((id) =>
|
||||||
|
this.graph.getNode(id),
|
||||||
|
),
|
||||||
|
] as Node[];
|
||||||
|
|
||||||
|
const vec = [
|
||||||
|
activeNode.position[0] - (activeNode?.tmp.x || 0),
|
||||||
|
activeNode.position[1] - (activeNode?.tmp.y || 0),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (!node) continue;
|
||||||
|
node.tmp = node.tmp || {};
|
||||||
|
const { x, y } = node.tmp;
|
||||||
|
if (x !== undefined && y !== undefined) {
|
||||||
|
node.position[0] = x + vec[0];
|
||||||
|
node.position[1] = y + vec[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodes.push(activeNode);
|
||||||
|
animate(500, (a: number) => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (
|
||||||
|
node?.tmp &&
|
||||||
|
node.tmp["x"] !== undefined &&
|
||||||
|
node.tmp["y"] !== undefined
|
||||||
|
) {
|
||||||
|
node.tmp.x = lerp(node.tmp.x, node.position[0], a);
|
||||||
|
node.tmp.y = lerp(node.tmp.y, node.position[1], a);
|
||||||
|
this.state.updateNodePosition(node);
|
||||||
|
if (node?.tmp?.isMoving) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.graph.save();
|
||||||
|
} else if (this.state.hoveredSocket && this.state.activeSocket) {
|
||||||
|
if (
|
||||||
|
typeof this.state.hoveredSocket.index === "number" &&
|
||||||
|
typeof this.state.activeSocket.index === "string"
|
||||||
|
) {
|
||||||
|
this.graph.createEdge(
|
||||||
|
this.state.hoveredSocket.node,
|
||||||
|
this.state.hoveredSocket.index || 0,
|
||||||
|
this.state.activeSocket.node,
|
||||||
|
this.state.activeSocket.index,
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
typeof this.state.activeSocket.index == "number" &&
|
||||||
|
typeof this.state.hoveredSocket.index === "string"
|
||||||
|
) {
|
||||||
|
this.graph.createEdge(
|
||||||
|
this.state.activeSocket.node,
|
||||||
|
this.state.activeSocket.index || 0,
|
||||||
|
this.state.hoveredSocket.node,
|
||||||
|
this.state.hoveredSocket.index,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.graph.save();
|
||||||
|
} else if (this.state.activeSocket && event.ctrlKey) {
|
||||||
|
// Handle automatic adding of nodes on ctrl+mouseUp
|
||||||
|
this.state.edgeEndPosition = [
|
||||||
|
this.state.mousePosition[0],
|
||||||
|
this.state.mousePosition[1],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (typeof this.state.activeSocket.index === "number") {
|
||||||
|
this.state.addMenuPosition = [
|
||||||
|
this.state.mousePosition[0],
|
||||||
|
this.state.mousePosition[1] - 25 / this.state.cameraPosition[2],
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
this.state.addMenuPosition = [
|
||||||
|
this.state.mousePosition[0] - 155 / this.state.cameraPosition[2],
|
||||||
|
this.state.mousePosition[1] - 25 / this.state.cameraPosition[2],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if camera moved
|
||||||
|
if (
|
||||||
|
clickedNodeId === -1 &&
|
||||||
|
!this.state.boxSelection &&
|
||||||
|
this.state.cameraDown[0] === this.state.cameraPosition[0] &&
|
||||||
|
this.state.cameraDown[1] === this.state.cameraPosition[1] &&
|
||||||
|
this.state.isBodyFocused()
|
||||||
|
) {
|
||||||
|
this.state.activeNodeId = -1;
|
||||||
|
this.state.clearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.mouseDown = null;
|
||||||
|
this.state.boxSelection = false;
|
||||||
|
this.state.activeSocket = null;
|
||||||
|
this.state.possibleSockets = [];
|
||||||
|
this.state.hoveredSocket = null;
|
||||||
|
this.state.addMenuPosition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
handleMouseDown(event: MouseEvent) {
|
||||||
|
if (this.state.mouseDown) return;
|
||||||
|
this.state.edgeEndPosition = null;
|
||||||
|
|
||||||
|
if (event.target instanceof HTMLElement) {
|
||||||
|
if (
|
||||||
|
event.target.nodeName !== "CANVAS" &&
|
||||||
|
!event.target.classList.contains("node") &&
|
||||||
|
!event.target.classList.contains("content")
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mx = event.clientX - this.state.rect.x;
|
||||||
|
let my = event.clientY - this.state.rect.y;
|
||||||
|
|
||||||
|
this.state.mouseDown = [mx, my];
|
||||||
|
this.state.cameraDown[0] = this.state.cameraPosition[0];
|
||||||
|
this.state.cameraDown[1] = this.state.cameraPosition[1];
|
||||||
|
|
||||||
|
const clickedNodeId = this.state.getNodeIdFromEvent(event);
|
||||||
|
this.state.mouseDownNodeId = clickedNodeId;
|
||||||
|
|
||||||
|
// if we clicked on a node
|
||||||
|
if (clickedNodeId !== -1) {
|
||||||
|
if (this.state.activeNodeId === -1) {
|
||||||
|
this.state.activeNodeId = clickedNodeId;
|
||||||
|
// if the selected node is the same as the clicked node
|
||||||
|
} else if (this.state.activeNodeId === clickedNodeId) {
|
||||||
|
//$activeNodeId = -1;
|
||||||
|
// if the clicked node is different from the selected node and secondary
|
||||||
|
} else if (event.ctrlKey) {
|
||||||
|
this.state.selectedNodes.add(this.state.activeNodeId);
|
||||||
|
this.state.selectedNodes.delete(clickedNodeId);
|
||||||
|
this.state.activeNodeId = clickedNodeId;
|
||||||
|
// select the node
|
||||||
|
} else if (event.shiftKey) {
|
||||||
|
const activeNode = this.graph.getNode(this.state.activeNodeId);
|
||||||
|
const newNode = this.graph.getNode(clickedNodeId);
|
||||||
|
if (activeNode && newNode) {
|
||||||
|
const edge = this.graph.getNodesBetween(activeNode, newNode);
|
||||||
|
if (edge) {
|
||||||
|
this.state.selectedNodes.clear();
|
||||||
|
for (const node of edge) {
|
||||||
|
this.state.selectedNodes.add(node.id);
|
||||||
|
}
|
||||||
|
this.state.selectedNodes.add(clickedNodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!this.state.selectedNodes.has(clickedNodeId)) {
|
||||||
|
this.state.activeNodeId = clickedNodeId;
|
||||||
|
this.state.clearSelection();
|
||||||
|
}
|
||||||
|
} else if (event.ctrlKey) {
|
||||||
|
this.state.boxSelection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = this.graph.getNode(this.state.activeNodeId);
|
||||||
|
if (!node) return;
|
||||||
|
node.tmp = node.tmp || {};
|
||||||
|
node.tmp.downX = node.position[0];
|
||||||
|
node.tmp.downY = node.position[1];
|
||||||
|
|
||||||
|
if (this.state.selectedNodes) {
|
||||||
|
for (const nodeId of this.state.selectedNodes) {
|
||||||
|
const n = this.graph.getNode(nodeId);
|
||||||
|
if (!n) continue;
|
||||||
|
n.tmp = n.tmp || {};
|
||||||
|
n.tmp.downX = n.position[0];
|
||||||
|
n.tmp.downY = n.position[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.edgeEndPosition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
handleMouseMove(event: MouseEvent) {
|
||||||
|
let mx = event.clientX - this.state.rect.x;
|
||||||
|
let my = event.clientY - this.state.rect.y;
|
||||||
|
|
||||||
|
this.state.mousePosition = this.state.projectScreenToWorld(mx, my);
|
||||||
|
this.state.hoveredNodeId = this.state.getNodeIdFromEvent(event);
|
||||||
|
|
||||||
|
if (!this.state.mouseDown) return;
|
||||||
|
|
||||||
|
// we are creating a new edge here
|
||||||
|
if (this.state.activeSocket || this.state.possibleSockets?.length) {
|
||||||
|
let smallestDist = 1000;
|
||||||
|
let _socket;
|
||||||
|
for (const socket of this.state.possibleSockets) {
|
||||||
|
const dist = Math.sqrt(
|
||||||
|
(socket.position[0] - this.state.mousePosition[0]) ** 2 +
|
||||||
|
(socket.position[1] - this.state.mousePosition[1]) ** 2,
|
||||||
|
);
|
||||||
|
if (dist < smallestDist) {
|
||||||
|
smallestDist = dist;
|
||||||
|
_socket = socket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_socket && smallestDist < 0.9) {
|
||||||
|
this.state.mousePosition = _socket.position;
|
||||||
|
this.state.hoveredSocket = _socket;
|
||||||
|
} else {
|
||||||
|
this.state.hoveredSocket = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle box selection
|
||||||
|
if (this.state.boxSelection) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
const mouseD = this.state.projectScreenToWorld(
|
||||||
|
this.state.mouseDown[0],
|
||||||
|
this.state.mouseDown[1],
|
||||||
|
);
|
||||||
|
const x1 = Math.min(mouseD[0], this.state.mousePosition[0]);
|
||||||
|
const x2 = Math.max(mouseD[0], this.state.mousePosition[0]);
|
||||||
|
const y1 = Math.min(mouseD[1], this.state.mousePosition[1]);
|
||||||
|
const y2 = Math.max(mouseD[1], this.state.mousePosition[1]);
|
||||||
|
for (const node of this.graph.nodes.values()) {
|
||||||
|
if (!node?.tmp) continue;
|
||||||
|
const x = node.position[0];
|
||||||
|
const y = node.position[1];
|
||||||
|
const height = this.state.getNodeHeight(node.type);
|
||||||
|
if (x > x1 - 20 && x < x2 && y > y1 - height && y < y2) {
|
||||||
|
this.state.selectedNodes?.add(node.id);
|
||||||
|
} else {
|
||||||
|
this.state.selectedNodes?.delete(node.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// here we are handling dragging of nodes
|
||||||
|
if (this.state.activeNodeId !== -1 && this.state.mouseDownNodeId !== -1) {
|
||||||
|
const node = this.graph.getNode(this.state.activeNodeId);
|
||||||
|
if (!node || event.buttons !== 1) return;
|
||||||
|
|
||||||
|
node.tmp = node.tmp || {};
|
||||||
|
|
||||||
|
const oldX = node.tmp.downX || 0;
|
||||||
|
const oldY = node.tmp.downY || 0;
|
||||||
|
|
||||||
|
let newX =
|
||||||
|
oldX + (mx - this.state.mouseDown[0]) / this.state.cameraPosition[2];
|
||||||
|
let newY =
|
||||||
|
oldY + (my - this.state.mouseDown[1]) / this.state.cameraPosition[2];
|
||||||
|
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
const snapLevel = this.state.getSnapLevel();
|
||||||
|
if (this.state.snapToGrid) {
|
||||||
|
newX = snapPointToGrid(newX, 5 / snapLevel);
|
||||||
|
newY = snapPointToGrid(newY, 5 / snapLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.tmp.isMoving) {
|
||||||
|
const dist = Math.sqrt((oldX - newX) ** 2 + (oldY - newY) ** 2);
|
||||||
|
if (dist > 0.2) {
|
||||||
|
node.tmp.isMoving = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vecX = oldX - newX;
|
||||||
|
const vecY = oldY - newY;
|
||||||
|
|
||||||
|
if (this.state.selectedNodes?.size) {
|
||||||
|
for (const nodeId of this.state.selectedNodes) {
|
||||||
|
const n = this.graph.getNode(nodeId);
|
||||||
|
if (!n?.tmp) continue;
|
||||||
|
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
||||||
|
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
|
||||||
|
this.state.updateNodePosition(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.tmp.x = newX;
|
||||||
|
node.tmp.y = newY;
|
||||||
|
|
||||||
|
this.state.updateNodePosition(node);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// here we are handling panning of camera
|
||||||
|
this.state.isPanning = true;
|
||||||
|
let newX =
|
||||||
|
this.state.cameraDown[0] -
|
||||||
|
(mx - this.state.mouseDown[0]) / this.state.cameraPosition[2];
|
||||||
|
let newY =
|
||||||
|
this.state.cameraDown[1] -
|
||||||
|
(my - this.state.mouseDown[1]) / this.state.cameraPosition[2];
|
||||||
|
|
||||||
|
this.state.setCameraTransform(newX, newY);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
handleMouseScroll(event: WheelEvent) {
|
||||||
|
const bodyIsFocused =
|
||||||
|
document.activeElement === document.body ||
|
||||||
|
document.activeElement === this.state.wrapper ||
|
||||||
|
document?.activeElement?.id === "graph";
|
||||||
|
if (!bodyIsFocused) return;
|
||||||
|
|
||||||
|
// Define zoom speed and clamp it between -1 and 1
|
||||||
|
const isNegative = event.deltaY < 0;
|
||||||
|
const normalizedDelta = Math.abs(event.deltaY * 0.01);
|
||||||
|
const delta = Math.pow(0.95, zoomSpeed * normalizedDelta);
|
||||||
|
|
||||||
|
// Calculate new zoom level and clamp it between minZoom and maxZoom
|
||||||
|
const newZoom = Math.max(
|
||||||
|
minZoom,
|
||||||
|
Math.min(
|
||||||
|
maxZoom,
|
||||||
|
isNegative
|
||||||
|
? this.state.cameraPosition[2] / delta
|
||||||
|
: this.state.cameraPosition[2] * delta,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate the ratio of the new zoom to the original zoom
|
||||||
|
const zoomRatio = newZoom / this.state.cameraPosition[2];
|
||||||
|
|
||||||
|
// Update camera position and zoom level
|
||||||
|
this.state.setCameraTransform(
|
||||||
|
this.state.mousePosition[0] -
|
||||||
|
(this.state.mousePosition[0] - this.state.cameraPosition[0]) /
|
||||||
|
zoomRatio,
|
||||||
|
this.state.mousePosition[1] -
|
||||||
|
(this.state.mousePosition[1] - this.state.cameraPosition[1]) /
|
||||||
|
zoomRatio,
|
||||||
|
newZoom,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,22 +1,340 @@
|
|||||||
import type { Socket } from "@nodes/types";
|
import type { Node, Socket } from "@nodarium/types";
|
||||||
import { getContext } from "svelte";
|
import { getContext, setContext } from "svelte";
|
||||||
import { SvelteSet } from 'svelte/reactivity';
|
import { SvelteSet } from "svelte/reactivity";
|
||||||
|
import type { GraphManager } from "../graph-manager.svelte";
|
||||||
|
import type { OrthographicCamera } from "three";
|
||||||
|
|
||||||
|
|
||||||
|
const graphStateKey = Symbol("graph-state");
|
||||||
export function getGraphState() {
|
export function getGraphState() {
|
||||||
return getContext<GraphState>("graphState");
|
return getContext<GraphState>(graphStateKey);
|
||||||
|
}
|
||||||
|
export function setGraphState(graphState: GraphState) {
|
||||||
|
return setContext(graphStateKey, graphState)
|
||||||
|
}
|
||||||
|
|
||||||
|
const graphManagerKey = Symbol("graph-manager");
|
||||||
|
export function getGraphManager() {
|
||||||
|
return getContext<GraphManager>(graphManagerKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setGraphManager(manager: GraphManager) {
|
||||||
|
return setContext(graphManagerKey, manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GraphState {
|
export class GraphState {
|
||||||
|
|
||||||
|
constructor(private graph: GraphManager) { }
|
||||||
|
|
||||||
|
width = $state(100);
|
||||||
|
height = $state(100);
|
||||||
|
|
||||||
|
wrapper = $state<HTMLDivElement>(null!);
|
||||||
|
rect: DOMRect = $derived(
|
||||||
|
(this.wrapper && this.width && this.height) ? this.wrapper.getBoundingClientRect() : new DOMRect(0, 0, 0, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
camera = $state<OrthographicCamera>(null!);
|
||||||
|
cameraPosition: [number, number, number] = $state([0, 0, 4]);
|
||||||
|
|
||||||
|
clipboard: null | {
|
||||||
|
nodes: Node[];
|
||||||
|
edges: [number, number, number, string][];
|
||||||
|
} = null;
|
||||||
|
|
||||||
|
cameraBounds = $derived([
|
||||||
|
this.cameraPosition[0] - this.width / this.cameraPosition[2] / 2,
|
||||||
|
this.cameraPosition[0] + this.width / this.cameraPosition[2] / 2,
|
||||||
|
this.cameraPosition[1] - this.height / this.cameraPosition[2] / 2,
|
||||||
|
this.cameraPosition[1] + this.height / this.cameraPosition[2] / 2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
boxSelection = $state(false);
|
||||||
|
edgeEndPosition = $state<[number, number] | null>();
|
||||||
|
addMenuPosition = $state<[number, number] | null>(null);
|
||||||
|
|
||||||
|
snapToGrid = $state(false);
|
||||||
|
showGrid = $state(true)
|
||||||
|
showHelp = $state(false)
|
||||||
|
|
||||||
|
cameraDown = [0, 0];
|
||||||
|
mouseDownNodeId = -1;
|
||||||
|
|
||||||
|
isPanning = $state(false);
|
||||||
|
isDragging = $state(false);
|
||||||
|
hoveredNodeId = $state(-1);
|
||||||
|
mousePosition = $state([0, 0]);
|
||||||
|
mouseDown = $state<[number, number] | null>(null);
|
||||||
activeNodeId = $state(-1);
|
activeNodeId = $state(-1);
|
||||||
selectedNodes = new SvelteSet<number>();
|
selectedNodes = new SvelteSet<number>();
|
||||||
activeSocket = $state<Socket | null>(null);
|
activeSocket = $state<Socket | null>(null);
|
||||||
hoveredSocket = $state<Socket | null>(null);
|
hoveredSocket = $state<Socket | null>(null);
|
||||||
possibleSockets = $state<Socket[]>([]);
|
possibleSockets = $state<Socket[]>([]);
|
||||||
possibleSocketIds = $derived(new Set(
|
possibleSocketIds = $derived(
|
||||||
this.possibleSockets.map((s) => `${s.node.id}-${s.index}`),
|
new Set(this.possibleSockets.map((s) => `${s.node.id}-${s.index}`)),
|
||||||
));
|
);
|
||||||
|
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
this.selectedNodes.clear();
|
this.selectedNodes.clear();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
isBodyFocused = () => document?.activeElement?.nodeName !== "INPUT";
|
||||||
|
|
||||||
|
setCameraTransform(
|
||||||
|
x = this.cameraPosition[0],
|
||||||
|
y = this.cameraPosition[1],
|
||||||
|
z = this.cameraPosition[2],
|
||||||
|
) {
|
||||||
|
if (this.camera) {
|
||||||
|
this.camera.position.x = x;
|
||||||
|
this.camera.position.z = y;
|
||||||
|
this.camera.zoom = z;
|
||||||
|
}
|
||||||
|
this.cameraPosition = [x, y, z];
|
||||||
|
localStorage.setItem("cameraPosition", JSON.stringify(this.cameraPosition));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updateNodePosition(node: Node) {
|
||||||
|
if (node?.tmp?.ref && node?.tmp?.mesh) {
|
||||||
|
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
|
||||||
|
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
|
||||||
|
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
|
||||||
|
node.tmp.mesh.position.x = node.tmp.x + 10;
|
||||||
|
node.tmp.mesh.position.z = node.tmp.y + this.getNodeHeight(node.type) / 2;
|
||||||
|
if (
|
||||||
|
node.tmp.x === node.position[0] &&
|
||||||
|
node.tmp.y === node.position[1]
|
||||||
|
) {
|
||||||
|
delete node.tmp.x;
|
||||||
|
delete node.tmp.y;
|
||||||
|
}
|
||||||
|
this.graph.edges = [...this.graph.edges];
|
||||||
|
} else {
|
||||||
|
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
|
||||||
|
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
|
||||||
|
node.tmp.mesh.position.x = node.position[0] + 10;
|
||||||
|
node.tmp.mesh.position.z =
|
||||||
|
node.position[1] + this.getNodeHeight(node.type) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSnapLevel() {
|
||||||
|
const z = this.cameraPosition[2];
|
||||||
|
if (z > 66) {
|
||||||
|
return 8;
|
||||||
|
} else if (z > 55) {
|
||||||
|
return 4;
|
||||||
|
} else if (z > 11) {
|
||||||
|
return 2;
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSocketPosition(
|
||||||
|
node: Node,
|
||||||
|
index: string | number,
|
||||||
|
): [number, number] {
|
||||||
|
if (typeof index === "number") {
|
||||||
|
return [
|
||||||
|
(node?.tmp?.x ?? node.position[0]) + 20,
|
||||||
|
(node?.tmp?.y ?? node.position[1]) + 2.5 + 10 * index,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index);
|
||||||
|
return [
|
||||||
|
node?.tmp?.x ?? node.position[0],
|
||||||
|
(node?.tmp?.y ?? node.position[1]) + 10 + 10 * _index,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private nodeHeightCache: Record<string, number> = {};
|
||||||
|
getNodeHeight(nodeTypeId: string) {
|
||||||
|
if (nodeTypeId in this.nodeHeightCache) {
|
||||||
|
return this.nodeHeightCache[nodeTypeId];
|
||||||
|
}
|
||||||
|
const node = this.graph.getNodeType(nodeTypeId);
|
||||||
|
if (!node?.inputs) {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
const height =
|
||||||
|
5 +
|
||||||
|
10 *
|
||||||
|
Object.keys(node.inputs).filter(
|
||||||
|
(p) =>
|
||||||
|
p !== "seed" &&
|
||||||
|
node?.inputs &&
|
||||||
|
!("setting" in node?.inputs?.[p]) &&
|
||||||
|
node.inputs[p].hidden !== true,
|
||||||
|
).length;
|
||||||
|
this.nodeHeightCache[nodeTypeId] = height;
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
setNodePosition(node: Node) {
|
||||||
|
if (node?.tmp?.ref && node?.tmp?.mesh) {
|
||||||
|
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
|
||||||
|
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
|
||||||
|
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
|
||||||
|
node.tmp.mesh.position.x = node.tmp.x + 10;
|
||||||
|
node.tmp.mesh.position.z = node.tmp.y + this.getNodeHeight(node.type) / 2;
|
||||||
|
if (
|
||||||
|
node.tmp.x === node.position[0] &&
|
||||||
|
node.tmp.y === node.position[1]
|
||||||
|
) {
|
||||||
|
delete node.tmp.x;
|
||||||
|
delete node.tmp.y;
|
||||||
|
}
|
||||||
|
this.graph.edges = [...this.graph.edges];
|
||||||
|
} else {
|
||||||
|
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
|
||||||
|
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
|
||||||
|
node.tmp.mesh.position.x = node.position[0] + 10;
|
||||||
|
node.tmp.mesh.position.z =
|
||||||
|
node.position[1] + this.getNodeHeight(node.type) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
copyNodes() {
|
||||||
|
if (this.activeNodeId === -1 && !this.selectedNodes?.size)
|
||||||
|
return;
|
||||||
|
let nodes = [
|
||||||
|
this.activeNodeId,
|
||||||
|
...(this.selectedNodes?.values() || []),
|
||||||
|
]
|
||||||
|
.map((id) => this.graph.getNode(id))
|
||||||
|
.filter(b => !!b);
|
||||||
|
|
||||||
|
const edges = this.graph.getEdgesBetweenNodes(nodes);
|
||||||
|
nodes = nodes.map((node) => ({
|
||||||
|
...node,
|
||||||
|
position: [
|
||||||
|
this.mousePosition[0] - node.position[0],
|
||||||
|
this.mousePosition[1] - node.position[1],
|
||||||
|
],
|
||||||
|
tmp: undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.clipboard = {
|
||||||
|
nodes: nodes,
|
||||||
|
edges: edges,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pasteNodes() {
|
||||||
|
if (!this.clipboard) return;
|
||||||
|
|
||||||
|
const nodes = this.clipboard.nodes
|
||||||
|
.map((node) => {
|
||||||
|
node.tmp = node.tmp || {};
|
||||||
|
node.position[0] = this.mousePosition[0] - node.position[0];
|
||||||
|
node.position[1] = this.mousePosition[1] - node.position[1];
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as Node[];
|
||||||
|
|
||||||
|
const newNodes = this.graph.createGraph(nodes, this.clipboard.edges);
|
||||||
|
this.selectedNodes.clear();
|
||||||
|
for (const node of newNodes) {
|
||||||
|
this.selectedNodes.add(node.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setDownSocket(socket: Socket) {
|
||||||
|
this.activeSocket = socket;
|
||||||
|
|
||||||
|
let { node, index, position } = socket;
|
||||||
|
|
||||||
|
// remove existing edge
|
||||||
|
if (typeof index === "string") {
|
||||||
|
const edges = this.graph.getEdgesToNode(node);
|
||||||
|
for (const edge of edges) {
|
||||||
|
if (edge[3] === index) {
|
||||||
|
node = edge[0];
|
||||||
|
index = edge[1];
|
||||||
|
position = this.getSocketPosition(node, index);
|
||||||
|
this.graph.removeEdge(edge);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mouseDown = position;
|
||||||
|
this.activeSocket = {
|
||||||
|
node,
|
||||||
|
index,
|
||||||
|
position,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.possibleSockets = this.graph
|
||||||
|
.getPossibleSockets(this.activeSocket)
|
||||||
|
.map(([node, index]) => {
|
||||||
|
return {
|
||||||
|
node,
|
||||||
|
index,
|
||||||
|
position: this.getSocketPosition(node, index),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
projectScreenToWorld(x: number, y: number): [number, number] {
|
||||||
|
return [
|
||||||
|
this.cameraPosition[0] +
|
||||||
|
(x - this.width / 2) / this.cameraPosition[2],
|
||||||
|
this.cameraPosition[1] +
|
||||||
|
(y - this.height / 2) / this.cameraPosition[2],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeIdFromEvent(event: MouseEvent) {
|
||||||
|
let clickedNodeId = -1;
|
||||||
|
|
||||||
|
let mx = event.clientX - this.rect.x;
|
||||||
|
let my = event.clientY - this.rect.y;
|
||||||
|
|
||||||
|
if (event.button === 0) {
|
||||||
|
// check if the clicked element is a node
|
||||||
|
if (event.target instanceof HTMLElement) {
|
||||||
|
const nodeElement = event.target.closest(".node");
|
||||||
|
const nodeId = nodeElement?.getAttribute?.("data-node-id");
|
||||||
|
if (nodeId) {
|
||||||
|
clickedNodeId = parseInt(nodeId, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we do not have an active node,
|
||||||
|
// we are going to check if we clicked on a node by coordinates
|
||||||
|
if (clickedNodeId === -1) {
|
||||||
|
const [downX, downY] = this.projectScreenToWorld(mx, my);
|
||||||
|
for (const node of this.graph.nodes.values()) {
|
||||||
|
const x = node.position[0];
|
||||||
|
const y = node.position[1];
|
||||||
|
const height = this.getNodeHeight(node.type);
|
||||||
|
if (downX > x && downX < x + 20 && downY > y && downY < y + height) {
|
||||||
|
clickedNodeId = node.id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clickedNodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
isNodeInView(node: Node) {
|
||||||
|
const height = this.getNodeHeight(node.type);
|
||||||
|
const width = 20;
|
||||||
|
return (
|
||||||
|
node.position[0] > this.cameraBounds[0] - width &&
|
||||||
|
node.position[0] < this.cameraBounds[1] &&
|
||||||
|
node.position[1] > this.cameraBounds[2] - height &&
|
||||||
|
node.position[1] < this.cameraBounds[3]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import throttle from './throttle.js';
|
import throttle from "$lib/helpers/throttle";
|
||||||
|
|
||||||
type EventMap = Record<string, unknown>;
|
type EventMap = Record<string, unknown>;
|
||||||
type EventKey<T extends EventMap> = string & keyof T;
|
type EventKey<T extends EventMap> = string & keyof T;
|
||||||
type EventReceiver<T> = (params: T, stuff?: Record<string, unknown>) => unknown;
|
type EventReceiver<T> = (params: T, stuff?: Record<string, unknown>) => unknown;
|
||||||
|
|
||||||
|
export default class EventEmitter<
|
||||||
export default class EventEmitter<T extends EventMap = { [key: string]: unknown }> {
|
T extends EventMap = { [key: string]: unknown },
|
||||||
|
> {
|
||||||
index = 0;
|
index = 0;
|
||||||
public eventMap: T = {} as T;
|
public eventMap: T = {} as T;
|
||||||
constructor() {
|
constructor() {}
|
||||||
}
|
|
||||||
|
|
||||||
private cbs: { [key: string]: ((data?: unknown) => unknown)[] } = {};
|
private cbs: { [key: string]: ((data?: unknown) => unknown)[] } = {};
|
||||||
private cbsOnce: { [key: string]: ((data?: unknown) => unknown)[] } = {};
|
private cbsOnce: { [key: string]: ((data?: unknown) => unknown)[] } = {};
|
||||||
@@ -29,7 +29,11 @@ export default class EventEmitter<T extends EventMap = { [key: string]: unknown
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public on<K extends EventKey<T>>(event: K, cb: EventReceiver<T[K]>, throttleTimer = 0) {
|
public on<K extends EventKey<T>>(
|
||||||
|
event: K,
|
||||||
|
cb: EventReceiver<T[K]>,
|
||||||
|
throttleTimer = 0,
|
||||||
|
) {
|
||||||
if (throttleTimer > 0) cb = throttle(cb, throttleTimer);
|
if (throttleTimer > 0) cb = throttle(cb, throttleTimer);
|
||||||
const cbs = Object.assign(this.cbs, {
|
const cbs = Object.assign(this.cbs, {
|
||||||
[event]: [...(this.cbs[event] || []), cb],
|
[event]: [...(this.cbs[event] || []), cb],
|
||||||
@@ -38,7 +42,7 @@ export default class EventEmitter<T extends EventMap = { [key: string]: unknown
|
|||||||
|
|
||||||
// console.log('New EventEmitter ', this.constructor.name);
|
// console.log('New EventEmitter ', this.constructor.name);
|
||||||
return () => {
|
return () => {
|
||||||
cbs[event]?.splice(cbs[event].indexOf(cb), 1);
|
this.cbs[event]?.splice(cbs[event].indexOf(cb), 1);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,10 +52,17 @@ export default class EventEmitter<T extends EventMap = { [key: string]: unknown
|
|||||||
* @param {function} cb Listener, gets called everytime the event is emitted
|
* @param {function} cb Listener, gets called everytime the event is emitted
|
||||||
* @returns {function} Returns a function which removes the listener when called
|
* @returns {function} Returns a function which removes the listener when called
|
||||||
*/
|
*/
|
||||||
public once<K extends EventKey<T>>(event: K, cb: EventReceiver<T[K]>): () => void {
|
public once<K extends EventKey<T>>(
|
||||||
this.cbsOnce[event] = [...(this.cbsOnce[event] || []), cb];
|
event: K,
|
||||||
|
cb: EventReceiver<T[K]>,
|
||||||
|
): () => void {
|
||||||
|
const cbsOnce = Object.assign(this.cbsOnce, {
|
||||||
|
[event]: [...(this.cbsOnce[event] || []), cb],
|
||||||
|
});
|
||||||
|
this.cbsOnce = cbsOnce;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
this.cbsOnce[event].splice(this.cbsOnce[event].indexOf(cb), 1);
|
cbsOnce[event]?.splice(cbsOnce[event].indexOf(cb), 1);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ export function lerp(a: number, b: number, t: number) {
|
|||||||
return a + (b - a) * t;
|
return a + (b - a) * t;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function animate(duration: number, callback: (progress: number) => void | false) {
|
export function animate(
|
||||||
|
duration: number,
|
||||||
|
callback: (progress: number) => void | false,
|
||||||
|
) {
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
const loop = (time: number) => {
|
const loop = (time: number) => {
|
||||||
const progress = (time - start) / duration;
|
const progress = (time - start) / duration;
|
||||||
@@ -18,7 +21,7 @@ export function animate(duration: number, callback: (progress: number) => void |
|
|||||||
} else {
|
} else {
|
||||||
callback(1);
|
callback(1);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,33 +36,37 @@ export function createNodePath({
|
|||||||
aspectRatio = 1,
|
aspectRatio = 1,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
return `M0,${cornerTop}
|
return `M0,${cornerTop}
|
||||||
${cornerTop
|
${
|
||||||
? ` V${cornerTop}
|
cornerTop
|
||||||
|
? ` V${cornerTop}
|
||||||
Q0,0 ${cornerTop * aspectRatio},0
|
Q0,0 ${cornerTop * aspectRatio},0
|
||||||
H${100 - cornerTop * aspectRatio}
|
H${100 - cornerTop * aspectRatio}
|
||||||
Q100,0 100,${cornerTop}
|
Q100,0 100,${cornerTop}
|
||||||
`
|
`
|
||||||
: ` V0
|
: ` V0
|
||||||
H100
|
H100
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
V${y - height / 2}
|
V${y - height / 2}
|
||||||
${rightBump
|
${
|
||||||
? ` C${100 - depth},${y - height / 2} ${100 - depth},${y + height / 2} 100,${y + height / 2}`
|
rightBump
|
||||||
: ` H100`
|
? ` C${100 - depth},${y - height / 2} ${100 - depth},${y + height / 2} 100,${y + height / 2}`
|
||||||
}
|
: ` H100`
|
||||||
${cornerBottom
|
}
|
||||||
? ` V${100 - cornerBottom}
|
${
|
||||||
|
cornerBottom
|
||||||
|
? ` V${100 - cornerBottom}
|
||||||
Q100,100 ${100 - cornerBottom * aspectRatio},100
|
Q100,100 ${100 - cornerBottom * aspectRatio},100
|
||||||
H${cornerBottom * aspectRatio}
|
H${cornerBottom * aspectRatio}
|
||||||
Q0,100 0,${100 - cornerBottom}
|
Q0,100 0,${100 - cornerBottom}
|
||||||
`
|
`
|
||||||
: `${leftBump ? `V100 H0` : `V100`}`
|
: `${leftBump ? `V100 H0` : `V100`}`
|
||||||
}
|
}
|
||||||
${leftBump
|
${
|
||||||
? ` V${y + height / 2} C${depth},${y + height / 2} ${depth},${y - height / 2} 0,${y - height / 2}`
|
leftBump
|
||||||
: ` H0`
|
? ` V${y + height / 2} C${depth},${y + height / 2} ${depth},${y - height / 2} 0,${y - height / 2}`
|
||||||
}
|
: ` H0`
|
||||||
|
}
|
||||||
Z`.replace(/\s+/g, " ");
|
Z`.replace(/\s+/g, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,35 +78,14 @@ export const debounce = (fn: Function, ms = 300) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clone: <T>(v: T) => T = "structedClone" in globalThis ? globalThis.structuredClone : (obj) => JSON.parse(JSON.stringify(obj));
|
export const clone: <T>(v: T) => T =
|
||||||
|
"structedClone" in globalThis
|
||||||
export const createLogger = (() => {
|
? globalThis.structuredClone
|
||||||
let maxLength = 5;
|
: (obj) => JSON.parse(JSON.stringify(obj));
|
||||||
return (scope: string) => {
|
|
||||||
maxLength = Math.max(maxLength, scope.length);
|
|
||||||
let muted = false;
|
|
||||||
return {
|
|
||||||
log: (...args: any[]) => !muted && console.log(`[%c${scope.padEnd(maxLength, " ")}]:`, "color: #888", ...args),
|
|
||||||
group: (...args: any[]) => !muted && console.groupCollapsed(`[%c${scope.padEnd(maxLength, " ")}]:`, "color: #888", ...args),
|
|
||||||
groupEnd: () => !muted && console.groupEnd(),
|
|
||||||
info: (...args: any[]) => !muted && console.info(`[%c${scope.padEnd(maxLength, " ")}]:`, "color: #888", ...args),
|
|
||||||
warn: (...args: any[]) => !muted && console.warn(`[%c${scope.padEnd(maxLength, " ")}]:`, "color: #888", ...args),
|
|
||||||
error: (...args: any[]) => console.error(`[%c${scope.padEnd(maxLength, " ")}]:`, "color: #f88", ...args),
|
|
||||||
mute() {
|
|
||||||
muted = true;
|
|
||||||
},
|
|
||||||
unmute() {
|
|
||||||
muted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
export function withSubComponents<A, B extends Record<string, any>>(
|
export function withSubComponents<A, B extends Record<string, any>>(
|
||||||
component: A,
|
component: A,
|
||||||
subcomponents: B
|
subcomponents: B,
|
||||||
): A & B {
|
): A & B {
|
||||||
Object.keys(subcomponents).forEach((key) => {
|
Object.keys(subcomponents).forEach((key) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
export default <R, A extends any[]>(
|
|
||||||
fn: (...args: A) => R,
|
|
||||||
delay: number
|
|
||||||
): ((...args: A) => R) => {
|
|
||||||
let wait = false;
|
|
||||||
|
|
||||||
return (...args: A) => {
|
|
||||||
if (wait) return undefined;
|
|
||||||
|
|
||||||
const val = fn(...args);
|
|
||||||
|
|
||||||
wait = true;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
wait = false;
|
|
||||||
}, delay);
|
|
||||||
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
import { create, type Delta } from "jsondiffpatch";
|
import { create, type Delta } from "jsondiffpatch";
|
||||||
import type { Graph } from "@nodes/types";
|
import type { Graph } from "@nodarium/types";
|
||||||
import { createLogger, clone } from "./helpers/index.js";
|
import { clone } from "./helpers/index.js";
|
||||||
|
import { createLogger } from "@nodarium/utils";
|
||||||
|
|
||||||
const diff = create({
|
const diff = create({
|
||||||
objectHash: function (obj, index) {
|
objectHash: function (obj, index) {
|
||||||
if (obj === null) return obj;
|
if (obj === null) return obj;
|
||||||
if ("id" in obj) return obj.id;
|
if ("id" in obj) return obj.id as string;
|
||||||
|
if ("_id" in obj) return obj._id as string;
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
return obj.join("-")
|
return obj.join("-");
|
||||||
}
|
}
|
||||||
return obj?.id || obj._id || '$$index:' + index;
|
return "$$index:" + index;
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const log = createLogger("history")
|
const log = createLogger("history");
|
||||||
log.mute();
|
log.mute();
|
||||||
|
|
||||||
export class HistoryManager {
|
export class HistoryManager {
|
||||||
|
|
||||||
index: number = -1;
|
index: number = -1;
|
||||||
history: Delta[] = [];
|
history: Delta[] = [];
|
||||||
private initialState: Graph | undefined;
|
private initialState: Graph | undefined;
|
||||||
@@ -27,26 +27,25 @@ export class HistoryManager {
|
|||||||
private opts = {
|
private opts = {
|
||||||
debounce: 400,
|
debounce: 400,
|
||||||
maxHistory: 100,
|
maxHistory: 100,
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor({ maxHistory = 100, debounce = 100 } = {}) {
|
constructor({ maxHistory = 100, debounce = 100 } = {}) {
|
||||||
this.history = [];
|
this.history = [];
|
||||||
this.index = -1;
|
this.index = -1;
|
||||||
this.opts.debounce = debounce;
|
this.opts.debounce = debounce;
|
||||||
this.opts.maxHistory = maxHistory;
|
this.opts.maxHistory = maxHistory;
|
||||||
globalThis["_history"] = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
save(state: Graph) {
|
save(state: Graph) {
|
||||||
if (!this.state) {
|
if (!this.state) {
|
||||||
this.state = clone(state);
|
this.state = clone(state);
|
||||||
this.initialState = this.state;
|
this.initialState = this.state;
|
||||||
log.log("initial state saved")
|
log.log("initial state saved");
|
||||||
} else {
|
} else {
|
||||||
const newState = state;
|
const newState = state;
|
||||||
const delta = diff.diff(this.state, newState);
|
const delta = diff.diff(this.state, newState);
|
||||||
if (delta) {
|
if (delta) {
|
||||||
log.log("saving state")
|
log.log("saving state");
|
||||||
// Add the delta to history
|
// Add the delta to history
|
||||||
if (this.index < this.history.length - 1) {
|
if (this.index < this.history.length - 1) {
|
||||||
// Clear the history after the current index if new changes are made
|
// Clear the history after the current index if new changes are made
|
||||||
@@ -62,7 +61,7 @@ export class HistoryManager {
|
|||||||
}
|
}
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
} else {
|
} else {
|
||||||
log.log("no changes")
|
log.log("no changes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +75,7 @@ export class HistoryManager {
|
|||||||
|
|
||||||
undo() {
|
undo() {
|
||||||
if (this.index === -1 && this.initialState) {
|
if (this.index === -1 && this.initialState) {
|
||||||
log.log("reached start, loading initial state")
|
log.log("reached start, loading initial state");
|
||||||
return clone(this.initialState);
|
return clone(this.initialState);
|
||||||
} else {
|
} else {
|
||||||
const delta = this.history[this.index];
|
const delta = this.history[this.index];
|
||||||
@@ -96,7 +95,7 @@ export class HistoryManager {
|
|||||||
this.state = nextState;
|
this.state = nextState;
|
||||||
return clone(nextState);
|
return clone(nextState);
|
||||||
} else {
|
} else {
|
||||||
log.log("reached end")
|
log.log("reached end");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
192
app/src/lib/graph-interface/keymaps.ts
Normal file
192
app/src/lib/graph-interface/keymaps.ts
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import { animate, lerp } from "$lib/helpers";
|
||||||
|
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
|
import FileSaver from "file-saver";
|
||||||
|
import type { GraphManager } from "./graph-manager.svelte";
|
||||||
|
import type { GraphState } from "./graph/state.svelte";
|
||||||
|
|
||||||
|
type Keymap = ReturnType<typeof createKeyMap>;
|
||||||
|
export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: GraphState) {
|
||||||
|
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "l",
|
||||||
|
description: "Select linked nodes",
|
||||||
|
callback: () => {
|
||||||
|
const activeNode = graph.getNode(graphState.activeNodeId);
|
||||||
|
if (activeNode) {
|
||||||
|
const nodes = graph.getLinkedNodes(activeNode);
|
||||||
|
graphState.selectedNodes.clear();
|
||||||
|
for (const node of nodes) {
|
||||||
|
graphState.selectedNodes.add(node.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "?",
|
||||||
|
description: "Toggle Help",
|
||||||
|
callback: () => {
|
||||||
|
// TODO: fix this
|
||||||
|
// showHelp = !showHelp;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "c",
|
||||||
|
ctrl: true,
|
||||||
|
description: "Copy active nodes",
|
||||||
|
callback: () => graphState.copyNodes(),
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "v",
|
||||||
|
ctrl: true,
|
||||||
|
description: "Paste nodes",
|
||||||
|
callback: () => graphState.pasteNodes(),
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "Escape",
|
||||||
|
description: "Deselect nodes",
|
||||||
|
callback: () => {
|
||||||
|
graphState.activeNodeId = -1;
|
||||||
|
graphState.clearSelection();
|
||||||
|
graphState.edgeEndPosition = null;
|
||||||
|
(document.activeElement as HTMLElement)?.blur();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "A",
|
||||||
|
shift: true,
|
||||||
|
description: "Add new Node",
|
||||||
|
callback: () => {
|
||||||
|
graphState.addMenuPosition = [graphState.mousePosition[0], graphState.mousePosition[1]];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: ".",
|
||||||
|
description: "Center camera",
|
||||||
|
callback: () => {
|
||||||
|
if (!graphState.isBodyFocused()) return;
|
||||||
|
|
||||||
|
const average = [0, 0];
|
||||||
|
for (const node of graph.nodes.values()) {
|
||||||
|
average[0] += node.position[0];
|
||||||
|
average[1] += node.position[1];
|
||||||
|
}
|
||||||
|
average[0] = average[0] ? average[0] / graph.nodes.size : 0;
|
||||||
|
average[1] = average[1] ? average[1] / graph.nodes.size : 0;
|
||||||
|
|
||||||
|
const camX = graphState.cameraPosition[0];
|
||||||
|
const camY = graphState.cameraPosition[1];
|
||||||
|
const camZ = graphState.cameraPosition[2];
|
||||||
|
|
||||||
|
const ease = (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);
|
||||||
|
|
||||||
|
animate(500, (a: number) => {
|
||||||
|
graphState.setCameraTransform(
|
||||||
|
lerp(camX, average[0], ease(a)),
|
||||||
|
lerp(camY, average[1], ease(a)),
|
||||||
|
lerp(camZ, 2, ease(a)),
|
||||||
|
);
|
||||||
|
if (graphState.mouseDown) return false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "a",
|
||||||
|
ctrl: true,
|
||||||
|
preventDefault: true,
|
||||||
|
description: "Select all nodes",
|
||||||
|
callback: () => {
|
||||||
|
if (!graphState.isBodyFocused()) return;
|
||||||
|
for (const node of graph.nodes.keys()) {
|
||||||
|
graphState.selectedNodes.add(node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "z",
|
||||||
|
ctrl: true,
|
||||||
|
description: "Undo",
|
||||||
|
callback: () => {
|
||||||
|
if (!graphState.isBodyFocused()) return;
|
||||||
|
graph.undo();
|
||||||
|
for (const node of graph.nodes.values()) {
|
||||||
|
graphState.updateNodePosition(node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "y",
|
||||||
|
ctrl: true,
|
||||||
|
description: "Redo",
|
||||||
|
callback: () => {
|
||||||
|
graph.redo();
|
||||||
|
for (const node of graph.nodes.values()) {
|
||||||
|
graphState.updateNodePosition(node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "s",
|
||||||
|
ctrl: true,
|
||||||
|
description: "Save",
|
||||||
|
preventDefault: true,
|
||||||
|
callback: () => {
|
||||||
|
const state = graph.serialize();
|
||||||
|
const blob = new Blob([JSON.stringify(state)], {
|
||||||
|
type: "application/json;charset=utf-8",
|
||||||
|
});
|
||||||
|
FileSaver.saveAs(blob, "nodarium-graph.json");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: ["Delete", "Backspace", "x"],
|
||||||
|
description: "Delete selected nodes",
|
||||||
|
callback: (event) => {
|
||||||
|
if (!graphState.isBodyFocused()) return;
|
||||||
|
graph.startUndoGroup();
|
||||||
|
if (graphState.activeNodeId !== -1) {
|
||||||
|
const node = graph.getNode(graphState.activeNodeId);
|
||||||
|
if (node) {
|
||||||
|
graph.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||||
|
graphState.activeNodeId = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (graphState.selectedNodes) {
|
||||||
|
for (const nodeId of graphState.selectedNodes) {
|
||||||
|
const node = graph.getNode(nodeId);
|
||||||
|
if (node) {
|
||||||
|
graph.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graphState.clearSelection();
|
||||||
|
}
|
||||||
|
graph.saveUndoGroup();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "f",
|
||||||
|
description: "Smart Connect Nodes",
|
||||||
|
callback: () => {
|
||||||
|
const nodes = [...graphState.selectedNodes.values()]
|
||||||
|
.map((g) => graph.getNode(g))
|
||||||
|
.filter((n) => !!n);
|
||||||
|
const edge = graph.smartConnect(nodes[0], nodes[1]);
|
||||||
|
if (!edge) graph.smartConnect(nodes[1], nodes[0]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node } from "@nodes/types";
|
import type { Node } from "@nodarium/types";
|
||||||
import { getContext, onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { getGraphState } from "../graph/state.svelte";
|
import { getGraphState } from "../graph/state.svelte";
|
||||||
import { T } from "@threlte/core";
|
import { T } from "@threlte/core";
|
||||||
import { type Mesh } from "three";
|
import { type Mesh } from "three";
|
||||||
@@ -17,39 +17,29 @@
|
|||||||
inView: boolean;
|
inView: boolean;
|
||||||
z: number;
|
z: number;
|
||||||
};
|
};
|
||||||
const { node, inView, z }: Props = $props();
|
let { node, inView, z }: Props = $props();
|
||||||
|
|
||||||
const isActive = $derived(graphState.activeNodeId === node.id);
|
const isActive = $derived(graphState.activeNodeId === node.id);
|
||||||
const isSelected = $derived(graphState.selectedNodes.has(node.id));
|
const isSelected = $derived(graphState.selectedNodes.has(node.id));
|
||||||
let strokeColor = $state(colors.selected);
|
const strokeColor = $derived(
|
||||||
$effect(() => {
|
appSettings.value.theme &&
|
||||||
appSettings.theme;
|
(isSelected
|
||||||
strokeColor = isSelected
|
? colors.selected
|
||||||
? colors.selected.clone()
|
: isActive
|
||||||
: isActive
|
? colors.active
|
||||||
? colors.active.clone()
|
: colors.outline),
|
||||||
: colors.outline.clone();
|
);
|
||||||
});
|
|
||||||
|
|
||||||
const updateNodePosition =
|
|
||||||
getContext<(n: Node) => void>("updateNodePosition");
|
|
||||||
|
|
||||||
const getNodeHeight = getContext<(n: string) => number>("getNodeHeight");
|
|
||||||
|
|
||||||
let meshRef: Mesh | undefined = $state();
|
let meshRef: Mesh | undefined = $state();
|
||||||
|
|
||||||
const height = getNodeHeight?.(node.type);
|
const height = graphState.getNodeHeight(node.type);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
node.tmp = node.tmp || {};
|
if (!node.tmp) node.tmp = {};
|
||||||
node.tmp.mesh = meshRef;
|
if (meshRef && !node.tmp?.mesh) {
|
||||||
updateNodePosition?.(node);
|
node.tmp.mesh = meshRef;
|
||||||
});
|
graphState.updateNodePosition(node);
|
||||||
|
}
|
||||||
onMount(() => {
|
|
||||||
node.tmp = node.tmp || {};
|
|
||||||
node.tmp.mesh = meshRef;
|
|
||||||
updateNodePosition?.(node);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node } from "@nodes/types";
|
import type { Node } from "@nodarium/types";
|
||||||
import NodeHeader from "./NodeHeader.svelte";
|
import NodeHeader from "./NodeHeader.svelte";
|
||||||
import NodeParameter from "./NodeParameter.svelte";
|
import NodeParameter from "./NodeParameter.svelte";
|
||||||
import { getContext, onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import { getGraphState } from "../graph/state.svelte";
|
||||||
|
|
||||||
let ref: HTMLDivElement;
|
let ref: HTMLDivElement;
|
||||||
|
|
||||||
|
const graphState = getGraphState();
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
node: Node;
|
node: Node;
|
||||||
|
position?: "absolute" | "fixed" | "relative";
|
||||||
position?: "absolute" | "fixed";
|
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
inView?: boolean;
|
inView?: boolean;
|
||||||
@@ -17,7 +19,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
let {
|
let {
|
||||||
node,
|
node = $bindable(),
|
||||||
position = "absolute",
|
position = "absolute",
|
||||||
isActive = false,
|
isActive = false,
|
||||||
isSelected = false,
|
isSelected = false,
|
||||||
@@ -25,23 +27,19 @@
|
|||||||
z = 2,
|
z = 2,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const zOffset = (node.tmp?.random || 0) * 0.5;
|
// If we dont have a random offset, all nodes becom visible at the same zoom level -> stuttering
|
||||||
|
const zOffset = Math.random() - 0.5;
|
||||||
const zLimit = 2 - zOffset;
|
const zLimit = 2 - zOffset;
|
||||||
|
|
||||||
const type = node?.tmp?.type;
|
const parameters = Object.entries(node?.tmp?.type?.inputs || {}).filter(
|
||||||
|
|
||||||
const parameters = Object.entries(type?.inputs || {}).filter(
|
|
||||||
(p) =>
|
(p) =>
|
||||||
p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true,
|
p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true,
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateNodePosition =
|
|
||||||
getContext<(n: Node) => void>("updateNodePosition");
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
node.tmp.ref = ref;
|
node.tmp.ref = ref;
|
||||||
updateNodePosition?.(node);
|
graphState?.updateNodePosition(node);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { getGraphState } from "../graph/state.svelte.js";
|
||||||
import { createNodePath } from "../helpers/index.js";
|
import { createNodePath } from "../helpers/index.js";
|
||||||
import type { Node, Socket } from "@nodes/types";
|
import type { Node } from "@nodarium/types";
|
||||||
import { getContext } from "svelte";
|
|
||||||
|
|
||||||
export let node: Node;
|
const graphState = getGraphState();
|
||||||
|
|
||||||
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
|
const { node }: { node: Node } = $props();
|
||||||
const getSocketPosition =
|
|
||||||
getContext<(node: Node, index: number) => [number, number]>(
|
|
||||||
"getSocketPosition",
|
|
||||||
);
|
|
||||||
|
|
||||||
function handleMouseDown(event: MouseEvent) {
|
function handleMouseDown(event: MouseEvent) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setDownSocket?.({
|
graphState.setDownSocket?.({
|
||||||
node,
|
node,
|
||||||
index: 0,
|
index: 0,
|
||||||
position: getSocketPosition?.(node, 0),
|
position: graphState.getSocketPosition?.(node, 0),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,14 +29,14 @@
|
|||||||
rightBump,
|
rightBump,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
});
|
});
|
||||||
const pathDisabled = createNodePath({
|
// const pathDisabled = createNodePath({
|
||||||
depth: 0,
|
// depth: 0,
|
||||||
height: 15,
|
// height: 15,
|
||||||
y: 50,
|
// y: 50,
|
||||||
cornerTop,
|
// cornerTop,
|
||||||
rightBump,
|
// rightBump,
|
||||||
aspectRatio,
|
// aspectRatio,
|
||||||
});
|
// });
|
||||||
const pathHover = createNodePath({
|
const pathHover = createNodePath({
|
||||||
depth: 8.5,
|
depth: 8.5,
|
||||||
height: 50,
|
height: 50,
|
||||||
@@ -59,7 +55,7 @@
|
|||||||
class="click-target"
|
class="click-target"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
on:mousedown={handleMouseDown}
|
onmousedown={handleMouseDown}
|
||||||
></div>
|
></div>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -1,25 +1,36 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node, NodeInput } from "@nodes/types";
|
import type { Node, NodeInput } from "@nodarium/types";
|
||||||
import { getGraphManager } from "../graph/context.js";
|
import { Input } from "@nodarium/ui";
|
||||||
import { Input } from "@nodes/ui";
|
import type { GraphManager } from "../graph-manager.svelte";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
node: Node;
|
node: Node;
|
||||||
input: NodeInput;
|
input: NodeInput;
|
||||||
id: string;
|
id: string;
|
||||||
elementId?: string;
|
elementId?: string;
|
||||||
|
graph?: GraphManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
node,
|
node = $bindable(),
|
||||||
input,
|
input,
|
||||||
id,
|
id,
|
||||||
elementId = `input-${Math.random().toString(36).substring(7)}`,
|
elementId = `input-${Math.random().toString(36).substring(7)}`,
|
||||||
|
graph,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const graph = getGraphManager();
|
function getDefaultValue() {
|
||||||
|
if (node?.props?.[id] !== undefined) return node?.props?.[id] as number;
|
||||||
|
if ("value" in input && input?.value !== undefined)
|
||||||
|
return input?.value as number;
|
||||||
|
if (input.type === "boolean") return 0;
|
||||||
|
if (input.type === "float") return 0.5;
|
||||||
|
if (input.type === "integer") return 0;
|
||||||
|
if (input.type === "select") return 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
let value = $state(node?.props?.[id] ?? input.value);
|
let value = $state(getDefaultValue());
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (value !== undefined && node?.props?.[id] !== value) {
|
if (value !== undefined && node?.props?.[id] !== value) {
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {
|
import type {
|
||||||
NodeInput as NodeInputType,
|
NodeInput as NodeInputType,
|
||||||
Socket,
|
|
||||||
Node as NodeType,
|
Node as NodeType,
|
||||||
} from "@nodes/types";
|
} from "@nodarium/types";
|
||||||
import { getContext } from "svelte";
|
|
||||||
import { createNodePath } from "../helpers/index.js";
|
import { createNodePath } from "../helpers/index.js";
|
||||||
import { getGraphManager } from "../graph/context.js";
|
|
||||||
import NodeInput from "./NodeInput.svelte";
|
import NodeInput from "./NodeInput.svelte";
|
||||||
import { getGraphState } from "../graph/state.svelte.js";
|
import { getGraphManager, getGraphState } from "../graph/state.svelte.js";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
node: NodeType;
|
node: NodeType;
|
||||||
@@ -17,32 +14,26 @@
|
|||||||
isLast?: boolean;
|
isLast?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { node = $bindable(), input, id, isLast }: Props = $props();
|
const graph = getGraphManager();
|
||||||
|
|
||||||
|
let { node = $bindable(), input, id, isLast }: Props = $props();
|
||||||
|
|
||||||
const inputType = node?.tmp?.type?.inputs?.[id]!;
|
const inputType = node?.tmp?.type?.inputs?.[id]!;
|
||||||
|
|
||||||
const socketId = `${node.id}-${id}`;
|
const socketId = `${node.id}-${id}`;
|
||||||
|
|
||||||
const graph = getGraphManager();
|
|
||||||
const graphState = getGraphState();
|
const graphState = getGraphState();
|
||||||
const graphId = graph?.id;
|
const graphId = graph?.id;
|
||||||
const inputSockets = graph?.inputSockets;
|
|
||||||
|
|
||||||
const elementId = `input-${Math.random().toString(36).substring(7)}`;
|
const elementId = `input-${Math.random().toString(36).substring(7)}`;
|
||||||
|
|
||||||
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
|
|
||||||
const getSocketPosition =
|
|
||||||
getContext<(node: NodeType, index: string) => [number, number]>(
|
|
||||||
"getSocketPosition",
|
|
||||||
);
|
|
||||||
|
|
||||||
function handleMouseDown(ev: MouseEvent) {
|
function handleMouseDown(ev: MouseEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
setDownSocket?.({
|
graphState.setDownSocket({
|
||||||
node,
|
node,
|
||||||
index: id,
|
index: id,
|
||||||
position: getSocketPosition?.(node, id),
|
position: graphState.getSocketPosition?.(node, id),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,23 +74,17 @@
|
|||||||
class:disabled={!graphState?.possibleSocketIds.has(socketId)}
|
class:disabled={!graphState?.possibleSocketIds.has(socketId)}
|
||||||
>
|
>
|
||||||
{#key id && graphId}
|
{#key id && graphId}
|
||||||
<div class="content" class:disabled={$inputSockets?.has(socketId)}>
|
<div class="content" class:disabled={graph?.inputSockets?.has(socketId)}>
|
||||||
{#if inputType.label !== ""}
|
{#if inputType.label !== ""}
|
||||||
<label for={elementId}>{input.label || id}</label>
|
<label for={elementId}>{input.label || id}</label>
|
||||||
{/if}
|
{/if}
|
||||||
{#if inputType.external !== true}
|
{#if inputType.external !== true}
|
||||||
<NodeInput {elementId} {node} {input} {id} />
|
<NodeInput {graph} {elementId} bind:node {input} {id} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
|
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
|
||||||
<div
|
<div data-node-socket class="large target"></div>
|
||||||
data-node-socket
|
|
||||||
class="large target"
|
|
||||||
onmousedown={handleMouseDown}
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
></div>
|
|
||||||
<div
|
<div
|
||||||
data-node-socket
|
data-node-socket
|
||||||
class="small target"
|
class="small target"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Graph } from "@nodes/types";
|
import type { Graph } from "@nodarium/types";
|
||||||
|
|
||||||
export function grid(width: number, height: number) {
|
export function grid(width: number, height: number) {
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Graph, Node } from "@nodes/types";
|
import type { Graph, Node } from "@nodarium/types";
|
||||||
|
|
||||||
export function tree(depth: number): Graph {
|
export function tree(depth: number): Graph {
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
|
||||||
|
|
||||||
let index = -1;
|
let index = -1;
|
||||||
let wrapper: HTMLDivElement;
|
let wrapper: HTMLDivElement;
|
||||||
@@ -9,7 +8,7 @@
|
|||||||
index = getContext<() => number>("registerCell")();
|
index = getContext<() => number>("registerCell")();
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizes = getContext<Writable<string[]>>("sizes");
|
const sizes = getContext<{ value: string[] }>("sizes");
|
||||||
|
|
||||||
let downSizes: string[] = [];
|
let downSizes: string[] = [];
|
||||||
let downWidth = 0;
|
let downWidth = 0;
|
||||||
@@ -17,7 +16,7 @@
|
|||||||
let startX = 0;
|
let startX = 0;
|
||||||
|
|
||||||
function handleMouseDown(event: MouseEvent) {
|
function handleMouseDown(event: MouseEvent) {
|
||||||
downSizes = [...$sizes];
|
downSizes = [...sizes.value];
|
||||||
mouseDown = true;
|
mouseDown = true;
|
||||||
startX = event.clientX;
|
startX = event.clientX;
|
||||||
downWidth = wrapper.getBoundingClientRect().width;
|
downWidth = wrapper.getBoundingClientRect().width;
|
||||||
@@ -26,8 +25,7 @@
|
|||||||
function handleMouseMove(event: MouseEvent) {
|
function handleMouseMove(event: MouseEvent) {
|
||||||
if (mouseDown) {
|
if (mouseDown) {
|
||||||
const width = downWidth + startX - event.clientX;
|
const width = downWidth + startX - event.clientX;
|
||||||
$sizes[index] = `${width}px`;
|
sizes.value[index] = `${width}px`;
|
||||||
$sizes = $sizes;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,27 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext, getContext } from "svelte";
|
import { setContext, getContext } from "svelte";
|
||||||
import localStore from "$lib/helpers/localStore";
|
import { localState } from "$lib/helpers/localState.svelte";
|
||||||
|
|
||||||
const gridId = getContext<string>("grid-id") || "grid-0";
|
const gridId = getContext<string>("grid-id") || "grid-0";
|
||||||
let sizes = localStore<string[]>(gridId, []);
|
let sizes = localState<string[]>(gridId, []);
|
||||||
|
|
||||||
|
const { children } = $props();
|
||||||
|
|
||||||
let registerIndex = 0;
|
let registerIndex = 0;
|
||||||
setContext("registerCell", function () {
|
setContext("registerCell", function () {
|
||||||
let index = registerIndex;
|
let index = registerIndex;
|
||||||
registerIndex++;
|
registerIndex++;
|
||||||
if (registerIndex > $sizes.length) {
|
if (registerIndex > sizes.value.length) {
|
||||||
$sizes = [...$sizes, "1fr"];
|
sizes.value = [...sizes.value, "1fr"];
|
||||||
}
|
}
|
||||||
return index;
|
return index;
|
||||||
});
|
});
|
||||||
|
|
||||||
setContext("sizes", sizes);
|
setContext("sizes", sizes);
|
||||||
|
|
||||||
$: cols = $sizes.map((size, i) => `${i > 0 ? "1px " : ""}` + size).join(" ");
|
const cols = $derived(
|
||||||
|
sizes.value.map((size, i) => `${i > 0 ? "1px " : ""}` + size).join(" "),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper" style={`grid-template-columns: ${cols};`}>
|
<div class="wrapper" style={`grid-template-columns: ${cols};`}>
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function createNodePath({
|
|||||||
|
|
||||||
export const debounce = (fn: Function, ms = 300) => {
|
export const debounce = (fn: Function, ms = 300) => {
|
||||||
let timeoutId: ReturnType<typeof setTimeout>;
|
let timeoutId: ReturnType<typeof setTimeout>;
|
||||||
return function(this: any, ...args: any[]) {
|
return function (this: any, ...args: any[]) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
timeoutId = setTimeout(() => fn.apply(this, args), ms);
|
timeoutId = setTimeout(() => fn.apply(this, args), ms);
|
||||||
};
|
};
|
||||||
@@ -131,41 +131,100 @@ export function humanizeDuration(durationInMilliseconds: number) {
|
|||||||
|
|
||||||
return durationString.trim();
|
return durationString.trim();
|
||||||
}
|
}
|
||||||
export function debounceAsyncFunction<T extends any[], R>(func: (...args: T) => Promise<R>): (...args: T) => Promise<R> {
|
// export function debounceAsyncFunction<T extends any[], R>(
|
||||||
let currentPromise: Promise<R> | null = null;
|
// func: (...args: T) => Promise<R>
|
||||||
let nextArgs: T | null = null;
|
// ): (...args: T) => Promise<R> {
|
||||||
let resolveNext: ((result: R) => void) | null = null;
|
// let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
// let lastPromise: Promise<R> | null = null;
|
||||||
|
// let lastReject: ((reason?: any) => void) | null = null;
|
||||||
|
//
|
||||||
|
// return (...args: T): Promise<R> => {
|
||||||
|
// if (timeoutId) {
|
||||||
|
// clearTimeout(timeoutId);
|
||||||
|
// if (lastReject) {
|
||||||
|
// lastReject(new Error("Debounced: Previous call was canceled."));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return new Promise<R>((resolve, reject) => {
|
||||||
|
// lastReject = reject;
|
||||||
|
// timeoutId = setTimeout(() => {
|
||||||
|
// timeoutId = null;
|
||||||
|
// lastReject = null;
|
||||||
|
// lastPromise = func(...args).then(resolve, reject);
|
||||||
|
// }, 300); // Default debounce time is 300ms; you can make this configurable.
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
export function debounceAsyncFunction<T extends (...args: any[]) => Promise<any>>(asyncFn: T): T {
|
||||||
|
let isRunning = false;
|
||||||
|
let latestArgs: Parameters<T> | null = null;
|
||||||
|
let resolveNext: (() => void) | null = null;
|
||||||
|
|
||||||
const debouncedFunction = async (...args: T): Promise<R> => {
|
return (async function serializedFunction(...args: Parameters<T>): Promise<ReturnType<T>> {
|
||||||
if (currentPromise) {
|
latestArgs = args;
|
||||||
// Store the latest arguments and create a new promise to resolve them later
|
|
||||||
nextArgs = args;
|
if (isRunning) {
|
||||||
return new Promise<R>((resolve) => {
|
// Wait for the current execution to finish
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
resolveNext = resolve;
|
resolveNext = resolve;
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
// Execute the function immediately
|
|
||||||
try {
|
// Indicate the function is running
|
||||||
currentPromise = func(...args);
|
isRunning = true;
|
||||||
const result = await currentPromise;
|
|
||||||
return result;
|
try {
|
||||||
} finally {
|
// Execute with the latest arguments
|
||||||
currentPromise = null;
|
const result = await asyncFn(...latestArgs!);
|
||||||
// If there are stored arguments, call the function again with the latest arguments
|
return result;
|
||||||
if (nextArgs) {
|
} finally {
|
||||||
const argsToUse = nextArgs;
|
// Allow the next execution
|
||||||
const resolver = resolveNext;
|
isRunning = false;
|
||||||
nextArgs = null;
|
|
||||||
resolveNext = null;
|
if (resolveNext) {
|
||||||
resolver!(await debouncedFunction(...argsToUse));
|
resolveNext();
|
||||||
}
|
resolveNext = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}) as T;
|
||||||
|
|
||||||
return debouncedFunction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export function debounceAsyncFunction<T extends any[], R>(func: (...args: T) => Promise<R>): (...args: T) => Promise<R> {
|
||||||
|
// let currentPromise: Promise<R> | null = null;
|
||||||
|
// let nextArgs: T | null = null;
|
||||||
|
// let resolveNext: ((result: R) => void) | null = null;
|
||||||
|
//
|
||||||
|
// const debouncedFunction = async (...args: T): Promise<R> => {
|
||||||
|
// if (currentPromise) {
|
||||||
|
// // Store the latest arguments and create a new promise to resolve them later
|
||||||
|
// nextArgs = args;
|
||||||
|
// return new Promise<R>((resolve) => {
|
||||||
|
// resolveNext = resolve;
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// // Execute the function immediately
|
||||||
|
// try {
|
||||||
|
// currentPromise = func(...args);
|
||||||
|
// const result = await currentPromise;
|
||||||
|
// return result;
|
||||||
|
// } finally {
|
||||||
|
// currentPromise = null;
|
||||||
|
// // If there are stored arguments, call the function again with the latest arguments
|
||||||
|
// if (nextArgs) {
|
||||||
|
// const argsToUse = nextArgs;
|
||||||
|
// const resolver = resolveNext;
|
||||||
|
// nextArgs = null;
|
||||||
|
// resolveNext = null;
|
||||||
|
// resolver!(await debouncedFunction(...argsToUse));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// return debouncedFunction;
|
||||||
|
// }
|
||||||
|
|
||||||
export function withArgsChangeOnly<T extends any[], R>(func: (...args: T) => R): (...args: T) => R {
|
export function withArgsChangeOnly<T extends any[], R>(func: (...args: T) => R): (...args: T) => R {
|
||||||
let lastArgs: T | undefined = undefined;
|
let lastArgs: T | undefined = undefined;
|
||||||
let lastResult: R;
|
let lastResult: R;
|
||||||
|
|||||||
@@ -1,11 +1,34 @@
|
|||||||
export function localState<T>(key: string, defaultValue: T): T {
|
import { browser } from "$app/environment";
|
||||||
const stored = localStorage.getItem(key)
|
|
||||||
const state = $state(stored ? JSON.parse(stored) : defaultValue)
|
export class LocalStore<T> {
|
||||||
$effect.root(() => {
|
value = $state<T>() as T;
|
||||||
$effect(() => {
|
key = "";
|
||||||
const value = $state.snapshot(state);
|
|
||||||
localStorage.setItem(key, JSON.stringify(value));
|
constructor(key: string, value: T) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
const item = localStorage.getItem(key);
|
||||||
|
if (item) this.value = this.deserialize(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect.root(() => {
|
||||||
|
$effect(() => {
|
||||||
|
localStorage.setItem(this.key, this.serialize(this.value));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
return state;
|
|
||||||
|
serialize(value: T): string {
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize(item: string): T {
|
||||||
|
return JSON.parse(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function localState<T>(key: string, value: T) {
|
||||||
|
return new LocalStore(key, value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
export default <R, A extends any[]>(
|
export default <T extends unknown[]>(
|
||||||
fn: (...args: A) => R,
|
callback: (...args: T) => void,
|
||||||
delay: number
|
delay: number,
|
||||||
): ((...args: A) => R) => {
|
) => {
|
||||||
let wait = false;
|
let isWaiting = false;
|
||||||
|
|
||||||
return (...args: A) => {
|
return (...args: T) => {
|
||||||
if (wait) return undefined;
|
if (isWaiting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const val = fn(...args);
|
callback(...args);
|
||||||
|
isWaiting = true;
|
||||||
wait = true;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
wait = false;
|
isWaiting = false;
|
||||||
}, delay);
|
}, delay);
|
||||||
|
};
|
||||||
return val;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createWasmWrapper } from "@nodes/utils"
|
import { createWasmWrapper } from "@nodarium/utils";
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises";
|
||||||
import path from "path"
|
import path from "path";
|
||||||
|
|
||||||
export async function getWasm(id: `${string}/${string}/${string}`) {
|
export async function getWasm(id: `${string}/${string}/${string}`) {
|
||||||
const filePath = path.resolve(`../nodes/${id}/pkg/index_bg.wasm`);
|
const filePath = path.resolve(`../nodes/${id}/pkg/index_bg.wasm`);
|
||||||
@@ -8,17 +8,15 @@ export async function getWasm(id: `${string}/${string}/${string}`) {
|
|||||||
try {
|
try {
|
||||||
await fs.access(filePath);
|
await fs.access(filePath);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = await fs.readFile(filePath);
|
const file = await fs.readFile(filePath);
|
||||||
|
|
||||||
return new Uint8Array(file);
|
return new Uint8Array(file);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getNodeWasm(id: `${string}/${string}/${string}`) {
|
export async function getNodeWasm(id: `${string}/${string}/${string}`) {
|
||||||
|
|
||||||
const wasmBytes = await getWasm(id);
|
const wasmBytes = await getWasm(id);
|
||||||
if (!wasmBytes) return null;
|
if (!wasmBytes) return null;
|
||||||
|
|
||||||
@@ -27,9 +25,7 @@ export async function getNodeWasm(id: `${string}/${string}/${string}`) {
|
|||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getNode(id: `${string}/${string}/${string}`) {
|
export async function getNode(id: `${string}/${string}/${string}`) {
|
||||||
|
|
||||||
const wrapper = await getNodeWasm(id);
|
const wrapper = await getNodeWasm(id);
|
||||||
|
|
||||||
const definition = wrapper?.get_definition?.();
|
const definition = wrapper?.get_definition?.();
|
||||||
@@ -37,18 +33,17 @@ export async function getNode(id: `${string}/${string}/${string}`) {
|
|||||||
if (!definition) return null;
|
if (!definition) return null;
|
||||||
|
|
||||||
return definition;
|
return definition;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCollectionNodes(userId: `${string}/${string}`) {
|
export async function getCollectionNodes(userId: `${string}/${string}`) {
|
||||||
const nodes = await fs.readdir(path.resolve(`../nodes/${userId}`));
|
const nodes = await fs.readdir(path.resolve(`../nodes/${userId}`));
|
||||||
return nodes
|
return nodes
|
||||||
.filter(n => n !== "pkg" && n !== ".template")
|
.filter((n) => n !== "pkg" && n !== ".template")
|
||||||
.map(n => {
|
.map((n) => {
|
||||||
return {
|
return {
|
||||||
id: `${userId}/${n}`,
|
id: `${userId}/${n}`,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCollection(userId: `${string}/${string}`) {
|
export async function getCollection(userId: `${string}/${string}`) {
|
||||||
@@ -56,36 +51,40 @@ export async function getCollection(userId: `${string}/${string}`) {
|
|||||||
return {
|
return {
|
||||||
id: userId,
|
id: userId,
|
||||||
nodes,
|
nodes,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserCollections(userId: string) {
|
export async function getUserCollections(userId: string) {
|
||||||
const collections = await fs.readdir(path.resolve(`../nodes/${userId}`));
|
const collections = await fs.readdir(path.resolve(`../nodes/${userId}`));
|
||||||
return Promise.all(collections.map(async n => {
|
return Promise.all(
|
||||||
const nodes = await getCollectionNodes(`${userId}/${n}`);
|
collections.map(async (n) => {
|
||||||
return {
|
const nodes = await getCollectionNodes(`${userId}/${n}`);
|
||||||
id: `${userId}/${n}`,
|
return {
|
||||||
nodes,
|
id: `${userId}/${n}`,
|
||||||
}
|
nodes,
|
||||||
}));
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUser(userId: string) {
|
export async function getUser(userId: string) {
|
||||||
const collections = await getUserCollections(userId);
|
const collections = await getUserCollections(userId);
|
||||||
return {
|
return {
|
||||||
id: userId,
|
id: userId,
|
||||||
collections
|
collections,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUsers() {
|
export async function getUsers() {
|
||||||
const nodes = await fs.readdir(path.resolve("../nodes"));
|
const nodes = await fs.readdir(path.resolve("../nodes"));
|
||||||
const users = await Promise.all(nodes.map(async n => {
|
const users = await Promise.all(
|
||||||
const collections = await getUserCollections(n);
|
nodes.map(async (n) => {
|
||||||
return {
|
const collections = await getUserCollections(n);
|
||||||
id: n,
|
return {
|
||||||
collections
|
id: n,
|
||||||
}
|
collections,
|
||||||
}))
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Select } from "@nodes/ui";
|
import { Select } from "@nodarium/ui";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
let activeStore = 0;
|
let activeStore = 0;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte";
|
import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte";
|
||||||
import type { NodeDefinition } from "@nodes/types";
|
import type { NodeDefinition } from "@nodarium/types";
|
||||||
|
|
||||||
export let node: NodeDefinition;
|
export let node: NodeDefinition;
|
||||||
console.log(node);
|
|
||||||
|
|
||||||
let dragging = false;
|
let dragging = false;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import BreadCrumbs from "./BreadCrumbs.svelte";
|
import BreadCrumbs from "./BreadCrumbs.svelte";
|
||||||
import DraggableNode from "./DraggableNode.svelte";
|
import DraggableNode from "./DraggableNode.svelte";
|
||||||
import type { RemoteNodeRegistry } from "@nodes/registry";
|
import type { RemoteNodeRegistry } from "@nodarium/registry";
|
||||||
|
|
||||||
export let registry: RemoteNodeRegistry;
|
export let registry: RemoteNodeRegistry;
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{#if !activeUser}
|
{#if !activeUser}
|
||||||
{#await registry.fetchUsers()}
|
{#await registry.fetchUsers()}
|
||||||
<div>Loading...</div>
|
<div>Loading Users...</div>
|
||||||
{:then users}
|
{:then users}
|
||||||
{#each users as user}
|
{#each users as user}
|
||||||
<button
|
<button
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
{/await}
|
{/await}
|
||||||
{:else if !activeCollection}
|
{:else if !activeCollection}
|
||||||
{#await registry.fetchUser(activeUser)}
|
{#await registry.fetchUser(activeUser)}
|
||||||
<div>Loading...</div>
|
<div>Loading User...</div>
|
||||||
{:then user}
|
{:then user}
|
||||||
{#each user.collections as collection}
|
{#each user.collections as collection}
|
||||||
<button
|
<button
|
||||||
@@ -53,11 +53,11 @@
|
|||||||
{/await}
|
{/await}
|
||||||
{:else if !activeNode}
|
{:else if !activeNode}
|
||||||
{#await registry.fetchCollection(`${activeUser}/${activeCollection}`)}
|
{#await registry.fetchCollection(`${activeUser}/${activeCollection}`)}
|
||||||
<div>Loading...</div>
|
<div>Loading Collection...</div>
|
||||||
{:then collection}
|
{:then collection}
|
||||||
{#each collection.nodes as node}
|
{#each collection.nodes as node}
|
||||||
{#await registry.fetchNodeDefinition(node.id)}
|
{#await registry.fetchNodeDefinition(node.id)}
|
||||||
<div>Loading... {node.id}</div>
|
<div>Loading Node... {node.id}</div>
|
||||||
{:then node}
|
{:then node}
|
||||||
{#if node}
|
{#if node}
|
||||||
<DraggableNode {node} />
|
<DraggableNode {node} />
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
<script lang="ts">
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<span class="spinner"></span>
|
<span class="spinner"></span>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -27,10 +27,12 @@
|
|||||||
function constructPath() {
|
function constructPath() {
|
||||||
max = max !== undefined ? max : Math.max(...points);
|
max = max !== undefined ? max : Math.max(...points);
|
||||||
min = min !== undefined ? min : Math.min(...points);
|
min = min !== undefined ? min : Math.min(...points);
|
||||||
|
const mi = min as number;
|
||||||
|
const ma = max as number;
|
||||||
return points
|
return points
|
||||||
.map((point, i) => {
|
.map((point, i) => {
|
||||||
const x = (i / (points.length - 1)) * 100;
|
const x = (i / (points.length - 1)) * 100;
|
||||||
const y = 100 - ((point - min) / (max - min)) * 100;
|
const y = 100 - ((point - mi) / (ma - mi)) * 100;
|
||||||
return `${x},${y}`;
|
return `${x},${y}`;
|
||||||
})
|
})
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Monitor from "./Monitor.svelte";
|
import Monitor from "./Monitor.svelte";
|
||||||
import { humanizeNumber } from "$lib/helpers";
|
import { humanizeNumber } from "$lib/helpers";
|
||||||
import { Checkbox } from "@nodes/ui";
|
import { Checkbox } from "@nodarium/ui";
|
||||||
import localStore from "$lib/helpers/localStore";
|
import localStore from "$lib/helpers/localStore";
|
||||||
import { type PerformanceData } from "@nodes/utils";
|
import { type PerformanceData } from "@nodarium/utils";
|
||||||
import BarSplit from "./BarSplit.svelte";
|
import BarSplit from "./BarSplit.svelte";
|
||||||
|
|
||||||
export let data: PerformanceData;
|
export let data: PerformanceData;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { humanizeDuration, humanizeNumber } from "$lib/helpers";
|
import { humanizeDuration, humanizeNumber } from "$lib/helpers";
|
||||||
import localStore from "$lib/helpers/localStore";
|
import localStore from "$lib/helpers/localStore";
|
||||||
import SmallGraph from "./SmallGraph.svelte";
|
import SmallGraph from "./SmallGraph.svelte";
|
||||||
import type { PerformanceData, PerformanceStore } from "@nodes/utils";
|
import type { PerformanceData, PerformanceStore } from "@nodarium/utils";
|
||||||
|
|
||||||
export let store: PerformanceStore;
|
export let store: PerformanceStore;
|
||||||
|
|
||||||
@@ -25,7 +25,10 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr on:click={() => ($open.runtime = !$open.runtime)}>
|
<tr
|
||||||
|
style="cursor:pointer;"
|
||||||
|
on:click={() => ($open.runtime = !$open.runtime)}
|
||||||
|
>
|
||||||
<td>{$open.runtime ? "-" : "+"} runtime </td>
|
<td>{$open.runtime ? "-" : "+"} runtime </td>
|
||||||
<td>{humanizeDuration(runtime || 1000)}</td>
|
<td>{humanizeDuration(runtime || 1000)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -37,7 +40,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<tr on:click={() => ($open.fps = !$open.fps)}>
|
<tr style="cursor:pointer;" on:click={() => ($open.fps = !$open.fps)}>
|
||||||
<td>{$open.fps ? "-" : "+"} fps </td>
|
<td>{$open.fps ? "-" : "+"} fps </td>
|
||||||
<td>
|
<td>
|
||||||
{Math.floor(fps[fps.length - 1])}fps
|
{Math.floor(fps[fps.length - 1])}fps
|
||||||
@@ -74,9 +77,6 @@
|
|||||||
border: solid thin var(--outline);
|
border: solid thin var(--outline);
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
tr {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
td {
|
td {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
padding-inline: 8px;
|
padding-inline: 8px;
|
||||||
|
|||||||
@@ -7,11 +7,15 @@
|
|||||||
import type { PerspectiveCamera, Vector3Tuple } from "three";
|
import type { PerspectiveCamera, Vector3Tuple } from "three";
|
||||||
import type { OrbitControls as OrbitControlsType } from "three/examples/jsm/controls/OrbitControls.js";
|
import type { OrbitControls as OrbitControlsType } from "three/examples/jsm/controls/OrbitControls.js";
|
||||||
|
|
||||||
let camera: PerspectiveCamera;
|
let camera = $state<PerspectiveCamera>();
|
||||||
let controls: OrbitControlsType;
|
let controls = $state<OrbitControlsType>();
|
||||||
|
|
||||||
export let center: Vector3;
|
type Props = {
|
||||||
export let centerCamera: boolean = true;
|
center: Vector3;
|
||||||
|
centerCamera: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { center, centerCamera }: Props = $props();
|
||||||
|
|
||||||
const cameraTransform = localStore<{
|
const cameraTransform = localStore<{
|
||||||
camera: Vector3Tuple;
|
camera: Vector3Tuple;
|
||||||
@@ -22,7 +26,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function saveCameraState() {
|
function saveCameraState() {
|
||||||
if (!camera) return;
|
if (!camera || !controls) return;
|
||||||
let cPos = camera.position.toArray();
|
let cPos = camera.position.toArray();
|
||||||
let tPos = controls.target.toArray();
|
let tPos = controls.target.toArray();
|
||||||
// check if tPos is NaN or tPos is NaN
|
// check if tPos is NaN or tPos is NaN
|
||||||
@@ -35,6 +39,7 @@
|
|||||||
|
|
||||||
let isRunning = false;
|
let isRunning = false;
|
||||||
const task = useTask(() => {
|
const task = useTask(() => {
|
||||||
|
if (!controls) return;
|
||||||
let length = center.clone().sub(controls.target).length();
|
let length = center.clone().sub(controls.target).length();
|
||||||
if (length < 0.01 || !centerCamera) {
|
if (length < 0.01 || !centerCamera) {
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
@@ -47,22 +52,24 @@
|
|||||||
});
|
});
|
||||||
task.stop();
|
task.stop();
|
||||||
|
|
||||||
$: if (
|
$effect(() => {
|
||||||
center &&
|
if (
|
||||||
controls &&
|
center &&
|
||||||
centerCamera &&
|
controls &&
|
||||||
(center.x !== controls.target.x ||
|
centerCamera &&
|
||||||
center.y !== controls.target.y ||
|
(center.x !== controls.target.x ||
|
||||||
center.z !== controls.target.z) &&
|
center.y !== controls.target.y ||
|
||||||
!isRunning
|
center.z !== controls.target.z) &&
|
||||||
) {
|
!isRunning
|
||||||
isRunning = true;
|
) {
|
||||||
task.start();
|
isRunning = true;
|
||||||
}
|
task.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
controls.target.fromArray($cameraTransform.target);
|
controls?.target.fromArray($cameraTransform.target);
|
||||||
controls.update();
|
controls?.update();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { T, useTask, useThrelte } from "@threlte/core";
|
import { T, useTask, useThrelte } from "@threlte/core";
|
||||||
import { MeshLineGeometry, MeshLineMaterial, Text } from "@threlte/extras";
|
import {
|
||||||
|
Grid,
|
||||||
|
MeshLineGeometry,
|
||||||
|
MeshLineMaterial,
|
||||||
|
Text,
|
||||||
|
} from "@threlte/extras";
|
||||||
import {
|
import {
|
||||||
type Group,
|
type Group,
|
||||||
type BufferGeometry,
|
type BufferGeometry,
|
||||||
@@ -9,7 +14,6 @@
|
|||||||
Box3,
|
Box3,
|
||||||
Mesh,
|
Mesh,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
Color,
|
|
||||||
} from "three";
|
} from "three";
|
||||||
import { appSettings } from "../settings/app-settings.svelte";
|
import { appSettings } from "../settings/app-settings.svelte";
|
||||||
import Camera from "./Camera.svelte";
|
import Camera from "./Camera.svelte";
|
||||||
@@ -42,11 +46,9 @@
|
|||||||
export const invalidate = function () {
|
export const invalidate = function () {
|
||||||
if (scene) {
|
if (scene) {
|
||||||
geometries = scene.children
|
geometries = scene.children
|
||||||
.filter(
|
.filter((child) => "geometry" in child && child.isObject3D)
|
||||||
(child) => "geometry" in child && child.isObject3D && child.geometry,
|
|
||||||
)
|
|
||||||
.map((child) => {
|
.map((child) => {
|
||||||
return child.geometry;
|
return (child as Mesh).geometry;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +74,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const wireframe = appSettings.debug.wireframe;
|
const wireframe = appSettings.value.debug.wireframe;
|
||||||
scene.traverse(function (child) {
|
scene.traverse(function (child) {
|
||||||
if (isMesh(child) && isMatCapMaterial(child.material)) {
|
if (isMesh(child) && isMatCapMaterial(child.material)) {
|
||||||
child.material.wireframe = wireframe;
|
child.material.wireframe = wireframe;
|
||||||
@@ -92,18 +94,24 @@
|
|||||||
|
|
||||||
<Camera {center} {centerCamera} />
|
<Camera {center} {centerCamera} />
|
||||||
|
|
||||||
{#if appSettings.showGrid}
|
{#if appSettings.value.showGrid}
|
||||||
<T.GridHelper
|
<Grid
|
||||||
args={[20, 20]}
|
cellColor={colors["outline"]}
|
||||||
colorGrid={colors["outline"]}
|
cellThickness={0.7}
|
||||||
colorCenterLine={new Color("red")}
|
infiniteGrid
|
||||||
|
sectionThickness={0.7}
|
||||||
|
sectionDistance={2}
|
||||||
|
sectionColor={colors["outline"]}
|
||||||
|
fadeDistance={50}
|
||||||
|
fadeStrength={10}
|
||||||
|
fadeOrigin={new Vector3(0, 0, 0)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<T.Group>
|
<T.Group>
|
||||||
{#if geometries}
|
{#if geometries}
|
||||||
{#each geometries as geo}
|
{#each geometries as geo}
|
||||||
{#if appSettings.debug.showIndices}
|
{#if appSettings.value.debug.showIndices}
|
||||||
{#each geo.attributes.position.array as _, i}
|
{#each geo.attributes.position.array as _, i}
|
||||||
{#if i % 3 === 0}
|
{#if i % 3 === 0}
|
||||||
<Text fontSize={0.25} position={getPosition(geo, i)} />
|
<Text fontSize={0.25} position={getPosition(geo, i)} />
|
||||||
@@ -111,7 +119,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if appSettings.debug.showVertices}
|
{#if appSettings.value.debug.showVertices}
|
||||||
<T.Points visible={true}>
|
<T.Points visible={true}>
|
||||||
<T is={geo} />
|
<T is={geo} />
|
||||||
<T.PointsMaterial size={0.25} />
|
<T.PointsMaterial size={0.25} />
|
||||||
@@ -123,7 +131,7 @@
|
|||||||
<T.Group bind:ref={scene}></T.Group>
|
<T.Group bind:ref={scene}></T.Group>
|
||||||
</T.Group>
|
</T.Group>
|
||||||
|
|
||||||
{#if appSettings.debug.showStemLines && lines}
|
{#if appSettings.value.debug.showStemLines && lines}
|
||||||
{#each lines as line}
|
{#each lines as line}
|
||||||
<T.Mesh>
|
<T.Mesh>
|
||||||
<MeshLineGeometry points={line} />
|
<MeshLineGeometry points={line} />
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import { Canvas } from "@threlte/core";
|
import { Canvas } from "@threlte/core";
|
||||||
import Scene from "./Scene.svelte";
|
import Scene from "./Scene.svelte";
|
||||||
import { Vector3 } from "three";
|
import { Vector3 } from "three";
|
||||||
import { decodeFloat, splitNestedArray } from "@nodes/utils";
|
import { decodeFloat, splitNestedArray } from "@nodarium/utils";
|
||||||
import type { PerformanceStore } from "@nodes/utils";
|
import type { PerformanceStore } from "@nodarium/utils";
|
||||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||||
import SmallPerformanceViewer from "$lib/performance/SmallPerformanceViewer.svelte";
|
import SmallPerformanceViewer from "$lib/performance/SmallPerformanceViewer.svelte";
|
||||||
import { MeshMatcapMaterial, TextureLoader, type Group } from "three";
|
import { MeshMatcapMaterial, TextureLoader, type Group } from "three";
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
const inputs = splitNestedArray(result);
|
const inputs = splitNestedArray(result);
|
||||||
perf.endPoint();
|
perf.endPoint();
|
||||||
|
|
||||||
if (appSettings.debug.showStemLines) {
|
if (appSettings.value.debug.showStemLines) {
|
||||||
perf.addPoint("create-lines");
|
perf.addPoint("create-lines");
|
||||||
lines = inputs
|
lines = inputs
|
||||||
.map((input) => {
|
.map((input) => {
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if appSettings.debug.showPerformancePanel}
|
{#if appSettings.value.debug.showPerformancePanel}
|
||||||
<SmallPerformanceViewer {fps} store={perf} />
|
<SmallPerformanceViewer {fps} store={perf} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,27 @@
|
|||||||
import { fastHashArrayBuffer } from "@nodes/utils";
|
import { fastHashArrayBuffer } from "@nodarium/utils";
|
||||||
import { BufferAttribute, BufferGeometry, Float32BufferAttribute, Group, InstancedMesh, Material, Matrix4, Mesh } from "three";
|
import {
|
||||||
|
BufferAttribute,
|
||||||
|
BufferGeometry,
|
||||||
|
Float32BufferAttribute,
|
||||||
|
Group,
|
||||||
|
InstancedMesh,
|
||||||
|
Material,
|
||||||
|
Matrix4,
|
||||||
|
Mesh,
|
||||||
|
} from "three";
|
||||||
|
|
||||||
function fastArrayHash(arr: ArrayBuffer) {
|
function fastArrayHash(arr: Int32Array) {
|
||||||
let ints = new Uint8Array(arr);
|
const sampleDistance = Math.max(Math.floor(arr.length / 100), 1);
|
||||||
|
const sampleCount = Math.floor(arr.length / sampleDistance);
|
||||||
|
|
||||||
const sampleDistance = Math.max(Math.floor(ints.length / 100), 1);
|
let hash = new Int32Array(sampleCount);
|
||||||
const sampleCount = Math.floor(ints.length / sampleDistance);
|
|
||||||
|
|
||||||
let hash = new Uint8Array(sampleCount);
|
|
||||||
|
|
||||||
for (let i = 0; i < sampleCount; i++) {
|
for (let i = 0; i < sampleCount; i++) {
|
||||||
const index = i * sampleDistance;
|
const index = i * sampleDistance;
|
||||||
hash[i] = ints[index];
|
hash[i] = arr[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
return fastHashArrayBuffer(hash.buffer);
|
return fastHashArrayBuffer(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createGeometryPool(parentScene: Group, material: Material) {
|
export function createGeometryPool(parentScene: Group, material: Material) {
|
||||||
@@ -26,8 +33,10 @@ export function createGeometryPool(parentScene: Group, material: Material) {
|
|||||||
let totalVertices = 0;
|
let totalVertices = 0;
|
||||||
let totalFaces = 0;
|
let totalFaces = 0;
|
||||||
|
|
||||||
function updateSingleGeometry(data: Int32Array, existingMesh: Mesh | null = null) {
|
function updateSingleGeometry(
|
||||||
|
data: Int32Array,
|
||||||
|
existingMesh: Mesh | null = null,
|
||||||
|
) {
|
||||||
let hash = fastArrayHash(data);
|
let hash = fastArrayHash(data);
|
||||||
|
|
||||||
let geometry = existingMesh ? existingMesh.geometry : new BufferGeometry();
|
let geometry = existingMesh ? existingMesh.geometry : new BufferGeometry();
|
||||||
@@ -50,11 +59,7 @@ export function createGeometryPool(parentScene: Group, material: Material) {
|
|||||||
index = indicesEnd;
|
index = indicesEnd;
|
||||||
|
|
||||||
// Vertices
|
// Vertices
|
||||||
const vertices = new Float32Array(
|
const vertices = new Float32Array(data.buffer, index * 4, vertexCount * 3);
|
||||||
data.buffer,
|
|
||||||
index * 4,
|
|
||||||
vertexCount * 3,
|
|
||||||
);
|
|
||||||
index = index + vertexCount * 3;
|
index = index + vertexCount * 3;
|
||||||
|
|
||||||
let posAttribute = geometry.getAttribute(
|
let posAttribute = geometry.getAttribute(
|
||||||
@@ -71,11 +76,7 @@ export function createGeometryPool(parentScene: Group, material: Material) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const normals = new Float32Array(
|
const normals = new Float32Array(data.buffer, index * 4, vertexCount * 3);
|
||||||
data.buffer,
|
|
||||||
index * 4,
|
|
||||||
vertexCount * 3,
|
|
||||||
);
|
|
||||||
index = index + vertexCount * 3;
|
index = index + vertexCount * 3;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -109,11 +110,8 @@ export function createGeometryPool(parentScene: Group, material: Material) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
update(
|
update(newData: Int32Array[]) {
|
||||||
newData: Int32Array[],
|
|
||||||
) {
|
|
||||||
totalVertices = 0;
|
totalVertices = 0;
|
||||||
totalFaces = 0;
|
totalFaces = 0;
|
||||||
for (let i = 0; i < Math.max(newData.length, meshes.length); i++) {
|
for (let i = 0; i < Math.max(newData.length, meshes.length); i++) {
|
||||||
@@ -127,11 +125,14 @@ export function createGeometryPool(parentScene: Group, material: Material) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { totalVertices, totalFaces };
|
return { totalVertices, totalFaces };
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createInstancedGeometryPool(parentScene: Group, material: Material) {
|
export function createInstancedGeometryPool(
|
||||||
|
parentScene: Group,
|
||||||
|
material: Material,
|
||||||
|
) {
|
||||||
const scene = new Group();
|
const scene = new Group();
|
||||||
parentScene.add(scene);
|
parentScene.add(scene);
|
||||||
|
|
||||||
@@ -139,19 +140,25 @@ export function createInstancedGeometryPool(parentScene: Group, material: Materi
|
|||||||
let totalVertices = 0;
|
let totalVertices = 0;
|
||||||
let totalFaces = 0;
|
let totalFaces = 0;
|
||||||
|
|
||||||
function updateSingleInstance(data: Int32Array, existingInstance: InstancedMesh | null = null) {
|
function updateSingleInstance(
|
||||||
|
data: Int32Array,
|
||||||
|
existingInstance: InstancedMesh | null = null,
|
||||||
|
) {
|
||||||
let hash = fastArrayHash(data);
|
let hash = fastArrayHash(data);
|
||||||
|
|
||||||
let geometry = existingInstance ? existingInstance.geometry : new BufferGeometry();
|
let geometry = existingInstance
|
||||||
|
? existingInstance.geometry
|
||||||
|
: new BufferGeometry();
|
||||||
|
|
||||||
// Extract data from the encoded array
|
// Extract data from the encoded array
|
||||||
let index = 0;
|
let index = 0;
|
||||||
const geometryType = data[index++];
|
// const geometryType = data[index++];
|
||||||
|
index++;
|
||||||
const vertexCount = data[index++];
|
const vertexCount = data[index++];
|
||||||
const faceCount = data[index++];
|
const faceCount = data[index++];
|
||||||
const instanceCount = data[index++];
|
const instanceCount = data[index++];
|
||||||
const stemDepth = data[index++];
|
// const stemDepth = data[index++];
|
||||||
|
index++;
|
||||||
totalVertices += vertexCount * instanceCount;
|
totalVertices += vertexCount * instanceCount;
|
||||||
totalFaces += faceCount * instanceCount;
|
totalFaces += faceCount * instanceCount;
|
||||||
|
|
||||||
@@ -168,11 +175,7 @@ export function createInstancedGeometryPool(parentScene: Group, material: Materi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vertices
|
// Vertices
|
||||||
const vertices = new Float32Array(
|
const vertices = new Float32Array(data.buffer, index * 4, vertexCount * 3);
|
||||||
data.buffer,
|
|
||||||
index * 4,
|
|
||||||
vertexCount * 3,
|
|
||||||
);
|
|
||||||
index = index + vertexCount * 3;
|
index = index + vertexCount * 3;
|
||||||
let posAttribute = geometry.getAttribute(
|
let posAttribute = geometry.getAttribute(
|
||||||
"position",
|
"position",
|
||||||
@@ -187,11 +190,7 @@ export function createInstancedGeometryPool(parentScene: Group, material: Materi
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const normals = new Float32Array(
|
const normals = new Float32Array(data.buffer, index * 4, vertexCount * 3);
|
||||||
data.buffer,
|
|
||||||
index * 4,
|
|
||||||
vertexCount * 3,
|
|
||||||
);
|
|
||||||
index = index + vertexCount * 3;
|
index = index + vertexCount * 3;
|
||||||
const normalsAttribute = geometry.getAttribute(
|
const normalsAttribute = geometry.getAttribute(
|
||||||
"normal",
|
"normal",
|
||||||
@@ -203,20 +202,20 @@ export function createInstancedGeometryPool(parentScene: Group, material: Materi
|
|||||||
geometry.setAttribute("normal", new Float32BufferAttribute(normals, 3));
|
geometry.setAttribute("normal", new Float32BufferAttribute(normals, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingInstance && instanceCount > existingInstance.geometry.userData.count) {
|
if (
|
||||||
console.log("recreating instance")
|
existingInstance &&
|
||||||
|
instanceCount > existingInstance.geometry.userData.count
|
||||||
|
) {
|
||||||
scene.remove(existingInstance);
|
scene.remove(existingInstance);
|
||||||
instances.splice(instances.indexOf(existingInstance), 1);
|
instances.splice(instances.indexOf(existingInstance), 1);
|
||||||
existingInstance = new InstancedMesh(geometry, material, instanceCount);
|
existingInstance = new InstancedMesh(geometry, material, instanceCount);
|
||||||
scene.add(existingInstance)
|
scene.add(existingInstance);
|
||||||
instances.push(existingInstance)
|
instances.push(existingInstance);
|
||||||
} else if (!existingInstance) {
|
} else if (!existingInstance) {
|
||||||
console.log("creating instance")
|
|
||||||
existingInstance = new InstancedMesh(geometry, material, instanceCount);
|
existingInstance = new InstancedMesh(geometry, material, instanceCount);
|
||||||
scene.add(existingInstance)
|
scene.add(existingInstance);
|
||||||
instances.push(existingInstance)
|
instances.push(existingInstance);
|
||||||
} else {
|
} else {
|
||||||
console.log("updating instance")
|
|
||||||
existingInstance.count = instanceCount;
|
existingInstance.count = instanceCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,28 +224,31 @@ export function createInstancedGeometryPool(parentScene: Group, material: Materi
|
|||||||
const matrices = new Float32Array(
|
const matrices = new Float32Array(
|
||||||
data.buffer,
|
data.buffer,
|
||||||
index * 4,
|
index * 4,
|
||||||
instanceCount * 16);
|
instanceCount * 16,
|
||||||
|
);
|
||||||
|
|
||||||
for (let i = 0; i < instanceCount; i++) {
|
for (let i = 0; i < instanceCount; i++) {
|
||||||
const matrix = new Matrix4().fromArray(matrices.subarray(i * 16, i * 16 + 16));
|
const matrix = new Matrix4().fromArray(
|
||||||
|
matrices.subarray(i * 16, i * 16 + 16),
|
||||||
|
);
|
||||||
existingInstance.setMatrixAt(i, matrix);
|
existingInstance.setMatrixAt(i, matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
geometry.userData = {
|
geometry.userData = {
|
||||||
vertexCount,
|
vertexCount,
|
||||||
faceCount,
|
faceCount,
|
||||||
count: Math.max(instanceCount, existingInstance.geometry.userData.count || 0),
|
count: Math.max(
|
||||||
|
instanceCount,
|
||||||
|
existingInstance.geometry.userData.count || 0,
|
||||||
|
),
|
||||||
hash,
|
hash,
|
||||||
};
|
};
|
||||||
|
|
||||||
existingInstance.instanceMatrix.needsUpdate = true;
|
existingInstance.instanceMatrix.needsUpdate = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
update(
|
update(newData: Int32Array[]) {
|
||||||
newData: Int32Array[],
|
|
||||||
) {
|
|
||||||
totalVertices = 0;
|
totalVertices = 0;
|
||||||
totalFaces = 0;
|
totalFaces = 0;
|
||||||
for (let i = 0; i < Math.max(newData.length, instances.length); i++) {
|
for (let i = 0; i < Math.max(newData.length, instances.length); i++) {
|
||||||
@@ -260,6 +262,6 @@ export function createInstancedGeometryPool(parentScene: Group, material: Materi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { totalVertices, totalFaces };
|
return { totalVertices, totalFaces };
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Graph, RuntimeExecutor } from "@nodes/types";
|
import type { Graph, RuntimeExecutor } from "@nodarium/types";
|
||||||
|
|
||||||
export class RemoteRuntimeExecutor implements RuntimeExecutor {
|
export class RemoteRuntimeExecutor implements RuntimeExecutor {
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type SyncCache } from "@nodes/types";
|
import { type SyncCache } from "@nodarium/types";
|
||||||
|
|
||||||
export class MemoryRuntimeCache implements SyncCache {
|
export class MemoryRuntimeCache implements SyncCache {
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
import type { Graph, NodeDefinition, NodeInput, NodeRegistry, RuntimeExecutor, SyncCache } from "@nodes/types";
|
import type {
|
||||||
import { concatEncodedArrays, createLogger, encodeFloat, fastHashArrayBuffer, type PerformanceStore } from "@nodes/utils";
|
Graph,
|
||||||
|
Node,
|
||||||
|
NodeDefinition,
|
||||||
|
NodeInput,
|
||||||
|
NodeRegistry,
|
||||||
|
RuntimeExecutor,
|
||||||
|
SyncCache,
|
||||||
|
} from "@nodarium/types";
|
||||||
|
import {
|
||||||
|
concatEncodedArrays,
|
||||||
|
createLogger,
|
||||||
|
encodeFloat,
|
||||||
|
fastHashArrayBuffer,
|
||||||
|
type PerformanceStore,
|
||||||
|
} from "@nodarium/utils";
|
||||||
|
|
||||||
const log = createLogger("runtime-executor");
|
const log = createLogger("runtime-executor");
|
||||||
log.mute()
|
log.mute();
|
||||||
|
|
||||||
function getValue(input: NodeInput, value?: unknown) {
|
function getValue(input: NodeInput, value?: unknown) {
|
||||||
if (value === undefined && "value" in input) {
|
if (value === undefined && "value" in input) {
|
||||||
value = input.value
|
value = input.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.type === "float") {
|
if (input.type === "float") {
|
||||||
@@ -15,7 +29,13 @@ function getValue(input: NodeInput, value?: unknown) {
|
|||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
if (input.type === "vec3") {
|
if (input.type === "vec3") {
|
||||||
return [0, value.length + 1, ...value.map(v => encodeFloat(v)), 1, 1] as number[];
|
return [
|
||||||
|
0,
|
||||||
|
value.length + 1,
|
||||||
|
...value.map((v) => encodeFloat(v)),
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
] as number[];
|
||||||
}
|
}
|
||||||
return [0, value.length + 1, ...value, 1, 1] as number[];
|
return [0, value.length + 1, ...value, 1, 1] as number[];
|
||||||
}
|
}
|
||||||
@@ -36,22 +56,25 @@ function getValue(input: NodeInput, value?: unknown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||||
|
|
||||||
private definitionMap: Map<string, NodeDefinition> = new Map();
|
private definitionMap: Map<string, NodeDefinition> = new Map();
|
||||||
|
|
||||||
private randomSeed = Math.floor(Math.random() * 100000000);
|
private seed = Math.floor(Math.random() * 100000000);
|
||||||
|
|
||||||
perf?: PerformanceStore;
|
perf?: PerformanceStore;
|
||||||
|
|
||||||
constructor(private registry: NodeRegistry, private cache?: SyncCache<Int32Array>) { }
|
constructor(
|
||||||
|
private registry: NodeRegistry,
|
||||||
|
private cache?: SyncCache<Int32Array>,
|
||||||
|
) {
|
||||||
|
this.cache = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private async getNodeDefinitions(graph: Graph) {
|
private async getNodeDefinitions(graph: Graph) {
|
||||||
|
|
||||||
if (this.registry.status !== "ready") {
|
if (this.registry.status !== "ready") {
|
||||||
throw new Error("Node registry is not ready");
|
throw new Error("Node registry is not ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.registry.load(graph.nodes.map(node => node.type));
|
await this.registry.load(graph.nodes.map((node) => node.type));
|
||||||
|
|
||||||
const typeMap = new Map<string, NodeDefinition>();
|
const typeMap = new Map<string, NodeDefinition>();
|
||||||
for (const node of graph.nodes) {
|
for (const node of graph.nodes) {
|
||||||
@@ -66,18 +89,22 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async addMetaData(graph: Graph) {
|
private async addMetaData(graph: Graph) {
|
||||||
|
|
||||||
// First, lets check if all nodes have a definition
|
// First, lets check if all nodes have a definition
|
||||||
this.definitionMap = await this.getNodeDefinitions(graph);
|
this.definitionMap = await this.getNodeDefinitions(graph);
|
||||||
|
|
||||||
const outputNode = graph.nodes.find(node => node.type.endsWith("/output"));
|
const outputNode = graph.nodes.find((node) =>
|
||||||
|
node.type.endsWith("/output"),
|
||||||
|
) as Node;
|
||||||
if (!outputNode) {
|
if (!outputNode) {
|
||||||
throw new Error("No output node found");
|
throw new Error("No output node found");
|
||||||
}
|
}
|
||||||
|
|
||||||
outputNode.tmp = outputNode.tmp || {};
|
outputNode.tmp = outputNode.tmp || {};
|
||||||
outputNode.tmp.depth = 0;
|
outputNode.tmp.depth = 0;
|
||||||
|
|
||||||
const nodeMap = new Map(graph.nodes.map(node => [node.id, node]));
|
const nodeMap = new Map<number, Node>(
|
||||||
|
graph.nodes.map((node) => [node.id, node]),
|
||||||
|
);
|
||||||
|
|
||||||
// loop through all edges and assign the parent and child nodes to each node
|
// loop through all edges and assign the parent and child nodes to each node
|
||||||
for (const edge of graph.edges) {
|
for (const edge of graph.edges) {
|
||||||
@@ -96,7 +123,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodes = []
|
const nodes = [];
|
||||||
|
|
||||||
// loop through all the nodes and assign each nodes its depth
|
// loop through all the nodes and assign each nodes its depth
|
||||||
const stack = [outputNode];
|
const stack = [outputNode];
|
||||||
@@ -125,7 +152,6 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async execute(graph: Graph, settings: Record<string, unknown>) {
|
async execute(graph: Graph, settings: Record<string, unknown>) {
|
||||||
|
|
||||||
this.perf?.addPoint("runtime");
|
this.perf?.addPoint("runtime");
|
||||||
|
|
||||||
let a = performance.now();
|
let a = performance.now();
|
||||||
@@ -137,71 +163,74 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
this.perf?.addPoint("collect-metadata", b - a);
|
this.perf?.addPoint("collect-metadata", b - a);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Here we sort the nodes into buckets, which we then execute one by one
|
* Here we sort the nodes into buckets, which we then execute one by one
|
||||||
* +-b2-+-b1-+---b0---+
|
* +-b2-+-b1-+---b0---+
|
||||||
* | | | |
|
* | | | |
|
||||||
* | n3 | n2 | Output |
|
* | n3 | n2 | Output |
|
||||||
* | n6 | n4 | Level |
|
* | n6 | n4 | Level |
|
||||||
* | | n5 | |
|
* | | n5 | |
|
||||||
* | | | |
|
* | | | |
|
||||||
* +----+----+--------+
|
* +----+----+--------+
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// we execute the nodes from the bottom up
|
// we execute the nodes from the bottom up
|
||||||
const sortedNodes = nodes.sort((a, b) => (b.tmp?.depth || 0) - (a.tmp?.depth || 0));
|
const sortedNodes = nodes.sort(
|
||||||
|
(a, b) => (b.tmp?.depth || 0) - (a.tmp?.depth || 0),
|
||||||
|
);
|
||||||
|
|
||||||
// here we store the intermediate results of the nodes
|
// here we store the intermediate results of the nodes
|
||||||
const results: Record<string, Int32Array> = {};
|
const results: Record<string, Int32Array> = {};
|
||||||
|
|
||||||
for (const node of sortedNodes) {
|
if (settings["randomSeed"]) {
|
||||||
|
this.seed = Math.floor(Math.random() * 100000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const node of sortedNodes) {
|
||||||
const node_type = this.definitionMap.get(node.type)!;
|
const node_type = this.definitionMap.get(node.type)!;
|
||||||
|
|
||||||
if (!node_type || !node.tmp || !node_type.execute) {
|
if (!node_type || !node.tmp || !node_type.execute) {
|
||||||
log.warn(`Node ${node.id} has no definition`);
|
log.warn(`Node ${node.id} has no definition`);
|
||||||
continue;
|
continue;
|
||||||
};
|
}
|
||||||
|
|
||||||
a = performance.now();
|
a = performance.now();
|
||||||
|
|
||||||
// Collect the inputs for the node
|
// Collect the inputs for the node
|
||||||
const inputs = Object.entries(node_type.inputs || {}).map(([key, input]) => {
|
const inputs = Object.entries(node_type.inputs || {}).map(
|
||||||
|
([key, input]) => {
|
||||||
if (input.type === "seed") {
|
if (input.type === "seed") {
|
||||||
if (settings["randomSeed"] === true) {
|
return this.seed;
|
||||||
return Math.floor(Math.random() * 100000000)
|
|
||||||
} else {
|
|
||||||
return this.randomSeed
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If the input is linked to a setting, we use that value
|
// If the input is linked to a setting, we use that value
|
||||||
if (input.setting) {
|
if (input.setting) {
|
||||||
return getValue(input, settings[input.setting]);
|
return getValue(input, settings[input.setting]);
|
||||||
}
|
|
||||||
|
|
||||||
// check if the input is connected to another node
|
|
||||||
const inputNode = node.tmp?.inputNodes?.[key];
|
|
||||||
if (inputNode) {
|
|
||||||
if (results[inputNode.id] === undefined) {
|
|
||||||
throw new Error(`Node ${node.type} is missing input from node ${inputNode.type}`);
|
|
||||||
}
|
}
|
||||||
return results[inputNode.id];
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the value is stored in the node itself, we use that value
|
// check if the input is connected to another node
|
||||||
if (node.props?.[key] !== undefined) {
|
const inputNode = node.tmp?.inputNodes?.[key];
|
||||||
return getValue(input, node.props[key]);
|
if (inputNode) {
|
||||||
}
|
if (results[inputNode.id] === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`Node ${node.type} is missing input from node ${inputNode.type}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return results[inputNode.id];
|
||||||
|
}
|
||||||
|
|
||||||
return getValue(input);
|
// If the value is stored in the node itself, we use that value
|
||||||
});
|
if (node.props?.[key] !== undefined) {
|
||||||
|
return getValue(input, node.props[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getValue(input);
|
||||||
|
},
|
||||||
|
);
|
||||||
b = performance.now();
|
b = performance.now();
|
||||||
|
|
||||||
this.perf?.addPoint("collected-inputs", b - a);
|
this.perf?.addPoint("collected-inputs", b - a);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
a = performance.now();
|
a = performance.now();
|
||||||
const encoded_inputs = concatEncodedArrays(inputs);
|
const encoded_inputs = concatEncodedArrays(inputs);
|
||||||
b = performance.now();
|
b = performance.now();
|
||||||
@@ -234,13 +263,10 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
this.perf?.addPoint("node/" + node_type.id, b - a);
|
this.perf?.addPoint("node/" + node_type.id, b - a);
|
||||||
log.log("Result:", results[node.id]);
|
log.log("Result:", results[node.id]);
|
||||||
log.groupEnd();
|
log.groupEnd();
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.groupEnd();
|
log.groupEnd();
|
||||||
log.error(`Error executing node ${node_type.id || node.id}`, e);
|
log.error(`Error executing node ${node_type.id || node.id}`, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the result of the parent of the output node
|
// return the result of the parent of the output node
|
||||||
@@ -253,11 +279,9 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
this.perf?.endPoint("runtime");
|
this.perf?.endPoint("runtime");
|
||||||
|
|
||||||
return res as unknown as Int32Array;
|
return res as unknown as Int32Array;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getPerformanceData() {
|
getPerformanceData() {
|
||||||
return this.perf?.get();
|
return this.perf?.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
import { MemoryRuntimeExecutor } from "./runtime-executor";
|
import { MemoryRuntimeExecutor } from "./runtime-executor";
|
||||||
import { RemoteNodeRegistry, IndexDBCache } from "@nodes/registry";
|
import { RemoteNodeRegistry, IndexDBCache } from "@nodarium/registry";
|
||||||
import type { Graph } from "@nodes/types";
|
import type { Graph } from "@nodarium/types";
|
||||||
import { createPerformanceStore } from "@nodes/utils";
|
import { createPerformanceStore } from "@nodarium/utils";
|
||||||
import { MemoryRuntimeCache } from "./runtime-executor-cache";
|
|
||||||
|
|
||||||
const cache = new MemoryRuntimeCache();
|
|
||||||
const indexDbCache = new IndexDBCache("node-registry");
|
const indexDbCache = new IndexDBCache("node-registry");
|
||||||
const nodeRegistry = new RemoteNodeRegistry("");
|
const nodeRegistry = new RemoteNodeRegistry("", indexDbCache);
|
||||||
nodeRegistry.cache = indexDbCache;
|
|
||||||
const executor = new MemoryRuntimeExecutor(nodeRegistry, cache);
|
const executor = new MemoryRuntimeExecutor(nodeRegistry);
|
||||||
|
|
||||||
const performanceStore = createPerformanceStore();
|
const performanceStore = createPerformanceStore();
|
||||||
executor.perf = performanceStore;
|
executor.perf = performanceStore;
|
||||||
|
|
||||||
export async function executeGraph(graph: Graph, settings: Record<string, unknown>): Promise<Int32Array> {
|
export async function executeGraph(
|
||||||
|
graph: Graph,
|
||||||
|
settings: Record<string, unknown>,
|
||||||
|
): Promise<Int32Array> {
|
||||||
await nodeRegistry.load(graph.nodes.map((n) => n.type));
|
await nodeRegistry.load(graph.nodes.map((n) => n.type));
|
||||||
performanceStore.startRun();
|
performanceStore.startRun();
|
||||||
let res = await executor.execute(graph, settings);
|
let res = await executor.execute(graph, settings);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// <reference types="vite-plugin-comlink/client" />
|
/// <reference types="vite-plugin-comlink/client" />
|
||||||
import type { Graph, RuntimeExecutor } from "@nodes/types";
|
import type { Graph, RuntimeExecutor } from "@nodarium/types";
|
||||||
|
|
||||||
|
|
||||||
export class WorkerRuntimeExecutor implements RuntimeExecutor {
|
export class WorkerRuntimeExecutor implements RuntimeExecutor {
|
||||||
|
|||||||
211
app/src/lib/settings/NestedSettings.svelte
Normal file
211
app/src/lib/settings/NestedSettings.svelte
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import NestedSettings from "./NestedSettings.svelte";
|
||||||
|
import { localState } from "$lib/helpers/localState.svelte";
|
||||||
|
import type { NodeInput } from "@nodarium/types";
|
||||||
|
import Input from "@nodarium/ui";
|
||||||
|
|
||||||
|
type Button = { type: "button"; callback: () => void; label?: string };
|
||||||
|
|
||||||
|
type InputType = NodeInput | Button;
|
||||||
|
|
||||||
|
type SettingsNode = InputType | SettingsGroup;
|
||||||
|
|
||||||
|
interface SettingsGroup {
|
||||||
|
title?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingsType = Record<string, SettingsNode>;
|
||||||
|
|
||||||
|
type SettingsValue = Record<
|
||||||
|
string,
|
||||||
|
Record<string, unknown> | string | number | boolean | number[]
|
||||||
|
>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id: string;
|
||||||
|
key?: string;
|
||||||
|
value: SettingsValue;
|
||||||
|
type: SettingsType;
|
||||||
|
depth?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Local persistent state for <details> sections
|
||||||
|
const openSections = localState<Record<string, boolean>>("open-details", {});
|
||||||
|
|
||||||
|
let { id, key = "", value = $bindable(), type, depth = 0 }: Props = $props();
|
||||||
|
|
||||||
|
function isNodeInput(v: SettingsNode | undefined): v is InputType {
|
||||||
|
return !!v && typeof v === "object" && "type" in v;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultValue(): unknown {
|
||||||
|
if (key === "" || key === "title") return;
|
||||||
|
|
||||||
|
const node = type[key];
|
||||||
|
|
||||||
|
if (!isNodeInput(node)) return;
|
||||||
|
|
||||||
|
const anyNode = node as any;
|
||||||
|
|
||||||
|
// select input: use index into options
|
||||||
|
if (Array.isArray(anyNode.options)) {
|
||||||
|
if (value?.[key] !== undefined) {
|
||||||
|
return anyNode.options.indexOf(value[key]);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value?.[key] !== undefined) return value[key];
|
||||||
|
|
||||||
|
if ("value" in node && anyNode.value !== undefined) {
|
||||||
|
return anyNode.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (node.type) {
|
||||||
|
case "boolean":
|
||||||
|
return 0;
|
||||||
|
case "float":
|
||||||
|
return 0.5;
|
||||||
|
case "integer":
|
||||||
|
case "select":
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let internalValue = $state(getDefaultValue());
|
||||||
|
|
||||||
|
let open = $state(openSections.value[id]);
|
||||||
|
|
||||||
|
// Persist <details> open/closed state for groups
|
||||||
|
if (depth > 0 && !isNodeInput(type[key!])) {
|
||||||
|
$effect(() => {
|
||||||
|
if (open !== undefined) {
|
||||||
|
openSections.value[id] = open;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync internalValue back into `value`
|
||||||
|
$effect(() => {
|
||||||
|
if (key === "" || internalValue === undefined) return;
|
||||||
|
|
||||||
|
const node = type[key];
|
||||||
|
|
||||||
|
if (
|
||||||
|
isNodeInput(node) &&
|
||||||
|
Array.isArray((node as any).options) &&
|
||||||
|
typeof internalValue === "number"
|
||||||
|
) {
|
||||||
|
value[key] = (node as any)?.options?.[internalValue] as any;
|
||||||
|
} else {
|
||||||
|
value[key] = internalValue as any;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if key && isNodeInput(type?.[key])}
|
||||||
|
<!-- Leaf input -->
|
||||||
|
<div class="input input-{type[key].type}" class:first-level={depth === 1}>
|
||||||
|
{#if type[key].type === "button"}
|
||||||
|
<button onclick={() => "callback" in type[key] && type[key].callback()}>
|
||||||
|
{type[key].label || key}
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
{#if type[key].label !== ""}
|
||||||
|
<label for={id}>{type[key].label || key}</label>
|
||||||
|
{/if}
|
||||||
|
<Input {id} input={type[key]} bind:value={internalValue} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else if depth === 0}
|
||||||
|
<!-- Root: iterate over top-level keys -->
|
||||||
|
{#each Object.keys(type ?? {}).filter((k) => k !== "title") as childKey}
|
||||||
|
<NestedSettings
|
||||||
|
id={`${id}.${childKey}`}
|
||||||
|
key={childKey}
|
||||||
|
bind:value
|
||||||
|
{type}
|
||||||
|
depth={depth + 1}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
<hr />
|
||||||
|
{:else if key && type?.[key]}
|
||||||
|
<!-- Group -->
|
||||||
|
{#if depth > 0}
|
||||||
|
<hr />
|
||||||
|
{/if}
|
||||||
|
<details bind:open>
|
||||||
|
<summary><p>{(type[key] as SettingsGroup).title || key}</p></summary>
|
||||||
|
<div class="content">
|
||||||
|
{#each Object.keys(type[key] as SettingsGroup).filter((k) => k !== "title") as childKey}
|
||||||
|
<NestedSettings
|
||||||
|
id={`${id}.${childKey}`}
|
||||||
|
key={childKey}
|
||||||
|
bind:value={value[key] as SettingsValue}
|
||||||
|
type={type[key] as unknown as SettingsType}
|
||||||
|
depth={depth + 1}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
summary {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary > p {
|
||||||
|
display: inline;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
details {
|
||||||
|
padding: 1em;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-left: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-boolean {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-boolean > label {
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.first-level.input {
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
position: absolute;
|
||||||
|
margin: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
border: none;
|
||||||
|
border-bottom: solid thin var(--outline);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,14 @@
|
|||||||
import { localState } from "$lib/helpers/localState.svelte";
|
import { localState } from "$lib/helpers/localState.svelte";
|
||||||
import type { NodeInput } from "@nodes/types";
|
|
||||||
|
|
||||||
const themes = ["dark", "light", "catppuccin", "solarized", "high-contrast", "nord", "dracula"];
|
const themes = [
|
||||||
|
"dark",
|
||||||
|
"light",
|
||||||
|
"catppuccin",
|
||||||
|
"solarized",
|
||||||
|
"high-contrast",
|
||||||
|
"nord",
|
||||||
|
"dracula",
|
||||||
|
] as const;
|
||||||
|
|
||||||
export const AppSettingTypes = {
|
export const AppSettingTypes = {
|
||||||
theme: {
|
theme: {
|
||||||
@@ -18,25 +25,25 @@ export const AppSettingTypes = {
|
|||||||
centerCamera: {
|
centerCamera: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Center Camera",
|
label: "Center Camera",
|
||||||
value: true
|
value: true,
|
||||||
},
|
},
|
||||||
nodeInterface: {
|
nodeInterface: {
|
||||||
title: "Node Interface",
|
title: "Node Interface",
|
||||||
showNodeGrid: {
|
showNodeGrid: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Show Grid",
|
label: "Show Grid",
|
||||||
value: true
|
value: true,
|
||||||
},
|
},
|
||||||
snapToGrid: {
|
snapToGrid: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Snap to Grid",
|
label: "Snap to Grid",
|
||||||
value: true
|
value: true,
|
||||||
},
|
},
|
||||||
showHelp: {
|
showHelp: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Show Help",
|
label: "Show Help",
|
||||||
value: false
|
value: false,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
debug: {
|
debug: {
|
||||||
title: "Debug",
|
title: "Debug",
|
||||||
@@ -75,58 +82,63 @@ export const AppSettingTypes = {
|
|||||||
label: "Show Stem Lines",
|
label: "Show Stem Lines",
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
showGraphJson: {
|
||||||
|
type: "boolean",
|
||||||
|
label: "Show Graph Source",
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
stressTest: {
|
stressTest: {
|
||||||
title: "Stress Test",
|
title: "Stress Test",
|
||||||
amount: {
|
amount: {
|
||||||
type: "integer",
|
type: "integer",
|
||||||
min: 2,
|
min: 2,
|
||||||
max: 15,
|
max: 15,
|
||||||
value: 4
|
value: 4,
|
||||||
},
|
},
|
||||||
loadGrid: {
|
loadGrid: {
|
||||||
type: "button",
|
type: "button",
|
||||||
label: "Load Grid"
|
label: "Load Grid",
|
||||||
},
|
},
|
||||||
loadTree: {
|
loadTree: {
|
||||||
type: "button",
|
type: "button",
|
||||||
label: "Load Tree"
|
label: "Load Tree",
|
||||||
},
|
},
|
||||||
lottaFaces: {
|
lottaFaces: {
|
||||||
type: "button",
|
type: "button",
|
||||||
label: "Load 'lots of faces'"
|
label: "Load 'lots of faces'",
|
||||||
},
|
},
|
||||||
lottaNodes: {
|
lottaNodes: {
|
||||||
type: "button",
|
type: "button",
|
||||||
label: "Load 'lots of nodes'"
|
label: "Load 'lots of nodes'",
|
||||||
},
|
},
|
||||||
lottaNodesAndFaces: {
|
lottaNodesAndFaces: {
|
||||||
type: "button",
|
type: "button",
|
||||||
label: "Load 'lots of nodes and faces'"
|
label: "Load 'lots of nodes and faces'",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type SettingsToStore<T> =
|
||||||
|
T extends { value: infer V }
|
||||||
|
? V extends readonly string[]
|
||||||
|
? V[number]
|
||||||
|
: V
|
||||||
|
: T extends any[]
|
||||||
|
? {}
|
||||||
|
: T extends object
|
||||||
|
? {
|
||||||
|
[K in keyof T as T[K] extends object ? K : never]:
|
||||||
|
SettingsToStore<T[K]>
|
||||||
}
|
}
|
||||||
} as const
|
|
||||||
|
|
||||||
type IsInputDefinition<T> = T extends NodeInput ? T : never;
|
|
||||||
type HasTitle = { title: string };
|
|
||||||
type ExtractSettingsValues<T> = {
|
|
||||||
[K in keyof T]: T[K] extends HasTitle
|
|
||||||
? ExtractSettingsValues<Omit<T[K], 'title'>>
|
|
||||||
: T[K] extends IsInputDefinition<T[K]>
|
|
||||||
? T[K] extends { value: any }
|
|
||||||
? T[K]['value']
|
|
||||||
: never
|
|
||||||
: T[K] extends Record<string, any>
|
|
||||||
? ExtractSettingsValues<T[K]>
|
|
||||||
: never;
|
: never;
|
||||||
};
|
|
||||||
|
|
||||||
function settingsToStore<T>(settings: T): ExtractSettingsValues<T> {
|
export function settingsToStore<T>(settings: T): SettingsToStore<T> {
|
||||||
const result = {} as any;
|
const result = {} as any;
|
||||||
for (const key in settings) {
|
for (const key in settings) {
|
||||||
const value = settings[key];
|
const value = settings[key];
|
||||||
if (value && typeof value === 'object') {
|
if (value && typeof value === "object") {
|
||||||
if ('value' in value) {
|
if ("value" in value) {
|
||||||
result[key] = value.value;
|
result[key] = value.value;
|
||||||
} else {
|
} else {
|
||||||
result[key] = settingsToStore(value);
|
result[key] = settingsToStore(value);
|
||||||
@@ -136,11 +148,14 @@ function settingsToStore<T>(settings: T): ExtractSettingsValues<T> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appSettings = localState("app-settings", settingsToStore(AppSettingTypes));
|
export let appSettings = localState(
|
||||||
|
"app-settings",
|
||||||
|
settingsToStore(AppSettingTypes),
|
||||||
|
);
|
||||||
|
|
||||||
$effect.root(() => {
|
$effect.root(() => {
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const theme = appSettings.theme;
|
const theme = appSettings.value.theme;
|
||||||
const classes = document.documentElement.classList;
|
const classes = document.documentElement.classList;
|
||||||
const newClassName = `theme-${theme}`;
|
const newClassName = `theme-${theme}`;
|
||||||
if (classes) {
|
if (classes) {
|
||||||
|
|||||||
27
app/src/lib/settings/index.ts
Normal file
27
app/src/lib/settings/index.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { NodeInput } from "@nodarium/types";
|
||||||
|
|
||||||
|
type Button = { type: "button"; label?: string };
|
||||||
|
|
||||||
|
export type SettingsStore = {
|
||||||
|
[key: string]: SettingsStore | string | number | boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InputType = NodeInput | Button;
|
||||||
|
|
||||||
|
type SettingsNode = InputType | SettingsGroup;
|
||||||
|
|
||||||
|
export interface SettingsGroup {
|
||||||
|
title?: string;
|
||||||
|
[key: string]: SettingsNode | string | number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SettingsType = Record<string, SettingsNode>;
|
||||||
|
|
||||||
|
export type SettingsValue = Record<
|
||||||
|
string,
|
||||||
|
Record<string, unknown> | string | number | boolean | number[]
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function isNodeInput(v: SettingsNode | undefined): v is InputType {
|
||||||
|
return !!v && "type" in v;
|
||||||
|
}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { NodeInput } from "@nodes/types";
|
|
||||||
import NestedSettings from "./NestedSettings.svelte";
|
|
||||||
import type { Writable } from "svelte/store";
|
|
||||||
|
|
||||||
interface Nested {
|
|
||||||
[key: string]: NodeInput | Nested;
|
|
||||||
}
|
|
||||||
|
|
||||||
export let type: Record<string, NodeInput>;
|
|
||||||
|
|
||||||
export let store: Writable<Record<string, any>>;
|
|
||||||
|
|
||||||
function constructNested(type: Record<string, NodeInput>) {
|
|
||||||
const nested: Nested = {};
|
|
||||||
|
|
||||||
for (const key in type) {
|
|
||||||
const parts = key.split(".");
|
|
||||||
let current = nested;
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
|
||||||
if (i === parts.length - 1) {
|
|
||||||
current[parts[i]] = type[key];
|
|
||||||
} else {
|
|
||||||
current[parts[i]] = current[parts[i]] || {};
|
|
||||||
current = current[parts[i]] as Nested;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nested;
|
|
||||||
}
|
|
||||||
|
|
||||||
$: settings = constructNested({
|
|
||||||
randomSeed: { type: "boolean", value: false },
|
|
||||||
...type,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#key settings}
|
|
||||||
<NestedSettings id="graph-settings" {settings} {store} />
|
|
||||||
{/key}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
<script module lang="ts">
|
|
||||||
let openSections = localState<Record<string,boolean>>("open-details", {});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import NestedSettings from "./NestedSettings.svelte";
|
|
||||||
import {localState} from "$lib/helpers/localState.svelte";
|
|
||||||
import type { NodeInput } from "@nodes/types";
|
|
||||||
import Input from "@nodes/ui";
|
|
||||||
|
|
||||||
type Button = { type: "button"; label?: string };
|
|
||||||
|
|
||||||
type InputType = NodeInput | Button;
|
|
||||||
|
|
||||||
interface Nested {
|
|
||||||
[key: string]: (Nested & { title?: string }) | InputType;
|
|
||||||
}
|
|
||||||
type SettingsType = Record<string, Nested>;
|
|
||||||
type SettingsValue = Record<string, Record<string, unknown> | string | number | boolean>;
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
id: string;
|
|
||||||
key?: string;
|
|
||||||
value: SettingsValue;
|
|
||||||
type: SettingsType;
|
|
||||||
depth?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
let { id, key = "", value = $bindable(), type, depth = 0 }: Props = $props();
|
|
||||||
|
|
||||||
function isNodeInput(v: InputType | Nested): v is InputType {
|
|
||||||
return v && "type" in v;
|
|
||||||
}
|
|
||||||
|
|
||||||
let internalValue = $state(Array.isArray(type?.[key]?.options) ? type[key]?.options?.indexOf(value?.[key]) : value?.[key]);
|
|
||||||
|
|
||||||
let open = $state(openSections[id]);
|
|
||||||
if(depth > 0 && !isNodeInput(type[key])){
|
|
||||||
$effect(() => {
|
|
||||||
if(open !== undefined){
|
|
||||||
openSections[id] = open;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if(key === "" || internalValue === undefined) return;
|
|
||||||
if(isNodeInput(type[key]) && Array.isArray(type[key]?.options) && typeof internalValue === "number"){
|
|
||||||
value[key] = type[key].options?.[internalValue];
|
|
||||||
}else{
|
|
||||||
value[key] = internalValue;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if key && isNodeInput(type?.[key]) }
|
|
||||||
<div class="input input-{type[key].type}" class:first-level={depth === 1}>
|
|
||||||
{#if type[key].type === "button"}
|
|
||||||
<button onclick={() => console.log(type[key])}>
|
|
||||||
{type[key].label || key}
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<label for={id}>{type[key].label || key}</label>
|
|
||||||
<Input id={id} input={type[key]} bind:value={internalValue} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
{#if depth === 0}
|
|
||||||
{#each Object.keys(type).filter((key) => key !== "title") as childKey}
|
|
||||||
<NestedSettings
|
|
||||||
id={`${id}.${childKey}`}
|
|
||||||
key={childKey}
|
|
||||||
value={value}
|
|
||||||
type={type}
|
|
||||||
depth={depth + 1}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
<hr />
|
|
||||||
{:else if key && type?.[key]}
|
|
||||||
{#if depth > 0}
|
|
||||||
<hr />
|
|
||||||
{/if}
|
|
||||||
<details bind:open>
|
|
||||||
<summary><p>{type[key]?.title||key}</p></summary>
|
|
||||||
<div class="content">
|
|
||||||
{#each Object.keys(type[key]).filter((key) => key !== "title") as childKey}
|
|
||||||
<NestedSettings
|
|
||||||
id={`${id}.${childKey}`}
|
|
||||||
key={childKey}
|
|
||||||
value={value[key] as SettingsValue}
|
|
||||||
type={type[key] as SettingsType}
|
|
||||||
depth={depth + 1}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
summary {
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary::marker { }
|
|
||||||
|
|
||||||
summary > p {
|
|
||||||
display: inline;
|
|
||||||
padding-left: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
details {
|
|
||||||
padding: 1em;
|
|
||||||
padding-bottom: 0;
|
|
||||||
padding-left: 21px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
margin-top: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-boolean {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.input-boolean > label {
|
|
||||||
order: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first-level.input {
|
|
||||||
padding-left: 1em;
|
|
||||||
padding-right: 1em;
|
|
||||||
padding-bottom: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
position: absolute;
|
|
||||||
margin: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
border: none;
|
|
||||||
border-bottom: solid thin var(--outline);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: solid thin var(--outline);
|
border-bottom: solid thin var(--outline);
|
||||||
border-left: solid thin var(--outline);
|
border-left: solid thin var(--outline);
|
||||||
background: var(--layer-0);
|
background: var(--layer-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs > button > span {
|
.tabs > button > span {
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tabs > button.active {
|
.tabs > button.active {
|
||||||
background: var(--layer-1);
|
background: var(--layer-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs > button.active span {
|
.tabs > button.active span {
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node, NodeInput } from "@nodes/types";
|
import type { Node, NodeInput } from "@nodarium/types";
|
||||||
import NestedSettings from "./NestedSettings.svelte";
|
import NestedSettings from "$lib/settings/NestedSettings.svelte";
|
||||||
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
manager: GraphManager;
|
manager: GraphManager;
|
||||||
node: Node;
|
node: Node;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { manager, node }: Props = $props();
|
const { manager, node = $bindable() }: Props = $props();
|
||||||
|
|
||||||
const nodeDefinition = filterInputs(node.tmp?.type?.inputs);
|
|
||||||
function filterInputs(inputs?: Record<string, NodeInput>) {
|
function filterInputs(inputs?: Record<string, NodeInput>) {
|
||||||
const _inputs = $state.snapshot(inputs);
|
const _inputs = $state.snapshot(inputs);
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
@@ -27,6 +26,7 @@
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const nodeDefinition = filterInputs(node.tmp?.type?.inputs);
|
||||||
|
|
||||||
type Store = Record<string, number | number[]>;
|
type Store = Record<string, number | number[]>;
|
||||||
let store = $state<Store>(createStore(node?.props, nodeDefinition));
|
let store = $state<Store>(createStore(node?.props, nodeDefinition));
|
||||||
@@ -69,24 +69,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (store && store) {
|
if (store) {
|
||||||
updateNode();
|
updateNode();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if node}
|
{#if Object.keys(nodeDefinition).length}
|
||||||
{#key node.id}
|
<NestedSettings
|
||||||
{#if nodeDefinition && store && Object.keys(nodeDefinition).length > 0}
|
id="activeNodeSettings"
|
||||||
<NestedSettings
|
bind:value={store}
|
||||||
id="activeNodeSettings"
|
type={nodeDefinition}
|
||||||
bind:value={store}
|
/>
|
||||||
type={nodeDefinition}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<p class="mx-4">Active Node has no Settings</p>
|
|
||||||
{/if}
|
|
||||||
{/key}
|
|
||||||
{:else}
|
{:else}
|
||||||
<p class="mx-4">No active node</p>
|
<p class="mx-4">Node has no settings</p>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node } from "@nodes/types";
|
import type { Node } from "@nodarium/types";
|
||||||
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
|
||||||
import ActiveNodeSelected from "./ActiveNodeSelected.svelte";
|
import ActiveNodeSelected from "./ActiveNodeSelected.svelte";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import localStore from "$lib/helpers/localStore";
|
import localStore from "$lib/helpers/localStore";
|
||||||
import { Integer } from "@nodes/ui";
|
import { Integer } from "@nodarium/ui";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import { humanizeDuration } from "$lib/helpers";
|
import { humanizeDuration } from "$lib/helpers";
|
||||||
import Monitor from "$lib/performance/Monitor.svelte";
|
import Monitor from "$lib/performance/Monitor.svelte";
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
import type { OBJExporter } from "three/addons/exporters/OBJExporter.js";
|
import type { OBJExporter } from "three/addons/exporters/OBJExporter.js";
|
||||||
import type { GLTFExporter } from "three/addons/exporters/GLTFExporter.js";
|
import type { GLTFExporter } from "three/addons/exporters/GLTFExporter.js";
|
||||||
import FileSaver from "file-saver";
|
import FileSaver from "file-saver";
|
||||||
import { appSettings } from "../app-settings.svelte";
|
|
||||||
|
|
||||||
// Download
|
// Download
|
||||||
const download = (
|
const download = (
|
||||||
@@ -52,8 +51,6 @@
|
|||||||
// download .obj file
|
// download .obj file
|
||||||
download(result, "plant", "text/plain", "obj");
|
download(result, "plant", "text/plain", "obj");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
20
app/src/lib/sidebar/panels/GraphSource.svelte
Normal file
20
app/src/lib/sidebar/panels/GraphSource.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Graph } from "$lib/types";
|
||||||
|
|
||||||
|
const { graph }: { graph: Graph } = $props();
|
||||||
|
|
||||||
|
function convert(g: Graph): string {
|
||||||
|
return JSON.stringify(
|
||||||
|
{
|
||||||
|
...g,
|
||||||
|
nodes: g.nodes.map((n: any) => ({ ...n, tmp: undefined })),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
{convert(graph)}
|
||||||
|
</pre>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
import { ShortCut } from "@nodes/ui";
|
import { ShortCut } from "@nodarium/ui";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
let { keymaps }: Props = $props();
|
let { keymaps }: Props = $props();
|
||||||
console.log({ keymaps });
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<table class="wrapper">
|
<table class="wrapper">
|
||||||
6
app/src/lib/types.ts
Normal file
6
app/src/lib/types.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import type {
|
||||||
|
Graph,
|
||||||
|
NodeDefinition,
|
||||||
|
NodeInput,
|
||||||
|
} from "@nodarium/types";
|
||||||
|
export type { Graph, NodeDefinition, NodeInput };
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "@nodes/ui/app.css";
|
import "@nodarium/ui/app.css";
|
||||||
import "virtual:uno.css";
|
import "virtual:uno.css";
|
||||||
import "@unocss/reset/normalize.css";
|
import "@unocss/reset/normalize.css";
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,59 +2,61 @@
|
|||||||
import Grid from "$lib/grid";
|
import Grid from "$lib/grid";
|
||||||
import GraphInterface from "$lib/graph-interface";
|
import GraphInterface from "$lib/graph-interface";
|
||||||
import * as templates from "$lib/graph-templates";
|
import * as templates from "$lib/graph-templates";
|
||||||
import type { Graph, Node } from "@nodes/types";
|
import type { Graph, Node } from "@nodarium/types";
|
||||||
import Viewer from "$lib/result-viewer/Viewer.svelte";
|
import Viewer from "$lib/result-viewer/Viewer.svelte";
|
||||||
import Settings from "$lib/settings/Settings.svelte";
|
|
||||||
import {
|
import {
|
||||||
appSettings,
|
appSettings,
|
||||||
AppSettingTypes,
|
AppSettingTypes,
|
||||||
} from "$lib/settings/app-settings.svelte";
|
} from "$lib/settings/app-settings.svelte";
|
||||||
import Keymap from "$lib/settings/panels/Keymap.svelte";
|
import Keymap from "$lib/sidebar/panels/Keymap.svelte";
|
||||||
|
import Sidebar from "$lib/sidebar/Sidebar.svelte";
|
||||||
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
import NodeStore from "$lib/node-store/NodeStore.svelte";
|
import NodeStore from "$lib/node-store/NodeStore.svelte";
|
||||||
import ActiveNodeSettings from "$lib/settings/panels/ActiveNodeSettings.svelte";
|
import ActiveNodeSettings from "$lib/sidebar/panels/ActiveNodeSettings.svelte";
|
||||||
import PerformanceViewer from "$lib/performance/PerformanceViewer.svelte";
|
import PerformanceViewer from "$lib/performance/PerformanceViewer.svelte";
|
||||||
import Panel from "$lib/settings/Panel.svelte";
|
import Panel from "$lib/sidebar/Panel.svelte";
|
||||||
import GraphSettings from "$lib/settings/panels/GraphSettings.svelte";
|
import NestedSettings from "$lib/settings/NestedSettings.svelte";
|
||||||
import NestedSettings from "$lib/settings/panels/NestedSettings.svelte";
|
|
||||||
import type { Group } from "three";
|
import type { Group } from "three";
|
||||||
import ExportSettings from "$lib/settings/panels/ExportSettings.svelte";
|
import ExportSettings from "$lib/sidebar/panels/ExportSettings.svelte";
|
||||||
import {
|
import {
|
||||||
MemoryRuntimeCache,
|
MemoryRuntimeCache,
|
||||||
WorkerRuntimeExecutor,
|
WorkerRuntimeExecutor,
|
||||||
MemoryRuntimeExecutor,
|
MemoryRuntimeExecutor,
|
||||||
} from "$lib/runtime";
|
} from "$lib/runtime";
|
||||||
import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry";
|
import { IndexDBCache, RemoteNodeRegistry } from "@nodarium/registry";
|
||||||
import { createPerformanceStore } from "@nodes/utils";
|
import { createPerformanceStore } from "@nodarium/utils";
|
||||||
import BenchmarkPanel from "$lib/settings/panels/BenchmarkPanel.svelte";
|
import BenchmarkPanel from "$lib/sidebar/panels/BenchmarkPanel.svelte";
|
||||||
import { debounceAsyncFunction } from "$lib/helpers";
|
import { debounceAsyncFunction } from "$lib/helpers";
|
||||||
import { onMount } from "svelte";
|
import GraphSource from "$lib/sidebar/panels/GraphSource.svelte";
|
||||||
|
|
||||||
let performanceStore = createPerformanceStore();
|
let performanceStore = createPerformanceStore();
|
||||||
|
|
||||||
const registryCache = new IndexDBCache("node-registry");
|
const registryCache = new IndexDBCache("node-registry");
|
||||||
const nodeRegistry = new RemoteNodeRegistry("");
|
const nodeRegistry = new RemoteNodeRegistry("", registryCache);
|
||||||
nodeRegistry.cache = registryCache;
|
|
||||||
const workerRuntime = new WorkerRuntimeExecutor();
|
const workerRuntime = new WorkerRuntimeExecutor();
|
||||||
const runtimeCache = new MemoryRuntimeCache();
|
const runtimeCache = new MemoryRuntimeCache();
|
||||||
const memoryRuntime = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
|
const memoryRuntime = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
|
||||||
memoryRuntime.perf = performanceStore;
|
memoryRuntime.perf = performanceStore;
|
||||||
|
|
||||||
const runtime = $derived(
|
const runtime = $derived(
|
||||||
appSettings.debug.useWorker ? workerRuntime : memoryRuntime,
|
appSettings.value.debug.useWorker ? workerRuntime : memoryRuntime,
|
||||||
);
|
);
|
||||||
|
|
||||||
let activeNode = $state<Node | undefined>(undefined);
|
let activeNode = $state<Node | undefined>(undefined);
|
||||||
let scene = $state<Group>(null!);
|
let scene = $state<Group>(null!);
|
||||||
|
|
||||||
let graph = localStorage.getItem("graph")
|
let graph = $state(
|
||||||
? JSON.parse(localStorage.getItem("graph")!)
|
localStorage.getItem("graph")
|
||||||
: templates.defaultPlant;
|
? JSON.parse(localStorage.getItem("graph")!)
|
||||||
|
: templates.defaultPlant,
|
||||||
|
);
|
||||||
|
function handleSave(graph: Graph) {
|
||||||
|
localStorage.setItem("graph", JSON.stringify(graph));
|
||||||
|
}
|
||||||
|
|
||||||
let graphInterface = $state<ReturnType<typeof GraphInterface>>(null!);
|
let graphInterface = $state<ReturnType<typeof GraphInterface>>(null!);
|
||||||
let viewerComponent = $state<ReturnType<typeof Viewer>>();
|
let viewerComponent = $state<ReturnType<typeof Viewer>>();
|
||||||
const manager = $derived(graphInterface?.manager);
|
const manager = $derived(graphInterface?.manager);
|
||||||
const managerStatus = $derived(manager?.status);
|
|
||||||
|
|
||||||
async function randomGenerate() {
|
async function randomGenerate() {
|
||||||
if (!manager) return;
|
if (!manager) return;
|
||||||
@@ -67,76 +69,93 @@
|
|||||||
{
|
{
|
||||||
key: "r",
|
key: "r",
|
||||||
description: "Regenerate the plant model",
|
description: "Regenerate the plant model",
|
||||||
callback: randomGenerate,
|
callback: () => randomGenerate(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
let graphSettings = $state<Record<string, any>>({});
|
let graphSettings = $state<Record<string, any>>({});
|
||||||
let graphSettingTypes = $state({});
|
$effect(() => {
|
||||||
|
if (graphSettings) {
|
||||||
|
manager?.setSettings($state.snapshot(graphSettings));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
type BooleanSchema = {
|
||||||
|
[key: string]: {
|
||||||
|
type: "boolean";
|
||||||
|
value: false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let graphSettingTypes = $state<BooleanSchema>({
|
||||||
|
randomSeed: { type: "boolean", value: false },
|
||||||
|
});
|
||||||
|
|
||||||
const handleUpdate = debounceAsyncFunction(
|
let runIndex = 0;
|
||||||
async (g: Graph, s: Record<string, any> = graphSettings) => {
|
|
||||||
performanceStore.startRun();
|
|
||||||
try {
|
|
||||||
let a = performance.now();
|
|
||||||
const graphResult = await runtime.execute(g, $state.snapshot(s));
|
|
||||||
let b = performance.now();
|
|
||||||
|
|
||||||
if (appSettings.debug.useWorker) {
|
async function update(
|
||||||
let perfData = await runtime.getPerformanceData();
|
g: Graph,
|
||||||
let lastRun = perfData?.at(-1);
|
s: Record<string, any> = $state.snapshot(graphSettings),
|
||||||
if (lastRun?.total) {
|
) {
|
||||||
lastRun.runtime = lastRun.total;
|
runIndex++;
|
||||||
delete lastRun.total;
|
performanceStore.startRun();
|
||||||
performanceStore.mergeData(lastRun);
|
try {
|
||||||
performanceStore.addPoint(
|
let a = performance.now();
|
||||||
"worker-transfer",
|
const graphResult = await runtime.execute(g, s);
|
||||||
b - a - lastRun.runtime[0],
|
let b = performance.now();
|
||||||
);
|
|
||||||
}
|
if (appSettings.value.debug.useWorker) {
|
||||||
|
let perfData = await runtime.getPerformanceData();
|
||||||
|
let lastRun = perfData?.at(-1);
|
||||||
|
if (lastRun?.total) {
|
||||||
|
lastRun.runtime = lastRun.total;
|
||||||
|
delete lastRun.total;
|
||||||
|
performanceStore.mergeData(lastRun);
|
||||||
|
performanceStore.addPoint(
|
||||||
|
"worker-transfer",
|
||||||
|
b - a - lastRun.runtime[0],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
viewerComponent?.update(graphResult);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("errors", error);
|
|
||||||
} finally {
|
|
||||||
performanceStore.stopRun();
|
|
||||||
}
|
}
|
||||||
},
|
viewerComponent?.update(graphResult);
|
||||||
);
|
} catch (error) {
|
||||||
|
console.log("errors", error);
|
||||||
// $ if (AppSettings) {
|
} finally {
|
||||||
// //@ts-ignore
|
performanceStore.stopRun();
|
||||||
// AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
|
}
|
||||||
// graph = templates.grid($AppSettings.amount, $AppSettings.amount);
|
|
||||||
// };
|
|
||||||
// //@ts-ignore
|
|
||||||
// AppSettingTypes.debug.stressTest.loadTree.callback = () => {
|
|
||||||
// graph = templates.tree($AppSettings.amount);
|
|
||||||
// };
|
|
||||||
// //@ts-ignore
|
|
||||||
// AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
|
|
||||||
// graph = templates.lottaFaces;
|
|
||||||
// };
|
|
||||||
// //@ts-ignore
|
|
||||||
// AppSettingTypes.debug.stressTest.lottaNodes.callback = () => {
|
|
||||||
// graph = templates.lottaNodes;
|
|
||||||
// };
|
|
||||||
// //@ts-ignore
|
|
||||||
// AppSettingTypes.debug.stressTest.lottaNodesAndFaces.callback = () => {
|
|
||||||
// graph = templates.lottaNodesAndFaces;
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
function handleSave(graph: Graph) {
|
|
||||||
localStorage.setItem("graph", JSON.stringify(graph));
|
|
||||||
}
|
}
|
||||||
onMount(() => {
|
|
||||||
handleUpdate(graph);
|
const handleUpdate = debounceAsyncFunction(update);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
//@ts-ignore
|
||||||
|
AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
|
||||||
|
manager.load(
|
||||||
|
templates.grid(
|
||||||
|
appSettings.value.debug.stressTest.amount,
|
||||||
|
appSettings.value.debug.stressTest.amount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
//@ts-ignore
|
||||||
|
AppSettingTypes.debug.stressTest.loadTree.callback = () => {
|
||||||
|
manager.load(templates.tree(appSettings.value.debug.stressTest.amount));
|
||||||
|
};
|
||||||
|
//@ts-ignore
|
||||||
|
AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
|
||||||
|
manager.load(templates.lottaFaces as unknown as Graph);
|
||||||
|
};
|
||||||
|
//@ts-ignore
|
||||||
|
AppSettingTypes.debug.stressTest.lottaNodes.callback = () => {
|
||||||
|
manager.load(templates.lottaNodes as unknown as Graph);
|
||||||
|
};
|
||||||
|
//@ts-ignore
|
||||||
|
AppSettingTypes.debug.stressTest.lottaNodesAndFaces.callback = () => {
|
||||||
|
manager.load(templates.lottaNodesAndFaces as unknown as Graph);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:document on:keydown={applicationKeymap.handleKeyboardEvent} />
|
<svelte:document onkeydown={applicationKeymap.handleKeyboardEvent} />
|
||||||
<div class="wrapper manager-{$managerStatus}">
|
|
||||||
|
<div class="wrapper manager-{manager?.status}">
|
||||||
<header></header>
|
<header></header>
|
||||||
<Grid.Row>
|
<Grid.Row>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
@@ -144,102 +163,109 @@
|
|||||||
bind:scene
|
bind:scene
|
||||||
bind:this={viewerComponent}
|
bind:this={viewerComponent}
|
||||||
perf={performanceStore}
|
perf={performanceStore}
|
||||||
centerCamera={appSettings.centerCamera}
|
centerCamera={appSettings.value.centerCamera}
|
||||||
/>
|
/>
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
{#key graph}
|
<GraphInterface
|
||||||
<GraphInterface
|
{graph}
|
||||||
bind:this={graphInterface}
|
bind:this={graphInterface}
|
||||||
{graph}
|
registry={nodeRegistry}
|
||||||
registry={nodeRegistry}
|
showGrid={appSettings.value.nodeInterface.showNodeGrid}
|
||||||
showGrid={appSettings.nodeInterface.showNodeGrid}
|
snapToGrid={appSettings.value.nodeInterface.snapToGrid}
|
||||||
snapToGrid={appSettings.nodeInterface.snapToGrid}
|
bind:activeNode
|
||||||
bind:activeNode
|
bind:showHelp={appSettings.value.nodeInterface.showHelp}
|
||||||
bind:showHelp={appSettings.nodeInterface.showHelp}
|
bind:settings={graphSettings}
|
||||||
bind:settings={graphSettings}
|
bind:settingTypes={graphSettingTypes}
|
||||||
bind:settingTypes={graphSettingTypes}
|
onresult={(result) => handleUpdate(result)}
|
||||||
onresult={(result) => handleUpdate(result)}
|
onsave={(graph) => handleSave(graph)}
|
||||||
onsave={(graph) => handleSave(graph)}
|
/>
|
||||||
/>
|
<Sidebar>
|
||||||
<Settings>
|
<Panel id="general" title="General" icon="i-tabler-settings">
|
||||||
<Panel id="general" title="General" icon="i-tabler-settings">
|
<NestedSettings
|
||||||
<NestedSettings
|
id="general"
|
||||||
id="general"
|
bind:value={appSettings.value}
|
||||||
value={appSettings}
|
type={AppSettingTypes}
|
||||||
type={AppSettingTypes}
|
/>
|
||||||
/>
|
</Panel>
|
||||||
</Panel>
|
<Panel
|
||||||
<Panel
|
id="shortcuts"
|
||||||
id="shortcuts"
|
title="Keyboard Shortcuts"
|
||||||
title="Keyboard Shortcuts"
|
icon="i-tabler-keyboard"
|
||||||
icon="i-tabler-keyboard"
|
>
|
||||||
>
|
<Keymap
|
||||||
<Keymap
|
keymaps={[
|
||||||
keymaps={[
|
{ keymap: applicationKeymap, title: "Application" },
|
||||||
{ keymap: applicationKeymap, title: "Application" },
|
{ keymap: graphInterface?.keymap, title: "Node-Editor" },
|
||||||
{ keymap: graphInterface.keymap, title: "Node-Editor" },
|
]}
|
||||||
]}
|
/>
|
||||||
/>
|
</Panel>
|
||||||
</Panel>
|
<Panel id="exports" title="Exporter" icon="i-tabler-package-export">
|
||||||
<Panel id="exports" title="Exporter" icon="i-tabler-package-export">
|
<ExportSettings {scene} />
|
||||||
<ExportSettings {scene} />
|
</Panel>
|
||||||
</Panel>
|
<Panel
|
||||||
<Panel
|
id="node-store"
|
||||||
id="node-store"
|
classes="text-green-400"
|
||||||
classes="text-green-400"
|
title="Node Store"
|
||||||
title="Node Store"
|
icon="i-tabler-database"
|
||||||
icon="i-tabler-database"
|
>
|
||||||
>
|
<NodeStore registry={nodeRegistry} />
|
||||||
<NodeStore registry={nodeRegistry} />
|
</Panel>
|
||||||
</Panel>
|
<Panel
|
||||||
<Panel
|
id="performance"
|
||||||
id="performance"
|
title="Performance"
|
||||||
title="Performance"
|
classes="text-red-400"
|
||||||
classes="text-red-400"
|
hidden={!appSettings.value.debug.showPerformancePanel}
|
||||||
hidden={!appSettings.debug.showPerformancePanel}
|
icon="i-tabler-brand-speedtest"
|
||||||
icon="i-tabler-brand-speedtest"
|
>
|
||||||
>
|
{#if $performanceStore}
|
||||||
{#if $performanceStore}
|
<PerformanceViewer data={$performanceStore} />
|
||||||
<PerformanceViewer data={$performanceStore} />
|
{/if}
|
||||||
{/if}
|
</Panel>
|
||||||
</Panel>
|
<Panel
|
||||||
<Panel
|
id="graph-source"
|
||||||
id="benchmark"
|
title="Graph Source"
|
||||||
title="Benchmark"
|
hidden={!appSettings.value.debug.showGraphJson}
|
||||||
classes="text-red-400"
|
icon="i-tabler-code"
|
||||||
hidden={!appSettings.debug.showBenchmarkPanel}
|
>
|
||||||
icon="i-tabler-graph"
|
<GraphSource {graph} />
|
||||||
>
|
</Panel>
|
||||||
<BenchmarkPanel run={randomGenerate} />
|
<Panel
|
||||||
</Panel>
|
id="benchmark"
|
||||||
<Panel
|
title="Benchmark"
|
||||||
|
classes="text-red-400"
|
||||||
|
hidden={!appSettings.value.debug.showBenchmarkPanel}
|
||||||
|
icon="i-tabler-graph"
|
||||||
|
>
|
||||||
|
<BenchmarkPanel run={randomGenerate} />
|
||||||
|
</Panel>
|
||||||
|
<Panel
|
||||||
|
id="graph-settings"
|
||||||
|
title="Graph Settings"
|
||||||
|
classes="text-blue-400"
|
||||||
|
icon="i-custom-graph"
|
||||||
|
>
|
||||||
|
<NestedSettings
|
||||||
id="graph-settings"
|
id="graph-settings"
|
||||||
title="Graph Settings"
|
type={graphSettingTypes}
|
||||||
classes="text-blue-400"
|
bind:value={graphSettings}
|
||||||
icon="i-custom-graph"
|
/>
|
||||||
>
|
</Panel>
|
||||||
{#if Object.keys(graphSettingTypes).length > 0}
|
<Panel
|
||||||
<GraphSettings type={graphSettingTypes} store={graphSettings} />
|
id="active-node"
|
||||||
{/if}
|
title="Node Settings"
|
||||||
</Panel>
|
classes="text-blue-400"
|
||||||
<Panel
|
icon="i-tabler-adjustments"
|
||||||
id="active-node"
|
>
|
||||||
title="Node Settings"
|
<ActiveNodeSettings {manager} node={activeNode} />
|
||||||
classes="text-blue-400"
|
</Panel>
|
||||||
icon="i-tabler-adjustments"
|
</Sidebar>
|
||||||
>
|
|
||||||
<ActiveNodeSettings {manager} node={activeNode} />
|
|
||||||
</Panel>
|
|
||||||
</Settings>
|
|
||||||
{/key}
|
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
/* border-bottom: solid thin var(--outline); */
|
|
||||||
background-color: var(--layer-1);
|
background-color: var(--layer-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,36 @@
|
|||||||
import type { RequestHandler } from "./$types";
|
import type { EntryGenerator, RequestHandler } from "./$types";
|
||||||
import * as registry from "$lib/node-registry";
|
import * as registry from "$lib/node-registry";
|
||||||
import type { EntryGenerator } from "../$types";
|
|
||||||
|
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
|
|
||||||
export const entries: EntryGenerator = async () => {
|
export const entries: EntryGenerator = async () => {
|
||||||
const users = await registry.getUsers();
|
const users = await registry.getUsers();
|
||||||
return users.map(user => {
|
return users
|
||||||
return user.collections.map(collection => {
|
.map((user) => {
|
||||||
return collection.nodes.map(node => {
|
return user.collections.map((collection) => {
|
||||||
return { user: user.id, collection: collection.id.split("/")[1], node: node.id.split("/")[2] }
|
return collection.nodes.map((node) => {
|
||||||
|
return {
|
||||||
|
user: user.id,
|
||||||
|
collection: collection.id.split("/")[1],
|
||||||
|
node: node.id.split("/")[2],
|
||||||
|
};
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}).flat(2);
|
.flat(2);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const GET: RequestHandler = async function GET({ params }) {
|
export const GET: RequestHandler = async function GET({ params }) {
|
||||||
|
const wasm = await registry.getWasm(
|
||||||
const wasm = await registry.getWasm(`${params.user}/${params.collection}/${params.node}`);
|
`${params.user}/${params.collection}/${params.node}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!wasm) {
|
if (!wasm) {
|
||||||
return new Response("Not found", { status: 404 });
|
return new Response("Not found", { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(wasm, { status: 200, headers: { "Content-Type": "application/wasm" } });
|
return new Response(wasm, {
|
||||||
}
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/wasm" },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
decodeFloat,
|
|
||||||
encodeFloat,
|
|
||||||
decodeNestedArray,
|
|
||||||
encodeNestedArray,
|
|
||||||
concatEncodedArrays,
|
|
||||||
} from "@nodes/utils";
|
|
||||||
|
|
||||||
console.clear();
|
|
||||||
|
|
||||||
{
|
|
||||||
const encodedPositions = new Int32Array([
|
|
||||||
encodeFloat(1.1),
|
|
||||||
encodeFloat(2.0),
|
|
||||||
encodeFloat(3.0),
|
|
||||||
encodeFloat(4.0),
|
|
||||||
encodeFloat(5.0),
|
|
||||||
encodeFloat(6.0),
|
|
||||||
encodeFloat(7.0),
|
|
||||||
encodeFloat(8.0),
|
|
||||||
encodeFloat(9.0),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Create a Float32Array using the same buffer that backs the Int32Array
|
|
||||||
const floatView = new Float32Array(encodedPositions.buffer);
|
|
||||||
console.log({ encodedPositions, floatView });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false) {
|
|
||||||
const input_a = encodeNestedArray([1, 2, 3]);
|
|
||||||
const input_b = 2;
|
|
||||||
const input_c = 89;
|
|
||||||
const input_d = encodeNestedArray([4, 5, 6]);
|
|
||||||
|
|
||||||
const output = concatNestedArrays([input_a, input_b, input_c, input_d]);
|
|
||||||
|
|
||||||
const decoded = decodeNestedArray(output);
|
|
||||||
console.log("CONCAT", [input_a, input_b, input_c, input_d]);
|
|
||||||
console.log(output);
|
|
||||||
console.log(decoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false) {
|
|
||||||
let maxError = 0;
|
|
||||||
new Array(10_000).fill(null).forEach((v, i) => {
|
|
||||||
const input = i < 5_000 ? i : Math.random() * 100;
|
|
||||||
const encoded = encodeFloat(input);
|
|
||||||
const output = decodeFloat(encoded[0], encoded[1]);
|
|
||||||
|
|
||||||
const error = Math.abs(input - output);
|
|
||||||
if (error > maxError) {
|
|
||||||
maxError = error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("DECODE FLOAT");
|
|
||||||
console.log(maxError);
|
|
||||||
console.log(encodeFloat(2.0));
|
|
||||||
console.log("----");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false) {
|
|
||||||
console.log("Turning Int32Array into Array");
|
|
||||||
const test_size = 2_000_000;
|
|
||||||
const a = new Int32Array(test_size);
|
|
||||||
let t0 = performance.now();
|
|
||||||
for (let i = 0; i < test_size; i++) {
|
|
||||||
a[i] = Math.floor(Math.random() * 100);
|
|
||||||
}
|
|
||||||
console.log("TIME", performance.now() - t0);
|
|
||||||
t0 = performance.now();
|
|
||||||
const b = [...a.slice(0, test_size)];
|
|
||||||
console.log("TIME", performance.now() - t0);
|
|
||||||
console.log(typeof b, Array.isArray(b), b instanceof Int32Array);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false) {
|
|
||||||
// const input = [5, [6, 1], [7, 2, [5, 1]]];
|
|
||||||
// const input = [5, [], [6, []], []];
|
|
||||||
// const input = [52];
|
|
||||||
const input = [0, 0, [0, 2, 0, 128, 0, 128], 0, 128];
|
|
||||||
|
|
||||||
console.log("INPUT");
|
|
||||||
console.log(input);
|
|
||||||
|
|
||||||
let encoded = encodeNestedArray(input);
|
|
||||||
// encoded = [];
|
|
||||||
console.log("ENCODED");
|
|
||||||
console.log(encoded);
|
|
||||||
|
|
||||||
encoded = [0, 2, 1, 0, 4, 4, 2, 4, 1, 2, 2, 0, 3, 2, 3, 1, 1, 1, 1];
|
|
||||||
|
|
||||||
const decoded = decodeNestedArray(encoded);
|
|
||||||
console.log("DECODED");
|
|
||||||
console.log(decoded);
|
|
||||||
|
|
||||||
console.log("EQUALS", JSON.stringify(input) === JSON.stringify(decoded));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -10,22 +10,22 @@
|
|||||||
"strength": {
|
"strength": {
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 1
|
"max": 1,
|
||||||
"value": 1,
|
"value": 1
|
||||||
},
|
},
|
||||||
"curviness": {
|
"curviness": {
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 1,
|
"max": 1,
|
||||||
"value": 0.5,
|
"value": 0.5
|
||||||
},
|
},
|
||||||
"depth": {
|
"depth": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"min": 1,
|
"min": 1,
|
||||||
"max": 10,
|
"max": 10,
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"value": 1,
|
"value": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm build:nodes && pnpm build:app",
|
"build": "pnpm build:nodes && pnpm build:app",
|
||||||
|
"build:story": "pnpm -r --filter 'ui' story:build",
|
||||||
"build:app": "BASE_PATH=/ui pnpm -r --filter 'ui' build && pnpm -r --filter 'app' build",
|
"build:app": "BASE_PATH=/ui pnpm -r --filter 'ui' build && pnpm -r --filter 'app' build",
|
||||||
"build:nodes": "pnpm -r --filter './nodes/**' build",
|
"build:nodes": "pnpm -r --filter './nodes/**' build",
|
||||||
"dev:nodes": "pnpm -r --parallel --filter './nodes/**' dev",
|
"dev:nodes": "pnpm -r --parallel --filter './nodes/**' dev",
|
||||||
"build:deploy": "pnpm build && cp -r ./packages/ui/storybook-static ./app/build/ui",
|
"build:deploy": "pnpm build",
|
||||||
"dev": "pnpm -r --filter 'app' --filter './packages/node-registry' dev"
|
"dev": "pnpm -r --filter 'app' --filter './packages/node-registry' dev"
|
||||||
}
|
},
|
||||||
|
"packageManager": "pnpm@10.24.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@nodes/registry",
|
"name": "@nodarium/registry",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodes/types": "link:../types",
|
"@nodarium/types": "link:../types",
|
||||||
"@nodes/utils": "link:../utils",
|
"@nodarium/utils": "link:../utils",
|
||||||
"idb": "^8.0.0"
|
"idb": "^8.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { AsyncCache } from '@nodes/types';
|
import type { AsyncCache } from '@nodarium/types';
|
||||||
import { openDB, type IDBPDatabase } from 'idb';
|
import { openDB, type IDBPDatabase } from 'idb';
|
||||||
|
|
||||||
export class IndexDBCache implements AsyncCache<ArrayBuffer> {
|
export class IndexDBCache implements AsyncCache<ArrayBuffer> {
|
||||||
|
|||||||
@@ -1,83 +1,88 @@
|
|||||||
import { type NodeRegistry, type NodeDefinition, NodeDefinitionSchema, type AsyncCache } from "@nodes/types";
|
import {
|
||||||
import { createWasmWrapper, createLogger } from "@nodes/utils";
|
NodeDefinitionSchema,
|
||||||
|
type AsyncCache,
|
||||||
|
type NodeDefinition,
|
||||||
|
type NodeRegistry,
|
||||||
|
} from "@nodarium/types";
|
||||||
|
import { createLogger, createWasmWrapper } from "@nodarium/utils";
|
||||||
|
|
||||||
const log = createLogger("node-registry");
|
const log = createLogger("node-registry");
|
||||||
log.mute();
|
log.mute();
|
||||||
|
|
||||||
export class RemoteNodeRegistry implements NodeRegistry {
|
export class RemoteNodeRegistry implements NodeRegistry {
|
||||||
|
|
||||||
status: "loading" | "ready" | "error" = "loading";
|
status: "loading" | "ready" | "error" = "loading";
|
||||||
private nodes: Map<string, NodeDefinition> = new Map();
|
private nodes: Map<string, NodeDefinition> = new Map();
|
||||||
|
|
||||||
cache?: AsyncCache<ArrayBuffer>;
|
async fetchJson(url: string) {
|
||||||
|
const response = await fetch(`${this.url}/${url}`);
|
||||||
|
|
||||||
fetch: typeof fetch = globalThis.fetch.bind(globalThis);
|
|
||||||
|
|
||||||
constructor(private url: string) { }
|
|
||||||
|
|
||||||
async fetchUsers() {
|
|
||||||
const response = await this.fetch(`${this.url}/nodes/users.json`);
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to load users`);
|
log.error(`Failed to load ${url}`, { response, url, host: this.url });
|
||||||
|
throw new Error(`Failed to load ${url}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchUser(userId: `${string}`) {
|
async fetchArrayBuffer(url: string) {
|
||||||
const response = await this.fetch(`${this.url}/nodes/${userId}.json`);
|
const response = await fetch(`${this.url}/${url}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to load user ${userId}`);
|
log.error(`Failed to load ${url}`, { response, url, host: this.url });
|
||||||
}
|
throw new Error(`Failed to load ${url}`);
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchCollection(userCollectionId: `${string}/${string}`) {
|
|
||||||
const response = await this.fetch(`${this.url}/nodes/${userCollectionId}.json`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to load collection ${userCollectionId}`);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) {
|
|
||||||
const response = await this.fetch(`${this.url}/nodes/${nodeId}.json`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to load node definition ${nodeId}`);
|
|
||||||
}
|
|
||||||
return response.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) {
|
|
||||||
|
|
||||||
const response = await this.fetch(`${this.url}/nodes/${nodeId}.wasm`);
|
|
||||||
if (!response.ok) {
|
|
||||||
if (this.cache) {
|
|
||||||
let value = await this.cache.get(nodeId);
|
|
||||||
if (value) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(`Failed to load node wasm ${nodeId}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.arrayBuffer();
|
return response.arrayBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private url: string,
|
||||||
|
private cache?: AsyncCache<ArrayBuffer>,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async fetchUsers() {
|
||||||
|
return this.fetchJson(`nodes/users.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchUser(userId: `${string}`) {
|
||||||
|
return this.fetchJson(`user/${userId}.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchCollection(userCollectionId: `${string}/${string}`) {
|
||||||
|
return this.fetchJson(`nodes/${userCollectionId}.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) {
|
||||||
|
return this.fetchJson(`nodes/${nodeId}.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) {
|
||||||
|
const cachedNode = await this.cache?.get(nodeId);
|
||||||
|
if (cachedNode) {
|
||||||
|
return cachedNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = await this.fetchArrayBuffer(`nodes/${nodeId}.wasm`);
|
||||||
|
if (!node) {
|
||||||
|
throw new Error(`Failed to load node wasm ${nodeId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
async load(nodeIds: `${string}/${string}/${string}`[]) {
|
async load(nodeIds: `${string}/${string}/${string}`[]) {
|
||||||
const a = performance.now();
|
const a = performance.now();
|
||||||
|
|
||||||
const nodes = await Promise.all([...new Set(nodeIds).values()].map(async id => {
|
const nodes = await Promise.all(
|
||||||
|
[...new Set(nodeIds).values()].map(async (id) => {
|
||||||
|
if (this.nodes.has(id)) {
|
||||||
|
return this.nodes.get(id)!;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.nodes.has(id)) {
|
const wasmBuffer = await this.fetchNodeWasm(id);
|
||||||
return this.nodes.get(id)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wasmBuffer = await this.fetchNodeWasm(id);
|
|
||||||
|
|
||||||
return this.register(wasmBuffer);
|
|
||||||
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
return this.register(wasmBuffer);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const duration = performance.now() - a;
|
const duration = performance.now() - a;
|
||||||
|
|
||||||
@@ -87,11 +92,10 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
log.groupEnd();
|
log.groupEnd();
|
||||||
this.status = "ready";
|
this.status = "ready";
|
||||||
|
|
||||||
return nodes
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
async register(wasmBuffer: ArrayBuffer) {
|
async register(wasmBuffer: ArrayBuffer) {
|
||||||
|
|
||||||
const wrapper = createWasmWrapper(wasmBuffer);
|
const wrapper = createWasmWrapper(wasmBuffer);
|
||||||
|
|
||||||
const definition = NodeDefinitionSchema.safeParse(wrapper.get_definition());
|
const definition = NodeDefinitionSchema.safeParse(wrapper.get_definition());
|
||||||
@@ -107,8 +111,8 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
|
|
||||||
let node = {
|
let node = {
|
||||||
...definition.data,
|
...definition.data,
|
||||||
execute: wrapper.execute
|
execute: wrapper.execute,
|
||||||
}
|
};
|
||||||
|
|
||||||
this.nodes.set(definition.data.id, node);
|
this.nodes.set(definition.data.id, node);
|
||||||
|
|
||||||
|
|||||||
19
packages/store-client/package.json
Normal file
19
packages/store-client/package.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "store-client",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"generate": "openapi-ts -i ../../store/openapi.json -o src/client -c @hey-api/client-fetch"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@hey-api/client-fetch": "^0.13.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@hey-api/openapi-ts": "^0.88.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/store-client/src/client/index.ts
Normal file
3
packages/store-client/src/client/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
export * from './sdk.gen';
|
||||||
|
export * from './types.gen';
|
||||||
55
packages/store-client/src/client/sdk.gen.ts
Normal file
55
packages/store-client/src/client/sdk.gen.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
|
import { createClient, createConfig, type OptionsLegacyParser } from '@hey-api/client-fetch';
|
||||||
|
import type { GetV1NodesByUserJsonData, GetV1NodesByUserJsonError, GetV1NodesByUserJsonResponse, GetV1NodesByUserBySystemJsonData, GetV1NodesByUserBySystemJsonError, GetV1NodesByUserBySystemJsonResponse, GetV1NodesByUserBySystemByNodeIdby-.+jsonData, GetV1NodesByUserBySystemByNodeIdby-.+jsonError, GetV1NodesByUserBySystemByNodeIdby-.+jsonResponse, GetV1NodesByUserBySystemByNodeIdby-.+WasmData, GetV1NodesByUserBySystemByNodeIdby-.+WasmError, GetV1NodesByUserBySystemByNodeIdby-.+WasmResponse, PostV1NodesError, PostV1NodesResponse, GetV1UsersUsersJsonError, GetV1UsersUsersJsonResponse, GetV1UsersByUserIdJsonData, GetV1UsersByUserIdJsonError, GetV1UsersByUserIdJsonResponse } from './types.gen';
|
||||||
|
|
||||||
|
export const client = createClient(createConfig());
|
||||||
|
|
||||||
|
export const getV1NodesByUserJson = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<GetV1NodesByUserJsonData, ThrowOnError>) => {
|
||||||
|
return (options?.client ?? client).get<GetV1NodesByUserJsonResponse, GetV1NodesByUserJsonError, ThrowOnError>({
|
||||||
|
...options,
|
||||||
|
url: '/v1/nodes/{user}.json'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getV1NodesByUserBySystemJson = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<GetV1NodesByUserBySystemJsonData, ThrowOnError>) => {
|
||||||
|
return (options?.client ?? client).get<GetV1NodesByUserBySystemJsonResponse, GetV1NodesByUserBySystemJsonError, ThrowOnError>({
|
||||||
|
...options,
|
||||||
|
url: '/v1/nodes/{user}/{system}.json'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getV1NodesByUserBySystemByNodeIdby-.+Json = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<GetV1NodesByUserBySystemByNodeIdby-.+jsonData, ThrowOnError>) => {
|
||||||
|
return (options?.client ?? client).get<GetV1NodesByUserBySystemByNodeIdby-.+jsonResponse, GetV1NodesByUserBySystemByNodeIdby-.+jsonError, ThrowOnError>({
|
||||||
|
...options,
|
||||||
|
url: '/v1/nodes/{user}/{system}/{nodeId}{.+\\.json}'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getV1NodesByUserBySystemByNodeIdby-.+Wasm = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<GetV1NodesByUserBySystemByNodeIdby-.+WasmData, ThrowOnError>) => {
|
||||||
|
return (options?.client ?? client).get<GetV1NodesByUserBySystemByNodeIdby-.+WasmResponse, GetV1NodesByUserBySystemByNodeIdby-.+WasmError, ThrowOnError>({
|
||||||
|
...options,
|
||||||
|
url: '/v1/nodes/{user}/{system}/{nodeId}{.+\\.wasm}'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const postV1Nodes = <ThrowOnError extends boolean = false>(options?: OptionsLegacyParser<unknown, ThrowOnError>) => {
|
||||||
|
return (options?.client ?? client).post<PostV1NodesResponse, PostV1NodesError, ThrowOnError>({
|
||||||
|
...options,
|
||||||
|
url: '/v1/nodes'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getV1UsersUsersJson = <ThrowOnError extends boolean = false>(options?: OptionsLegacyParser<unknown, ThrowOnError>) => {
|
||||||
|
return (options?.client ?? client).get<GetV1UsersUsersJsonResponse, GetV1UsersUsersJsonError, ThrowOnError>({
|
||||||
|
...options,
|
||||||
|
url: '/v1/users/users.json'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getV1UsersByUserIdJson = <ThrowOnError extends boolean = false>(options?: OptionsLegacyParser<GetV1UsersByUserIdJsonData, ThrowOnError>) => {
|
||||||
|
return (options?.client ?? client).get<GetV1UsersByUserIdJsonResponse, GetV1UsersByUserIdJsonError, ThrowOnError>({
|
||||||
|
...options,
|
||||||
|
url: '/v1/users/{userId}.json'
|
||||||
|
});
|
||||||
|
};
|
||||||
173
packages/store-client/src/client/types.gen.ts
Normal file
173
packages/store-client/src/client/types.gen.ts
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
|
export type NodeDefinition = {
|
||||||
|
id: string;
|
||||||
|
inputs?: {
|
||||||
|
[key: string]: NodeInput;
|
||||||
|
};
|
||||||
|
outputs?: Array<(string)>;
|
||||||
|
meta?: {
|
||||||
|
description?: string;
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NodeInput = {
|
||||||
|
internal?: boolean;
|
||||||
|
external?: boolean;
|
||||||
|
setting?: string;
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
accepts?: Array<(string)>;
|
||||||
|
hidden?: boolean;
|
||||||
|
type: 'seed';
|
||||||
|
value?: number;
|
||||||
|
} | {
|
||||||
|
internal?: boolean;
|
||||||
|
external?: boolean;
|
||||||
|
setting?: string;
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
accepts?: Array<(string)>;
|
||||||
|
hidden?: boolean;
|
||||||
|
type: 'boolean';
|
||||||
|
value?: boolean;
|
||||||
|
} | {
|
||||||
|
internal?: boolean;
|
||||||
|
external?: boolean;
|
||||||
|
setting?: string;
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
accepts?: Array<(string)>;
|
||||||
|
hidden?: boolean;
|
||||||
|
type: 'float';
|
||||||
|
element?: 'slider';
|
||||||
|
value?: number;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
} | {
|
||||||
|
internal?: boolean;
|
||||||
|
external?: boolean;
|
||||||
|
setting?: string;
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
accepts?: Array<(string)>;
|
||||||
|
hidden?: boolean;
|
||||||
|
type: 'integer';
|
||||||
|
element?: 'slider';
|
||||||
|
value?: number;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
} | {
|
||||||
|
internal?: boolean;
|
||||||
|
external?: boolean;
|
||||||
|
setting?: string;
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
accepts?: Array<(string)>;
|
||||||
|
hidden?: boolean;
|
||||||
|
type: 'select';
|
||||||
|
options?: Array<(string)>;
|
||||||
|
value?: number;
|
||||||
|
} | {
|
||||||
|
internal?: boolean;
|
||||||
|
external?: boolean;
|
||||||
|
setting?: string;
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
accepts?: Array<(string)>;
|
||||||
|
hidden?: boolean;
|
||||||
|
type: 'vec3';
|
||||||
|
value?: Array<(number)>;
|
||||||
|
} | {
|
||||||
|
internal?: boolean;
|
||||||
|
external?: boolean;
|
||||||
|
setting?: string;
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
accepts?: Array<(string)>;
|
||||||
|
hidden?: boolean;
|
||||||
|
type: 'geometry';
|
||||||
|
} | {
|
||||||
|
internal?: boolean;
|
||||||
|
external?: boolean;
|
||||||
|
setting?: string;
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
accepts?: Array<(string)>;
|
||||||
|
hidden?: boolean;
|
||||||
|
type: 'path';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type type = 'seed';
|
||||||
|
|
||||||
|
export type element = 'slider';
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetV1NodesByUserJsonData = {
|
||||||
|
path: {
|
||||||
|
user: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetV1NodesByUserJsonResponse = (Array<NodeDefinition>);
|
||||||
|
|
||||||
|
export type GetV1NodesByUserJsonError = unknown;
|
||||||
|
|
||||||
|
export type GetV1NodesByUserBySystemJsonData = {
|
||||||
|
path: {
|
||||||
|
system?: string;
|
||||||
|
user: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetV1NodesByUserBySystemJsonResponse = (Array<NodeDefinition>);
|
||||||
|
|
||||||
|
export type GetV1NodesByUserBySystemJsonError = unknown;
|
||||||
|
|
||||||
|
export type GetV1NodesByUserBySystemByNodeIdby-.+jsonData = {
|
||||||
|
path: {
|
||||||
|
nodeId: string;
|
||||||
|
system: string;
|
||||||
|
user: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetV1NodesByUserBySystemByNodeIdby-.+jsonResponse = (NodeDefinition);
|
||||||
|
|
||||||
|
export type GetV1NodesByUserBySystemByNodeIdby-.+jsonError = unknown;
|
||||||
|
|
||||||
|
export type GetV1NodesByUserBySystemByNodeIdby-.+WasmData = {
|
||||||
|
path: {
|
||||||
|
nodeId: string;
|
||||||
|
system: string;
|
||||||
|
user: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetV1NodesByUserBySystemByNodeIdby-.+WasmResponse = (unknown);
|
||||||
|
|
||||||
|
export type GetV1NodesByUserBySystemByNodeIdby-.+WasmError = unknown;
|
||||||
|
|
||||||
|
export type PostV1NodesResponse = (NodeDefinition);
|
||||||
|
|
||||||
|
export type PostV1NodesError = unknown;
|
||||||
|
|
||||||
|
export type GetV1UsersUsersJsonResponse = (Array<User>);
|
||||||
|
|
||||||
|
export type GetV1UsersUsersJsonError = unknown;
|
||||||
|
|
||||||
|
export type GetV1UsersByUserIdJsonData = {
|
||||||
|
path?: {
|
||||||
|
userId?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetV1UsersByUserIdJsonResponse = (User);
|
||||||
|
|
||||||
|
export type GetV1UsersByUserIdJsonError = unknown;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@nodes/types",
|
"name": "@nodarium/types",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -13,6 +13,6 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "^3.23.8"
|
"zod": "^4.1.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import { Graph, NodeDefinition, NodeId } from "./types";
|
import type { Graph, NodeDefinition, NodeType } from "./types";
|
||||||
|
|
||||||
export interface NodeRegistry {
|
export interface NodeRegistry {
|
||||||
/**
|
/**
|
||||||
* The status of the node registry
|
* The status of the node registry
|
||||||
* @remarks The status should be "loading" when the registry is loading, "ready" when the registry is ready, and "error" if an error occurred while loading the registry
|
* @remarks The status should be "loading" when the registry is loading, "ready" when the registry is ready, and "error" if an error occurred while loading the registry
|
||||||
*/
|
*/
|
||||||
status: "loading" | "ready" | "error";
|
status: "loading" | "ready" | "error";
|
||||||
/**
|
/**
|
||||||
* Load the nodes with the given ids
|
* Load the nodes with the given ids
|
||||||
* @param nodeIds - The ids of the nodes to load
|
* @param nodeIds - The ids of the nodes to load
|
||||||
* @returns A promise that resolves when the nodes are loaded
|
* @returns A promise that resolves when the nodes are loaded
|
||||||
* @throws An error if the nodes could not be loaded
|
* @throws An error if the nodes could not be loaded
|
||||||
* @remarks This method should be called before calling getNode or getAllNodes
|
* @remarks This method should be called before calling getNode or getAllNodes
|
||||||
*/
|
*/
|
||||||
load: (nodeIds: NodeId[]) => Promise<NodeDefinition[]>;
|
load: (nodeIds: NodeType[]) => Promise<NodeDefinition[]>;
|
||||||
/**
|
/**
|
||||||
* Get a node by id
|
* Get a node by id
|
||||||
* @param id - The id of the node to get
|
* @param id - The id of the node to get
|
||||||
* @returns The node with the given id, or undefined if no such node exists
|
* @returns The node with the given id, or undefined if no such node exists
|
||||||
*/
|
*/
|
||||||
getNode: (id: NodeId | string) => NodeDefinition | undefined;
|
getNode: (id: NodeType | string) => NodeDefinition | undefined;
|
||||||
/**
|
/**
|
||||||
* Get all nodes
|
* Get all nodes
|
||||||
* @returns An array of all nodes
|
* @returns An array of all nodes
|
||||||
@@ -27,30 +27,30 @@ export interface NodeRegistry {
|
|||||||
getAllNodes: () => NodeDefinition[];
|
getAllNodes: () => NodeDefinition[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new node
|
* Register a new node
|
||||||
* @param wasmBuffer - The WebAssembly buffer for the node
|
* @param wasmBuffer - The WebAssembly buffer for the node
|
||||||
* @returns The node definition
|
* @returns The node definition
|
||||||
*/
|
*/
|
||||||
register: (wasmBuffer: ArrayBuffer) => Promise<NodeDefinition>;
|
register: (wasmBuffer: ArrayBuffer) => Promise<NodeDefinition>;
|
||||||
|
|
||||||
|
|
||||||
cache?: AsyncCache<ArrayBuffer>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeExecutor {
|
export interface RuntimeExecutor {
|
||||||
/**
|
/**
|
||||||
* Execute the given graph
|
* Execute the given graph
|
||||||
* @param graph - The graph to execute
|
* @param graph - The graph to execute
|
||||||
* @returns The result of the execution
|
* @returns The result of the execution
|
||||||
*/
|
*/
|
||||||
execute: (graph: Graph, settings: Record<string, unknown>) => Promise<Int32Array>;
|
execute: (
|
||||||
|
graph: Graph,
|
||||||
|
settings: Record<string, unknown>,
|
||||||
|
) => Promise<Int32Array>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SyncCache<T = unknown> {
|
export interface SyncCache<T = unknown> {
|
||||||
/**
|
/**
|
||||||
* The maximum number of items that can be stored in the cache
|
* The maximum number of items that can be stored in the cache
|
||||||
* @remarks When the cache size exceeds this value, the oldest items should be removed
|
* @remarks When the cache size exceeds this value, the oldest items should be removed
|
||||||
*/
|
*/
|
||||||
size: number;
|
size: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,14 +69,13 @@ export interface SyncCache<T = unknown> {
|
|||||||
* Clear the cache
|
* Clear the cache
|
||||||
*/
|
*/
|
||||||
clear: () => void;
|
clear: () => void;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AsyncCache<T = unknown> {
|
export interface AsyncCache<T = unknown> {
|
||||||
/**
|
/**
|
||||||
* The maximum number of items that can be stored in the cache
|
* The maximum number of items that can be stored in the cache
|
||||||
* @remarks When the cache size exceeds this value, the oldest items should be removed
|
* @remarks When the cache size exceeds this value, the oldest items should be removed
|
||||||
*/
|
*/
|
||||||
size: number;
|
size: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
export type { NodeInput } from "./inputs";
|
export type { NodeInput } from "./inputs";
|
||||||
export type { NodeRegistry, RuntimeExecutor, SyncCache, AsyncCache } from "./components";
|
export type {
|
||||||
export type { Node, NodeDefinition, Socket, NodeId, Edge, Graph } from "./types";
|
NodeRegistry,
|
||||||
|
RuntimeExecutor,
|
||||||
|
SyncCache,
|
||||||
|
AsyncCache,
|
||||||
|
} from "./components";
|
||||||
|
export type {
|
||||||
|
Node,
|
||||||
|
NodeDefinition,
|
||||||
|
Socket,
|
||||||
|
NodeType,
|
||||||
|
Edge,
|
||||||
|
Graph,
|
||||||
|
} from "./types";
|
||||||
export { NodeSchema, GraphSchema } from "./types";
|
export { NodeSchema, GraphSchema } from "./types";
|
||||||
export { NodeDefinitionSchema } from "./types";
|
export { NodeDefinitionSchema } from "./types";
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,23 @@ const DefaultOptionsSchema = z.object({
|
|||||||
setting: z.string().optional(),
|
setting: z.string().optional(),
|
||||||
label: z.string().optional(),
|
label: z.string().optional(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
accepts: z.array(z.string()).optional(),
|
accepts: z
|
||||||
|
.array(
|
||||||
|
z.union([
|
||||||
|
z.literal("float"),
|
||||||
|
z.literal("integer"),
|
||||||
|
z.literal("boolean"),
|
||||||
|
z.literal("select"),
|
||||||
|
z.literal("seed"),
|
||||||
|
z.literal("vec3"),
|
||||||
|
z.literal("geometry"),
|
||||||
|
z.literal("path"),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
hidden: z.boolean().optional(),
|
hidden: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export const NodeInputFloatSchema = z.object({
|
export const NodeInputFloatSchema = z.object({
|
||||||
...DefaultOptionsSchema.shape,
|
...DefaultOptionsSchema.shape,
|
||||||
type: z.literal("float"),
|
type: z.literal("float"),
|
||||||
@@ -40,7 +52,7 @@ export const NodeInputSelectSchema = z.object({
|
|||||||
...DefaultOptionsSchema.shape,
|
...DefaultOptionsSchema.shape,
|
||||||
type: z.literal("select"),
|
type: z.literal("select"),
|
||||||
options: z.array(z.string()).optional(),
|
options: z.array(z.string()).optional(),
|
||||||
value: z.number().optional(),
|
value: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NodeInputSeedSchema = z.object({
|
export const NodeInputSeedSchema = z.object({
|
||||||
@@ -74,7 +86,7 @@ export const NodeInputSchema = z.union([
|
|||||||
NodeInputSeedSchema,
|
NodeInputSeedSchema,
|
||||||
NodeInputVec3Schema,
|
NodeInputVec3Schema,
|
||||||
NodeInputGeometrySchema,
|
NodeInputGeometrySchema,
|
||||||
NodeInputPathSchema
|
NodeInputPathSchema,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type NodeInput = z.infer<typeof NodeInputSchema>;
|
export type NodeInput = z.infer<typeof NodeInputSchema>;
|
||||||
|
|||||||
@@ -1,27 +1,23 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { NodeInputSchema } from "./inputs";
|
import { NodeInputSchema } from "./inputs";
|
||||||
|
|
||||||
export type NodeId = `${string}/${string}/${string}`;
|
export const NodeTypeSchema = z
|
||||||
|
.string()
|
||||||
|
.regex(/^[^/]+\/[^/]+\/[^/]+$/, "Invalid NodeId format")
|
||||||
|
.transform((value) => value as `${string}/${string}/${string}`);
|
||||||
|
|
||||||
export const NodeSchema = z.object({
|
export type NodeType = z.infer<typeof NodeTypeSchema>;
|
||||||
id: z.number(),
|
|
||||||
type: z.string(),
|
|
||||||
props: z.record(z.union([z.number(), z.array(z.number())])).optional(),
|
|
||||||
meta: z.object({
|
|
||||||
title: z.string().optional(),
|
|
||||||
lastModified: z.string().optional(),
|
|
||||||
}).optional(),
|
|
||||||
position: z.tuple([z.number(), z.number()])
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Node = {
|
export type Node = {
|
||||||
|
/**
|
||||||
|
* .tmp only exists at runtime
|
||||||
|
*/
|
||||||
tmp?: {
|
tmp?: {
|
||||||
depth?: number;
|
depth?: number;
|
||||||
mesh?: any;
|
mesh?: any;
|
||||||
random?: number;
|
parents?: Node[];
|
||||||
parents?: Node[],
|
children?: Node[];
|
||||||
children?: Node[],
|
inputNodes?: Record<string, Node>;
|
||||||
inputNodes?: Record<string, Node>
|
|
||||||
type?: NodeDefinition;
|
type?: NodeDefinition;
|
||||||
downX?: number;
|
downX?: number;
|
||||||
downY?: number;
|
downY?: number;
|
||||||
@@ -30,17 +26,34 @@ export type Node = {
|
|||||||
ref?: HTMLElement;
|
ref?: HTMLElement;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
isMoving?: boolean;
|
isMoving?: boolean;
|
||||||
}
|
};
|
||||||
} & z.infer<typeof NodeSchema>;
|
} & z.infer<typeof NodeSchema>;
|
||||||
|
|
||||||
export const NodeDefinitionSchema = z.object({
|
export const NodeDefinitionSchema = z.object({
|
||||||
id: z.string(),
|
id: NodeTypeSchema,
|
||||||
inputs: z.record(NodeInputSchema).optional(),
|
inputs: z.record(z.string(), NodeInputSchema).optional(),
|
||||||
outputs: z.array(z.string()).optional(),
|
outputs: z.array(z.string()).optional(),
|
||||||
meta: z.object({
|
meta: z
|
||||||
description: z.string().optional(),
|
.object({
|
||||||
title: z.string().optional(),
|
description: z.string().optional(),
|
||||||
}).optional(),
|
title: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NodeSchema = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
type: NodeTypeSchema,
|
||||||
|
props: z
|
||||||
|
.record(z.string(), z.union([z.number(), z.array(z.number())]))
|
||||||
|
.optional(),
|
||||||
|
meta: z
|
||||||
|
.object({
|
||||||
|
title: z.string().optional(),
|
||||||
|
lastModified: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
position: z.tuple([z.number(), z.number()]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type NodeDefinition = z.infer<typeof NodeDefinitionSchema> & {
|
export type NodeDefinition = z.infer<typeof NodeDefinitionSchema> & {
|
||||||
@@ -56,12 +69,14 @@ export type Socket = {
|
|||||||
export type Edge = [Node, number, Node, string];
|
export type Edge = [Node, number, Node, string];
|
||||||
|
|
||||||
export const GraphSchema = z.object({
|
export const GraphSchema = z.object({
|
||||||
id: z.number().optional(),
|
id: z.number(),
|
||||||
meta: z.object({
|
meta: z
|
||||||
title: z.string().optional(),
|
.object({
|
||||||
lastModified: z.string().optional(),
|
title: z.string().optional(),
|
||||||
}).optional(),
|
lastModified: z.string().optional(),
|
||||||
settings: z.record(z.any()).optional(),
|
})
|
||||||
|
.optional(),
|
||||||
|
settings: z.record(z.string(), z.any()).optional(),
|
||||||
nodes: z.array(NodeSchema),
|
nodes: z.array(NodeSchema),
|
||||||
edges: z.array(z.tuple([z.number(), z.number(), z.number(), z.string()])),
|
edges: z.array(z.tuple([z.number(), z.number(), z.number(), z.string()])),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@nodes/ui",
|
"name": "@nodarium/ui",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
@@ -30,36 +30,36 @@
|
|||||||
"svelte": "^4.0.0"
|
"svelte": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@storybook/addon-essentials": "^8.4.1",
|
"@storybook/addon-essentials": "^8.6.14",
|
||||||
"@storybook/addon-svelte-csf": "5.0.0-next.10",
|
"@storybook/addon-svelte-csf": "5.0.10",
|
||||||
"@storybook/addon-themes": "^8.4.1",
|
"@storybook/addon-themes": "^10.0.8",
|
||||||
"@storybook/svelte": "^8.4.1",
|
"@storybook/svelte": "^10.0.8",
|
||||||
"@storybook/sveltekit": "^8.4.1",
|
"@storybook/sveltekit": "^10.0.8",
|
||||||
"@sveltejs/adapter-static": "^3.0.6",
|
"@sveltejs/adapter-static": "^3.0.10",
|
||||||
"@sveltejs/kit": "^2.7.4",
|
"@sveltejs/kit": "^2.49.0",
|
||||||
"@sveltejs/package": "^2.3.7",
|
"@sveltejs/package": "^2.5.6",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||||
"@types/eslint": "^9.6.1",
|
"@types/eslint": "^9.6.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.12.2",
|
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
||||||
"@typescript-eslint/parser": "^8.12.2",
|
"@typescript-eslint/parser": "^8.47.0",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.39.1",
|
||||||
"eslint-plugin-storybook": "^0.10.2",
|
"eslint-plugin-storybook": "^10.0.8",
|
||||||
"eslint-plugin-svelte": "^2.46.0",
|
"eslint-plugin-svelte": "^3.13.0",
|
||||||
"publint": "^0.2.12",
|
"publint": "^0.3.15",
|
||||||
"storybook": "^8.4.1",
|
"storybook": "^10.0.8",
|
||||||
"svelte": "^5.1.9",
|
"svelte": "^5.43.14",
|
||||||
"svelte-check": "^4.0.5",
|
"svelte-check": "^4.3.4",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^5.4.10",
|
"vite": "^7.2.4",
|
||||||
"vitest": "^2.1.4"
|
"vitest": "^4.0.13",
|
||||||
|
"@nodarium/types": "link:../types"
|
||||||
},
|
},
|
||||||
"svelte": "./dist/index.js",
|
"svelte": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodes/types": "link:../types",
|
"@threlte/core": "^8.3.0",
|
||||||
"@threlte/core": "^7.3.1",
|
"@threlte/extras": "^9.7.0"
|
||||||
"@threlte/extras": "^8.12.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
title?: string;
|
title?: string;
|
||||||
transparent?: boolean;
|
transparent?: boolean;
|
||||||
children?: import('svelte').Snippet;
|
children?: import('svelte').Snippet;
|
||||||
|
open?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { title = "Details", transparent = false, children }: Props = $props();
|
let { title = "Details", transparent = false, children, open = $bindable(false) }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<details class:transparent>
|
<details class:transparent bind:open>
|
||||||
<summary>{title}</summary>
|
<summary>{title}</summary>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
@@ -33,7 +34,4 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
|
||||||
/* padding-left: 12px; */
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import Integer from './elements/Integer.svelte';
|
import Integer from './elements/Integer.svelte';
|
||||||
import Select from './elements/Select.svelte';
|
import Select from './elements/Select.svelte';
|
||||||
|
|
||||||
import type { NodeInput } from '@nodes/types';
|
import type { NodeInput } from '@nodarium/types';
|
||||||
import Vec3 from './elements/Vec3.svelte';
|
import Vec3 from './elements/Vec3.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -27,4 +27,3 @@
|
|||||||
{:else if input.type === 'vec3'}
|
{:else if input.type === 'vec3'}
|
||||||
<Vec3 {id} bind:value />
|
<Vec3 {id} bind:value />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
ctrl?: boolean;
|
ctrl?: boolean;
|
||||||
shift?: boolean;
|
shift?: boolean;
|
||||||
alt?: boolean;
|
alt?: boolean;
|
||||||
key: string;
|
key: string | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@nodes/utils",
|
"name": "@nodarium/utils",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -10,10 +10,10 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodes/types": "link:../types"
|
"@nodarium/types": "link:../types"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^5.4.10",
|
"vite": "^7.2.4",
|
||||||
"vitest": "^2.1.4"
|
"vitest": "^4.0.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,84 @@
|
|||||||
|
|
||||||
// https://github.com/6502/sha256/blob/main/sha256.js
|
// https://github.com/6502/sha256/blob/main/sha256.js
|
||||||
function sha256(data?: string | Uint8Array) {
|
function sha256(data?: string | Int32Array) {
|
||||||
let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a,
|
let h0 = 0x6a09e667,
|
||||||
h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19,
|
h1 = 0xbb67ae85,
|
||||||
tsz = 0, bp = 0;
|
h2 = 0x3c6ef372,
|
||||||
const k = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
h3 = 0xa54ff53a,
|
||||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
h4 = 0x510e527f,
|
||||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
h5 = 0x9b05688c,
|
||||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
h6 = 0x1f83d9ab,
|
||||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
h7 = 0x5be0cd19,
|
||||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
tsz = 0,
|
||||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
bp = 0;
|
||||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
|
const k = [
|
||||||
rrot = (x, n) => (x >>> n) | (x << (32 - n)),
|
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
|
||||||
|
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||||
|
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
||||||
|
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||||
|
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
|
||||||
|
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||||
|
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
|
||||||
|
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||||
|
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
||||||
|
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||||
|
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||||
|
],
|
||||||
|
rrot = (x: number, n: number) => (x >>> n) | (x << (32 - n)),
|
||||||
w = new Uint32Array(64),
|
w = new Uint32Array(64),
|
||||||
buf = new Uint8Array(64),
|
buf = new Uint8Array(64),
|
||||||
process = () => {
|
process = () => {
|
||||||
for (let j = 0, r = 0; j < 16; j++, r += 4) {
|
for (let j = 0, r = 0; j < 16; j++, r += 4) {
|
||||||
w[j] = (buf[r] << 24) | (buf[r + 1] << 16) | (buf[r + 2] << 8) | buf[r + 3];
|
w[j] =
|
||||||
|
(buf[r] << 24) | (buf[r + 1] << 16) | (buf[r + 2] << 8) | buf[r + 3];
|
||||||
}
|
}
|
||||||
for (let j = 16; j < 64; j++) {
|
for (let j = 16; j < 64; j++) {
|
||||||
let s0 = rrot(w[j - 15], 7) ^ rrot(w[j - 15], 18) ^ (w[j - 15] >>> 3);
|
let s0 = rrot(w[j - 15], 7) ^ rrot(w[j - 15], 18) ^ (w[j - 15] >>> 3);
|
||||||
let s1 = rrot(w[j - 2], 17) ^ rrot(w[j - 2], 19) ^ (w[j - 2] >>> 10);
|
let s1 = rrot(w[j - 2], 17) ^ rrot(w[j - 2], 19) ^ (w[j - 2] >>> 10);
|
||||||
w[j] = (w[j - 16] + s0 + w[j - 7] + s1) | 0;
|
w[j] = (w[j - 16] + s0 + w[j - 7] + s1) | 0;
|
||||||
}
|
}
|
||||||
let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7;
|
let a = h0,
|
||||||
|
b = h1,
|
||||||
|
c = h2,
|
||||||
|
d = h3,
|
||||||
|
e = h4,
|
||||||
|
f = h5,
|
||||||
|
g = h6,
|
||||||
|
h = h7;
|
||||||
for (let j = 0; j < 64; j++) {
|
for (let j = 0; j < 64; j++) {
|
||||||
let S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25),
|
let S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25),
|
||||||
ch = (e & f) ^ ((~e) & g),
|
ch = (e & f) ^ (~e & g),
|
||||||
t1 = (h + S1 + ch + k[j] + w[j]) | 0,
|
t1 = (h + S1 + ch + k[j] + w[j]) | 0,
|
||||||
S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22),
|
S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22),
|
||||||
maj = (a & b) ^ (a & c) ^ (b & c),
|
maj = (a & b) ^ (a & c) ^ (b & c),
|
||||||
t2 = (S0 + maj) | 0;
|
t2 = (S0 + maj) | 0;
|
||||||
h = g; g = f; f = e; e = (d + t1) | 0; d = c; c = b; b = a; a = (t1 + t2) | 0;
|
h = g;
|
||||||
|
g = f;
|
||||||
|
f = e;
|
||||||
|
e = (d + t1) | 0;
|
||||||
|
d = c;
|
||||||
|
c = b;
|
||||||
|
b = a;
|
||||||
|
a = (t1 + t2) | 0;
|
||||||
}
|
}
|
||||||
h0 = (h0 + a) | 0; h1 = (h1 + b) | 0; h2 = (h2 + c) | 0; h3 = (h3 + d) | 0;
|
h0 = (h0 + a) | 0;
|
||||||
h4 = (h4 + e) | 0; h5 = (h5 + f) | 0; h6 = (h6 + g) | 0; h7 = (h7 + h) | 0;
|
h1 = (h1 + b) | 0;
|
||||||
|
h2 = (h2 + c) | 0;
|
||||||
|
h3 = (h3 + d) | 0;
|
||||||
|
h4 = (h4 + e) | 0;
|
||||||
|
h5 = (h5 + f) | 0;
|
||||||
|
h6 = (h6 + g) | 0;
|
||||||
|
h7 = (h7 + h) | 0;
|
||||||
bp = 0;
|
bp = 0;
|
||||||
},
|
},
|
||||||
add = data => {
|
add = (input: string | Int32Array) => {
|
||||||
if (typeof data === "string") {
|
const data =
|
||||||
data = typeof TextEncoder === "undefined" ? Buffer.from(data) : (new TextEncoder).encode(data);
|
typeof input === "string"
|
||||||
}
|
? typeof TextEncoder === "undefined"
|
||||||
|
? //@ts-ignore
|
||||||
|
Buffer.from(input)
|
||||||
|
: new TextEncoder().encode(input)
|
||||||
|
: input;
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
buf[bp++] = data[i];
|
buf[bp++] = data[i];
|
||||||
if (bp === 64) process();
|
if (bp === 64) process();
|
||||||
@@ -49,7 +86,8 @@ function sha256(data?: string | Uint8Array) {
|
|||||||
tsz += data.length;
|
tsz += data.length;
|
||||||
},
|
},
|
||||||
digest = () => {
|
digest = () => {
|
||||||
buf[bp++] = 0x80; if (bp == 64) process();
|
buf[bp++] = 0x80;
|
||||||
|
if (bp == 64) process();
|
||||||
if (bp + 8 > 64) {
|
if (bp + 8 > 64) {
|
||||||
while (bp < 64) buf[bp++] = 0x00;
|
while (bp < 64) buf[bp++] = 0x00;
|
||||||
process();
|
process();
|
||||||
@@ -57,24 +95,48 @@ function sha256(data?: string | Uint8Array) {
|
|||||||
while (bp < 58) buf[bp++] = 0x00;
|
while (bp < 58) buf[bp++] = 0x00;
|
||||||
// Max number of bytes is 35,184,372,088,831
|
// Max number of bytes is 35,184,372,088,831
|
||||||
let L = tsz * 8;
|
let L = tsz * 8;
|
||||||
buf[bp++] = (L / 1099511627776.) & 255;
|
buf[bp++] = (L / 1099511627776) & 255;
|
||||||
buf[bp++] = (L / 4294967296.) & 255;
|
buf[bp++] = (L / 4294967296) & 255;
|
||||||
buf[bp++] = L >>> 24;
|
buf[bp++] = L >>> 24;
|
||||||
buf[bp++] = (L >>> 16) & 255;
|
buf[bp++] = (L >>> 16) & 255;
|
||||||
buf[bp++] = (L >>> 8) & 255;
|
buf[bp++] = (L >>> 8) & 255;
|
||||||
buf[bp++] = L & 255;
|
buf[bp++] = L & 255;
|
||||||
process();
|
process();
|
||||||
let reply = new Uint8Array(32);
|
let reply = new Uint8Array(32);
|
||||||
reply[0] = h0 >>> 24; reply[1] = (h0 >>> 16) & 255; reply[2] = (h0 >>> 8) & 255; reply[3] = h0 & 255;
|
reply[0] = h0 >>> 24;
|
||||||
reply[4] = h1 >>> 24; reply[5] = (h1 >>> 16) & 255; reply[6] = (h1 >>> 8) & 255; reply[7] = h1 & 255;
|
reply[1] = (h0 >>> 16) & 255;
|
||||||
reply[8] = h2 >>> 24; reply[9] = (h2 >>> 16) & 255; reply[10] = (h2 >>> 8) & 255; reply[11] = h2 & 255;
|
reply[2] = (h0 >>> 8) & 255;
|
||||||
reply[12] = h3 >>> 24; reply[13] = (h3 >>> 16) & 255; reply[14] = (h3 >>> 8) & 255; reply[15] = h3 & 255;
|
reply[3] = h0 & 255;
|
||||||
reply[16] = h4 >>> 24; reply[17] = (h4 >>> 16) & 255; reply[18] = (h4 >>> 8) & 255; reply[19] = h4 & 255;
|
reply[4] = h1 >>> 24;
|
||||||
reply[20] = h5 >>> 24; reply[21] = (h5 >>> 16) & 255; reply[22] = (h5 >>> 8) & 255; reply[23] = h5 & 255;
|
reply[5] = (h1 >>> 16) & 255;
|
||||||
reply[24] = h6 >>> 24; reply[25] = (h6 >>> 16) & 255; reply[26] = (h6 >>> 8) & 255; reply[27] = h6 & 255;
|
reply[6] = (h1 >>> 8) & 255;
|
||||||
reply[28] = h7 >>> 24; reply[29] = (h7 >>> 16) & 255; reply[30] = (h7 >>> 8) & 255; reply[31] = h7 & 255;
|
reply[7] = h1 & 255;
|
||||||
|
reply[8] = h2 >>> 24;
|
||||||
|
reply[9] = (h2 >>> 16) & 255;
|
||||||
|
reply[10] = (h2 >>> 8) & 255;
|
||||||
|
reply[11] = h2 & 255;
|
||||||
|
reply[12] = h3 >>> 24;
|
||||||
|
reply[13] = (h3 >>> 16) & 255;
|
||||||
|
reply[14] = (h3 >>> 8) & 255;
|
||||||
|
reply[15] = h3 & 255;
|
||||||
|
reply[16] = h4 >>> 24;
|
||||||
|
reply[17] = (h4 >>> 16) & 255;
|
||||||
|
reply[18] = (h4 >>> 8) & 255;
|
||||||
|
reply[19] = h4 & 255;
|
||||||
|
reply[20] = h5 >>> 24;
|
||||||
|
reply[21] = (h5 >>> 16) & 255;
|
||||||
|
reply[22] = (h5 >>> 8) & 255;
|
||||||
|
reply[23] = h5 & 255;
|
||||||
|
reply[24] = h6 >>> 24;
|
||||||
|
reply[25] = (h6 >>> 16) & 255;
|
||||||
|
reply[26] = (h6 >>> 8) & 255;
|
||||||
|
reply[27] = h6 & 255;
|
||||||
|
reply[28] = h7 >>> 24;
|
||||||
|
reply[29] = (h7 >>> 16) & 255;
|
||||||
|
reply[30] = (h7 >>> 8) & 255;
|
||||||
|
reply[31] = h7 & 255;
|
||||||
let res = "";
|
let res = "";
|
||||||
reply.forEach(x => res += ("0" + x.toString(16)).slice(-2));
|
reply.forEach((x) => (res += ("0" + x.toString(16)).slice(-2)));
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,8 +145,8 @@ function sha256(data?: string | Uint8Array) {
|
|||||||
return { add, digest };
|
return { add, digest };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fastHashArrayBuffer(buffer: ArrayBuffer): string {
|
export function fastHashArrayBuffer(buffer: string | Int32Array): string {
|
||||||
return sha256(new Uint8Array(buffer)).digest();
|
return sha256(buffer).digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shamelessly copied from
|
// Shamelessly copied from
|
||||||
@@ -101,22 +163,19 @@ export function fastHashString(input: string) {
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function fastHash(input: (string | Int32Array | number)[]) {
|
export function fastHash(input: (string | Int32Array | number)[]) {
|
||||||
|
|
||||||
const s = sha256();
|
const s = sha256();
|
||||||
|
|
||||||
for (let i = 0; i < input.length; i++) {
|
for (let i = 0; i < input.length; i++) {
|
||||||
const v = input[i]
|
const v = input[i];
|
||||||
if (typeof v === "string") {
|
if (typeof v === "string") {
|
||||||
s.add(v);
|
s.add(v);
|
||||||
} else if (v instanceof Int32Array) {
|
} else if (v instanceof Int32Array) {
|
||||||
s.add(new Uint8Array(v.buffer));
|
s.add(v);
|
||||||
} else {
|
} else {
|
||||||
s.add(v.toString());
|
s.add(v.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.digest()
|
return s.digest();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
type SparseArray<T = number> = (T | T[] | SparseArray<T>)[];
|
type SparseArray<T = number> = (T | T[] | SparseArray<T>)[];
|
||||||
|
|
||||||
export function concatEncodedArrays(input: (number | number[] | Int32Array)[]): Int32Array {
|
export function concatEncodedArrays(
|
||||||
|
input: (number | number[] | Int32Array)[],
|
||||||
|
): Int32Array {
|
||||||
let totalLength = 4;
|
let totalLength = 4;
|
||||||
for (let i = 0; i < input.length; i++) {
|
for (let i = 0; i < input.length; i++) {
|
||||||
const item = input[i];
|
const item = input[i];
|
||||||
@@ -36,7 +37,7 @@ export function concatEncodedArrays(input: (number | number[] | Int32Array)[]):
|
|||||||
result[totalLength - 2] = 1;
|
result[totalLength - 2] = 1;
|
||||||
result[totalLength - 1] = 1;
|
result[totalLength - 1] = 1;
|
||||||
|
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encodes a nested array into a flat array with bracket and distance notation
|
// Encodes a nested array into a flat array with bracket and distance notation
|
||||||
@@ -68,12 +69,11 @@ export function encodeNestedArray(array: SparseArray): number[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [...encoded, 1, 1];
|
return [...encoded, 1, 1];
|
||||||
};
|
}
|
||||||
|
|
||||||
function decode_recursive(dense: number[] | Int32Array, index = 0) {
|
function decode_recursive(dense: number[] | Int32Array, index = 0) {
|
||||||
|
|
||||||
if (dense instanceof Int32Array) {
|
if (dense instanceof Int32Array) {
|
||||||
dense = Array.from(dense)
|
dense = Array.from(dense);
|
||||||
}
|
}
|
||||||
|
|
||||||
const decoded: (number | number[])[] = [];
|
const decoded: (number | number[])[] = [];
|
||||||
@@ -82,12 +82,17 @@ function decode_recursive(dense: number[] | Int32Array, index = 0) {
|
|||||||
index += 2; // Skip the initial bracket notation
|
index += 2; // Skip the initial bracket notation
|
||||||
while (index < dense.length) {
|
while (index < dense.length) {
|
||||||
if (index === nextBracketIndex) {
|
if (index === nextBracketIndex) {
|
||||||
if (dense[index] === 0) { // Opening bracket detected
|
if (dense[index] === 0) {
|
||||||
const [p, nextIndex, _nextBracketIndex] = decode_recursive(dense, index);
|
// Opening bracket detected
|
||||||
decoded.push(p);
|
const [p, nextIndex, _nextBracketIndex] = decode_recursive(
|
||||||
|
dense,
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
decoded.push(...p);
|
||||||
index = nextIndex + 1;
|
index = nextIndex + 1;
|
||||||
nextBracketIndex = _nextBracketIndex;
|
nextBracketIndex = _nextBracketIndex;
|
||||||
} else { // Closing bracket detected
|
} else {
|
||||||
|
// Closing bracket detected
|
||||||
nextBracketIndex = dense[index + 1] + index + 1;
|
nextBracketIndex = dense[index + 1] + index + 1;
|
||||||
return [decoded, index, nextBracketIndex] as const;
|
return [decoded, index, nextBracketIndex] as const;
|
||||||
}
|
}
|
||||||
@@ -103,7 +108,6 @@ export function decodeNestedArray(dense: number[] | Int32Array) {
|
|||||||
return decode_recursive(dense, 0)[0];
|
return decode_recursive(dense, 0)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function splitNestedArray(input: Int32Array) {
|
export function splitNestedArray(input: Int32Array) {
|
||||||
let index = 0;
|
let index = 0;
|
||||||
const length = input.length;
|
const length = input.length;
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
import { NodeDefinition } from "@nodes/types";
|
//@ts-nocheck
|
||||||
|
import { NodeDefinition } from "@nodarium/types";
|
||||||
|
|
||||||
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
const cachedTextDecoder = new TextDecoder("utf-8", {
|
||||||
|
ignoreBOM: true,
|
||||||
|
fatal: true,
|
||||||
|
});
|
||||||
const cachedTextEncoder = new TextEncoder();
|
const cachedTextEncoder = new TextEncoder();
|
||||||
|
|
||||||
|
const encodeString =
|
||||||
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
|
typeof cachedTextEncoder.encodeInto === "function"
|
||||||
? function (arg, view) {
|
? function (arg, view) {
|
||||||
return cachedTextEncoder.encodeInto(arg, view);
|
return cachedTextEncoder.encodeInto(arg, view);
|
||||||
}
|
}
|
||||||
: function (arg, view) {
|
: function (arg, view) {
|
||||||
const buf = cachedTextEncoder.encode(arg);
|
const buf = cachedTextEncoder.encode(arg);
|
||||||
view.set(buf);
|
view.set(buf);
|
||||||
return {
|
return {
|
||||||
read: arg.length,
|
read: arg.length,
|
||||||
written: buf.length
|
written: buf.length,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
|
||||||
function createWrapper() {
|
function createWrapper() {
|
||||||
let wasm: any;
|
let wasm: any;
|
||||||
@@ -53,7 +57,9 @@ function createWrapper() {
|
|||||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getObject(idx: number) { return heap[idx]; }
|
function getObject(idx: number) {
|
||||||
|
return heap[idx];
|
||||||
|
}
|
||||||
|
|
||||||
function addHeapObject(obj: any) {
|
function addHeapObject(obj: any) {
|
||||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||||
@@ -63,9 +69,11 @@ function createWrapper() {
|
|||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let WASM_VECTOR_LEN = 0;
|
let WASM_VECTOR_LEN = 0;
|
||||||
function passArray32ToWasm0(arg: ArrayLike<number>, malloc: (arg0: number, arg1: number) => number) {
|
function passArray32ToWasm0(
|
||||||
|
arg: ArrayLike<number>,
|
||||||
|
malloc: (arg0: number, arg1: number) => number,
|
||||||
|
) {
|
||||||
const ptr = malloc(arg.length * 4, 4) >>> 0;
|
const ptr = malloc(arg.length * 4, 4) >>> 0;
|
||||||
getUint32Memory0().set(arg, ptr / 4);
|
getUint32Memory0().set(arg, ptr / 4);
|
||||||
WASM_VECTOR_LEN = arg.length;
|
WASM_VECTOR_LEN = arg.length;
|
||||||
@@ -89,21 +97,10 @@ function createWrapper() {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArrayJsValueFromWasm0(ptr: number, len: number) {
|
|
||||||
ptr = ptr >>> 0;
|
|
||||||
const mem = getUint32Memory0();
|
|
||||||
const slice = mem.subarray(ptr / 4, ptr / 4 + len);
|
|
||||||
const result = [];
|
|
||||||
for (let i = 0; i < slice.length; i++) {
|
|
||||||
result.push(takeObject(slice[i]));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function __wbindgen_string_new(arg0: number, arg1: number) {
|
function __wbindgen_string_new(arg0: number, arg1: number) {
|
||||||
const ret = getStringFromWasm0(arg0, arg1);
|
const ret = getStringFromWasm0(arg0, arg1);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
}
|
||||||
|
|
||||||
// Additional methods and their internal helpers can also be refactored in a similar manner.
|
// Additional methods and their internal helpers can also be refactored in a similar manner.
|
||||||
function get_definition() {
|
function get_definition() {
|
||||||
@@ -124,7 +121,6 @@ function createWrapper() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function execute(args: Int32Array) {
|
function execute(args: Int32Array) {
|
||||||
try {
|
try {
|
||||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||||
@@ -141,12 +137,19 @@ function createWrapper() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function passStringToWasm0(arg: string, malloc: (arg0: any, arg1: number) => number, realloc: ((arg0: number, arg1: any, arg2: number, arg3: number) => number) | undefined) {
|
function passStringToWasm0(
|
||||||
|
arg: string,
|
||||||
|
malloc: (arg0: any, arg1: number) => number,
|
||||||
|
realloc:
|
||||||
|
| ((arg0: number, arg1: any, arg2: number, arg3: number) => number)
|
||||||
|
| undefined,
|
||||||
|
) {
|
||||||
if (realloc === undefined) {
|
if (realloc === undefined) {
|
||||||
const buf = cachedTextEncoder.encode(arg);
|
const buf = cachedTextEncoder.encode(arg);
|
||||||
const ptr = malloc(buf.length, 1) >>> 0;
|
const ptr = malloc(buf.length, 1) >>> 0;
|
||||||
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
|
getUint8Memory0()
|
||||||
|
.subarray(ptr, ptr + buf.length)
|
||||||
|
.set(buf);
|
||||||
WASM_VECTOR_LEN = buf.length;
|
WASM_VECTOR_LEN = buf.length;
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
@@ -160,7 +163,7 @@ function createWrapper() {
|
|||||||
|
|
||||||
for (; offset < len; offset++) {
|
for (; offset < len; offset++) {
|
||||||
const code = arg.charCodeAt(offset);
|
const code = arg.charCodeAt(offset);
|
||||||
if (code > 0x7F) break;
|
if (code > 0x7f) break;
|
||||||
mem[ptr + offset] = code;
|
mem[ptr + offset] = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +171,7 @@ function createWrapper() {
|
|||||||
if (offset !== 0) {
|
if (offset !== 0) {
|
||||||
arg = arg.slice(offset);
|
arg = arg.slice(offset);
|
||||||
}
|
}
|
||||||
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0;
|
||||||
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
|
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
|
||||||
const ret = encodeString(arg, view);
|
const ret = encodeString(arg, view);
|
||||||
|
|
||||||
@@ -183,15 +186,19 @@ function createWrapper() {
|
|||||||
function __wbg_new_abda76e883ba8a5f() {
|
function __wbg_new_abda76e883ba8a5f() {
|
||||||
const ret = new Error();
|
const ret = new Error();
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
}
|
||||||
|
|
||||||
function __wbg_stack_658279fe44541cf6(arg0, arg1) {
|
function __wbg_stack_658279fe44541cf6(arg0, arg1) {
|
||||||
const ret = getObject(arg1).stack;
|
const ret = getObject(arg1).stack;
|
||||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
const ptr1 = passStringToWasm0(
|
||||||
|
ret,
|
||||||
|
wasm.__wbindgen_malloc,
|
||||||
|
wasm.__wbindgen_realloc,
|
||||||
|
);
|
||||||
const len1 = WASM_VECTOR_LEN;
|
const len1 = WASM_VECTOR_LEN;
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||||
};
|
}
|
||||||
|
|
||||||
function __wbg_error_f851667af71bcfc6(arg0, arg1) {
|
function __wbg_error_f851667af71bcfc6(arg0, arg1) {
|
||||||
let deferred0_0;
|
let deferred0_0;
|
||||||
@@ -203,27 +210,25 @@ function createWrapper() {
|
|||||||
} finally {
|
} finally {
|
||||||
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
function __wbindgen_object_drop_ref(arg0) {
|
function __wbindgen_object_drop_ref(arg0) {
|
||||||
takeObject(arg0);
|
takeObject(arg0);
|
||||||
};
|
}
|
||||||
|
|
||||||
function __wbg_log_5bb5f88f245d7762(arg0) {
|
function __wbg_log_5bb5f88f245d7762(arg0) {
|
||||||
console.log(getObject(arg0));
|
console.log(getObject(arg0));
|
||||||
};
|
}
|
||||||
|
|
||||||
function __wbindgen_throw(arg0, arg1) {
|
function __wbindgen_throw(arg0, arg1) {
|
||||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||||
};
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setInstance(instance: WebAssembly.Instance) {
|
setInstance(instance: WebAssembly.Instance) {
|
||||||
wasm = instance.exports;
|
wasm = instance.exports;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
exports: {
|
exports: {
|
||||||
// Expose other methods that interact with the wasm instance
|
// Expose other methods that interact with the wasm instance
|
||||||
execute,
|
execute,
|
||||||
@@ -240,11 +245,12 @@ function createWrapper() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createWasmWrapper(wasmBuffer: ArrayBuffer | Uint8Array) {
|
||||||
export function createWasmWrapper(wasmBuffer: ArrayBuffer) {
|
|
||||||
const wrapper = createWrapper();
|
const wrapper = createWrapper();
|
||||||
const module = new WebAssembly.Module(wasmBuffer);
|
const module = new WebAssembly.Module(wasmBuffer);
|
||||||
const instance = new WebAssembly.Instance(module, { ["./index_bg.js"]: wrapper });
|
const instance = new WebAssembly.Instance(module, {
|
||||||
|
["./index_bg.js"]: wrapper,
|
||||||
|
});
|
||||||
wrapper.setInstance(instance);
|
wrapper.setInstance(instance);
|
||||||
return wrapper.exports;
|
return wrapper.exports;
|
||||||
}
|
}
|
||||||
|
|||||||
5854
pnpm-lock.yaml
generated
5854
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user