chore: move some more components to svelte 5
Some checks failed
Deploy to GitHub Pages / build_site (push) Has been cancelled

This commit is contained in:
Max Richter
2025-11-23 19:35:33 +01:00
parent 716df245ab
commit 6ca1ff2a34
25 changed files with 470 additions and 550 deletions

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import type { GraphManager } from "./graph-manager.js";
import type { GraphManager } from "./graph-manager.svelte";
import { HTML } from "@threlte/extras";
import { onMount } from "svelte";
import type { NodeType } from "@nodes/types";
@@ -10,7 +10,7 @@
let input: HTMLInputElement;
let value: string = "";
let activeNodeId: NodeType = "";
let activeNodeId: NodeType | undefined = undefined;
const allNodes = graph.getNodeDefinitions();
@@ -20,7 +20,7 @@
$: nodes = value === "" ? allNodes : filterNodes();
$: if (nodes) {
if (activeNodeId === "") {
if (activeNodeId === undefined) {
activeNodeId = nodes[0].id;
} else if (nodes.length) {
const node = nodes.find((node) => node.id === activeNodeId);

View File

@@ -3,7 +3,6 @@
import BackgroundVert from "./Background.vert";
import BackgroundFrag from "./Background.frag";
import { colors } from "../graph/colors.svelte";
import { Color } from "three";
import { appSettings } from "$lib/settings/app-settings.svelte";
type Props = {
@@ -42,10 +41,10 @@
value: [0, 1, 0],
},
backgroundColor: {
value: colors["layer-0"].clone(),
value: colors["layer-0"],
},
lineColor: {
value: colors["outline"].clone(),
value: colors["outline"],
},
zoomLimits: {
value: [2, 50],
@@ -55,9 +54,8 @@
},
}}
uniforms.camPos.value={cameraPosition}
uniforms.backgroundColor.value={appSettings.theme &&
colors["layer-0"].clone()}
uniforms.lineColor.value={appSettings.theme && colors["outline"].clone()}
uniforms.backgroundColor.value={appSettings.theme && colors["layer-0"]}
uniforms.lineColor.value={appSettings.theme && colors["outline"]}
uniforms.zoomLimits.value={[minZoom, maxZoom]}
uniforms.dimensions.value={[width, height]}
/>

View File

@@ -4,17 +4,17 @@ import type {
Node,
NodeInput,
NodeRegistry,
NodeType,
Socket,
} from "@nodes/types";
import { fastHashString } from "@nodes/utils";
import { writable, type Writable } from "svelte/store";
import EventEmitter from "./helpers/EventEmitter.js";
import { createLogger } from "./helpers/index.js";
import throttle from "./helpers/throttle.js";
import { HistoryManager } from "./history-manager.js";
import { SvelteMap } from "svelte/reactivity";
import EventEmitter from "./helpers/EventEmitter";
import { createLogger } from "./helpers/index";
import throttle from "./helpers/throttle";
import { HistoryManager } from "./history-manager";
const logger = createLogger("graph-manager");
logger.mute();
const clone =
@@ -40,24 +40,28 @@ export class GraphManager extends EventEmitter<{
values: Record<string, unknown>;
};
}> {
status: Writable<"loading" | "idle" | "error"> = writable("loading");
status = $state<"loading" | "idle" | "error">();
loaded = false;
graph: Graph = { id: 0, nodes: [], edges: [] };
id = writable(0);
id = $state(0);
private _nodes: Map<number, Node> = new Map();
nodes: Writable<Map<number, Node>> = writable(new Map());
nodes = new SvelteMap<number, Node>();
private _edges: Edge[] = [];
edges: Writable<Edge[]> = writable([]);
edges = $state<Edge[]>([]);
settingTypes: Record<string, NodeInput> = {};
settings: Record<string, unknown> = {};
settings = $state<Record<string, unknown>>();
currentUndoGroup: number | null = null;
inputSockets: Writable<Set<string>> = writable(new Set());
inputSockets = $derived.by(() => {
const s = new Set<string>();
for (const edge of this.edges) {
s.add(`${edge[2].id}-${edge[3]}`);
}
return s;
});
history: HistoryManager = new HistoryManager();
execute = throttle(() => {
@@ -67,28 +71,17 @@ export class GraphManager extends EventEmitter<{
constructor(public registry: NodeRegistry) {
super();
this.nodes.subscribe((nodes) => {
this._nodes = nodes;
});
this.edges.subscribe((edges) => {
this._edges = edges;
const s = new Set<string>();
for (const edge of edges) {
s.add(`${edge[2].id}-${edge[3]}`);
}
this.inputSockets.set(s);
});
}
serialize(): Graph {
logger.group("serializing graph");
const nodes = Array.from(this._nodes.values()).map((node) => ({
const nodes = Array.from(this.nodes.values()).map((node) => ({
id: node.id,
position: [...node.position],
type: node.type,
props: node.props,
})) as Node[];
const edges = this._edges.map((edge) => [
const edges = this.edges.map((edge) => [
edge[0].id,
edge[1],
edge[2].id,
@@ -101,12 +94,12 @@ export class GraphManager extends EventEmitter<{
edges,
};
logger.groupEnd();
return clone(serialized);
return clone($state.snapshot(serialized));
}
private lastSettingsHash = 0;
setSettings(settings: Record<string, unknown>) {
console.log("GraphManager.setSettings", settings);
let hash = fastHashString(JSON.stringify(settings));
if (hash === this.lastSettingsHash) return;
this.lastSettingsHash = hash;
@@ -141,7 +134,7 @@ export class GraphManager extends EventEmitter<{
const children = node.tmp?.children || [];
for (const child of children) {
if (nodes.includes(child)) {
const edge = this._edges.find(
const edge = this.edges.find(
(e) => e[0].id === node.id && e[2].id === child.id,
);
if (edge) {
@@ -188,8 +181,12 @@ export class GraphManager extends EventEmitter<{
return [from, edge[1], to, edge[3]] as Edge;
});
this.edges.set(edges);
this.nodes.set(nodes);
this.edges = [...edges];
this.nodes.clear();
for (const [id, node] of nodes) {
this.nodes.set(id, node);
}
this.execute();
}
@@ -199,8 +196,8 @@ export class GraphManager extends EventEmitter<{
this.loaded = false;
this.graph = graph;
this.status.set("loading");
this.id.set(graph.id);
this.status = "loading";
this.id = graph.id;
const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)]));
await this.registry.load(nodeIds);
@@ -209,7 +206,7 @@ export class GraphManager extends EventEmitter<{
const nodeType = this.registry.getNode(node.type);
if (!nodeType) {
logger.error(`Node type not found: ${node.type}`);
this.status.set("error");
this.status = "error";
return;
}
node.tmp = node.tmp || {};
@@ -254,7 +251,7 @@ export class GraphManager extends EventEmitter<{
this.save();
this.status.set("idle");
this.status = "idle";
this.loaded = true;
logger.log(`Graph loaded in ${performance.now() - a}ms`);
@@ -262,18 +259,18 @@ export class GraphManager extends EventEmitter<{
}
getAllNodes() {
return Array.from(this._nodes.values());
return Array.from(this.nodes.values());
}
getNode(id: number) {
return this._nodes.get(id);
return this.nodes.get(id);
}
getNodeType(id: string) {
return this.registry.getNode(id);
}
async loadNode(id: string) {
async loadNode(id: NodeType) {
await this.registry.load([id]);
const nodeType = this.registry.getNode(id);
@@ -287,7 +284,8 @@ export class GraphManager extends EventEmitter<{
if (settingId) {
settingTypes[settingId] = nodeType.inputs[key];
if (
settingValues[settingId] === undefined &&
settingValues &&
settingValues?.[settingId] === undefined &&
"value" in nodeType.inputs[key]
) {
settingValues[settingId] = nodeType.inputs[key].value;
@@ -297,6 +295,7 @@ export class GraphManager extends EventEmitter<{
}
this.settings = settingValues;
console.log("GraphManager.setSettings", settingValues);
this.settingTypes = settingTypes;
this.emit("settings", { types: settingTypes, values: settingValues });
}
@@ -331,8 +330,8 @@ export class GraphManager extends EventEmitter<{
}
removeNode(node: Node, { restoreEdges = false } = {}) {
const edgesToNode = this._edges.filter((edge) => edge[2].id === node.id);
const edgesFromNode = this._edges.filter((edge) => edge[0].id === node.id);
const edgesToNode = this.edges.filter((edge) => edge[2].id === node.id);
const edgesFromNode = this.edges.filter((edge) => edge[0].id === node.id);
for (const edge of [...edgesToNode, ...edgesFromNode]) {
this.removeEdge(edge, { applyDeletion: false });
}
@@ -355,18 +354,13 @@ export class GraphManager extends EventEmitter<{
}
}
this.edges.set(this._edges);
this.nodes.update((nodes) => {
nodes.delete(node.id);
return nodes;
});
this.nodes.delete(node.id);
this.execute();
this.save();
}
createNodeId() {
const max = Math.max(...this._nodes.keys());
const max = Math.max(0, ...this.nodes.keys());
return max + 1;
}
@@ -406,13 +400,11 @@ export class GraphManager extends EventEmitter<{
});
for (const node of nodes) {
this._nodes.set(node.id, node);
this.nodes.set(node.id, node);
}
this._edges.push(..._edges);
this.edges.push(..._edges);
this.nodes.set(this._nodes);
this.edges.set(this._edges);
this.save();
return nodes;
}
@@ -440,10 +432,7 @@ export class GraphManager extends EventEmitter<{
props,
};
this.nodes.update((nodes) => {
nodes.set(node.id, node);
return nodes;
});
this.nodes.set(node.id, node);
this.save();
}
@@ -480,7 +469,7 @@ export class GraphManager extends EventEmitter<{
return;
}
const edgeToBeReplaced = this._edges.find(
const edgeToBeReplaced = this.edges.find(
(e) => e[2].id === to.id && e[3] === toSocket,
);
if (edgeToBeReplaced) {
@@ -488,9 +477,9 @@ export class GraphManager extends EventEmitter<{
}
if (applyUpdate) {
this._edges.push([from, fromSocket, to, toSocket]);
this.edges.push([from, fromSocket, to, toSocket]);
} else {
this._edges.push([from, fromSocket, to, toSocket]);
this.edges.push([from, fromSocket, to, toSocket]);
}
from.tmp = from.tmp || {};
@@ -502,7 +491,6 @@ export class GraphManager extends EventEmitter<{
to.tmp.parents.push(from);
if (applyUpdate) {
this.edges.set(this._edges);
this.save();
}
this.execute();
@@ -630,7 +618,7 @@ export class GraphManager extends EventEmitter<{
const id2 = edge[2].id;
const sid2 = edge[3];
const _edge = this._edges.find(
const _edge = this.edges.find(
(e) =>
e[0].id === id0 && e[1] === sid0 && e[2].id === id2 && e[3] === sid2,
);
@@ -651,18 +639,16 @@ export class GraphManager extends EventEmitter<{
}
if (applyDeletion) {
this.edges.update((edges) => {
return edges.filter((e) => e !== _edge);
});
this.edges = this.edges.filter((e) => e !== _edge);
this.execute();
this.save();
} else {
this._edges = this._edges.filter((e) => e !== _edge);
this.edges = this.edges.filter((e) => e !== _edge);
}
}
getEdgesToNode(node: Node) {
return this._edges
return this.edges
.filter((edge) => edge[2].id === node.id)
.map((edge) => {
const from = this.getNode(edge[0].id);
@@ -674,7 +660,7 @@ export class GraphManager extends EventEmitter<{
}
getEdgesFromNode(node: Node) {
return this._edges
return this.edges
.filter((edge) => edge[0].id === node.id)
.map((edge) => {
const from = this.getNode(edge[0].id);

View File

@@ -1,73 +1,67 @@
<script lang="ts">
import type { Node, NodeType, Socket } from "@nodes/types";
import { GraphSchema } from "@nodes/types";
import { getContext, onMount, setContext } from "svelte";
import type { OrthographicCamera } from "three";
import { createKeyMap } from "../../helpers/createKeyMap";
import AddMenu from "../AddMenu.svelte";
import Background from "../background/Background.svelte";
import BoxSelection from "../BoxSelection.svelte";
import Camera from "../Camera.svelte";
import FloatingEdge from "../edges/FloatingEdge.svelte";
import {
animate,
lerp,
snapToGrid as snapPointToGrid,
} from "../helpers/index.js";
import type { OrthographicCamera } from "three";
import Background from "../background/Background.svelte";
import { getContext, onMount, setContext } from "svelte";
import Camera from "../Camera.svelte";
import GraphView from "./GraphView.svelte";
import type { Node, NodeId, Node as NodeType, Socket } from "@nodes/types";
import { GraphSchema } from "@nodes/types";
import FloatingEdge from "../edges/FloatingEdge.svelte";
import { getGraphState } from "./state.svelte";
import { createKeyMap } from "../../helpers/createKeyMap";
import BoxSelection from "../BoxSelection.svelte";
import AddMenu from "../AddMenu.svelte";
import HelpView from "../HelpView.svelte";
import FileSaver from "file-saver";
import { Canvas } from "@threlte/core";
import { getGraphManager } from "./context.js";
import FileSaver from "file-saver";
import HelpView from "../HelpView.svelte";
import { getGraphManager } from "./context";
const graph = getGraphManager();
const graphState = getGraphState();
export let snapToGrid = true;
export let showGrid = true;
export let showHelp = false;
const {
snapToGrid = $bindable(true),
showGrid = $bindable(true),
showHelp = $bindable(false),
} = $props();
const keymap = getContext<ReturnType<typeof createKeyMap>>("keymap");
const manager = getGraphManager();
let wrapper = $state<HTMLDivElement>(null!);
const status = manager.status;
const nodes = manager.nodes;
const edges = manager.edges;
const rect: DOMRect = $derived(
wrapper ? wrapper.getBoundingClientRect() : new DOMRect(0, 0, 0, 0),
);
let width = $derived(rect?.width ?? 100);
let height = $derived(rect?.height ?? 100);
let wrapper: HTMLDivElement;
let rect: DOMRect;
$: rect =
wrapper && width
? wrapper.getBoundingClientRect()
: ({ x: 0, y: 0, width: 0, height: 0 } as DOMRect);
let camera: OrthographicCamera;
let camera = $state<OrthographicCamera>(null!);
const minZoom = 1;
const maxZoom = 40;
let mousePosition = [0, 0];
let mouseDown: null | [number, number] = null;
let mousePosition = $state([0, 0]);
let mouseDown = $state<[number, number] | null>(null);
let mouseDownId = -1;
let boxSelection = false;
let boxSelection = $state(false);
const cameraDown = [0, 0];
let cameraPosition: [number, number, number] = [0, 0, 4];
let addMenuPosition: [number, number] | null = null;
let cameraPosition: [number, number, number] = $state([0, 0, 4]);
let addMenuPosition = $state<[number, number] | null>(null);
let clipboard: null | {
nodes: Node[];
edges: [number, number, number, string][];
} = null;
let width = rect?.width ?? 100;
let height = rect?.height ?? 100;
let cameraBounds = [-1000, 1000, -1000, 1000];
$: cameraBounds = [
const cameraBounds = $derived([
cameraPosition[0] - width / cameraPosition[2] / 2,
cameraPosition[0] + width / cameraPosition[2] / 2,
cameraPosition[1] - height / cameraPosition[2] / 2,
cameraPosition[1] + height / cameraPosition[2] / 2,
];
]);
function setCameraTransform(
x = cameraPosition[0],
y = cameraPosition[1],
@@ -82,7 +76,7 @@
localStorage.setItem("cameraPosition", JSON.stringify(cameraPosition));
}
function updateNodePosition(node: NodeType) {
function updateNodePosition(node: Node) {
if (node?.tmp?.ref && node?.tmp?.mesh) {
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
@@ -96,6 +90,7 @@
delete node.tmp.x;
delete node.tmp.y;
}
graph.edges = [...graph.edges];
} else {
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
@@ -112,7 +107,7 @@
if (nodeTypeId in nodeHeightCache) {
return nodeHeightCache[nodeTypeId];
}
const node = manager.getNodeType(nodeTypeId);
const node = graph.getNodeType(nodeTypeId);
if (!node?.inputs) {
return 5;
}
@@ -131,7 +126,7 @@
}
setContext("getNodeHeight", getNodeHeight);
setContext("isNodeInView", (node: NodeType) => {
setContext("isNodeInView", (node: Node) => {
const height = getNodeHeight(node.type);
const width = 20;
return (
@@ -162,7 +157,7 @@
// we are going to check if we clicked on a node by coordinates
if (clickedNodeId === -1) {
const [downX, downY] = projectScreenToWorld(mx, my);
for (const node of $nodes.values()) {
for (const node of graph.nodes.values()) {
const x = node.position[0];
const y = node.position[1];
const height = getNodeHeight(node.type);
@@ -183,13 +178,13 @@
// remove existing edge
if (typeof index === "string") {
const edges = manager.getEdgesToNode(node);
const edges = graph.getEdgesToNode(node);
for (const edge of edges) {
if (edge[3] === index) {
node = edge[0];
index = edge[1];
position = getSocketPosition(node, index);
manager.removeEdge(edge);
graph.removeEdge(edge);
break;
}
}
@@ -202,7 +197,7 @@
position,
};
graphState.possibleSockets = manager
graphState.possibleSockets = graph
.getPossibleSockets(graphState.activeSocket)
.map(([node, index]) => {
return {
@@ -227,7 +222,7 @@
}
function getSocketPosition(
node: NodeType,
node: Node,
index: string | number,
): [number, number] {
if (typeof index === "number") {
@@ -294,7 +289,7 @@
const x2 = Math.max(mouseD[0], mousePosition[0]);
const y1 = Math.min(mouseD[1], mousePosition[1]);
const y2 = Math.max(mouseD[1], mousePosition[1]);
for (const node of $nodes.values()) {
for (const node of graph.nodes.values()) {
if (!node?.tmp) continue;
const x = node.position[0];
const y = node.position[1];
@@ -310,7 +305,7 @@
// here we are handling dragging of nodes
if (graphState.activeNodeId !== -1 && mouseDownId !== -1) {
const node = manager.getNode(graphState.activeNodeId);
const node = graph.getNode(graphState.activeNodeId);
if (!node || event.buttons !== 1) return;
node.tmp = node.tmp || {};
@@ -341,7 +336,7 @@
if (graphState.selectedNodes?.size) {
for (const nodeId of graphState.selectedNodes) {
const n = manager.getNode(nodeId);
const n = graph.getNode(nodeId);
if (!n?.tmp) continue;
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
@@ -354,7 +349,6 @@
updateNodePosition(node);
$edges = $edges;
return;
}
@@ -436,10 +430,10 @@
graphState.activeNodeId = clickedNodeId;
// select the node
} else if (event.shiftKey) {
const activeNode = manager.getNode(graphState.activeNodeId);
const newNode = manager.getNode(clickedNodeId);
const activeNode = graph.getNode(graphState.activeNodeId);
const newNode = graph.getNode(clickedNodeId);
if (activeNode && newNode) {
const edge = manager.getNodesBetween(activeNode, newNode);
const edge = graph.getNodesBetween(activeNode, newNode);
if (edge) {
graphState.selectedNodes.clear();
for (const node of edge) {
@@ -456,7 +450,7 @@
boxSelection = true;
}
const node = manager.getNode(graphState.activeNodeId);
const node = graph.getNode(graphState.activeNodeId);
if (!node) return;
node.tmp = node.tmp || {};
node.tmp.downX = node.position[0];
@@ -464,7 +458,7 @@
if (graphState.selectedNodes) {
for (const nodeId of graphState.selectedNodes) {
const n = manager.getNode(nodeId);
const n = graph.getNode(nodeId);
if (!n) continue;
n.tmp = n.tmp || {};
n.tmp.downX = n.position[0];
@@ -480,10 +474,10 @@
graphState.activeNodeId,
...(graphState.selectedNodes?.values() || []),
]
.map((id) => manager.getNode(id))
.map((id) => graph.getNode(id))
.filter(Boolean) as Node[];
const _edges = manager.getEdgesBetweenNodes(_nodes);
const _edges = graph.getEdgesBetweenNodes(_nodes);
_nodes = _nodes.map((_node) => {
const node = globalThis.structuredClone({
@@ -514,7 +508,7 @@
})
.filter(Boolean) as Node[];
const newNodes = manager.createGraph(_nodes, clipboard.edges);
const newNodes = graph.createGraph(_nodes, clipboard.edges);
graphState.selectedNodes.clear();
for (const node of newNodes) {
graphState.selectedNodes.add(node.id);
@@ -527,9 +521,9 @@
key: "l",
description: "Select linked nodes",
callback: () => {
const activeNode = manager.getNode(graphState.activeNodeId);
const activeNode = graph.getNode(graphState.activeNodeId);
if (activeNode) {
const nodes = manager.getLinkedNodes(activeNode);
const nodes = graph.getLinkedNodes(activeNode);
graphState.selectedNodes.clear();
for (const node of nodes) {
graphState.selectedNodes.add(node.id);
@@ -542,7 +536,8 @@
key: "?",
description: "Toggle Help",
callback: () => {
showHelp = !showHelp;
// TODO: fix this
// showHelp = !showHelp;
},
});
@@ -586,12 +581,12 @@
if (!isBodyFocused()) return;
const average = [0, 0];
for (const node of $nodes.values()) {
for (const node of graph.nodes.values()) {
average[0] += node.position[0];
average[1] += node.position[1];
}
average[0] = average[0] ? average[0] / $nodes.size : 0;
average[1] = average[1] ? average[1] / $nodes.size : 0;
average[0] = average[0] ? average[0] / graph.nodes.size : 0;
average[1] = average[1] ? average[1] / graph.nodes.size : 0;
const camX = cameraPosition[0];
const camY = cameraPosition[1];
@@ -617,7 +612,7 @@
description: "Select all nodes",
callback: () => {
if (!isBodyFocused()) return;
for (const node of $nodes.keys()) {
for (const node of graph.nodes.keys()) {
graphState.selectedNodes.add(node);
}
},
@@ -629,8 +624,8 @@
description: "Undo",
callback: () => {
if (!isBodyFocused()) return;
manager.undo();
for (const node of $nodes.values()) {
graph.undo();
for (const node of graph.nodes.values()) {
updateNodePosition(node);
}
},
@@ -641,8 +636,8 @@
ctrl: true,
description: "Redo",
callback: () => {
manager.redo();
for (const node of $nodes.values()) {
graph.redo();
for (const node of graph.nodes.values()) {
updateNodePosition(node);
}
},
@@ -654,7 +649,7 @@
description: "Save",
preventDefault: true,
callback: () => {
const state = manager.serialize();
const state = graph.serialize();
const blob = new Blob([JSON.stringify(state)], {
type: "application/json;charset=utf-8",
});
@@ -667,24 +662,24 @@
description: "Delete selected nodes",
callback: (event) => {
if (!isBodyFocused()) return;
manager.startUndoGroup();
graph.startUndoGroup();
if (graphState.activeNodeId !== -1) {
const node = manager.getNode(graphState.activeNodeId);
const node = graph.getNode(graphState.activeNodeId);
if (node) {
manager.removeNode(node, { restoreEdges: event.ctrlKey });
graph.removeNode(node, { restoreEdges: event.ctrlKey });
graphState.activeNodeId = -1;
}
}
if (graphState.selectedNodes) {
for (const nodeId of graphState.selectedNodes) {
const node = manager.getNode(nodeId);
const node = graph.getNode(nodeId);
if (node) {
manager.removeNode(node, { restoreEdges: event.ctrlKey });
graph.removeNode(node, { restoreEdges: event.ctrlKey });
}
}
graphState.clearSelection();
}
manager.saveUndoGroup();
graph.saveUndoGroup();
},
});
@@ -692,7 +687,7 @@
isPanning = false;
if (!mouseDown) return;
const activeNode = manager.getNode(graphState.activeNodeId);
const activeNode = graph.getNode(graphState.activeNodeId);
const clickedNodeId = getNodeIdFromEvent(event);
@@ -724,9 +719,9 @@
}
const nodes = [
...[...(graphState.selectedNodes?.values() || [])].map((id) =>
manager.getNode(id),
graph.getNode(id),
),
] as NodeType[];
] as Node[];
const vec = [
activeNode.position[0] - (activeNode?.tmp.x || 0),
@@ -758,16 +753,14 @@
}
}
}
$edges = $edges;
});
manager.save();
graph.save();
} else if (graphState.hoveredSocket && graphState.activeSocket) {
if (
typeof graphState.hoveredSocket.index === "number" &&
typeof graphState.activeSocket.index === "string"
) {
manager.createEdge(
graph.createEdge(
graphState.hoveredSocket.node,
graphState.hoveredSocket.index || 0,
graphState.activeSocket.node,
@@ -777,14 +770,14 @@
typeof graphState.activeSocket.index == "number" &&
typeof graphState.hoveredSocket.index === "string"
) {
manager.createEdge(
graph.createEdge(
graphState.activeSocket.node,
graphState.activeSocket.index || 0,
graphState.hoveredSocket.node,
graphState.hoveredSocket.index,
);
}
manager.save();
graph.save();
}
// check if camera moved
@@ -807,9 +800,9 @@
addMenuPosition = null;
}
let isPanning = false;
let isDragging = false;
let hoveredNodeId = -1;
let isPanning = $state(false);
let isDragging = $state(false);
let hoveredNodeId = $state(-1);
function handleMouseLeave() {
isDragging = false;
@@ -820,7 +813,7 @@
event.preventDefault();
isDragging = false;
if (!event.dataTransfer) return;
const nodeId = event.dataTransfer.getData("data/node-id") as NodeId;
const nodeId = event.dataTransfer.getData("data/node-id") as NodeType;
let mx = event.clientX - rect.x;
let my = event.clientY - rect.y;
@@ -841,8 +834,8 @@
}
const pos = projectScreenToWorld(mx, my);
manager.registry.load([nodeId]).then(() => {
manager.createNode({
graph.registry.load([nodeId]).then(() => {
graph.createNode({
type: nodeId,
props,
position: pos,
@@ -856,9 +849,9 @@
reader.onload = async (e) => {
const buffer = e.target?.result;
if (buffer?.constructor === ArrayBuffer) {
const nodeType = await manager.registry.register(buffer);
const nodeType = await graph.registry.register(buffer);
manager.createNode({
graph.createNode({
type: nodeType.id,
props: {},
position: projectScreenToWorld(mx, my),
@@ -869,10 +862,10 @@
} else if (file.type === "application/json") {
const reader = new FileReader();
reader.onload = (e) => {
const buffer = e.target?.result as Buffer;
const buffer = e.target?.result as ArrayBuffer;
if (buffer) {
const state = GraphSchema.parse(JSON.parse(buffer.toString()));
manager.load(state);
graph.load(state);
}
};
reader.readAsText(file);
@@ -908,10 +901,10 @@
});
</script>
<svelte:window on:mousemove={handleMouseMove} on:mouseup={handleMouseUp} />
<svelte:window onmousemove={handleMouseMove} onmouseup={handleMouseUp} />
<div
on:wheel={handleMouseScroll}
onwheel={handleMouseScroll}
bind:this={wrapper}
class="graph-wrapper"
class:is-panning={isPanning}
@@ -921,21 +914,21 @@
tabindex="0"
bind:clientWidth={width}
bind:clientHeight={height}
on:dragenter={handleDragEnter}
on:dragover={handlerDragOver}
on:dragexit={handleDragEnd}
on:drop={handleDrop}
on:mouseleave={handleMouseLeave}
on:keydown={keymap.handleKeyboardEvent}
on:mousedown={handleMouseDown}
ondragenter={handleDragEnter}
ondragover={handlerDragOver}
ondragexit={handleDragEnd}
ondrop={handleDrop}
onmouseleave={handleMouseLeave}
onkeydown={keymap.handleKeyboardEvent}
onmousedown={handleMouseDown}
>
<input
type="file"
accept="application/wasm,application/json"
id="drop-zone"
disabled={!isDragging}
on:dragend={handleDragEnd}
on:dragleave={handleDragEnd}
ondragend={handleDragEnd}
ondragleave={handleDragEnd}
/>
<label for="drop-zone"></label>
@@ -958,9 +951,9 @@
/>
{/if}
{#if $status === "idle"}
{#if graph.status === "idle"}
{#if addMenuPosition}
<AddMenu bind:position={addMenuPosition} graph={manager} />
<AddMenu bind:position={addMenuPosition} {graph} />
{/if}
{#if graphState.activeSocket}
@@ -974,17 +967,17 @@
/>
{/if}
<GraphView {nodes} {edges} {cameraPosition} />
{:else if $status === "loading"}
<GraphView nodes={graph.nodes} edges={graph.edges} {cameraPosition} />
{:else if graph.status === "loading"}
<span>Loading</span>
{:else if $status === "error"}
{:else if graph.status === "error"}
<span>Error</span>
{/if}
</Canvas>
</div>
{#if showHelp}
<HelpView registry={manager.registry} />
<HelpView registry={graph.registry} />
{/if}
<style>

View File

@@ -4,14 +4,13 @@
import Edge from "../edges/Edge.svelte";
import Node from "../node/Node.svelte";
import { getContext, onMount } from "svelte";
import type { Writable } from "svelte/store";
import { getGraphState } from "./state.svelte";
import { useThrelte } from "@threlte/core";
import { appSettings } from "$lib/settings/app-settings.svelte";
type Props = {
nodes: Writable<Map<number, NodeType>>;
edges: Writable<EdgeType[]>;
nodes: Map<number, NodeType>;
edges: EdgeType[];
cameraPosition: [number, number, number];
};
@@ -24,6 +23,8 @@
invalidate();
});
$effect(() => console.log({ nodes }));
const graphState = getGraphState();
const isNodeInView = getContext<(n: NodeType) => boolean>("isNodeInView");
@@ -33,14 +34,26 @@
"getSocketPosition",
);
function getEdgePosition(edge: EdgeType) {
const pos1 = getSocketPosition(edge[0], edge[1]);
const pos2 = getSocketPosition(edge[2], edge[3]);
return [pos1[0], pos1[1], pos2[0], pos2[1]];
const edgePositions = $derived(
edges.map((edge) => {
const fromNode = nodes.get(edge[0].id);
const toNode = nodes.get(edge[2].id);
// This check is important because nodes might not be there during some transitions.
if (!fromNode || !toNode) {
return [0, 0, 0, 0];
}
const pos1 = getSocketPosition(fromNode, edge[1]);
const pos2 = getSocketPosition(toNode, edge[3]);
return [pos1[0], pos1[1], pos2[0], pos2[1]];
}),
);
const nodeArray = $derived(Array.from(nodes.values()));
onMount(() => {
for (const node of $nodes.values()) {
for (const node of nodes.values()) {
if (node?.tmp?.ref) {
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
@@ -49,9 +62,8 @@
});
</script>
{#each $edges as edge (`${edge[0].id}-${edge[1]}-${edge[2].id}-${edge[3]}`)}
{@const pos = getEdgePosition(edge)}
{@const [x1, y1, x2, y2] = pos}
{#each edgePositions as edge (`${edge.join("-")}`)}
{@const [x1, y1, x2, y2] = edge}
<Edge
z={cameraPosition[2]}
from={{
@@ -74,9 +86,9 @@
style:transform={`scale(${cameraPosition[2] * 0.1})`}
class:hovering-sockets={graphState.activeSocket}
>
{#each $nodes.values() as node (node.id)}
{#each nodeArray as node, i (node.id)}
<Node
{node}
bind:node={nodeArray[i]}
inView={cameraPosition && isNodeInView(node)}
z={cameraPosition[2]}
/>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type { Graph, Node, NodeRegistry } from "@nodes/types";
import GraphEl from "./Graph.svelte";
import { GraphManager } from "../graph-manager.js";
import { GraphManager } from "../graph-manager.svelte";
import { setContext } from "svelte";
import { debounce } from "$lib/helpers";
import { createKeyMap } from "$lib/helpers/createKeyMap";
@@ -53,13 +53,13 @@
}
});
const updateSettings = debounce((s) => {
const updateSettings = debounce((s: Record<string, any>) => {
manager.setSettings(s);
}, 200);
$effect(() => {
if (settingTypes && settings) {
updateSettings($state.snapshot(settings));
updateSettings(settings);
}
});

View File

@@ -1,4 +1,4 @@
import type { GraphManager } from "../graph-manager.js";
import type { GraphManager } from "../graph-manager.svelte";
import { getContext } from "svelte";
export function getGraphManager(): GraphManager {

View File

@@ -1,6 +1,6 @@
import type { Socket } from "@nodes/types";
import { getContext } from "svelte";
import { SvelteSet } from 'svelte/reactivity';
import { SvelteSet } from "svelte/reactivity";
export function getGraphState() {
return getContext<GraphState>("graphState");
@@ -12,11 +12,10 @@ export class GraphState {
activeSocket = $state<Socket | null>(null);
hoveredSocket = $state<Socket | null>(null);
possibleSockets = $state<Socket[]>([]);
possibleSocketIds = $derived(new Set(
this.possibleSockets.map((s) => `${s.node.id}-${s.index}`),
));
possibleSocketIds = $derived(
new Set(this.possibleSockets.map((s) => `${s.node.id}-${s.index}`)),
);
clearSelection() {
this.selectedNodes.clear();
}
}

View File

@@ -17,7 +17,7 @@
inView: boolean;
z: number;
};
const { node, inView, z }: Props = $props();
let { node = $bindable(), inView, z }: Props = $props();
const isActive = $derived(graphState.activeNodeId === node.id);
const isSelected = $derived(graphState.selectedNodes.has(node.id));
@@ -41,13 +41,11 @@
const height = getNodeHeight?.(node.type);
$effect(() => {
node.tmp = node.tmp || {};
node.tmp.mesh = meshRef;
updateNodePosition?.(node);
});
onMount(() => {
node.tmp = node.tmp || {};
if (!node.tmp) node.tmp = {};
node.tmp.mesh = meshRef;
updateNodePosition?.(node);
});
@@ -79,4 +77,4 @@
/>
</T.Mesh>
<NodeHtml {node} {inView} {isActive} {isSelected} {z} />
<NodeHtml bind:node {inView} {isActive} {isSelected} {z} />

View File

@@ -59,7 +59,7 @@
{#each parameters as [key, value], i}
<NodeParameter
bind:node
{node}
id={key}
input={value}
isLast={i == parameters.length - 1}

View File

@@ -3,7 +3,7 @@
import type { Node, Socket } from "@nodes/types";
import { getContext } from "svelte";
export let node: Node;
const { node = $bindable<Node>() } = $props();
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
const getSocketPosition =
@@ -33,14 +33,14 @@
rightBump,
aspectRatio,
});
const pathDisabled = createNodePath({
depth: 0,
height: 15,
y: 50,
cornerTop,
rightBump,
aspectRatio,
});
// const pathDisabled = createNodePath({
// depth: 0,
// height: 15,
// y: 50,
// cornerTop,
// rightBump,
// aspectRatio,
// });
const pathHover = createNodePath({
depth: 8.5,
height: 50,
@@ -59,7 +59,7 @@
class="click-target"
role="button"
tabindex="0"
on:mousedown={handleMouseDown}
onmousedown={handleMouseDown}
></div>
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@@ -17,7 +17,7 @@
isLast?: boolean;
};
const { node = $bindable(), input, id, isLast }: Props = $props();
const { node, input, id, isLast }: Props = $props();
const inputType = node?.tmp?.type?.inputs?.[id]!;
@@ -26,7 +26,6 @@
const graph = getGraphManager();
const graphState = getGraphState();
const graphId = graph?.id;
const inputSockets = graph?.inputSockets;
const elementId = `input-${Math.random().toString(36).substring(7)}`;
@@ -83,7 +82,7 @@
class:disabled={!graphState?.possibleSocketIds.has(socketId)}
>
{#key id && graphId}
<div class="content" class:disabled={$inputSockets?.has(socketId)}>
<div class="content" class:disabled={graph.inputSockets?.has(socketId)}>
{#if inputType.label !== ""}
<label for={elementId}>{input.label || id}</label>
{/if}

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import { getContext } from "svelte";
import type { Writable } from "svelte/store";
let index = -1;
let wrapper: HTMLDivElement;
@@ -9,7 +8,7 @@
index = getContext<() => number>("registerCell")();
}
const sizes = getContext<Writable<string[]>>("sizes");
const sizes = getContext<string[]>("sizes");
let downSizes: string[] = [];
let downWidth = 0;
@@ -17,7 +16,7 @@
let startX = 0;
function handleMouseDown(event: MouseEvent) {
downSizes = [...$sizes];
downSizes = [...sizes];
mouseDown = true;
startX = event.clientX;
downWidth = wrapper.getBoundingClientRect().width;
@@ -26,8 +25,7 @@
function handleMouseMove(event: MouseEvent) {
if (mouseDown) {
const width = downWidth + startX - event.clientX;
$sizes[index] = `${width}px`;
$sizes = $sizes;
sizes[index] = `${width}px`;
}
}
</script>

View File

@@ -1,27 +1,33 @@
<script lang="ts">
import { setContext, getContext } from "svelte";
import localStore from "$lib/helpers/localStore";
import { localState } from "$lib/helpers/localState.svelte";
const gridId = getContext<string>("grid-id") || "grid-0";
let sizes = localStore<string[]>(gridId, []);
let sizes = localState<string[]>(gridId, []);
const { children } = $props();
console.log("RowChildren", children);
let registerIndex = 0;
setContext("registerCell", function () {
let index = registerIndex;
registerIndex++;
if (registerIndex > $sizes.length) {
$sizes = [...$sizes, "1fr"];
if (registerIndex > sizes.length) {
sizes = [...sizes, "1fr"];
}
return index;
});
setContext("sizes", sizes);
$: cols = $sizes.map((size, i) => `${i > 0 ? "1px " : ""}` + size).join(" ");
const cols = $derived(
sizes.map((size, i) => `${i > 0 ? "1px " : ""}` + size).join(" "),
);
</script>
<div class="wrapper" style={`grid-template-columns: ${cols};`}>
<slot />
{@render children()}
</div>
<style>

View File

@@ -1,6 +1,6 @@
export function localState<T>(key: string, defaultValue: T): T {
const stored = localStorage.getItem(key)
const state = $state(stored ? JSON.parse(stored) : defaultValue)
const stored = localStorage.getItem(key);
const state = $state(stored ? JSON.parse(stored) : defaultValue);
$effect.root(() => {
$effect(() => {
const value = $state.snapshot(state);

View File

@@ -7,11 +7,15 @@
import type { PerspectiveCamera, Vector3Tuple } from "three";
import type { OrbitControls as OrbitControlsType } from "three/examples/jsm/controls/OrbitControls.js";
let camera: PerspectiveCamera;
let controls: OrbitControlsType;
let camera = $state<PerspectiveCamera>();
let controls = $state<OrbitControlsType>();
export let center: Vector3;
export let centerCamera: boolean = true;
type Props = {
center: Vector3;
centerCamera: boolean;
};
const { center, centerCamera }: Props = $props();
const cameraTransform = localStore<{
camera: Vector3Tuple;
@@ -22,7 +26,7 @@
});
function saveCameraState() {
if (!camera) return;
if (!camera || !controls) return;
let cPos = camera.position.toArray();
let tPos = controls.target.toArray();
// check if tPos is NaN or tPos is NaN
@@ -35,6 +39,7 @@
let isRunning = false;
const task = useTask(() => {
if (!controls) return;
let length = center.clone().sub(controls.target).length();
if (length < 0.01 || !centerCamera) {
isRunning = false;
@@ -47,7 +52,8 @@
});
task.stop();
$: if (
$effect(() => {
if (
center &&
controls &&
centerCamera &&
@@ -59,10 +65,11 @@
isRunning = true;
task.start();
}
});
onMount(() => {
controls.target.fromArray($cameraTransform.target);
controls.update();
controls?.target.fromArray($cameraTransform.target);
controls?.update();
});
</script>

View File

@@ -1,7 +1,15 @@
import { localState } from "$lib/helpers/localState.svelte";
import type { NodeInput } from "@nodes/types";
const themes = ["dark", "light", "catppuccin", "solarized", "high-contrast", "nord", "dracula"];
const themes = [
"dark",
"light",
"catppuccin",
"solarized",
"high-contrast",
"nord",
"dracula",
];
export const AppSettingTypes = {
theme: {
@@ -18,28 +26,33 @@ export const AppSettingTypes = {
centerCamera: {
type: "boolean",
label: "Center Camera",
value: true
value: true,
},
nodeInterface: {
title: "Node Interface",
showNodeGrid: {
type: "boolean",
label: "Show Grid",
value: true
value: true,
},
snapToGrid: {
type: "boolean",
label: "Snap to Grid",
value: true
value: true,
},
showHelp: {
type: "boolean",
label: "Show Help",
value: false
}
value: false,
},
},
debug: {
title: "Debug",
amount: {
type: "number",
label: "Amount",
value: 4,
},
wireframe: {
type: "boolean",
label: "Wireframe",
@@ -81,30 +94,30 @@ export const AppSettingTypes = {
type: "integer",
min: 2,
max: 15,
value: 4
value: 4,
},
loadGrid: {
type: "button",
label: "Load Grid"
label: "Load Grid",
},
loadTree: {
type: "button",
label: "Load Tree"
label: "Load Tree",
},
lottaFaces: {
type: "button",
label: "Load 'lots of faces'"
label: "Load 'lots of faces'",
},
lottaNodes: {
type: "button",
label: "Load 'lots of nodes'"
label: "Load 'lots of nodes'",
},
lottaNodesAndFaces: {
type: "button",
label: "Load 'lots of nodes and faces'"
}
label: "Load 'lots of nodes and faces'",
},
},
},
}
} as const;
type IsInputDefinition<T> = T extends NodeInput ? T : never;
@@ -118,10 +131,9 @@ type Widen<T> = T extends boolean
? string
: T;
type ExtractSettingsValues<T> = {
-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 { value: infer V }
? Widen<V>
@@ -135,8 +147,8 @@ function settingsToStore<T>(settings: T): ExtractSettingsValues<T> {
const result = {} as any;
for (const key in settings) {
const value = settings[key];
if (value && typeof value === 'object') {
if ('value' in value) {
if (value && typeof value === "object") {
if ("value" in value) {
result[key] = value.value;
} else {
result[key] = settingsToStore(value);
@@ -146,7 +158,10 @@ function settingsToStore<T>(settings: T): ExtractSettingsValues<T> {
return result;
}
export const appSettings = localState("app-settings", settingsToStore(AppSettingTypes));
export let appSettings = localState(
"app-settings",
settingsToStore(AppSettingTypes),
);
$effect.root(() => {
$effect(() => {

View File

@@ -1,7 +1,7 @@
<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";
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
type Props = {
manager: GraphManager;

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import type { Node } from "@nodes/types";
import type { GraphManager } from "$lib/graph-interface/graph-manager";
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
import ActiveNodeSelected from "./ActiveNodeSelected.svelte";
type Props = {

View File

@@ -26,8 +26,6 @@
import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry";
import { createPerformanceStore } from "@nodes/utils";
import BenchmarkPanel from "$lib/sidebar/panels/BenchmarkPanel.svelte";
import { debounceAsyncFunction } from "$lib/helpers";
import { onMount } from "svelte";
let performanceStore = createPerformanceStore();
@@ -45,14 +43,15 @@
let activeNode = $state<Node | undefined>(undefined);
let scene = $state<Group>(null!);
let graph = localStorage.getItem("graph")
let graph = $state(
localStorage.getItem("graph")
? JSON.parse(localStorage.getItem("graph")!)
: templates.defaultPlant;
: templates.defaultPlant,
);
let graphInterface = $state<ReturnType<typeof GraphInterface>>(null!);
let viewerComponent = $state<ReturnType<typeof Viewer>>();
const manager = $derived(graphInterface?.manager);
const managerStatus = $derived(manager?.status);
async function randomGenerate() {
if (!manager) return;
@@ -69,16 +68,29 @@
},
]);
let graphSettings = $state<Record<string, any>>({});
let graphSettingTypes = $state({
type BooleanSchema = {
[key: string]: {
type: "boolean";
value: false;
};
};
let graphSettingTypes = $state<BooleanSchema>({
randomSeed: { type: "boolean", value: false },
});
const handleUpdate = debounceAsyncFunction(
async (g: Graph, s: Record<string, any> = graphSettings) => {
let runIndex = 0;
const handleUpdate = async (
g: Graph,
s: Record<string, any> = graphSettings,
) => {
runIndex++;
performanceStore.startRun();
try {
let a = performance.now();
const graphResult = await runtime.execute(g, $state.snapshot(s));
const graphResult = await runtime.execute(
$state.snapshot(g),
$state.snapshot(s),
);
let b = performance.now();
if (appSettings.debug.useWorker) {
@@ -94,49 +106,48 @@
);
}
}
viewerComponent?.update(graphResult);
} catch (error) {
console.log("errors", error);
} finally {
performanceStore.stopRun();
}
},
);
};
// $ if (AppSettings) {
// //@ts-ignore
// AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
// graph = templates.grid($AppSettings.amount, $AppSettings.amount);
// };
// //@ts-ignore
// AppSettingTypes.debug.stressTest.loadTree.callback = () => {
// graph = templates.tree($AppSettings.amount);
// };
// //@ts-ignore
// AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
// graph = templates.lottaFaces;
// };
// //@ts-ignore
// AppSettingTypes.debug.stressTest.lottaNodes.callback = () => {
// graph = templates.lottaNodes;
// };
// //@ts-ignore
// AppSettingTypes.debug.stressTest.lottaNodesAndFaces.callback = () => {
// graph = templates.lottaNodesAndFaces;
// };
// }
$effect(() => {
//@ts-ignore
AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
graph = templates.grid(
appSettings.debug.amount.value,
appSettings.debug.amount.value,
);
};
//@ts-ignore
AppSettingTypes.debug.stressTest.loadTree.callback = () => {
graph = templates.tree(appSettings.debug.amount.value);
};
//@ts-ignore
AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
graph = templates.lottaFaces;
};
//@ts-ignore
AppSettingTypes.debug.stressTest.lottaNodes.callback = () => {
graph = templates.lottaNodes;
};
//@ts-ignore
AppSettingTypes.debug.stressTest.lottaNodesAndFaces.callback = () => {
graph = templates.lottaNodesAndFaces;
};
});
function handleSave(graph: Graph) {
localStorage.setItem("graph", JSON.stringify(graph));
}
onMount(() => {
handleUpdate(graph);
});
</script>
<svelte:document on:keydown={applicationKeymap.handleKeyboardEvent} />
<div class="wrapper manager-{$managerStatus}">
<svelte:document onkeydown={applicationKeymap.handleKeyboardEvent} />
<div class="wrapper manager-{manager?.status}">
<header></header>
<Grid.Row>
<Grid.Cell>
@@ -148,10 +159,9 @@
/>
</Grid.Cell>
<Grid.Cell>
{#key graph}
<GraphInterface
bind:this={graphInterface}
{graph}
bind:this={graphInterface}
registry={nodeRegistry}
showGrid={appSettings.nodeInterface.showNodeGrid}
snapToGrid={appSettings.nodeInterface.snapToGrid}
@@ -234,7 +244,6 @@
<ActiveNodeSettings {manager} node={activeNode} />
</Panel>
</Sidebar>
{/key}
</Grid.Cell>
</Grid.Row>
</div>

View File

@@ -1,100 +0,0 @@
<script lang="ts">
import {
decodeFloat,
encodeFloat,
decodeNestedArray,
encodeNestedArray,
concatEncodedArrays,
} from "@nodes/utils";
console.clear();
{
const encodedPositions = new Int32Array([
encodeFloat(1.1),
encodeFloat(2.0),
encodeFloat(3.0),
encodeFloat(4.0),
encodeFloat(5.0),
encodeFloat(6.0),
encodeFloat(7.0),
encodeFloat(8.0),
encodeFloat(9.0),
]);
// Create a Float32Array using the same buffer that backs the Int32Array
const floatView = new Float32Array(encodedPositions.buffer);
console.log({ encodedPositions, floatView });
}
if (false) {
const input_a = encodeNestedArray([1, 2, 3]);
const input_b = 2;
const input_c = 89;
const input_d = encodeNestedArray([4, 5, 6]);
const output = concatNestedArrays([input_a, input_b, input_c, input_d]);
const decoded = decodeNestedArray(output);
console.log("CONCAT", [input_a, input_b, input_c, input_d]);
console.log(output);
console.log(decoded);
}
if (false) {
let maxError = 0;
new Array(10_000).fill(null).forEach((v, i) => {
const input = i < 5_000 ? i : Math.random() * 100;
const encoded = encodeFloat(input);
const output = decodeFloat(encoded[0], encoded[1]);
const error = Math.abs(input - output);
if (error > maxError) {
maxError = error;
}
});
console.log("DECODE FLOAT");
console.log(maxError);
console.log(encodeFloat(2.0));
console.log("----");
}
if (false) {
console.log("Turning Int32Array into Array");
const test_size = 2_000_000;
const a = new Int32Array(test_size);
let t0 = performance.now();
for (let i = 0; i < test_size; i++) {
a[i] = Math.floor(Math.random() * 100);
}
console.log("TIME", performance.now() - t0);
t0 = performance.now();
const b = [...a.slice(0, test_size)];
console.log("TIME", performance.now() - t0);
console.log(typeof b, Array.isArray(b), b instanceof Int32Array);
}
if (false) {
// const input = [5, [6, 1], [7, 2, [5, 1]]];
// const input = [5, [], [6, []], []];
// const input = [52];
const input = [0, 0, [0, 2, 0, 128, 0, 128], 0, 128];
console.log("INPUT");
console.log(input);
let encoded = encodeNestedArray(input);
// encoded = [];
console.log("ENCODED");
console.log(encoded);
encoded = [0, 2, 1, 0, 4, 4, 2, 4, 1, 2, 2, 0, 3, 2, 3, 1, 1, 1, 1];
const decoded = decodeNestedArray(encoded);
console.log("DECODED");
console.log(decoded);
console.log("EQUALS", JSON.stringify(input) === JSON.stringify(decoded));
}
</script>

View File

@@ -1,4 +1,4 @@
import { Graph, NodeDefinition, NodeId } from "./types";
import { Graph, NodeDefinition, NodeType } from "./types";
export interface NodeRegistry {
/**
@@ -13,13 +13,13 @@ export interface NodeRegistry {
* @throws An error if the nodes could not be loaded
* @remarks This method should be called before calling getNode or getAllNodes
*/
load: (nodeIds: NodeId[]) => Promise<NodeDefinition[]>;
load: (nodeIds: NodeType[]) => Promise<NodeDefinition[]>;
/**
* Get a node by id
* @param id - The id of the node to get
* @returns The node with the given id, or undefined if no such node exists
*/
getNode: (id: NodeId | string) => NodeDefinition | undefined;
getNode: (id: NodeType | string) => NodeDefinition | undefined;
/**
* Get all nodes
* @returns An array of all nodes

View File

@@ -9,9 +9,9 @@ export type {
Node,
NodeDefinition,
Socket,
NodeType as NodeId,
NodeType,
Edge,
Graph,
} from "./types";
export { NodeSchema, GraphSchema, NodeType } from "./types";
export { NodeSchema, GraphSchema } from "./types";
export { NodeDefinitionSchema } from "./types";

View File

@@ -44,7 +44,7 @@ export type Node = {
} & z.infer<typeof NodeSchema>;
export const NodeDefinitionSchema = z.object({
id: z.string(),
id: NodeTypeSchema,
inputs: z.record(z.string(), NodeInputSchema).optional(),
outputs: z.array(z.string()).optional(),
meta: z

View File

@@ -3,7 +3,7 @@
ctrl?: boolean;
shift?: boolean;
alt?: boolean;
key: string;
key: string | string[];
}
let {