feat: merge svelte-5
Some checks failed
Deploy to GitHub Pages / build_site (push) Failing after 38s
Some checks failed
Deploy to GitHub Pages / build_site (push) Failing after 38s
This commit is contained in:
commit
a740da1099
@ -13,34 +13,34 @@
|
|||||||
"@nodes/registry": "link:../packages/registry",
|
"@nodes/registry": "link:../packages/registry",
|
||||||
"@nodes/ui": "link:../packages/ui",
|
"@nodes/ui": "link:../packages/ui",
|
||||||
"@nodes/utils": "link:../packages/utils",
|
"@nodes/utils": "link:../packages/utils",
|
||||||
"@sveltejs/kit": "^2.7.4",
|
"@sveltejs/kit": "^2.12.2",
|
||||||
"@threlte/core": "8.0.0-next.23",
|
"@threlte/core": "8.0.0-next.23",
|
||||||
"@threlte/extras": "9.0.0-next.33",
|
"@threlte/extras": "9.0.0-next.33",
|
||||||
"@types/three": "^0.169.0",
|
"@types/three": "^0.171.0",
|
||||||
"@unocss/reset": "^0.63.6",
|
"@unocss/reset": "^0.65.2",
|
||||||
"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.1",
|
||||||
"jsondiffpatch": "^0.6.0",
|
"jsondiffpatch": "^0.6.0",
|
||||||
"three": "^0.170.0"
|
"three": "^0.171.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/tabler": "^1.2.7",
|
"@iconify-json/tabler": "^1.2.13",
|
||||||
"@nodes/types": "link:../packages/types",
|
"@nodes/types": "link:../packages/types",
|
||||||
"@sveltejs/adapter-static": "^3.0.6",
|
"@sveltejs/adapter-static": "^3.0.6",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@unocss/preset-icons": "^0.63.6",
|
"@unocss/preset-icons": "^0.65.2",
|
||||||
"svelte": "^5.1.9",
|
"svelte": "^5.14.4",
|
||||||
"svelte-check": "^4.0.5",
|
"svelte-check": "^4.1.1",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.7.2",
|
||||||
"unocss": "^0.63.6",
|
"unocss": "^0.65.2",
|
||||||
"vite": "^5.4.10",
|
"vite": "^6.0.4",
|
||||||
"vite-plugin-comlink": "^5.1.0",
|
"vite-plugin-comlink": "^5.1.0",
|
||||||
"vite-plugin-glsl": "^1.3.0",
|
"vite-plugin-glsl": "^1.3.1",
|
||||||
"vite-plugin-wasm": "^3.3.0",
|
"vite-plugin-wasm": "^3.3.0",
|
||||||
"vitest": "^2.1.4"
|
"vitest": "^2.1.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
var value = JSON.parse(store);
|
var value = JSON.parse(store);
|
||||||
var themes = ["dark", "light", "catppuccin"];
|
var themes = ["dark", "light", "catppuccin"];
|
||||||
if (themes[value.theme]) {
|
if (themes[value.theme]) {
|
||||||
document.body.classList.add("theme-" + themes[value.theme]);
|
document.documentElement.classList.add("theme-" + themes[value.theme]);
|
||||||
}
|
}
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { NodeDefinition, NodeRegistry } from "@nodes/types";
|
import type { NodeDefinition, NodeRegistry } from "@nodes/types";
|
||||||
import { onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
|
|
||||||
let mx = $state(0);
|
let mx = $state(0);
|
||||||
let my = $state(0);
|
let my = $state(0);
|
||||||
@ -39,9 +39,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
wrapper?.parentElement?.setAttribute("style", "cursor:help !important");
|
const style = wrapper.parentElement?.style;
|
||||||
|
style?.setProperty("cursor", "help");
|
||||||
return () => {
|
return () => {
|
||||||
wrapper?.parentElement?.style.removeProperty("cursor");
|
style?.removeProperty("cursor");
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -91,8 +92,9 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
|
max-width: 250px;
|
||||||
border: 1px solid var(--outline);
|
border: 1px solid var(--outline);
|
||||||
z-index: 1000;
|
z-index: 10000;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { T } from "@threlte/core";
|
import { T } from "@threlte/core";
|
||||||
|
|
||||||
import BackgroundVert from "./Background.vert";
|
import BackgroundVert from "./Background.vert";
|
||||||
import BackgroundFrag from "./Background.frag";
|
import BackgroundFrag from "./Background.frag";
|
||||||
import { colors } from "../graph/state.svelte";
|
import { colors } from "../graph/colors.svelte";
|
||||||
import { Color } from "three";
|
import { Color } from "three";
|
||||||
|
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
minZoom: number;
|
minZoom: number;
|
||||||
@ -42,10 +42,10 @@
|
|||||||
value: [0, 1, 0],
|
value: [0, 1, 0],
|
||||||
},
|
},
|
||||||
backgroundColor: {
|
backgroundColor: {
|
||||||
value: new Color(0x171717),
|
value: colors["layer-0"].clone(),
|
||||||
},
|
},
|
||||||
lineColor: {
|
lineColor: {
|
||||||
value: new Color(0x111111),
|
value: colors["outline"].clone(),
|
||||||
},
|
},
|
||||||
zoomLimits: {
|
zoomLimits: {
|
||||||
value: [2, 50],
|
value: [2, 50],
|
||||||
@ -55,8 +55,9 @@
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
uniforms.camPos.value={cameraPosition}
|
uniforms.camPos.value={cameraPosition}
|
||||||
uniforms.backgroundColor.value={$colors["layer-0"]}
|
uniforms.backgroundColor.value={appSettings.theme &&
|
||||||
uniforms.lineColor.value={$colors["outline"]}
|
colors["layer-0"].clone()}
|
||||||
|
uniforms.lineColor.value={appSettings.theme && colors["outline"].clone()}
|
||||||
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
||||||
uniforms.dimensions.value={[width, height]}
|
uniforms.dimensions.value={[width, height]}
|
||||||
/>
|
/>
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
<script module lang="ts">
|
<script module lang="ts">
|
||||||
import { colors } from "../graph/state.svelte";
|
import { colors } from "../graph/colors.svelte";
|
||||||
|
|
||||||
const circleMaterial = new MeshBasicMaterial({
|
const circleMaterial = new MeshBasicMaterial({
|
||||||
color: get(colors).edge,
|
color: colors.edge.clone(),
|
||||||
toneMapped: false,
|
toneMapped: false,
|
||||||
});
|
});
|
||||||
|
$effect.root(() => {
|
||||||
colors.subscribe((c) => {
|
$effect(() => {
|
||||||
circleMaterial.color.copy(c.edge.clone().convertSRGBToLinear());
|
appSettings.theme;
|
||||||
|
circleMaterial.color = colors.edge.clone().convertSRGBToLinear();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const lineCache = new Map<number, BufferGeometry>();
|
const lineCache = new Map<number, BufferGeometry>();
|
||||||
@ -27,18 +29,21 @@
|
|||||||
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 { createEdgeGeometry } from "./createEdgeGeometry.js";
|
||||||
import { get } from "svelte/store";
|
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
from: { x: number; y: number };
|
from: { x: number; y: number };
|
||||||
to: { x: number; y: number };
|
to: { x: number; y: number };
|
||||||
|
z: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { from, to }: Props = $props();
|
const { from, to, z }: Props = $props();
|
||||||
|
|
||||||
let samples = 5;
|
let geometry: BufferGeometry | null = $state(null);
|
||||||
|
|
||||||
let geometry: BufferGeometry|null = $state(null);
|
const lineColor = $derived(
|
||||||
|
appSettings.theme && colors.edge.clone().convertSRGBToLinear(),
|
||||||
|
);
|
||||||
|
|
||||||
let lastId: number | null = null;
|
let lastId: number | null = null;
|
||||||
|
|
||||||
@ -63,7 +68,8 @@
|
|||||||
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,
|
||||||
);
|
);
|
||||||
samples = Math.min(Math.max(10, length), 60) * 2;
|
|
||||||
|
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(mid.x, 0);
|
||||||
@ -77,15 +83,13 @@
|
|||||||
|
|
||||||
geometry = createEdgeGeometry(points);
|
geometry = createEdgeGeometry(points);
|
||||||
lineCache.set(curveId, geometry);
|
lineCache.set(curveId, geometry);
|
||||||
};
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (from || to) {
|
if (from || to) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const lineColor = $derived($colors.edge.clone().convertSRGBToLinear());
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<T.Mesh
|
<T.Mesh
|
||||||
@ -110,6 +114,6 @@
|
|||||||
|
|
||||||
{#if geometry}
|
{#if geometry}
|
||||||
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}>
|
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}>
|
||||||
<MeshLineMaterial width={3} attenuate={false} color={lineColor} />
|
<MeshLineMaterial width={Math.max(z * 0.0001, 0.00001)} color={lineColor} />
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Edge from "./Edge.svelte";
|
import Edge from "./Edge.svelte";
|
||||||
|
|
||||||
type Props = { from: { x: number; y: number }; to: { x: number; y: number } };
|
type Props = {
|
||||||
const { from, to }: Props = $props();
|
from: { x: number; y: number };
|
||||||
|
to: { x: number; y: number };
|
||||||
|
z: number;
|
||||||
|
};
|
||||||
|
const { from, to, z }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Edge {from} {to} />
|
<Edge {from} {to} {z} />
|
||||||
|
@ -1,11 +1,29 @@
|
|||||||
import { BufferGeometry, Vector3, BufferAttribute } from 'three'
|
import { BufferAttribute, BufferGeometry, Vector3 } from 'three';
|
||||||
import { setXY, setXYZ, setXYZW, setXYZXYZ } from './utils.js'
|
import { setXY, setXYZ, setXYZW, setXYZXYZ } from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
export function createEdgeGeometry(points: Vector3[]) {
|
export function createEdgeGeometry(points: Vector3[]) {
|
||||||
|
|
||||||
let shape = 'none'
|
const length = points[0].distanceTo(points[points.length - 1]);
|
||||||
let shapeFunction = (p: number) => 1
|
|
||||||
|
const startRadius = 8;
|
||||||
|
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
|
// When the component first runs we create the buffer geometry and allocate the buffer attributes
|
||||||
let pointCount = points.length
|
let pointCount = points.length
|
||||||
@ -19,9 +37,7 @@ export function createEdgeGeometry(points: Vector3[]) {
|
|||||||
let indices: number[] = []
|
let indices: number[] = []
|
||||||
let indicesIndex = 0
|
let indicesIndex = 0
|
||||||
|
|
||||||
if (shape === 'taper') {
|
|
||||||
shapeFunction = (p: number) => 1 * Math.pow(4 * p * (1 - p), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = 0; j < pointCount; j++) {
|
for (let j = 0; j < pointCount; j++) {
|
||||||
const c = j / points.length
|
const c = j / points.length
|
||||||
@ -30,7 +46,7 @@ export function createEdgeGeometry(points: Vector3[]) {
|
|||||||
counterIndex += 2
|
counterIndex += 2
|
||||||
|
|
||||||
setXY(side, doubleIndex, 1, -1)
|
setXY(side, doubleIndex, 1, -1)
|
||||||
let width = shape === 'none' ? 1 : shapeFunction(j / (pointCount - 1))
|
let width = shapeFunction((j / (pointCount - 1)))
|
||||||
setXY(widthArray, doubleIndex, width, width)
|
setXY(widthArray, doubleIndex, width, width)
|
||||||
doubleIndex += 2
|
doubleIndex += 2
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
} from "../helpers/index.js";
|
} from "../helpers/index.js";
|
||||||
import type { OrthographicCamera } from "three";
|
import type { OrthographicCamera } from "three";
|
||||||
import Background from "../background/Background.svelte";
|
import Background from "../background/Background.svelte";
|
||||||
import type { GraphManager } from "../graph-manager.js";
|
|
||||||
import { getContext, onMount, setContext } from "svelte";
|
import { getContext, onMount, setContext } from "svelte";
|
||||||
import Camera from "../Camera.svelte";
|
import Camera from "../Camera.svelte";
|
||||||
import GraphView from "./GraphView.svelte";
|
import GraphView from "./GraphView.svelte";
|
||||||
@ -23,14 +22,13 @@
|
|||||||
import { Canvas } from "@threlte/core";
|
import { Canvas } from "@threlte/core";
|
||||||
import { getGraphManager } from "./context.js";
|
import { getGraphManager } from "./context.js";
|
||||||
|
|
||||||
const state = getGraphState();
|
const graphState = getGraphState();
|
||||||
|
|
||||||
export let snapToGrid = true;
|
export let snapToGrid = true;
|
||||||
export let showGrid = true;
|
export let showGrid = true;
|
||||||
export let showHelp = false;
|
export let showHelp = false;
|
||||||
|
|
||||||
let keymap =
|
const keymap = getContext<ReturnType<typeof createKeyMap>>("keymap");
|
||||||
getContext<ReturnType<typeof createKeyMap>>("keymap") || createKeyMap([]);
|
|
||||||
|
|
||||||
const manager = getGraphManager();
|
const manager = getGraphManager();
|
||||||
|
|
||||||
@ -179,7 +177,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
setContext("setDownSocket", (socket: Socket) => {
|
setContext("setDownSocket", (socket: Socket) => {
|
||||||
state.activeSocket = socket;
|
graphState.activeSocket = socket;
|
||||||
|
|
||||||
let { node, index, position } = socket;
|
let { node, index, position } = socket;
|
||||||
|
|
||||||
@ -198,14 +196,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
mouseDown = position;
|
mouseDown = position;
|
||||||
state.activeSocket = {
|
graphState.activeSocket = {
|
||||||
node,
|
node,
|
||||||
index,
|
index,
|
||||||
position,
|
position,
|
||||||
};
|
};
|
||||||
|
|
||||||
state.possibleSockets = manager
|
graphState.possibleSockets = manager
|
||||||
.getPossibleSockets(state.activeSocket)
|
.getPossibleSockets(graphState.activeSocket)
|
||||||
.map(([node, index]) => {
|
.map(([node, index]) => {
|
||||||
return {
|
return {
|
||||||
node,
|
node,
|
||||||
@ -259,14 +257,15 @@
|
|||||||
let my = event.clientY - rect.y;
|
let my = event.clientY - rect.y;
|
||||||
|
|
||||||
mousePosition = projectScreenToWorld(mx, my);
|
mousePosition = projectScreenToWorld(mx, my);
|
||||||
|
hoveredNodeId = getNodeIdFromEvent(event);
|
||||||
|
|
||||||
if (!mouseDown) return;
|
if (!mouseDown) return;
|
||||||
|
|
||||||
// we are creating a new edge here
|
// we are creating a new edge here
|
||||||
if (state.activeSocket || state.possibleSockets?.length) {
|
if (graphState.activeSocket || graphState.possibleSockets?.length) {
|
||||||
let smallestDist = 1000;
|
let smallestDist = 1000;
|
||||||
let _socket;
|
let _socket;
|
||||||
for (const socket of state.possibleSockets) {
|
for (const socket of graphState.possibleSockets) {
|
||||||
const dist = Math.sqrt(
|
const dist = Math.sqrt(
|
||||||
(socket.position[0] - mousePosition[0]) ** 2 +
|
(socket.position[0] - mousePosition[0]) ** 2 +
|
||||||
(socket.position[1] - mousePosition[1]) ** 2,
|
(socket.position[1] - mousePosition[1]) ** 2,
|
||||||
@ -279,9 +278,9 @@
|
|||||||
|
|
||||||
if (_socket && smallestDist < 0.9) {
|
if (_socket && smallestDist < 0.9) {
|
||||||
mousePosition = _socket.position;
|
mousePosition = _socket.position;
|
||||||
state.hoveredSocket = _socket;
|
graphState.hoveredSocket = _socket;
|
||||||
} else {
|
} else {
|
||||||
state.hoveredSocket = null;
|
graphState.hoveredSocket = null;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -301,17 +300,17 @@
|
|||||||
const y = node.position[1];
|
const y = node.position[1];
|
||||||
const height = getNodeHeight(node.type);
|
const height = getNodeHeight(node.type);
|
||||||
if (x > x1 - 20 && x < x2 && y > y1 - height && y < y2) {
|
if (x > x1 - 20 && x < x2 && y > y1 - height && y < y2) {
|
||||||
state.selectedNodes?.add(node.id);
|
graphState.selectedNodes?.add(node.id);
|
||||||
} else {
|
} else {
|
||||||
state.selectedNodes?.delete(node.id);
|
graphState.selectedNodes?.delete(node.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// here we are handling dragging of nodes
|
// here we are handling dragging of nodes
|
||||||
if (state.activeNodeId !== -1 && mouseDownId !== -1) {
|
if (graphState.activeNodeId !== -1 && mouseDownId !== -1) {
|
||||||
const node = manager.getNode(state.activeNodeId);
|
const node = manager.getNode(graphState.activeNodeId);
|
||||||
if (!node || event.buttons !== 1) return;
|
if (!node || event.buttons !== 1) return;
|
||||||
|
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
@ -340,8 +339,8 @@
|
|||||||
const vecX = oldX - newX;
|
const vecX = oldX - newX;
|
||||||
const vecY = oldY - newY;
|
const vecY = oldY - newY;
|
||||||
|
|
||||||
if (state.selectedNodes?.size) {
|
if (graphState.selectedNodes?.size) {
|
||||||
for (const nodeId of state.selectedNodes) {
|
for (const nodeId of graphState.selectedNodes) {
|
||||||
const n = manager.getNode(nodeId);
|
const n = manager.getNode(nodeId);
|
||||||
if (!n?.tmp) continue;
|
if (!n?.tmp) continue;
|
||||||
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
||||||
@ -360,6 +359,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// here we are handling panning of camera
|
// here we are handling panning of camera
|
||||||
|
isPanning = true;
|
||||||
let newX = cameraDown[0] - (mx - mouseDown[0]) / cameraPosition[2];
|
let newX = cameraDown[0] - (mx - mouseDown[0]) / cameraPosition[2];
|
||||||
let newY = cameraDown[1] - (my - mouseDown[1]) / cameraPosition[2];
|
let newY = cameraDown[1] - (my - mouseDown[1]) / cameraPosition[2];
|
||||||
|
|
||||||
@ -424,43 +424,46 @@
|
|||||||
|
|
||||||
// if we clicked on a node
|
// if we clicked on a node
|
||||||
if (clickedNodeId !== -1) {
|
if (clickedNodeId !== -1) {
|
||||||
if (state.activeNodeId === -1) {
|
if (graphState.activeNodeId === -1) {
|
||||||
state.activeNodeId = clickedNodeId;
|
graphState.activeNodeId = clickedNodeId;
|
||||||
// if the selected node is the same as the clicked node
|
// if the selected node is the same as the clicked node
|
||||||
} else if (state.activeNodeId === clickedNodeId) {
|
} else if (graphState.activeNodeId === clickedNodeId) {
|
||||||
//$activeNodeId = -1;
|
//$activeNodeId = -1;
|
||||||
// if the clicked node is different from the selected node and secondary
|
// if the clicked node is different from the selected node and secondary
|
||||||
} else if (event.ctrlKey) {
|
} else if (event.ctrlKey) {
|
||||||
state.selectedNodes = state.selectedNodes || new Set();
|
graphState.selectedNodes.add(graphState.activeNodeId);
|
||||||
state.selectedNodes.add(state.activeNodeId);
|
graphState.selectedNodes.delete(clickedNodeId);
|
||||||
state.selectedNodes.delete(clickedNodeId);
|
graphState.activeNodeId = clickedNodeId;
|
||||||
state.activeNodeId = clickedNodeId;
|
|
||||||
// select the node
|
// select the node
|
||||||
} else if (event.shiftKey) {
|
} else if (event.shiftKey) {
|
||||||
const activeNode = manager.getNode(state.activeNodeId);
|
const activeNode = manager.getNode(graphState.activeNodeId);
|
||||||
const newNode = manager.getNode(clickedNodeId);
|
const newNode = manager.getNode(clickedNodeId);
|
||||||
if (activeNode && newNode) {
|
if (activeNode && newNode) {
|
||||||
const edge = manager.getNodesBetween(activeNode, newNode);
|
const edge = manager.getNodesBetween(activeNode, newNode);
|
||||||
if (edge) {
|
if (edge) {
|
||||||
const selected = new Set(edge.map((n) => n.id));
|
graphState.selectedNodes.clear();
|
||||||
selected.add(clickedNodeId);
|
for (const node of edge) {
|
||||||
state.selectedNodes = selected;
|
graphState.selectedNodes.add(node.id);
|
||||||
|
}
|
||||||
|
graphState.selectedNodes.add(clickedNodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!state.selectedNodes?.has(clickedNodeId)) {
|
} else if (!graphState.selectedNodes.has(clickedNodeId)) {
|
||||||
state.activeNodeId = clickedNodeId;
|
graphState.activeNodeId = clickedNodeId;
|
||||||
state.clearSelection();
|
graphState.clearSelection();
|
||||||
}
|
}
|
||||||
} else if (event.ctrlKey) {
|
} else if (event.ctrlKey) {
|
||||||
boxSelection = true;
|
boxSelection = true;
|
||||||
}
|
}
|
||||||
const node = manager.getNode(state.activeNodeId);
|
|
||||||
|
const node = manager.getNode(graphState.activeNodeId);
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
node.tmp.downX = node.position[0];
|
node.tmp.downX = node.position[0];
|
||||||
node.tmp.downY = node.position[1];
|
node.tmp.downY = node.position[1];
|
||||||
if (state.selectedNodes) {
|
|
||||||
for (const nodeId of state.selectedNodes) {
|
if (graphState.selectedNodes) {
|
||||||
|
for (const nodeId of graphState.selectedNodes) {
|
||||||
const n = manager.getNode(nodeId);
|
const n = manager.getNode(nodeId);
|
||||||
if (!n) continue;
|
if (!n) continue;
|
||||||
n.tmp = n.tmp || {};
|
n.tmp = n.tmp || {};
|
||||||
@ -471,8 +474,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function copyNodes() {
|
function copyNodes() {
|
||||||
if (state.activeNodeId === -1 && !state.selectedNodes?.size) return;
|
if (graphState.activeNodeId === -1 && !graphState.selectedNodes?.size)
|
||||||
let _nodes = [state.activeNodeId, ...(state.selectedNodes?.values() || [])]
|
return;
|
||||||
|
let _nodes = [
|
||||||
|
graphState.activeNodeId,
|
||||||
|
...(graphState.selectedNodes?.values() || []),
|
||||||
|
]
|
||||||
.map((id) => manager.getNode(id))
|
.map((id) => manager.getNode(id))
|
||||||
.filter(Boolean) as Node[];
|
.filter(Boolean) as Node[];
|
||||||
|
|
||||||
@ -508,7 +515,10 @@
|
|||||||
.filter(Boolean) as Node[];
|
.filter(Boolean) as Node[];
|
||||||
|
|
||||||
const newNodes = manager.createGraph(_nodes, clipboard.edges);
|
const newNodes = manager.createGraph(_nodes, clipboard.edges);
|
||||||
state.selectedNodes = new Set(newNodes.map((n) => n.id));
|
graphState.selectedNodes.clear();
|
||||||
|
for (const node of newNodes) {
|
||||||
|
graphState.selectedNodes.add(node.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isBodyFocused = () => document?.activeElement?.nodeName !== "INPUT";
|
const isBodyFocused = () => document?.activeElement?.nodeName !== "INPUT";
|
||||||
@ -517,12 +527,14 @@
|
|||||||
key: "l",
|
key: "l",
|
||||||
description: "Select linked nodes",
|
description: "Select linked nodes",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const activeNode = manager.getNode(state.activeNodeId);
|
const activeNode = manager.getNode(graphState.activeNodeId);
|
||||||
if (activeNode) {
|
if (activeNode) {
|
||||||
const nodes = manager.getLinkedNodes(activeNode);
|
const nodes = manager.getLinkedNodes(activeNode);
|
||||||
state.selectedNodes = new Set(nodes.map((n) => n.id));
|
graphState.selectedNodes.clear();
|
||||||
|
for (const node of nodes) {
|
||||||
|
graphState.selectedNodes.add(node.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.log(activeNode);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -552,8 +564,8 @@
|
|||||||
key: "Escape",
|
key: "Escape",
|
||||||
description: "Deselect nodes",
|
description: "Deselect nodes",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
state.activeNodeId = -1;
|
graphState.activeNodeId = -1;
|
||||||
state.clearSelection();
|
graphState.clearSelection();
|
||||||
(document.activeElement as HTMLElement)?.blur();
|
(document.activeElement as HTMLElement)?.blur();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -605,7 +617,9 @@
|
|||||||
description: "Select all nodes",
|
description: "Select all nodes",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (!isBodyFocused()) return;
|
if (!isBodyFocused()) return;
|
||||||
state.selectedNodes = new Set($nodes.keys());
|
for (const node of $nodes.keys()) {
|
||||||
|
graphState.selectedNodes.add(node);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -654,38 +668,39 @@
|
|||||||
callback: (event) => {
|
callback: (event) => {
|
||||||
if (!isBodyFocused()) return;
|
if (!isBodyFocused()) return;
|
||||||
manager.startUndoGroup();
|
manager.startUndoGroup();
|
||||||
if (state.activeNodeId !== -1) {
|
if (graphState.activeNodeId !== -1) {
|
||||||
const node = manager.getNode(state.activeNodeId);
|
const node = manager.getNode(graphState.activeNodeId);
|
||||||
if (node) {
|
if (node) {
|
||||||
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||||
state.activeNodeId = -1;
|
graphState.activeNodeId = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (state.selectedNodes) {
|
if (graphState.selectedNodes) {
|
||||||
for (const nodeId of state.selectedNodes) {
|
for (const nodeId of graphState.selectedNodes) {
|
||||||
const node = manager.getNode(nodeId);
|
const node = manager.getNode(nodeId);
|
||||||
if (node) {
|
if (node) {
|
||||||
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.clearSelection();
|
graphState.clearSelection();
|
||||||
}
|
}
|
||||||
manager.saveUndoGroup();
|
manager.saveUndoGroup();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleMouseUp(event: MouseEvent) {
|
function handleMouseUp(event: MouseEvent) {
|
||||||
|
isPanning = false;
|
||||||
if (!mouseDown) return;
|
if (!mouseDown) return;
|
||||||
|
|
||||||
const activeNode = manager.getNode(state.activeNodeId);
|
const activeNode = manager.getNode(graphState.activeNodeId);
|
||||||
|
|
||||||
const clickedNodeId = getNodeIdFromEvent(event);
|
const clickedNodeId = getNodeIdFromEvent(event);
|
||||||
|
|
||||||
if (clickedNodeId !== -1) {
|
if (clickedNodeId !== -1) {
|
||||||
if (activeNode) {
|
if (activeNode) {
|
||||||
if (!activeNode?.tmp?.isMoving && !event.ctrlKey && !event.shiftKey) {
|
if (!activeNode?.tmp?.isMoving && !event.ctrlKey && !event.shiftKey) {
|
||||||
state.clearSelection();
|
graphState.activeNodeId = clickedNodeId;
|
||||||
state.activeNodeId = clickedNodeId;
|
graphState.clearSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -708,7 +723,7 @@
|
|||||||
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1];
|
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1];
|
||||||
}
|
}
|
||||||
const nodes = [
|
const nodes = [
|
||||||
...[...(state.selectedNodes?.values() || [])].map((id) =>
|
...[...(graphState.selectedNodes?.values() || [])].map((id) =>
|
||||||
manager.getNode(id),
|
manager.getNode(id),
|
||||||
),
|
),
|
||||||
] as NodeType[];
|
] as NodeType[];
|
||||||
@ -747,26 +762,26 @@
|
|||||||
$edges = $edges;
|
$edges = $edges;
|
||||||
});
|
});
|
||||||
manager.save();
|
manager.save();
|
||||||
} else if (state.hoveredSocket && state.activeSocket) {
|
} else if (graphState.hoveredSocket && graphState.activeSocket) {
|
||||||
if (
|
if (
|
||||||
typeof state.hoveredSocket.index === "number" &&
|
typeof graphState.hoveredSocket.index === "number" &&
|
||||||
typeof state.activeSocket.index === "string"
|
typeof graphState.activeSocket.index === "string"
|
||||||
) {
|
) {
|
||||||
manager.createEdge(
|
manager.createEdge(
|
||||||
state.hoveredSocket.node,
|
graphState.hoveredSocket.node,
|
||||||
state.hoveredSocket.index || 0,
|
graphState.hoveredSocket.index || 0,
|
||||||
state.activeSocket.node,
|
graphState.activeSocket.node,
|
||||||
state.activeSocket.index,
|
graphState.activeSocket.index,
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (
|
||||||
typeof state.activeSocket.index == "number" &&
|
typeof graphState.activeSocket.index == "number" &&
|
||||||
typeof state.hoveredSocket.index === "string"
|
typeof graphState.hoveredSocket.index === "string"
|
||||||
) {
|
) {
|
||||||
manager.createEdge(
|
manager.createEdge(
|
||||||
state.activeSocket.node,
|
graphState.activeSocket.node,
|
||||||
state.activeSocket.index || 0,
|
graphState.activeSocket.index || 0,
|
||||||
state.hoveredSocket.node,
|
graphState.hoveredSocket.node,
|
||||||
state.hoveredSocket.index,
|
graphState.hoveredSocket.index,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
manager.save();
|
manager.save();
|
||||||
@ -780,22 +795,25 @@
|
|||||||
cameraDown[1] === cameraPosition[1] &&
|
cameraDown[1] === cameraPosition[1] &&
|
||||||
isBodyFocused()
|
isBodyFocused()
|
||||||
) {
|
) {
|
||||||
state.activeNodeId = -1;
|
graphState.activeNodeId = -1;
|
||||||
state.clearSelection();
|
graphState.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseDown = null;
|
mouseDown = null;
|
||||||
boxSelection = false;
|
boxSelection = false;
|
||||||
state.activeSocket = null;
|
graphState.activeSocket = null;
|
||||||
state.possibleSockets = [];
|
graphState.possibleSockets = [];
|
||||||
state.hoveredSocket = null;
|
graphState.hoveredSocket = null;
|
||||||
addMenuPosition = null;
|
addMenuPosition = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isPanning = false;
|
||||||
let isDragging = false;
|
let isDragging = false;
|
||||||
|
let hoveredNodeId = -1;
|
||||||
|
|
||||||
function handleMouseLeave() {
|
function handleMouseLeave() {
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
|
isPanning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDrop(event: DragEvent) {
|
function handleDrop(event: DragEvent) {
|
||||||
@ -865,16 +883,19 @@
|
|||||||
function handleDragEnter(e: DragEvent) {
|
function handleDragEnter(e: DragEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
isDragging = true;
|
isDragging = true;
|
||||||
|
isPanning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlerDragOver(e: DragEvent) {
|
function handlerDragOver(e: DragEvent) {
|
||||||
isDragging = true;
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
isDragging = true;
|
||||||
|
isPanning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragEnd(e: DragEvent) {
|
function handleDragEnd(e: DragEvent) {
|
||||||
isDragging = false;
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
isDragging = true;
|
||||||
|
isPanning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@ -893,6 +914,8 @@
|
|||||||
on:wheel={handleMouseScroll}
|
on:wheel={handleMouseScroll}
|
||||||
bind:this={wrapper}
|
bind:this={wrapper}
|
||||||
class="graph-wrapper"
|
class="graph-wrapper"
|
||||||
|
class:is-panning={isPanning}
|
||||||
|
class:is-hovering={hoveredNodeId !== -1}
|
||||||
aria-label="Graph"
|
aria-label="Graph"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@ -916,9 +939,6 @@
|
|||||||
/>
|
/>
|
||||||
<label for="drop-zone"></label>
|
<label for="drop-zone"></label>
|
||||||
|
|
||||||
{#if showHelp}
|
|
||||||
<HelpView registry={manager.registry} />
|
|
||||||
{/if}
|
|
||||||
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
|
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
|
||||||
<Camera bind:camera position={cameraPosition} />
|
<Camera bind:camera position={cameraPosition} />
|
||||||
|
|
||||||
@ -943,11 +963,12 @@
|
|||||||
<AddMenu bind:position={addMenuPosition} graph={manager} />
|
<AddMenu bind:position={addMenuPosition} graph={manager} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if state.activeSocket}
|
{#if graphState.activeSocket}
|
||||||
<FloatingEdge
|
<FloatingEdge
|
||||||
|
z={cameraPosition[2]}
|
||||||
from={{
|
from={{
|
||||||
x: state.activeSocket.position[0],
|
x: graphState.activeSocket.position[0],
|
||||||
y: state.activeSocket.position[1],
|
y: graphState.activeSocket.position[1],
|
||||||
}}
|
}}
|
||||||
to={{ x: mousePosition[0], y: mousePosition[1] }}
|
to={{ x: mousePosition[0], y: mousePosition[1] }}
|
||||||
/>
|
/>
|
||||||
@ -962,6 +983,10 @@
|
|||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if showHelp}
|
||||||
|
<HelpView registry={manager.registry} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.graph-wrapper {
|
.graph-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -969,6 +994,15 @@
|
|||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is-hovering {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-panning {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
@ -6,10 +6,23 @@
|
|||||||
import { getContext, onMount } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { getGraphState } from "./state.svelte";
|
import { getGraphState } from "./state.svelte";
|
||||||
|
import { useThrelte } from "@threlte/core";
|
||||||
|
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||||
|
|
||||||
export let nodes: Writable<Map<number, NodeType>>;
|
type Props = {
|
||||||
export let edges: Writable<EdgeType[]>;
|
nodes: Writable<Map<number, NodeType>>;
|
||||||
export let cameraPosition = [0, 0, 4];
|
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 graphState = getGraphState();
|
||||||
|
|
||||||
@ -23,7 +36,6 @@
|
|||||||
function getEdgePosition(edge: EdgeType) {
|
function getEdgePosition(edge: EdgeType) {
|
||||||
const pos1 = getSocketPosition(edge[0], edge[1]);
|
const pos1 = getSocketPosition(edge[0], edge[1]);
|
||||||
const pos2 = getSocketPosition(edge[2], edge[3]);
|
const pos2 = getSocketPosition(edge[2], edge[3]);
|
||||||
|
|
||||||
return [pos1[0], pos1[1], pos2[0], pos2[1]];
|
return [pos1[0], pos1[1], pos2[0], pos2[1]];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +53,7 @@
|
|||||||
{@const pos = getEdgePosition(edge)}
|
{@const pos = getEdgePosition(edge)}
|
||||||
{@const [x1, y1, x2, y2] = pos}
|
{@const [x1, y1, x2, y2] = pos}
|
||||||
<Edge
|
<Edge
|
||||||
|
z={cameraPosition[2]}
|
||||||
from={{
|
from={{
|
||||||
x: x1,
|
x: x1,
|
||||||
y: y1,
|
y: y1,
|
||||||
|
@ -3,19 +3,18 @@
|
|||||||
import GraphEl from "./Graph.svelte";
|
import GraphEl from "./Graph.svelte";
|
||||||
import { GraphManager } from "../graph-manager.js";
|
import { GraphManager } from "../graph-manager.js";
|
||||||
import { setContext } from "svelte";
|
import { setContext } from "svelte";
|
||||||
import { type Writable } from "svelte/store";
|
|
||||||
import { debounce } from "$lib/helpers";
|
import { debounce } from "$lib/helpers";
|
||||||
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
import { GraphState } from "./state.svelte";
|
import { GraphState } from "./state.svelte";
|
||||||
|
|
||||||
const state = new GraphState();
|
const graphState = new GraphState();
|
||||||
setContext("graphState", state);
|
setContext("graphState", graphState);
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
graph: Graph;
|
graph: Graph;
|
||||||
registry: NodeRegistry;
|
registry: NodeRegistry;
|
||||||
|
|
||||||
settings?: Writable<Record<string, any>>;
|
settings?: Record<string, any>;
|
||||||
|
|
||||||
activeNode?: Node;
|
activeNode?: Node;
|
||||||
showGrid?: boolean;
|
showGrid?: boolean;
|
||||||
@ -41,33 +40,32 @@
|
|||||||
}: 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);
|
setContext("graphManager", manager);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (state.activeNodeId !== -1) {
|
if (graphState.activeNodeId !== -1) {
|
||||||
activeNode = manager.getNode(state.activeNodeId);
|
activeNode = manager.getNode(graphState.activeNodeId);
|
||||||
} else {
|
} else if (activeNode) {
|
||||||
activeNode = undefined;
|
activeNode = undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setContext("keymap", keymap);
|
|
||||||
|
|
||||||
const updateSettings = debounce((s) => {
|
const updateSettings = debounce((s) => {
|
||||||
manager.setSettings(s);
|
manager.setSettings(s);
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (settingTypes && settings) {
|
if (settingTypes && settings) {
|
||||||
updateSettings($settings);
|
updateSettings($state.snapshot(settings));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.on("settings", (_settings) => {
|
manager.on("settings", (_settings) => {
|
||||||
settingTypes = _settings.types;
|
settingTypes = { ...settingTypes, ..._settings.types };
|
||||||
settings?.set(_settings.values);
|
settings = _settings.values;
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.on("result", (result) => onresult?.(result));
|
manager.on("result", (result) => onresult?.(result));
|
||||||
|
32
app/src/lib/graph-interface/graph/colors.svelte.ts
Normal file
32
app/src/lib/graph-interface/graph/colors.svelte.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||||
|
import { Color, LinearSRGBColorSpace } from "three";
|
||||||
|
|
||||||
|
const variables = [
|
||||||
|
"layer-0",
|
||||||
|
"layer-1",
|
||||||
|
"layer-2",
|
||||||
|
"layer-3",
|
||||||
|
"outline",
|
||||||
|
"active",
|
||||||
|
"selected",
|
||||||
|
"edge",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
function getColor(variable: typeof variables[number]) {
|
||||||
|
const style = getComputedStyle(document.body.parentElement!);
|
||||||
|
let color = style.getPropertyValue(`--${variable}`);
|
||||||
|
return new Color().setStyle(color, LinearSRGBColorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const colors = Object.fromEntries(variables.map(v => [v, getColor(v)])) as Record<typeof variables[number], Color>;
|
||||||
|
|
||||||
|
$effect.root(() => {
|
||||||
|
$effect(() => {
|
||||||
|
if (!appSettings.theme || !("getComputedStyle" in globalThis)) return;
|
||||||
|
const style = getComputedStyle(document.body.parentElement!);
|
||||||
|
for (const v of variables) {
|
||||||
|
const hex = style.getPropertyValue(`--${v}`);
|
||||||
|
colors[v].setStyle(hex, LinearSRGBColorSpace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
@ -1,48 +0,0 @@
|
|||||||
import { readable } from "svelte/store";
|
|
||||||
import { Color } from "three";
|
|
||||||
|
|
||||||
const variables = [
|
|
||||||
"layer-0",
|
|
||||||
"layer-1",
|
|
||||||
"layer-2",
|
|
||||||
"layer-3",
|
|
||||||
"outline",
|
|
||||||
"active",
|
|
||||||
"selected",
|
|
||||||
"edge",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const store = Object.fromEntries(variables.map(v => [v, new Color()])) as Record<typeof variables[number], Color>;
|
|
||||||
|
|
||||||
let lastStyle = "";
|
|
||||||
|
|
||||||
function updateColors() {
|
|
||||||
if (!("getComputedStyle" in globalThis)) return;
|
|
||||||
const style = getComputedStyle(document.body.parentElement!);
|
|
||||||
let hash = "";
|
|
||||||
for (const v of variables) {
|
|
||||||
let color = style.getPropertyValue(`--${v}`);
|
|
||||||
hash += color;
|
|
||||||
store[v].setStyle(color);
|
|
||||||
}
|
|
||||||
if (hash === lastStyle) return;
|
|
||||||
lastStyle = hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const colors = readable(store, set => {
|
|
||||||
|
|
||||||
updateColors();
|
|
||||||
set(store);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
updateColors();
|
|
||||||
set(store);
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
window.onload = function () { updateColors(); set(store) };
|
|
||||||
|
|
||||||
document.body.addEventListener("transitionstart", () => {
|
|
||||||
updateColors();
|
|
||||||
set(store);
|
|
||||||
})
|
|
||||||
});
|
|
@ -1,26 +1,22 @@
|
|||||||
import type { Socket } from "@nodes/types";
|
import type { Socket } from "@nodes/types";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
import { SvelteSet } from 'svelte/reactivity';
|
||||||
|
|
||||||
export function getGraphState() {
|
export function getGraphState() {
|
||||||
return getContext<GraphState>("graphState");
|
return getContext<GraphState>("graphState");
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GraphState {
|
export class GraphState {
|
||||||
|
|
||||||
activeNodeId = $state(-1);
|
activeNodeId = $state(-1);
|
||||||
selectedNodes = $state(new Set<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(new Set(
|
||||||
this.possibleSockets.map((s) => `${s.node.id}-${s.index}`),
|
this.possibleSockets.map((s) => `${s.node.id}-${s.index}`),
|
||||||
));
|
));
|
||||||
|
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
this.selectedNodes = new Set();
|
this.selectedNodes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { colors } from "./colors";
|
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node } from "@nodes/types";
|
import type { Node } from "@nodes/types";
|
||||||
import { getContext, onMount } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
import { colors, getGraphState } from "../graph/state.svelte";
|
import { getGraphState } from "../graph/state.svelte";
|
||||||
import { T } from "@threlte/core";
|
import { T } from "@threlte/core";
|
||||||
import { Color, type Mesh } from "three";
|
import { type Mesh } from "three";
|
||||||
import NodeFrag from "./Node.frag";
|
import NodeFrag from "./Node.frag";
|
||||||
import NodeVert from "./Node.vert";
|
import NodeVert from "./Node.vert";
|
||||||
import NodeHtml from "./NodeHTML.svelte";
|
import NodeHtml from "./NodeHTML.svelte";
|
||||||
|
import { colors } from "../graph/colors.svelte";
|
||||||
|
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||||
|
|
||||||
const graphState = getGraphState();
|
const graphState = getGraphState();
|
||||||
|
|
||||||
@ -18,7 +20,16 @@
|
|||||||
const { node, inView, z }: Props = $props();
|
const { 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);
|
||||||
|
$effect(() => {
|
||||||
|
appSettings.theme;
|
||||||
|
strokeColor = isSelected
|
||||||
|
? colors.selected
|
||||||
|
: isActive
|
||||||
|
? colors.active
|
||||||
|
: colors.outline;
|
||||||
|
});
|
||||||
|
|
||||||
const updateNodePosition =
|
const updateNodePosition =
|
||||||
getContext<(n: Node) => void>("updateNodePosition");
|
getContext<(n: Node) => void>("updateNodePosition");
|
||||||
@ -30,16 +41,11 @@
|
|||||||
const height = getNodeHeight?.(node.type);
|
const height = getNodeHeight?.(node.type);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (node && meshRef) {
|
node.tmp = node.tmp || {};
|
||||||
node.tmp = node.tmp || {};
|
node.tmp.mesh = meshRef;
|
||||||
node.tmp.mesh = meshRef;
|
updateNodePosition?.(node);
|
||||||
updateNodePosition?.(node);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const colorBright = $colors["layer-2"];
|
|
||||||
const colorDark = $colors["layer-1"];
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
node.tmp.mesh = meshRef;
|
node.tmp.mesh = meshRef;
|
||||||
@ -61,20 +67,14 @@
|
|||||||
fragmentShader={NodeFrag}
|
fragmentShader={NodeFrag}
|
||||||
transparent
|
transparent
|
||||||
uniforms={{
|
uniforms={{
|
||||||
uColorBright: { value: new Color("#171717") },
|
uColorBright: { value: colors["layer-2"] },
|
||||||
uColorDark: { value: new Color("#151515") },
|
uColorDark: { value: colors["layer-1"] },
|
||||||
uStrokeColor: { value: new Color("#9d5f28") },
|
uStrokeColor: { value: colors.outline.clone() },
|
||||||
uStrokeWidth: { value: 1.0 },
|
uStrokeWidth: { value: 1.0 },
|
||||||
uWidth: { value: 20 },
|
uWidth: { value: 20 },
|
||||||
uHeight: { value: height },
|
uHeight: { value: height },
|
||||||
}}
|
}}
|
||||||
uniforms.uColorBright.value={colorBright}
|
uniforms.uStrokeColor.value={strokeColor.clone()}
|
||||||
uniforms.uColorDark.value={colorDark}
|
|
||||||
uniforms.uStrokeColor.value={isSelected
|
|
||||||
? $colors.selected
|
|
||||||
: isActive
|
|
||||||
? $colors.active
|
|
||||||
: $colors.outline}
|
|
||||||
uniforms.uStrokeWidth.value={(7 - z) / 3}
|
uniforms.uStrokeWidth.value={(7 - z) / 3}
|
||||||
/>
|
/>
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
|
@ -3,14 +3,26 @@
|
|||||||
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 { getContext, onMount } from "svelte";
|
||||||
export let isActive = false;
|
|
||||||
export let isSelected = false;
|
|
||||||
export let inView = true;
|
|
||||||
export let z = 2;
|
|
||||||
|
|
||||||
let ref: HTMLDivElement;
|
let ref: HTMLDivElement;
|
||||||
export let node: Node;
|
|
||||||
export let position = "absolute";
|
type Props = {
|
||||||
|
node: Node;
|
||||||
|
position?: "absolute" | "fixed" | "relative";
|
||||||
|
isActive?: boolean;
|
||||||
|
isSelected?: boolean;
|
||||||
|
inView?: boolean;
|
||||||
|
z?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
node = $bindable(),
|
||||||
|
position = "absolute",
|
||||||
|
isActive = false,
|
||||||
|
isSelected = false,
|
||||||
|
inView = true,
|
||||||
|
z = 2,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
const zOffset = (node.tmp?.random || 0) * 0.5;
|
const zOffset = (node.tmp?.random || 0) * 0.5;
|
||||||
const zLimit = 2 - zOffset;
|
const zLimit = 2 - zOffset;
|
||||||
@ -25,12 +37,6 @@
|
|||||||
const updateNodePosition =
|
const updateNodePosition =
|
||||||
getContext<(n: Node) => void>("updateNodePosition");
|
getContext<(n: Node) => void>("updateNodePosition");
|
||||||
|
|
||||||
$: if (node && ref) {
|
|
||||||
node.tmp = node.tmp || {};
|
|
||||||
node.tmp.ref = ref;
|
|
||||||
updateNodePosition?.(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
node.tmp.ref = ref;
|
node.tmp.ref = ref;
|
||||||
|
@ -26,9 +26,9 @@
|
|||||||
const aspectRatio = 0.25;
|
const aspectRatio = 0.25;
|
||||||
|
|
||||||
const path = createNodePath({
|
const path = createNodePath({
|
||||||
depth: 5,
|
depth: 5.5,
|
||||||
height: 29,
|
height: 34,
|
||||||
y: 50,
|
y: 49,
|
||||||
cornerTop,
|
cornerTop,
|
||||||
rightBump,
|
rightBump,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
@ -42,9 +42,9 @@
|
|||||||
aspectRatio,
|
aspectRatio,
|
||||||
});
|
});
|
||||||
const pathHover = createNodePath({
|
const pathHover = createNodePath({
|
||||||
depth: 9,
|
depth: 8.5,
|
||||||
height: 50,
|
height: 50,
|
||||||
y: 50,
|
y: 49,
|
||||||
cornerTop,
|
cornerTop,
|
||||||
rightBump,
|
rightBump,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
@ -103,12 +103,12 @@
|
|||||||
|
|
||||||
svg {
|
svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 1px;
|
||||||
left: 0;
|
left: 1px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: calc(100% - 2px);
|
||||||
height: 100%;
|
height: calc(100% - 1px);
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,18 @@
|
|||||||
|
|
||||||
const graph = getGraphManager();
|
const graph = getGraphManager();
|
||||||
|
|
||||||
let value = $state(node?.props?.[id] ?? input.value);
|
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(getDefaultValue());
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (value !== undefined && node?.props?.[id] !== value) {
|
if (value !== undefined && node?.props?.[id] !== value) {
|
||||||
|
@ -53,14 +53,14 @@
|
|||||||
const path = createNodePath({
|
const path = createNodePath({
|
||||||
depth: 7,
|
depth: 7,
|
||||||
height: 20,
|
height: 20,
|
||||||
y: 51,
|
y: 50.5,
|
||||||
cornerBottom,
|
cornerBottom,
|
||||||
leftBump,
|
leftBump,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
});
|
});
|
||||||
const pathDisabled = createNodePath({
|
const pathDisabled = createNodePath({
|
||||||
depth: 4.5,
|
depth: 6,
|
||||||
height: 14,
|
height: 18,
|
||||||
y: 50.5,
|
y: 50.5,
|
||||||
cornerBottom,
|
cornerBottom,
|
||||||
leftBump,
|
leftBump,
|
||||||
@ -172,11 +172,11 @@
|
|||||||
svg {
|
svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: calc(100% - 2px);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 1px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ export function createKeyMap(keys: Shortcut[]) {
|
|||||||
|
|
||||||
const store = writable(new Map(keys.map(k => [getShortcutId(k), k])));
|
const store = writable(new Map(keys.map(k => [getShortcutId(k), k])));
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleKeyboardEvent: (event: KeyboardEvent) => {
|
handleKeyboardEvent: (event: KeyboardEvent) => {
|
||||||
const activeElement = document.activeElement as HTMLElement;
|
const activeElement = document.activeElement as HTMLElement;
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
fps: false,
|
fps: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
$: vertices = $store?.at(-1)?.["total-vertices"][0] || 0;
|
$: vertices = $store?.at(-1)?.["total-vertices"]?.[0] || 0;
|
||||||
$: faces = $store?.at(-1)?.["total-faces"][0] || 0;
|
$: faces = $store?.at(-1)?.["total-faces"]?.[0] || 0;
|
||||||
$: runtime = $store?.at(-1)?.["runtime"][0] || 0;
|
$: runtime = $store?.at(-1)?.["runtime"]?.[0] || 0;
|
||||||
|
|
||||||
function getPoints(data: PerformanceData, key: string) {
|
function getPoints(data: PerformanceData, key: string) {
|
||||||
return data?.map((run) => run[key]?.[0] || 0) || [];
|
return data?.map((run) => run[key]?.[0] || 0) || [];
|
||||||
|
@ -9,21 +9,28 @@
|
|||||||
Box3,
|
Box3,
|
||||||
Mesh,
|
Mesh,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
|
Color,
|
||||||
} from "three";
|
} from "three";
|
||||||
import { AppSettings } from "../settings/app-settings";
|
import { appSettings } from "../settings/app-settings.svelte";
|
||||||
import Camera from "./Camera.svelte";
|
import Camera from "./Camera.svelte";
|
||||||
|
import { colors } from "$lib/graph-interface/graph/colors.svelte";
|
||||||
|
|
||||||
const { renderStage, invalidate: _invalidate } = useThrelte();
|
const { renderStage, invalidate: _invalidate } = useThrelte();
|
||||||
|
|
||||||
export let fps: number[] = [];
|
type Props = {
|
||||||
// let renderer = threlte.renderer;
|
fps: number[];
|
||||||
// let rendererRender = renderer.render;
|
lines: Vector3[][];
|
||||||
// renderer.render = function (scene, camera) {
|
scene: Group;
|
||||||
// const a = performance.now();
|
centerCamera: boolean;
|
||||||
// rendererRender.call(renderer, scene, camera);
|
};
|
||||||
// fps.push(performance.now() - a);
|
|
||||||
// fps = fps.slice(-100);
|
let {
|
||||||
// };
|
lines,
|
||||||
|
centerCamera,
|
||||||
|
fps = $bindable(),
|
||||||
|
scene = $bindable(),
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
useTask(
|
useTask(
|
||||||
(delta) => {
|
(delta) => {
|
||||||
fps.push(1 / delta);
|
fps.push(1 / delta);
|
||||||
@ -53,12 +60,8 @@
|
|||||||
_invalidate();
|
_invalidate();
|
||||||
};
|
};
|
||||||
|
|
||||||
let geometries: BufferGeometry[] = [];
|
let geometries = $state<BufferGeometry[]>();
|
||||||
export let lines: Vector3[][];
|
let center = $state(new Vector3(0, 4, 0));
|
||||||
export let scene: Group;
|
|
||||||
|
|
||||||
export let centerCamera: boolean = true;
|
|
||||||
let center = new Vector3(0, 4, 0);
|
|
||||||
|
|
||||||
function isMesh(child: Mesh | any): child is Mesh {
|
function isMesh(child: Mesh | any): child is Mesh {
|
||||||
return child.isObject3D && "material" in child;
|
return child.isObject3D && "material" in child;
|
||||||
@ -68,14 +71,15 @@
|
|||||||
return material.isMaterial && "matcap" in material;
|
return material.isMaterial && "matcap" in material;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if ($AppSettings && scene) {
|
$effect(() => {
|
||||||
|
const wireframe = appSettings.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 = $AppSettings.wireframe;
|
child.material.wireframe = wireframe;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
invalidate();
|
_invalidate();
|
||||||
}
|
});
|
||||||
|
|
||||||
function getPosition(geo: BufferGeometry, i: number) {
|
function getPosition(geo: BufferGeometry, i: number) {
|
||||||
return [
|
return [
|
||||||
@ -88,32 +92,38 @@
|
|||||||
|
|
||||||
<Camera {center} {centerCamera} />
|
<Camera {center} {centerCamera} />
|
||||||
|
|
||||||
{#if $AppSettings.showGrid}
|
{#if appSettings.showGrid}
|
||||||
<T.GridHelper args={[20, 20]} />
|
<T.GridHelper
|
||||||
|
args={[20, 20]}
|
||||||
|
colorGrid={colors["outline"]}
|
||||||
|
colorCenterLine={new Color("red")}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<T.Group>
|
<T.Group>
|
||||||
{#each geometries as geo}
|
{#if geometries}
|
||||||
{#if $AppSettings.showIndices}
|
{#each geometries as geo}
|
||||||
{#each geo.attributes.position.array as _, i}
|
{#if appSettings.debug.showIndices}
|
||||||
{#if i % 3 === 0}
|
{#each geo.attributes.position.array as _, i}
|
||||||
<Text fontSize={0.25} position={getPosition(geo, i)} />
|
{#if i % 3 === 0}
|
||||||
{/if}
|
<Text fontSize={0.25} position={getPosition(geo, i)} />
|
||||||
{/each}
|
{/if}
|
||||||
{/if}
|
{/each}
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if $AppSettings.showVertices}
|
{#if appSettings.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} />
|
||||||
</T.Points>
|
</T.Points>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
{/if}
|
||||||
|
|
||||||
<T.Group bind:ref={scene}></T.Group>
|
<T.Group bind:ref={scene}></T.Group>
|
||||||
</T.Group>
|
</T.Group>
|
||||||
|
|
||||||
{#if $AppSettings.showStemLines && lines}
|
{#if appSettings.debug.showStemLines && lines}
|
||||||
{#each lines as line}
|
{#each lines as line}
|
||||||
<T.Mesh>
|
<T.Mesh>
|
||||||
<MeshLineGeometry points={line} />
|
<MeshLineGeometry points={line} />
|
||||||
|
@ -2,12 +2,10 @@
|
|||||||
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 "@nodes/utils";
|
||||||
import type { PerformanceStore } from "@nodes/utils";
|
import type { PerformanceStore } from "@nodes/utils";
|
||||||
import { AppSettings } from "$lib/settings/app-settings";
|
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";
|
||||||
import {
|
import {
|
||||||
createGeometryPool,
|
createGeometryPool,
|
||||||
@ -22,9 +20,11 @@
|
|||||||
matcap,
|
matcap,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let sceneComponent = $state<ReturnType<typeof Scene>>();
|
||||||
|
let fps = $state<number[]>([]);
|
||||||
|
|
||||||
let geometryPool: ReturnType<typeof createGeometryPool>;
|
let geometryPool: ReturnType<typeof createGeometryPool>;
|
||||||
let instancePool: ReturnType<typeof createInstancedGeometryPool>;
|
let instancePool: ReturnType<typeof createInstancedGeometryPool>;
|
||||||
|
|
||||||
export function updateGeometries(inputs: Int32Array[], group: Group) {
|
export function updateGeometries(inputs: Int32Array[], group: Group) {
|
||||||
geometryPool = geometryPool || createGeometryPool(group, material);
|
geometryPool = geometryPool || createGeometryPool(group, material);
|
||||||
instancePool = instancePool || createInstancedGeometryPool(group, material);
|
instancePool = instancePool || createInstancedGeometryPool(group, material);
|
||||||
@ -38,14 +38,15 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export let centerCamera: boolean = true;
|
type Props = {
|
||||||
export let perf: PerformanceStore;
|
scene: Group;
|
||||||
export let scene: Group;
|
centerCamera: boolean;
|
||||||
let fps: number[] = [];
|
perf: PerformanceStore;
|
||||||
|
};
|
||||||
|
|
||||||
let lines: Vector3[][] = [];
|
let { scene = $bindable(), centerCamera, perf }: Props = $props();
|
||||||
|
|
||||||
let invalidate: () => void;
|
let lines = $state<Vector3[][]>([]);
|
||||||
|
|
||||||
function createLineGeometryFromEncodedData(encodedData: Int32Array) {
|
function createLineGeometryFromEncodedData(encodedData: Int32Array) {
|
||||||
const positions: Vector3[] = [];
|
const positions: Vector3[] = [];
|
||||||
@ -63,12 +64,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const update = function update(result: Int32Array) {
|
export const update = function update(result: Int32Array) {
|
||||||
perf?.addPoint("split-result");
|
perf.addPoint("split-result");
|
||||||
const inputs = splitNestedArray(result);
|
const inputs = splitNestedArray(result);
|
||||||
perf?.endPoint();
|
perf.endPoint();
|
||||||
|
|
||||||
if ($AppSettings.showStemLines) {
|
if (appSettings.debug.showStemLines) {
|
||||||
perf?.addPoint("create-lines");
|
perf.addPoint("create-lines");
|
||||||
lines = inputs
|
lines = inputs
|
||||||
.map((input) => {
|
.map((input) => {
|
||||||
if (input[0] === 0) {
|
if (input[0] === 0) {
|
||||||
@ -79,21 +80,27 @@
|
|||||||
perf.endPoint();
|
perf.endPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
perf?.addPoint("update-geometries");
|
perf.addPoint("update-geometries");
|
||||||
|
|
||||||
const { totalVertices, totalFaces } = updateGeometries(inputs, scene);
|
const { totalVertices, totalFaces } = updateGeometries(inputs, scene);
|
||||||
perf?.endPoint();
|
perf.endPoint();
|
||||||
|
|
||||||
perf?.addPoint("total-vertices", totalVertices);
|
perf.addPoint("total-vertices", totalVertices);
|
||||||
perf?.addPoint("total-faces", totalFaces);
|
perf.addPoint("total-faces", totalFaces);
|
||||||
invalidate();
|
sceneComponent?.invalidate();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $AppSettings.showPerformancePanel}
|
{#if appSettings.debug.showPerformancePanel}
|
||||||
<SmallPerformanceViewer {fps} store={perf} />
|
<SmallPerformanceViewer {fps} store={perf} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Scene bind:scene bind:invalidate {lines} {centerCamera} bind:fps />
|
<Scene
|
||||||
|
bind:this={sceneComponent}
|
||||||
|
{lines}
|
||||||
|
{centerCamera}
|
||||||
|
bind:scene
|
||||||
|
bind:fps
|
||||||
|
/>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { fastHashArrayBuffer } from "@nodes/utils";
|
import { fastHashArrayBuffer } from "@nodes/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: ArrayBuffer) {
|
||||||
let ints = new Uint8Array(arr);
|
let ints = new Uint8Array(arr);
|
||||||
@ -108,7 +107,6 @@ export function createGeometryPool(parentScene: Group, material: Material) {
|
|||||||
scene.add(mesh);
|
scene.add(mesh);
|
||||||
meshes.push(mesh);
|
meshes.push(mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import type { Graph, NodeRegistry, NodeDefinition, RuntimeExecutor, NodeInput } from "@nodes/types";
|
import type { Graph, NodeDefinition, NodeInput, NodeRegistry, RuntimeExecutor, SyncCache } from "@nodes/types";
|
||||||
import { concatEncodedArrays, encodeFloat, fastHashArrayBuffer, createLogger, type PerformanceStore } from "@nodes/utils"
|
import { concatEncodedArrays, createLogger, encodeFloat, fastHashArrayBuffer, type PerformanceStore } from "@nodes/utils";
|
||||||
import type { SyncCache } from "@nodes/types";
|
|
||||||
|
|
||||||
const log = createLogger("runtime-executor");
|
const log = createLogger("runtime-executor");
|
||||||
log.mute()
|
log.mute()
|
||||||
@ -9,6 +8,7 @@ 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") {
|
||||||
return encodeFloat(value as number);
|
return encodeFloat(value as number);
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,6 @@ import type { Graph, RuntimeExecutor } from "@nodes/types";
|
|||||||
export class WorkerRuntimeExecutor implements RuntimeExecutor {
|
export class WorkerRuntimeExecutor implements RuntimeExecutor {
|
||||||
private worker = new ComlinkWorker<typeof import('./worker-runtime-executor-backend.ts')>(new URL(`./worker-runtime-executor-backend.ts`, import.meta.url));
|
private worker = new ComlinkWorker<typeof import('./worker-runtime-executor-backend.ts')>(new URL(`./worker-runtime-executor-backend.ts`, import.meta.url));
|
||||||
|
|
||||||
constructor() {
|
|
||||||
console.log(import.meta.url)
|
|
||||||
}
|
|
||||||
async execute(graph: Graph, settings: Record<string, unknown>) {
|
async execute(graph: Graph, settings: Record<string, unknown>) {
|
||||||
return this.worker.executeGraph(graph, settings);
|
return this.worker.executeGraph(graph, settings);
|
||||||
}
|
}
|
||||||
|
177
app/src/lib/settings/NestedSettings.svelte
Normal file
177
app/src/lib/settings/NestedSettings.svelte
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
<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 | number[]
|
||||||
|
>;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultValue() {
|
||||||
|
if (key === "") return;
|
||||||
|
if (key === "title") return;
|
||||||
|
if (Array.isArray(type[key]?.options)) {
|
||||||
|
if (value?.[key] !== undefined) {
|
||||||
|
return type[key]?.options?.indexOf(value?.[key]);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value?.[key] !== undefined) return value?.[key];
|
||||||
|
if (type[key]?.value !== undefined) return type[key]?.value;
|
||||||
|
|
||||||
|
if (isNodeInput(type[key])) {
|
||||||
|
if (type[key].type === "boolean") return 0;
|
||||||
|
if (type[key].type === "float") return 0.5;
|
||||||
|
if (type[key].type === "integer") return 0;
|
||||||
|
if (type[key].type === "select") return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let internalValue = $state(getDefaultValue());
|
||||||
|
|
||||||
|
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} 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}
|
||||||
|
{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}
|
||||||
|
|
||||||
|
<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
position: absolute;
|
||||||
|
margin: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
border: none;
|
||||||
|
border-bottom: solid thin var(--outline);
|
||||||
|
}
|
||||||
|
</style>
|
@ -39,6 +39,7 @@ export const AppSettingTypes = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
debug: {
|
debug: {
|
||||||
|
title: "Debug",
|
||||||
wireframe: {
|
wireframe: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Wireframe",
|
label: "Wireframe",
|
||||||
@ -79,7 +80,8 @@ export const AppSettingTypes = {
|
|||||||
amount: {
|
amount: {
|
||||||
type: "integer",
|
type: "integer",
|
||||||
min: 2,
|
min: 2,
|
||||||
max: 15
|
max: 15,
|
||||||
|
value: 4
|
||||||
},
|
},
|
||||||
loadGrid: {
|
loadGrid: {
|
||||||
type: "button",
|
type: "button",
|
||||||
@ -103,16 +105,26 @@ export const AppSettingTypes = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} as const
|
} as const;
|
||||||
|
|
||||||
type IsInputDefinition<T> = T extends NodeInput ? T : never;
|
type IsInputDefinition<T> = T extends NodeInput ? T : never;
|
||||||
type HasTitle = { title: string };
|
type HasTitle = { title: string };
|
||||||
|
|
||||||
|
type Widen<T> = T extends boolean
|
||||||
|
? boolean
|
||||||
|
: T extends number
|
||||||
|
? number
|
||||||
|
: T extends string
|
||||||
|
? string
|
||||||
|
: T;
|
||||||
|
|
||||||
|
|
||||||
type ExtractSettingsValues<T> = {
|
type ExtractSettingsValues<T> = {
|
||||||
[K in keyof T]: T[K] extends HasTitle
|
-readonly [K in keyof T]: T[K] extends HasTitle
|
||||||
? ExtractSettingsValues<Omit<T[K], 'title'>>
|
? ExtractSettingsValues<Omit<T[K], 'title'>>
|
||||||
: T[K] extends IsInputDefinition<T[K]>
|
: T[K] extends IsInputDefinition<T[K]>
|
||||||
? T[K] extends { value: any }
|
? T[K] extends { value: infer V }
|
||||||
? T[K]['value']
|
? Widen<V>
|
||||||
: never
|
: never
|
||||||
: T[K] extends Record<string, any>
|
: T[K] extends Record<string, any>
|
||||||
? ExtractSettingsValues<T[K]>
|
? ExtractSettingsValues<T[K]>
|
||||||
@ -138,8 +150,8 @@ export const appSettings = localState("app-settings", settingsToStore(AppSetting
|
|||||||
|
|
||||||
$effect.root(() => {
|
$effect.root(() => {
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const { theme } = $state.snapshot(appSettings);
|
const theme = appSettings.theme;
|
||||||
const classes = document.body.parentElement?.classList;
|
const classes = document.documentElement.classList;
|
||||||
const newClassName = `theme-${theme}`;
|
const newClassName = `theme-${theme}`;
|
||||||
if (classes) {
|
if (classes) {
|
||||||
for (const className of classes) {
|
for (const className of classes) {
|
||||||
@ -148,6 +160,6 @@ $effect.root(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.body?.parentElement?.classList.add(newClassName);
|
document.documentElement.classList.add(newClassName);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,136 +0,0 @@
|
|||||||
import localStore from "$lib/helpers/localStore";
|
|
||||||
|
|
||||||
export const AppSettings = localStore("node.settings", {
|
|
||||||
theme: 0,
|
|
||||||
showGrid: true,
|
|
||||||
showNodeGrid: true,
|
|
||||||
snapToGrid: true,
|
|
||||||
showHelp: false,
|
|
||||||
wireframe: false,
|
|
||||||
showIndices: false,
|
|
||||||
showVertices: false,
|
|
||||||
showPerformancePanel: false,
|
|
||||||
showBenchmarkPanel: false,
|
|
||||||
centerCamera: true,
|
|
||||||
showStemLines: false,
|
|
||||||
useWorker: true,
|
|
||||||
amount: 5
|
|
||||||
});
|
|
||||||
|
|
||||||
const themes = ["dark", "light", "catppuccin", "solarized", "high-contrast", "nord", "dracula"];
|
|
||||||
|
|
||||||
AppSettings.subscribe((value) => {
|
|
||||||
const classes = document.body.parentElement?.classList;
|
|
||||||
const newClassName = `theme-${themes[value.theme]}`;
|
|
||||||
if (classes) {
|
|
||||||
for (const className of classes) {
|
|
||||||
if (className.startsWith("theme-") && className !== newClassName) {
|
|
||||||
classes.remove(className);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.body?.parentElement?.classList.add(newClassName);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const AppSettingTypes = {
|
|
||||||
theme: {
|
|
||||||
type: "select",
|
|
||||||
options: themes,
|
|
||||||
label: "Theme",
|
|
||||||
value: themes[0],
|
|
||||||
},
|
|
||||||
showGrid: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Show Grid",
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
centerCamera: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Center Camera",
|
|
||||||
value: true
|
|
||||||
},
|
|
||||||
nodeInterface: {
|
|
||||||
__title: "Node Interface",
|
|
||||||
showNodeGrid: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Show Grid",
|
|
||||||
value: true
|
|
||||||
},
|
|
||||||
snapToGrid: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Snap to Grid",
|
|
||||||
value: true
|
|
||||||
},
|
|
||||||
showHelp: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Show Help",
|
|
||||||
value: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
debug: {
|
|
||||||
wireframe: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Wireframe",
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
useWorker: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Execute runtime in worker",
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
showIndices: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Show Indices",
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
showPerformancePanel: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Show Performance Panel",
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
showBenchmarkPanel: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Show Benchmark Panel",
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
showVertices: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Show Vertices",
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
showStemLines: {
|
|
||||||
type: "boolean",
|
|
||||||
label: "Show Stem Lines",
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
stressTest: {
|
|
||||||
__title: "Stress Test",
|
|
||||||
amount: {
|
|
||||||
type: "integer",
|
|
||||||
min: 2,
|
|
||||||
max: 15
|
|
||||||
},
|
|
||||||
loadGrid: {
|
|
||||||
type: "button",
|
|
||||||
label: "Load Grid"
|
|
||||||
},
|
|
||||||
loadTree: {
|
|
||||||
type: "button",
|
|
||||||
label: "Load Tree"
|
|
||||||
},
|
|
||||||
lottaFaces: {
|
|
||||||
type: "button",
|
|
||||||
label: "Load 'lots of faces'"
|
|
||||||
},
|
|
||||||
lottaNodes: {
|
|
||||||
type: "button",
|
|
||||||
label: "Load 'lots of nodes'"
|
|
||||||
},
|
|
||||||
lottaNodesAndFaces: {
|
|
||||||
type: "button",
|
|
||||||
label: "Load 'lots of nodes and faces'"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
13
app/src/lib/settings/index.ts
Normal file
13
app/src/lib/settings/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { NodeInput } from "@nodes/types";
|
||||||
|
|
||||||
|
type Button = { type: "button"; label?: string };
|
||||||
|
|
||||||
|
type InputType = NodeInput | Button;
|
||||||
|
|
||||||
|
export interface SettingsType {
|
||||||
|
[key: string]: (SettingsType & { title?: string }) | InputType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SettingsStore = {
|
||||||
|
[key: string]: SettingsStore | string | number | boolean
|
||||||
|
};
|
@ -1,89 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Node, NodeInput } from "@nodes/types";
|
|
||||||
import NestedSettings from "./NestedSettings.svelte";
|
|
||||||
import { writable } from "svelte/store";
|
|
||||||
|
|
||||||
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
|
||||||
|
|
||||||
export let manager: GraphManager;
|
|
||||||
export let node: Node | undefined;
|
|
||||||
|
|
||||||
function filterInputs(inputs: Record<string, NodeInput>) {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(inputs)
|
|
||||||
.filter(([_key, value]) => {
|
|
||||||
return value.hidden === true;
|
|
||||||
})
|
|
||||||
.map(([key, value]) => {
|
|
||||||
//@ts-ignore
|
|
||||||
value.__node_type = node?.tmp?.type.id;
|
|
||||||
//@ts-ignore
|
|
||||||
value.__node_input = key;
|
|
||||||
return [key, value];
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createStore(
|
|
||||||
props: Node["props"],
|
|
||||||
inputs: Record<string, NodeInput>,
|
|
||||||
) {
|
|
||||||
const store: Record<string, unknown> = {};
|
|
||||||
Object.keys(inputs).forEach((key) => {
|
|
||||||
if (props) {
|
|
||||||
//@ts-ignore
|
|
||||||
store[key] = props[key] || inputs[key].value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return writable(store);
|
|
||||||
}
|
|
||||||
|
|
||||||
let nodeDefinition: Record<string, NodeInput> | undefined;
|
|
||||||
$: nodeDefinition = node?.tmp?.type
|
|
||||||
? filterInputs(node.tmp.type.inputs)
|
|
||||||
: undefined;
|
|
||||||
$: store = node ? createStore(node.props, nodeDefinition) : undefined;
|
|
||||||
|
|
||||||
let lastPropsHash = "";
|
|
||||||
function updateNode() {
|
|
||||||
if (!node || !$store) return;
|
|
||||||
let needsUpdate = false;
|
|
||||||
Object.keys($store).forEach((_key: string) => {
|
|
||||||
node.props = node.props || {};
|
|
||||||
const key = _key as keyof typeof $store;
|
|
||||||
if (node && $store) {
|
|
||||||
needsUpdate = true;
|
|
||||||
node.props[key] = $store[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let propsHash = JSON.stringify(node.props);
|
|
||||||
if (propsHash === lastPropsHash) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastPropsHash = propsHash;
|
|
||||||
// console.log(needsUpdate, node.props, $store);
|
|
||||||
if (needsUpdate) {
|
|
||||||
manager.execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if (store && $store) {
|
|
||||||
updateNode();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if node}
|
|
||||||
{#key node.id}
|
|
||||||
{#if nodeDefinition && store && Object.keys(nodeDefinition).length > 0}
|
|
||||||
<NestedSettings
|
|
||||||
id="activeNodeSettings"
|
|
||||||
settings={nodeDefinition}
|
|
||||||
{store}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<p class="mx-4">Active Node has no Settings</p>
|
|
||||||
{/if}
|
|
||||||
{/key}
|
|
||||||
{:else}
|
|
||||||
<p class="mx-4">No active node</p>
|
|
||||||
{/if}
|
|
@ -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,60 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
|
||||||
import { ShortCut } from "@nodes/ui";
|
|
||||||
|
|
||||||
export let keymap: ReturnType<typeof createKeyMap>;
|
|
||||||
const keys = keymap?.keys;
|
|
||||||
export let title = "Keymap";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="wrapper">
|
|
||||||
<h3>{title}</h3>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
{#each $keys as key}
|
|
||||||
{#if key.description}
|
|
||||||
<div class="command-wrapper">
|
|
||||||
<ShortCut
|
|
||||||
alt={key.alt}
|
|
||||||
ctrl={key.ctrl}
|
|
||||||
shift={key.shift}
|
|
||||||
key={key.key}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p>{key.description}</p>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.wrapper {
|
|
||||||
padding: 1em;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: min-content 1fr;
|
|
||||||
gap: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.command-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: right;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 0.9em;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,147 +0,0 @@
|
|||||||
<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 Props = {
|
|
||||||
id: string;
|
|
||||||
key?: string;
|
|
||||||
value: Record<string, unknown> | string | number | boolean;
|
|
||||||
type: Nested;
|
|
||||||
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 openSections = localState("open-details", {});
|
|
||||||
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}">
|
|
||||||
{#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 as Record<string, unknown>}
|
|
||||||
type={type as Nested}
|
|
||||||
depth={depth + 1}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
{#if depth > 0}
|
|
||||||
<hr />
|
|
||||||
{/if}
|
|
||||||
{:else if key && type?.[key]}
|
|
||||||
{#if depth > 0}
|
|
||||||
<hr />
|
|
||||||
{/if}
|
|
||||||
<details bind:open>
|
|
||||||
<summary>{type[key]?.title||key}</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 Record<string, unknown>}
|
|
||||||
type={type[key] as Nested}
|
|
||||||
depth={depth + 1}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
summary {
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
details {
|
|
||||||
padding: 1em;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
margin-top: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
padding-left: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-boolean {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.input-boolean > label {
|
|
||||||
order: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first-level > .input {
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first-level {
|
|
||||||
border-bottom: solid thin var(--outline);
|
|
||||||
}
|
|
||||||
.first-level > details {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
hr {
|
|
||||||
position: absolute;
|
|
||||||
margin: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
border: none;
|
|
||||||
border-bottom: solid thin var(--outline);
|
|
||||||
}
|
|
||||||
</style>
|
|
82
app/src/lib/sidebar/panels/ActiveNodeSelected.svelte
Normal file
82
app/src/lib/sidebar/panels/ActiveNodeSelected.svelte
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Node, NodeInput } from "@nodes/types";
|
||||||
|
import NestedSettings from "$lib/settings/NestedSettings.svelte";
|
||||||
|
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
manager: GraphManager;
|
||||||
|
node: Node;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { manager, node }: Props = $props();
|
||||||
|
|
||||||
|
const nodeDefinition = filterInputs(node.tmp?.type?.inputs);
|
||||||
|
function filterInputs(inputs?: Record<string, NodeInput>) {
|
||||||
|
const _inputs = $state.snapshot(inputs);
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(structuredClone(_inputs ?? {}))
|
||||||
|
.filter(([_key, value]) => {
|
||||||
|
return value.hidden === true;
|
||||||
|
})
|
||||||
|
.map(([key, value]) => {
|
||||||
|
//@ts-ignore
|
||||||
|
value.__node_type = node?.tmp?.type.id;
|
||||||
|
//@ts-ignore
|
||||||
|
value.__node_input = key;
|
||||||
|
return [key, value];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type Store = Record<string, number | number[]>;
|
||||||
|
let store = $state<Store>(createStore(node?.props, nodeDefinition));
|
||||||
|
function createStore(
|
||||||
|
props: Node["props"],
|
||||||
|
inputs: Record<string, NodeInput>,
|
||||||
|
): Store {
|
||||||
|
const store: Store = {};
|
||||||
|
Object.keys(inputs).forEach((key) => {
|
||||||
|
if (props) {
|
||||||
|
//@ts-ignore
|
||||||
|
store[key] = props[key] || inputs[key].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastPropsHash = "";
|
||||||
|
function updateNode() {
|
||||||
|
if (!node || !store) return;
|
||||||
|
let needsUpdate = false;
|
||||||
|
Object.keys(store).forEach((_key: string) => {
|
||||||
|
node.props = node.props || {};
|
||||||
|
const key = _key as keyof typeof store;
|
||||||
|
if (node && store) {
|
||||||
|
needsUpdate = true;
|
||||||
|
node.props[key] = store[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let propsHash = JSON.stringify(node.props);
|
||||||
|
if (propsHash === lastPropsHash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastPropsHash = propsHash;
|
||||||
|
|
||||||
|
if (needsUpdate) {
|
||||||
|
manager.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (store) {
|
||||||
|
updateNode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<NestedSettings
|
||||||
|
id="activeNodeSettings"
|
||||||
|
bind:value={store}
|
||||||
|
type={nodeDefinition}
|
||||||
|
/>
|
24
app/src/lib/sidebar/panels/ActiveNodeSettings.svelte
Normal file
24
app/src/lib/sidebar/panels/ActiveNodeSettings.svelte
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Node } from "@nodes/types";
|
||||||
|
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
||||||
|
import ActiveNodeSelected from "./ActiveNodeSelected.svelte";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
manager: GraphManager;
|
||||||
|
node: Node | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { manager, node }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if node}
|
||||||
|
{#key node.id}
|
||||||
|
{#if node}
|
||||||
|
<ActiveNodeSelected {manager} {node} />
|
||||||
|
{:else}
|
||||||
|
<p class="mx-4">Active Node has no Settings</p>
|
||||||
|
{/if}
|
||||||
|
{/key}
|
||||||
|
{:else}
|
||||||
|
<p class="mx-4">No active node</p>
|
||||||
|
{/if}
|
@ -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">
|
66
app/src/lib/sidebar/panels/Keymap.svelte
Normal file
66
app/src/lib/sidebar/panels/Keymap.svelte
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
|
import { ShortCut } from "@nodes/ui";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
keymaps: {
|
||||||
|
keymap: ReturnType<typeof createKeyMap>;
|
||||||
|
title: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
let { keymaps }: Props = $props();
|
||||||
|
console.log({ keymaps });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<table class="wrapper">
|
||||||
|
<tbody>
|
||||||
|
{#each keymaps as keymap}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<h3>{keymap.title}</h3>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{#each get(keymap.keymap?.keys) as key}
|
||||||
|
<tr>
|
||||||
|
{#if key.description}
|
||||||
|
<td class="command-wrapper">
|
||||||
|
<ShortCut
|
||||||
|
alt={key.alt}
|
||||||
|
ctrl={key.ctrl}
|
||||||
|
shift={key.shift}
|
||||||
|
key={key.key}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{key.description}</td>
|
||||||
|
{/if}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: right;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin: 0;
|
||||||
|
padding: 7px;
|
||||||
|
padding-left: 0;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
@ -4,23 +4,20 @@
|
|||||||
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 "@nodes/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 { AppSettingTypes, AppSettings } from "$lib/settings/app-settings";
|
|
||||||
import {
|
import {
|
||||||
appSettings as _appSettings,
|
appSettings,
|
||||||
AppSettingTypes as _AppSettingTypes,
|
AppSettingTypes,
|
||||||
} from "$lib/settings/app-settings.svelte";
|
} from "$lib/settings/app-settings.svelte";
|
||||||
import { writable } from "svelte/store";
|
import Keymap from "$lib/sidebar/panels/Keymap.svelte";
|
||||||
import Keymap from "$lib/settings/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,
|
||||||
@ -28,8 +25,9 @@
|
|||||||
} from "$lib/runtime";
|
} from "$lib/runtime";
|
||||||
import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry";
|
import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry";
|
||||||
import { createPerformanceStore } from "@nodes/utils";
|
import { createPerformanceStore } from "@nodes/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";
|
||||||
|
|
||||||
let performanceStore = createPerformanceStore();
|
let performanceStore = createPerformanceStore();
|
||||||
|
|
||||||
@ -41,24 +39,26 @@
|
|||||||
const memoryRuntime = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
|
const memoryRuntime = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
|
||||||
memoryRuntime.perf = performanceStore;
|
memoryRuntime.perf = performanceStore;
|
||||||
|
|
||||||
$: runtime = $AppSettings.useWorker ? workerRuntime : memoryRuntime;
|
const runtime = $derived(
|
||||||
|
appSettings.debug.useWorker ? workerRuntime : memoryRuntime,
|
||||||
|
);
|
||||||
|
|
||||||
let activeNode: Node | undefined;
|
let activeNode = $state<Node | undefined>(undefined);
|
||||||
let scene: Group;
|
let scene = $state<Group>(null!);
|
||||||
let updateViewerResult: (result: Int32Array) => void;
|
|
||||||
|
|
||||||
let graph = localStorage.getItem("graph")
|
let graph = localStorage.getItem("graph")
|
||||||
? JSON.parse(localStorage.getItem("graph")!)
|
? JSON.parse(localStorage.getItem("graph")!)
|
||||||
: templates.defaultPlant;
|
: templates.defaultPlant;
|
||||||
|
|
||||||
let graphInterface: ReturnType<typeof GraphInterface>;
|
let graphInterface = $state<ReturnType<typeof GraphInterface>>(null!);
|
||||||
$: manager = graphInterface?.manager;
|
let viewerComponent = $state<ReturnType<typeof Viewer>>();
|
||||||
$: managerStatus = manager?.status;
|
const manager = $derived(graphInterface?.manager);
|
||||||
$: keymap = graphInterface?.keymap;
|
const managerStatus = $derived(manager?.status);
|
||||||
|
|
||||||
async function randomGenerate() {
|
async function randomGenerate() {
|
||||||
|
if (!manager) return;
|
||||||
const g = manager.serialize();
|
const g = manager.serialize();
|
||||||
const s = { ...$graphSettings, randomSeed: true };
|
const s = { ...graphSettings, randomSeed: true };
|
||||||
await handleUpdate(g, s);
|
await handleUpdate(g, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,18 +69,20 @@
|
|||||||
callback: randomGenerate,
|
callback: randomGenerate,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
let graphSettings = writable<Record<string, any>>({});
|
let graphSettings = $state<Record<string, any>>({});
|
||||||
let graphSettingTypes = {};
|
let graphSettingTypes = $state({
|
||||||
|
randomSeed: { type: "boolean", value: false },
|
||||||
|
});
|
||||||
|
|
||||||
const handleUpdate = debounceAsyncFunction(
|
const handleUpdate = debounceAsyncFunction(
|
||||||
async (g: Graph, s: Record<string, any>) => {
|
async (g: Graph, s: Record<string, any> = graphSettings) => {
|
||||||
performanceStore.startRun();
|
performanceStore.startRun();
|
||||||
try {
|
try {
|
||||||
let a = performance.now();
|
let a = performance.now();
|
||||||
const graphResult = await runtime.execute(g, s);
|
const graphResult = await runtime.execute(g, $state.snapshot(s));
|
||||||
let b = performance.now();
|
let b = performance.now();
|
||||||
|
|
||||||
if ($AppSettings.useWorker) {
|
if (appSettings.debug.useWorker) {
|
||||||
let perfData = await runtime.getPerformanceData();
|
let perfData = await runtime.getPerformanceData();
|
||||||
let lastRun = perfData?.at(-1);
|
let lastRun = perfData?.at(-1);
|
||||||
if (lastRun?.total) {
|
if (lastRun?.total) {
|
||||||
@ -94,7 +96,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateViewerResult(graphResult);
|
viewerComponent?.update(graphResult);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("errors", error);
|
console.log("errors", error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -103,32 +105,35 @@
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
$: if (AppSettings) {
|
// $ if (AppSettings) {
|
||||||
//@ts-ignore
|
// //@ts-ignore
|
||||||
AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
|
// AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
|
||||||
graph = templates.grid($AppSettings.amount, $AppSettings.amount);
|
// graph = templates.grid($AppSettings.amount, $AppSettings.amount);
|
||||||
};
|
// };
|
||||||
//@ts-ignore
|
// //@ts-ignore
|
||||||
AppSettingTypes.debug.stressTest.loadTree.callback = () => {
|
// AppSettingTypes.debug.stressTest.loadTree.callback = () => {
|
||||||
graph = templates.tree($AppSettings.amount);
|
// graph = templates.tree($AppSettings.amount);
|
||||||
};
|
// };
|
||||||
//@ts-ignore
|
// //@ts-ignore
|
||||||
AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
|
// AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
|
||||||
graph = templates.lottaFaces;
|
// graph = templates.lottaFaces;
|
||||||
};
|
// };
|
||||||
//@ts-ignore
|
// //@ts-ignore
|
||||||
AppSettingTypes.debug.stressTest.lottaNodes.callback = () => {
|
// AppSettingTypes.debug.stressTest.lottaNodes.callback = () => {
|
||||||
graph = templates.lottaNodes;
|
// graph = templates.lottaNodes;
|
||||||
};
|
// };
|
||||||
//@ts-ignore
|
// //@ts-ignore
|
||||||
AppSettingTypes.debug.stressTest.lottaNodesAndFaces.callback = () => {
|
// AppSettingTypes.debug.stressTest.lottaNodesAndFaces.callback = () => {
|
||||||
graph = templates.lottaNodesAndFaces;
|
// graph = templates.lottaNodesAndFaces;
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
function handleSave(graph: Graph) {
|
function handleSave(graph: Graph) {
|
||||||
localStorage.setItem("graph", JSON.stringify(graph));
|
localStorage.setItem("graph", JSON.stringify(graph));
|
||||||
}
|
}
|
||||||
|
onMount(() => {
|
||||||
|
handleUpdate(graph);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:document on:keydown={applicationKeymap.handleKeyboardEvent} />
|
<svelte:document on:keydown={applicationKeymap.handleKeyboardEvent} />
|
||||||
@ -137,10 +142,10 @@
|
|||||||
<Grid.Row>
|
<Grid.Row>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
<Viewer
|
<Viewer
|
||||||
perf={performanceStore}
|
|
||||||
bind:scene
|
bind:scene
|
||||||
bind:update={updateViewerResult}
|
bind:this={viewerComponent}
|
||||||
centerCamera={$AppSettings.centerCamera}
|
perf={performanceStore}
|
||||||
|
centerCamera={appSettings.centerCamera}
|
||||||
/>
|
/>
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
@ -149,21 +154,21 @@
|
|||||||
bind:this={graphInterface}
|
bind:this={graphInterface}
|
||||||
{graph}
|
{graph}
|
||||||
registry={nodeRegistry}
|
registry={nodeRegistry}
|
||||||
|
showGrid={appSettings.nodeInterface.showNodeGrid}
|
||||||
|
snapToGrid={appSettings.nodeInterface.snapToGrid}
|
||||||
bind:activeNode
|
bind:activeNode
|
||||||
showGrid={$AppSettings.showNodeGrid}
|
bind:showHelp={appSettings.nodeInterface.showHelp}
|
||||||
snapToGrid={$AppSettings.snapToGrid}
|
|
||||||
bind:showHelp={$AppSettings.showHelp}
|
|
||||||
bind:settings={graphSettings}
|
bind:settings={graphSettings}
|
||||||
bind:settingTypes={graphSettingTypes}
|
bind:settingTypes={graphSettingTypes}
|
||||||
onresult={(result) => handleUpdate(result, $graphSettings)}
|
onresult={(result) => handleUpdate(result)}
|
||||||
onsave={(graph) => handleSave(graph)}
|
onsave={(graph) => handleSave(graph)}
|
||||||
/>
|
/>
|
||||||
<Settings>
|
<Sidebar>
|
||||||
<Panel id="general" title="General" icon="i-tabler-settings">
|
<Panel id="general" title="General" icon="i-tabler-settings">
|
||||||
<NestedSettings
|
<NestedSettings
|
||||||
id="general"
|
id="general"
|
||||||
value={_appSettings}
|
value={appSettings}
|
||||||
type={_AppSettingTypes}
|
type={AppSettingTypes}
|
||||||
/>
|
/>
|
||||||
</Panel>
|
</Panel>
|
||||||
<Panel
|
<Panel
|
||||||
@ -171,10 +176,12 @@
|
|||||||
title="Keyboard Shortcuts"
|
title="Keyboard Shortcuts"
|
||||||
icon="i-tabler-keyboard"
|
icon="i-tabler-keyboard"
|
||||||
>
|
>
|
||||||
<Keymap title="Application" keymap={applicationKeymap} />
|
<Keymap
|
||||||
{#if keymap}
|
keymaps={[
|
||||||
<Keymap title="Node-Editor" {keymap} />
|
{ keymap: applicationKeymap, title: "Application" },
|
||||||
{/if}
|
{ 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} />
|
||||||
@ -191,7 +198,7 @@
|
|||||||
id="performance"
|
id="performance"
|
||||||
title="Performance"
|
title="Performance"
|
||||||
classes="text-red-400"
|
classes="text-red-400"
|
||||||
hidden={!$AppSettings.showPerformancePanel}
|
hidden={!appSettings.debug.showPerformancePanel}
|
||||||
icon="i-tabler-brand-speedtest"
|
icon="i-tabler-brand-speedtest"
|
||||||
>
|
>
|
||||||
{#if $performanceStore}
|
{#if $performanceStore}
|
||||||
@ -202,7 +209,7 @@
|
|||||||
id="benchmark"
|
id="benchmark"
|
||||||
title="Benchmark"
|
title="Benchmark"
|
||||||
classes="text-red-400"
|
classes="text-red-400"
|
||||||
hidden={!$AppSettings.showBenchmarkPanel}
|
hidden={!appSettings.debug.showBenchmarkPanel}
|
||||||
icon="i-tabler-graph"
|
icon="i-tabler-graph"
|
||||||
>
|
>
|
||||||
<BenchmarkPanel run={randomGenerate} />
|
<BenchmarkPanel run={randomGenerate} />
|
||||||
@ -213,9 +220,11 @@
|
|||||||
classes="text-blue-400"
|
classes="text-blue-400"
|
||||||
icon="i-custom-graph"
|
icon="i-custom-graph"
|
||||||
>
|
>
|
||||||
{#if Object.keys(graphSettingTypes).length > 0}
|
<NestedSettings
|
||||||
<GraphSettings type={graphSettingTypes} store={graphSettings} />
|
id="graph-settings"
|
||||||
{/if}
|
type={graphSettingTypes}
|
||||||
|
bind:value={graphSettings}
|
||||||
|
/>
|
||||||
</Panel>
|
</Panel>
|
||||||
<Panel
|
<Panel
|
||||||
id="active-node"
|
id="active-node"
|
||||||
@ -225,7 +234,7 @@
|
|||||||
>
|
>
|
||||||
<ActiveNodeSettings {manager} node={activeNode} />
|
<ActiveNodeSettings {manager} node={activeNode} />
|
||||||
</Panel>
|
</Panel>
|
||||||
</Settings>
|
</Sidebar>
|
||||||
{/key}
|
{/key}
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
|
@ -17,15 +17,15 @@
|
|||||||
"type": "float",
|
"type": "float",
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
|
"max": 1,
|
||||||
"value": 0.5,
|
"value": 0.5,
|
||||||
"max": 1
|
|
||||||
},
|
},
|
||||||
"depth": {
|
"depth": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"min": 1,
|
"min": 1,
|
||||||
"max": 10,
|
"max": 10,
|
||||||
|
"hidden": true,
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"hidden": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,5 @@
|
|||||||
"build:deploy": "pnpm build",
|
"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@9.11.0+sha512.0a203ffaed5a3f63242cd064c8fb5892366c103e328079318f78062f24ea8c9d50bc6a47aa3567cabefd824d170e78fa2745ed1f16b132e16436146b7688f19b",
|
"packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c"
|
||||||
"dependencies": {
|
|
||||||
"@types/pg": "^8.11.10",
|
|
||||||
"drizzle-kit": "^0.30.1",
|
|
||||||
"drizzle-orm": "^0.38.2",
|
|
||||||
"pg": "^8.13.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-left: 12px;
|
/* padding-left: 12px; */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
3355
pnpm-lock.yaml
generated
3355
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user