Compare commits

2 Commits

Author SHA1 Message Date
Max Richter
7ae1fae3b9 refactor: split ui/runtime/serialized node types
Closes #6
2025-12-03 19:18:56 +01:00
1126cf8f9f feat: dont use custom edge geometry
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m55s
2025-12-03 10:33:24 +01:00
25 changed files with 311 additions and 464 deletions

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import { HTML } from "@threlte/extras";
import { onMount } from "svelte";
import type { Node, NodeType } from "@nodarium/types";
import type { NodeInstance, NodeId } from "@nodarium/types";
import { getGraphManager, getGraphState } from "../graph/state.svelte";
type Props = {
onnode: (n: Node) => void;
onnode: (n: NodeInstance) => void;
};
const { onnode }: Props = $props();
@@ -15,7 +15,7 @@
let input: HTMLInputElement;
let value = $state<string>();
let activeNodeId = $state<NodeType>();
let activeNodeId = $state<NodeId>();
const allNodes = graphState.activeSocket
? graph.getPossibleNodes(graphState.activeSocket)
@@ -39,13 +39,14 @@
}
});
function handleNodeCreation(nodeType: Node["type"]) {
function handleNodeCreation(nodeType: NodeInstance["type"]) {
if (!graphState.addMenuPosition) return;
onnode?.({
id: -1,
type: nodeType,
position: [...graphState.addMenuPosition],
props: {},
state: {},
});
}

View File

@@ -26,11 +26,10 @@
<script lang="ts">
import { T } from "@threlte/core";
import { MeshLineMaterial } from "@threlte/extras";
import { Mesh, MeshBasicMaterial, Vector3 } from "three";
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
import { MeshBasicMaterial, Vector3 } from "three";
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
import { Vector2 } from "three/src/math/Vector2.js";
import { createEdgeGeometry } from "./createEdgeGeometry.js";
import { appSettings } from "$lib/settings/app-settings.svelte";
type Props = {
@@ -45,7 +44,7 @@
const thickness = $derived(Math.max(0.001, 0.00082 * Math.exp(0.055 * z)));
let mesh = $state<Mesh>();
let points = $state<Vector3[]>([]);
let lastId: string | null = null;
@@ -69,14 +68,10 @@
curve.v2.set(new_x / 2, new_y);
curve.v3.set(new_x, new_y);
const points = curve
points = curve
.getPoints(samples)
.map((p) => new Vector3(p.x, 0, p.y))
.flat();
if (mesh) {
mesh.geometry = createEdgeGeometry(points);
}
}
$effect(() => {
@@ -106,6 +101,7 @@
<T.CircleGeometry args={[0.5, 16]} />
</T.Mesh>
<T.Mesh bind:ref={mesh} position.x={x1} position.z={y1} position.y={0.1}>
<T.Mesh position.x={x1} position.z={y1} position.y={0.1}>
<MeshLineGeometry {points} />
<MeshLineMaterial width={thickness} color={lineColor} />
</T.Mesh>

View File

@@ -1,112 +0,0 @@
import { BufferAttribute, BufferGeometry, Vector3 } from 'three';
import { setXY, setXYZ, setXYZW, setXYZXYZ } from './utils.js';
export function createEdgeGeometry(points: Vector3[]) {
const length = points[0].distanceTo(points[points.length - 1]);
const startRadius = 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
let pointCount = points.length
let counters: number[] = []
let counterIndex = 0
let side: number[] = []
let widthArray: number[] = []
let doubleIndex = 0
let uvArray: number[] = []
let uvIndex = 0
let indices: number[] = []
let indicesIndex = 0
for (let j = 0; j < pointCount; j++) {
const c = j / points.length
counters[counterIndex + 0] = c
counters[counterIndex + 1] = c
counterIndex += 2
setXY(side, doubleIndex, 1, -1)
let width = shapeFunction((j / (pointCount - 1)))
setXY(widthArray, doubleIndex, width, width)
doubleIndex += 2
setXYZW(uvArray, uvIndex, j / (pointCount - 1), 0, j / (pointCount - 1), 1)
uvIndex += 4
if (j < pointCount - 1) {
const n = j * 2
setXYZ(indices, indicesIndex, n + 0, n + 1, n + 2)
setXYZ(indices, indicesIndex + 3, n + 2, n + 1, n + 3)
indicesIndex += 6
}
}
const geometry = new BufferGeometry()
// create these buffer attributes at the correct length but leave them empty for now
geometry.setAttribute('position', new BufferAttribute(new Float32Array(pointCount * 6), 3))
geometry.setAttribute('previous', new BufferAttribute(new Float32Array(pointCount * 6), 3))
geometry.setAttribute('next', new BufferAttribute(new Float32Array(pointCount * 6), 3))
// create and populate these buffer attributes
geometry.setAttribute('counters', new BufferAttribute(new Float32Array(counters), 1))
geometry.setAttribute('side', new BufferAttribute(new Float32Array(side), 1))
geometry.setAttribute('width', new BufferAttribute(new Float32Array(widthArray), 1))
geometry.setAttribute('uv', new BufferAttribute(new Float32Array(uvArray), 2))
geometry.setIndex(new BufferAttribute(new Uint16Array(indices), 1))
let positions: number[] = []
let previous: number[] = []
let next: number[] = []
let positionIndex = 0
let previousIndex = 0
let nextIndex = 0
setXYZXYZ(previous, previousIndex, points[0].x, points[0].y, points[0].z)
previousIndex += 6
for (let j = 0; j < pointCount; j++) {
const p = points[j]
setXYZXYZ(positions, positionIndex, p.x, p.y, p.z)
positionIndex += 6
if (j < pointCount - 1) {
setXYZXYZ(previous, previousIndex, p.x, p.y, p.z)
previousIndex += 6
}
if (j > 0 && j + 1 <= pointCount) {
setXYZXYZ(next, nextIndex, p.x, p.y, p.z)
nextIndex += 6
}
}
setXYZXYZ(
next,
nextIndex,
points[pointCount - 1].x,
points[pointCount - 1].y,
points[pointCount - 1].z
)
const positionAttribute = (geometry.getAttribute('position') as BufferAttribute).set(positions)
const previousAttribute = (geometry.getAttribute('previous') as BufferAttribute).set(previous)
const nextAttribute = (geometry.getAttribute('next') as BufferAttribute).set(next)
positionAttribute.needsUpdate = true
previousAttribute.needsUpdate = true
nextAttribute.needsUpdate = true
geometry.computeBoundingSphere()
return geometry;
}

View File

@@ -1,11 +1,11 @@
import type {
Edge,
Graph,
Node,
NodeInstance,
NodeDefinition,
NodeInput,
NodeRegistry,
NodeType,
NodeId,
Socket,
} from "@nodarium/types";
import { fastHashString } from "@nodarium/utils";
@@ -68,7 +68,7 @@ export class GraphManager extends EventEmitter<{
graph: Graph = { id: 0, nodes: [], edges: [] };
id = $state(0);
nodes = new SvelteMap<number, Node>();
nodes = new SvelteMap<number, NodeInstance>();
edges = $state<Edge[]>([]);
@@ -101,7 +101,7 @@ export class GraphManager extends EventEmitter<{
position: [...node.position],
type: node.type,
props: node.props,
})) as Node[];
})) as NodeInstance[];
const edges = this.edges.map((edge) => [
edge[0].id,
edge[1],
@@ -133,8 +133,8 @@ export class GraphManager extends EventEmitter<{
return this.registry.getAllNodes();
}
getLinkedNodes(node: Node) {
const nodes = new Set<Node>();
getLinkedNodes(node: NodeInstance) {
const nodes = new Set<NodeInstance>();
const stack = [node];
while (stack.length) {
const n = stack.pop();
@@ -148,10 +148,10 @@ export class GraphManager extends EventEmitter<{
return [...nodes.values()];
}
getEdgesBetweenNodes(nodes: Node[]): [number, number, number, string][] {
getEdgesBetweenNodes(nodes: NodeInstance[]): [number, number, number, string][] {
const edges = [];
for (const node of nodes) {
const children = node.tmp?.children || [];
const children = node.state?.children || [];
for (const child of children) {
if (nodes.includes(child)) {
const edge = this.edges.find(
@@ -174,14 +174,15 @@ export class GraphManager extends EventEmitter<{
private _init(graph: Graph) {
const nodes = new Map(
graph.nodes.map((node: Node) => {
graph.nodes.map((node) => {
const nodeType = this.registry.getNode(node.type);
const n = node as NodeInstance;
if (nodeType) {
node.tmp = {
n.state = {
type: nodeType,
};
}
return [node.id, node];
return [node.id, n];
}),
);
@@ -191,12 +192,10 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) {
throw new Error("Edge references non-existing node");
}
from.tmp = from.tmp || {};
from.tmp.children = from.tmp.children || [];
from.tmp.children.push(to);
to.tmp = to.tmp || {};
to.tmp.parents = to.tmp.parents || [];
to.tmp.parents.push(from);
from.state.children = from.state.children || [];
from.state.children.push(to);
to.state.parents = to.state.parents || [];
to.state.parents.push(from);
return [from, edge[1], to, edge[3]] as Edge;
});
@@ -232,8 +231,10 @@ export class GraphManager extends EventEmitter<{
this.status = "error";
return;
}
node.tmp = node.tmp || {};
node.tmp.type = nodeType;
// Turn into runtime node
const n = node as NodeInstance;
n.state = {};
n.state.type = nodeType;
}
// load settings
@@ -292,7 +293,7 @@ export class GraphManager extends EventEmitter<{
return this.registry.getNode(id);
}
async loadNodeType(id: NodeType) {
async loadNodeType(id: NodeId) {
await this.registry.load([id]);
const nodeType = this.registry.getNode(id);
@@ -321,19 +322,19 @@ export class GraphManager extends EventEmitter<{
this.emit("settings", { types: settingTypes, values: settingValues });
}
getChildren(node: Node) {
getChildren(node: NodeInstance) {
const children = [];
const stack = node.tmp?.children?.slice(0);
const stack = node.state?.children?.slice(0);
while (stack?.length) {
const child = stack.pop();
if (!child) continue;
children.push(child);
stack.push(...(child.tmp?.children || []));
stack.push(...(child.state?.children || []));
}
return children;
}
getNodesBetween(from: Node, to: Node): Node[] | undefined {
getNodesBetween(from: NodeInstance, to: NodeInstance): NodeInstance[] | undefined {
// < - - - - from
const toParents = this.getParentsOfNode(to);
// < - - - - from - - - - to
@@ -350,7 +351,7 @@ export class GraphManager extends EventEmitter<{
}
}
removeNode(node: Node, { restoreEdges = false } = {}) {
removeNode(node: NodeInstance, { restoreEdges = false } = {}) {
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]) {
@@ -363,8 +364,8 @@ export class GraphManager extends EventEmitter<{
for (const [to, toSocket] of inputSockets) {
for (const [from, fromSocket] of outputSockets) {
const outputType = from.tmp?.type?.outputs?.[fromSocket];
const inputType = to?.tmp?.type?.inputs?.[toSocket]?.type;
const outputType = from.state?.type?.outputs?.[fromSocket];
const inputType = to?.state?.type?.inputs?.[toSocket]?.type;
if (outputType === inputType) {
this.createEdge(from, fromSocket, to, toSocket, {
applyUpdate: false,
@@ -380,9 +381,9 @@ export class GraphManager extends EventEmitter<{
this.save();
}
smartConnect(from: Node, to: Node): Edge | undefined {
const inputs = Object.entries(to.tmp?.type?.inputs ?? {});
const outputs = from.tmp?.type?.outputs ?? [];
smartConnect(from: NodeInstance, to: NodeInstance): Edge | undefined {
const inputs = Object.entries(to.state?.type?.inputs ?? {});
const outputs = from.state?.type?.outputs ?? [];
for (let i = 0; i < inputs.length; i++) {
const [inputName, input] = inputs[0];
for (let o = 0; o < outputs.length; o++) {
@@ -398,7 +399,7 @@ export class GraphManager extends EventEmitter<{
return Math.max(0, ...this.nodes.keys()) + 1;
}
createGraph(nodes: Node[], edges: [number, number, number, string][]) {
createGraph(nodes: NodeInstance[], edges: [number, number, number, string][]) {
// map old ids to new ids
const idMap = new Map<number, number>();
@@ -422,13 +423,11 @@ export class GraphManager extends EventEmitter<{
throw new Error("Edge references non-existing node");
}
to.tmp = to.tmp || {};
to.tmp.parents = to.tmp.parents || [];
to.tmp.parents.push(from);
to.state.parents = to.state.parents || [];
to.state.parents.push(from);
from.tmp = from.tmp || {};
from.tmp.children = from.tmp.children || [];
from.tmp.children.push(to);
from.state.children = from.state.children || [];
from.state.children.push(to);
return [from, edge[1], to, edge[3]] as Edge;
});
@@ -448,9 +447,9 @@ export class GraphManager extends EventEmitter<{
position,
props = {},
}: {
type: Node["type"];
position: Node["position"];
props: Node["props"];
type: NodeInstance["type"];
position: NodeInstance["position"];
props: NodeInstance["props"];
}) {
const nodeType = this.registry.getNode(type);
if (!nodeType) {
@@ -458,11 +457,11 @@ export class GraphManager extends EventEmitter<{
return;
}
const node: Node = $state({
const node: NodeInstance = $state({
id: this.createNodeId(),
type,
position,
tmp: { type: nodeType },
state: { type: nodeType },
props,
});
@@ -474,9 +473,9 @@ export class GraphManager extends EventEmitter<{
}
createEdge(
from: Node,
from: NodeInstance,
fromSocket: number,
to: Node,
to: NodeInstance,
toSocket: string,
{ applyUpdate = true } = {},
): Edge | undefined {
@@ -493,10 +492,10 @@ export class GraphManager extends EventEmitter<{
}
// check if socket types match
const fromSocketType = from.tmp?.type?.outputs?.[fromSocket];
const toSocketType = [to.tmp?.type?.inputs?.[toSocket]?.type];
if (to.tmp?.type?.inputs?.[toSocket]?.accepts) {
toSocketType.push(...(to?.tmp?.type?.inputs?.[toSocket]?.accepts || []));
const fromSocketType = from.state?.type?.outputs?.[fromSocket];
const toSocketType = [to.state?.type?.inputs?.[toSocket]?.type];
if (to.state?.type?.inputs?.[toSocket]?.accepts) {
toSocketType.push(...(to?.state?.type?.inputs?.[toSocket]?.accepts || []));
}
if (!areSocketsCompatible(fromSocketType, toSocketType)) {
@@ -517,13 +516,10 @@ export class GraphManager extends EventEmitter<{
this.edges.push(edge);
from.tmp = from.tmp || {};
from.tmp.children = from.tmp.children || [];
from.tmp.children.push(to);
to.tmp = to.tmp || {};
to.tmp.parents = to.tmp.parents || [];
to.tmp.parents.push(from);
from.state.children = from.state.children || [];
from.state.children.push(to);
to.state.parents = to.state.parents || [];
to.state.parents.push(from);
if (applyUpdate) {
this.save();
@@ -566,9 +562,9 @@ export class GraphManager extends EventEmitter<{
logger.log("saving graphs", state);
}
getParentsOfNode(node: Node) {
getParentsOfNode(node: NodeInstance) {
const parents = [];
const stack = node.tmp?.parents?.slice(0);
const stack = node.state?.parents?.slice(0);
while (stack?.length) {
if (parents.length > 1000000) {
logger.warn("Infinite loop detected");
@@ -577,7 +573,7 @@ export class GraphManager extends EventEmitter<{
const parent = stack.pop();
if (!parent) continue;
parents.push(parent);
stack.push(...(parent.tmp?.parents || []));
stack.push(...(parent.state?.parents || []));
}
return parents.reverse();
}
@@ -585,7 +581,7 @@ export class GraphManager extends EventEmitter<{
getPossibleNodes(socket: Socket): NodeDefinition[] {
const allDefinitions = this.getNodeDefinitions();
const nodeType = socket.node.tmp?.type;
const nodeType = socket.node.state?.type;
if (!nodeType) {
return [];
}
@@ -612,11 +608,11 @@ export class GraphManager extends EventEmitter<{
}
getPossibleSockets({ node, index }: Socket): [Node, string | number][] {
const nodeType = node?.tmp?.type;
getPossibleSockets({ node, index }: Socket): [NodeInstance, string | number][] {
const nodeType = node?.state?.type;
if (!nodeType) return [];
const sockets: [Node, string | number][] = [];
const sockets: [NodeInstance, string | number][] = [];
// if index is a string, we are an input looking for outputs
if (typeof index === "string") {
@@ -629,7 +625,7 @@ export class GraphManager extends EventEmitter<{
const ownType = nodeType?.inputs?.[index].type;
for (const node of nodes) {
const nodeType = node?.tmp?.type;
const nodeType = node?.state?.type;
const inputs = nodeType?.outputs;
if (!inputs) continue;
for (let index = 0; index < inputs.length; index++) {
@@ -657,7 +653,7 @@ export class GraphManager extends EventEmitter<{
const ownType = nodeType.outputs?.[index];
for (const node of nodes) {
const inputs = node?.tmp?.type?.inputs;
const inputs = node?.state?.type?.inputs;
if (!inputs) continue;
for (const key in inputs) {
const otherType = [inputs[key].type];
@@ -692,17 +688,15 @@ export class GraphManager extends EventEmitter<{
if (!_edge) return;
edge[0].tmp = edge[0].tmp || {};
if (edge[0].tmp.children) {
edge[0].tmp.children = edge[0].tmp.children.filter(
(n: Node) => n.id !== id2,
if (edge[0].state.children) {
edge[0].state.children = edge[0].state.children.filter(
(n: NodeInstance) => n.id !== id2,
);
}
edge[2].tmp = edge[2].tmp || {};
if (edge[2].tmp.parents) {
edge[2].tmp.parents = edge[2].tmp.parents.filter(
(n: Node) => n.id !== id0,
if (edge[2].state.parents) {
edge[2].state.parents = edge[2].state.parents.filter(
(n: NodeInstance) => n.id !== id0,
);
}
@@ -714,7 +708,7 @@ export class GraphManager extends EventEmitter<{
}
getEdgesToNode(node: Node) {
getEdgesToNode(node: NodeInstance) {
return this.edges
.filter((edge) => edge[2].id === node.id)
.map((edge) => {
@@ -723,10 +717,10 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) return;
return [from, edge[1], to, edge[3]] as const;
})
.filter(Boolean) as unknown as [Node, number, Node, string][];
.filter(Boolean) as unknown as [NodeInstance, number, NodeInstance, string][];
}
getEdgesFromNode(node: Node) {
getEdgesFromNode(node: NodeInstance) {
return this.edges
.filter((edge) => edge[0].id === node.id)
.map((edge) => {
@@ -735,6 +729,6 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) return;
return [from, edge[1], to, edge[3]] as const;
})
.filter(Boolean) as unknown as [Node, number, Node, string][];
.filter(Boolean) as unknown as [NodeInstance, number, NodeInstance, string][];
}
}

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import type { Edge, Node } from "@nodarium/types";
import type { Edge, NodeInstance } from "@nodarium/types";
import { onMount } from "svelte";
import { createKeyMap } from "../../helpers/createKeyMap";
import AddMenu from "../components/AddMenu.svelte";
@@ -40,7 +40,7 @@
return [pos1[0], pos1[1], pos2[0], pos2[1]];
}
function handleNodeCreation(node: Node) {
function handleNodeCreation(node: NodeInstance) {
const newNode = graph.createNode({
type: node.type,
position: node.position,
@@ -51,11 +51,11 @@
if (graphState.activeSocket) {
if (typeof graphState.activeSocket.index === "number") {
const socketType =
graphState.activeSocket.node.tmp?.type?.outputs?.[
graphState.activeSocket.node.state?.type?.outputs?.[
graphState.activeSocket.index
];
const input = Object.entries(newNode?.tmp?.type?.inputs || {}).find(
const input = Object.entries(newNode?.state?.type?.inputs || {}).find(
(inp) => inp[1].type === socketType,
);
@@ -69,11 +69,11 @@
}
} else {
const socketType =
graphState.activeSocket.node.tmp?.type?.inputs?.[
graphState.activeSocket.node.state?.type?.inputs?.[
graphState.activeSocket.index
];
const output = newNode.tmp?.type?.outputs?.find((out) => {
const output = newNode.state?.type?.outputs?.find((out) => {
if (socketType?.type === out) return true;
if (socketType?.accepts?.includes(out as any)) return true;
return false;

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import type { Graph, Node, NodeRegistry } from "@nodarium/types";
import type { Graph, NodeInstance, NodeRegistry } from "@nodarium/types";
import GraphEl from "./Graph.svelte";
import { GraphManager } from "../graph-manager.svelte";
import { createKeyMap } from "$lib/helpers/createKeyMap";
@@ -12,7 +12,7 @@
settings?: Record<string, any>;
activeNode?: Node;
activeNode?: NodeInstance;
showGrid?: boolean;
snapToGrid?: boolean;
showHelp?: boolean;

View File

@@ -1,4 +1,4 @@
import { GraphSchema, type NodeType, type Node } from "@nodarium/types";
import { GraphSchema, type NodeId, type NodeInstance } from "@nodarium/types";
import type { GraphManager } from "../graph-manager.svelte";
import type { GraphState } from "./state.svelte";
import { animate, lerp } from "$lib/helpers";
@@ -17,7 +17,7 @@ export class FileDropEventManager {
event.preventDefault();
this.state.isDragging = false;
if (!event.dataTransfer) return;
const nodeId = event.dataTransfer.getData("data/node-id") as NodeType;
const nodeId = event.dataTransfer.getData("data/node-id") as NodeId;
let mx = event.clientX - this.state.rect.x;
let my = event.clientY - this.state.rect.y;
@@ -131,45 +131,45 @@ export class MouseEventManager {
if (clickedNodeId !== -1) {
if (activeNode) {
if (!activeNode?.tmp?.isMoving && !event.ctrlKey && !event.shiftKey) {
if (!activeNode?.state?.isMoving && !event.ctrlKey && !event.shiftKey) {
this.state.activeNodeId = clickedNodeId;
this.state.clearSelection();
}
}
}
if (activeNode?.tmp?.isMoving) {
activeNode.tmp = activeNode.tmp || {};
activeNode.tmp.isMoving = false;
if (activeNode?.state?.isMoving) {
activeNode.state = activeNode.state || {};
activeNode.state.isMoving = false;
if (this.state.snapToGrid) {
const snapLevel = this.state.getSnapLevel();
activeNode.position[0] = snapPointToGrid(
activeNode?.tmp?.x ?? activeNode.position[0],
activeNode?.state?.x ?? activeNode.position[0],
5 / snapLevel,
);
activeNode.position[1] = snapPointToGrid(
activeNode?.tmp?.y ?? activeNode.position[1],
activeNode?.state?.y ?? activeNode.position[1],
5 / snapLevel,
);
} else {
activeNode.position[0] = activeNode?.tmp?.x ?? activeNode.position[0];
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1];
activeNode.position[0] = activeNode?.state?.x ?? activeNode.position[0];
activeNode.position[1] = activeNode?.state?.y ?? activeNode.position[1];
}
const nodes = [
...[...(this.state.selectedNodes?.values() || [])].map((id) =>
this.graph.getNode(id),
),
] as Node[];
] as NodeInstance[];
const vec = [
activeNode.position[0] - (activeNode?.tmp.x || 0),
activeNode.position[1] - (activeNode?.tmp.y || 0),
activeNode.position[0] - (activeNode?.state.x || 0),
activeNode.position[1] - (activeNode?.state.y || 0),
];
for (const node of nodes) {
if (!node) continue;
node.tmp = node.tmp || {};
const { x, y } = node.tmp;
node.state = node.state || {};
const { x, y } = node.state;
if (x !== undefined && y !== undefined) {
node.position[0] = x + vec[0];
node.position[1] = y + vec[1];
@@ -179,14 +179,14 @@ export class MouseEventManager {
animate(500, (a: number) => {
for (const node of nodes) {
if (
node?.tmp &&
node.tmp["x"] !== undefined &&
node.tmp["y"] !== undefined
node?.state &&
node.state["x"] !== undefined &&
node.state["y"] !== undefined
) {
node.tmp.x = lerp(node.tmp.x, node.position[0], a);
node.tmp.y = lerp(node.tmp.y, node.position[1], a);
node.state.x = lerp(node.state.x, node.position[0], a);
node.state.y = lerp(node.state.y, node.position[1], a);
this.state.updateNodePosition(node);
if (node?.tmp?.isMoving) {
if (node?.state?.isMoving) {
return false;
}
}
@@ -318,17 +318,17 @@ export class MouseEventManager {
const node = this.graph.getNode(this.state.activeNodeId);
if (!node) return;
node.tmp = node.tmp || {};
node.tmp.downX = node.position[0];
node.tmp.downY = node.position[1];
node.state = node.state || {};
node.state.downX = node.position[0];
node.state.downY = node.position[1];
if (this.state.selectedNodes) {
for (const nodeId of this.state.selectedNodes) {
const n = this.graph.getNode(nodeId);
if (!n) continue;
n.tmp = n.tmp || {};
n.tmp.downX = n.position[0];
n.tmp.downY = n.position[1];
n.state = n.state || {};
n.state.downX = n.position[0];
n.state.downY = n.position[1];
}
}
@@ -382,7 +382,7 @@ export class MouseEventManager {
const y1 = Math.min(mouseD[1], this.state.mousePosition[1]);
const y2 = Math.max(mouseD[1], this.state.mousePosition[1]);
for (const node of this.graph.nodes.values()) {
if (!node?.tmp) continue;
if (!node?.state) continue;
const x = node.position[0];
const y = node.position[1];
const height = this.state.getNodeHeight(node.type);
@@ -400,10 +400,10 @@ export class MouseEventManager {
const node = this.graph.getNode(this.state.activeNodeId);
if (!node || event.buttons !== 1) return;
node.tmp = node.tmp || {};
node.state = node.state || {};
const oldX = node.tmp.downX || 0;
const oldY = node.tmp.downY || 0;
const oldX = node.state.downX || 0;
const oldY = node.state.downY || 0;
let newX =
oldX + (mx - this.state.mouseDown[0]) / this.state.cameraPosition[2];
@@ -418,10 +418,10 @@ export class MouseEventManager {
}
}
if (!node.tmp.isMoving) {
if (!node.state.isMoving) {
const dist = Math.sqrt((oldX - newX) ** 2 + (oldY - newY) ** 2);
if (dist > 0.2) {
node.tmp.isMoving = true;
node.state.isMoving = true;
}
}
@@ -431,15 +431,15 @@ export class MouseEventManager {
if (this.state.selectedNodes?.size) {
for (const nodeId of this.state.selectedNodes) {
const n = this.graph.getNode(nodeId);
if (!n?.tmp) continue;
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
if (!n?.state) continue;
n.state.x = (n?.state?.downX || 0) - vecX;
n.state.y = (n?.state?.downY || 0) - vecY;
this.state.updateNodePosition(n);
}
}
node.tmp.x = newX;
node.tmp.y = newY;
node.state.x = newX;
node.state.y = newY;
this.state.updateNodePosition(node);

View File

@@ -1,4 +1,4 @@
import type { Node, Socket } from "@nodarium/types";
import type { NodeInstance, Socket } from "@nodarium/types";
import { getContext, setContext } from "svelte";
import { SvelteSet } from "svelte/reactivity";
import type { GraphManager } from "../graph-manager.svelte";
@@ -38,7 +38,7 @@ export class GraphState {
cameraPosition: [number, number, number] = $state([0, 0, 4]);
clipboard: null | {
nodes: Node[];
nodes: NodeInstance[];
edges: [number, number, number, string][];
} = null;
@@ -95,26 +95,26 @@ export class GraphState {
}
updateNodePosition(node: Node) {
if (node?.tmp?.ref && node?.tmp?.mesh) {
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
node.tmp.mesh.position.x = node.tmp.x + 10;
node.tmp.mesh.position.z = node.tmp.y + this.getNodeHeight(node.type) / 2;
updateNodePosition(node: NodeInstance) {
if (node.state.ref && node.state.mesh) {
if (node.state["x"] !== undefined && node.state["y"] !== undefined) {
node.state.ref.style.setProperty("--nx", `${node.state.x * 10}px`);
node.state.ref.style.setProperty("--ny", `${node.state.y * 10}px`);
node.state.mesh.position.x = node.state.x + 10;
node.state.mesh.position.z = node.state.y + this.getNodeHeight(node.type) / 2;
if (
node.tmp.x === node.position[0] &&
node.tmp.y === node.position[1]
node.state.x === node.position[0] &&
node.state.y === node.position[1]
) {
delete node.tmp.x;
delete node.tmp.y;
delete node.state.x;
delete node.state.y;
}
this.graph.edges = [...this.graph.edges];
} else {
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
node.tmp.mesh.position.x = node.position[0] + 10;
node.tmp.mesh.position.z =
node.state.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.state.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
node.state.mesh.position.x = node.position[0] + 10;
node.state.mesh.position.z =
node.position[1] + this.getNodeHeight(node.type) / 2;
}
}
@@ -134,19 +134,19 @@ export class GraphState {
}
getSocketPosition(
node: Node,
node: NodeInstance,
index: string | number,
): [number, number] {
if (typeof index === "number") {
return [
(node?.tmp?.x ?? node.position[0]) + 20,
(node?.tmp?.y ?? node.position[1]) + 2.5 + 10 * index,
(node?.state?.x ?? node.position[0]) + 20,
(node?.state?.y ?? node.position[1]) + 2.5 + 10 * index,
];
} else {
const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index);
const _index = Object.keys(node.state?.type?.inputs || {}).indexOf(index);
return [
node?.tmp?.x ?? node.position[0],
(node?.tmp?.y ?? node.position[1]) + 10 + 10 * _index,
node?.state?.x ?? node.position[0],
(node?.state?.y ?? node.position[1]) + 10 + 10 * _index,
];
}
}
@@ -174,32 +174,6 @@ export class GraphState {
return height;
}
setNodePosition(node: Node) {
if (node?.tmp?.ref && node?.tmp?.mesh) {
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
node.tmp.mesh.position.x = node.tmp.x + 10;
node.tmp.mesh.position.z = node.tmp.y + this.getNodeHeight(node.type) / 2;
if (
node.tmp.x === node.position[0] &&
node.tmp.y === node.position[1]
) {
delete node.tmp.x;
delete node.tmp.y;
}
this.graph.edges = [...this.graph.edges];
} else {
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
node.tmp.mesh.position.x = node.position[0] + 10;
node.tmp.mesh.position.z =
node.position[1] + this.getNodeHeight(node.type) / 2;
}
}
}
copyNodes() {
if (this.activeNodeId === -1 && !this.selectedNodes?.size)
return;
@@ -231,12 +205,11 @@ export class GraphState {
const nodes = this.clipboard.nodes
.map((node) => {
node.tmp = node.tmp || {};
node.position[0] = this.mousePosition[0] - node.position[0];
node.position[1] = this.mousePosition[1] - node.position[1];
return node;
})
.filter(Boolean) as Node[];
.filter(Boolean) as NodeInstance[];
const newNodes = this.graph.createGraph(nodes, this.clipboard.edges);
this.selectedNodes.clear();
@@ -327,7 +300,7 @@ export class GraphState {
return clickedNodeId;
}
isNodeInView(node: Node) {
isNodeInView(node: NodeInstance) {
const height = this.getNodeHeight(node.type);
const width = 20;
return (

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import type { Node } from "@nodarium/types";
import { onMount } from "svelte";
import type { NodeInstance } from "@nodarium/types";
import { getGraphState } from "../graph/state.svelte";
import { T } from "@threlte/core";
import { type Mesh } from "three";
@@ -13,7 +12,7 @@
const graphState = getGraphState();
type Props = {
node: Node;
node: NodeInstance;
inView: boolean;
z: number;
};
@@ -35,9 +34,8 @@
const height = graphState.getNodeHeight(node.type);
$effect(() => {
if (!node.tmp) node.tmp = {};
if (meshRef && !node.tmp?.mesh) {
node.tmp.mesh = meshRef;
if (meshRef && !node.state?.mesh) {
node.state.mesh = meshRef;
graphState.updateNodePosition(node);
}
});

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import type { Node } from "@nodarium/types";
import type { NodeInstance, SerializedNode } from "@nodarium/types";
import NodeHeader from "./NodeHeader.svelte";
import NodeParameter from "./NodeParameter.svelte";
import { onMount } from "svelte";
import { getGraphState } from "../graph/state.svelte";
let ref: HTMLDivElement;
@@ -10,7 +9,7 @@
const graphState = getGraphState();
type Props = {
node: Node;
node: SerializedNode | NodeInstance;
position?: "absolute" | "fixed" | "relative";
isActive?: boolean;
isSelected?: boolean;
@@ -31,15 +30,21 @@
const zOffset = Math.random() - 0.5;
const zLimit = 2 - zOffset;
const parameters = Object.entries(node?.tmp?.type?.inputs || {}).filter(
(p) =>
p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true,
);
const parameters =
"state" in node
? Object.entries(node.state?.type?.inputs || {}).filter(
(p) =>
p[1].type !== "seed" &&
!("setting" in p[1]) &&
p[1]?.hidden !== true,
)
: [];
onMount(() => {
node.tmp = node.tmp || {};
node.tmp.ref = ref;
graphState?.updateNodePosition(node);
$effect(() => {
if ("state" in node && !node.state.ref) {
node.state.ref = ref;
graphState?.updateNodePosition(node);
}
});
</script>

View File

@@ -1,24 +1,27 @@
<script lang="ts">
import { getGraphState } from "../graph/state.svelte.js";
import { createNodePath } from "../helpers/index.js";
import type { Node } from "@nodarium/types";
import type { NodeInstance, SerializedNode } from "@nodarium/types";
const graphState = getGraphState();
const { node }: { node: Node } = $props();
const { node }: { node: NodeInstance | SerializedNode } = $props();
function handleMouseDown(event: MouseEvent) {
event.stopPropagation();
event.preventDefault();
graphState.setDownSocket?.({
node,
index: 0,
position: graphState.getSocketPosition?.(node, 0),
});
if ("state" in node) {
graphState.setDownSocket?.({
node,
index: 0,
position: graphState.getSocketPosition?.(node, 0),
});
}
}
const cornerTop = 10;
const rightBump = !!node?.tmp?.type?.outputs?.length;
const rightBump =
"state" in node ? !!node?.state?.type?.outputs?.length : false;
const aspectRatio = 0.25;
const path = createNodePath({
@@ -29,14 +32,6 @@
rightBump,
aspectRatio,
});
// const pathDisabled = createNodePath({
// depth: 0,
// height: 15,
// y: 50,
// cornerTop,
// rightBump,
// aspectRatio,
// });
const pathHover = createNodePath({
depth: 8.5,
height: 50,

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import type { Node, NodeInput } from "@nodarium/types";
import type { NodeInstance, NodeInput } from "@nodarium/types";
import { Input } from "@nodarium/ui";
import type { GraphManager } from "../graph-manager.svelte";
type Props = {
node: Node;
node: NodeInstance;
input: NodeInput;
id: string;
elementId?: string;

View File

@@ -1,14 +1,15 @@
<script lang="ts">
import type {
NodeInput as NodeInputType,
Node as NodeType,
NodeInstance,
SerializedNode,
} from "@nodarium/types";
import { createNodePath } from "../helpers/index.js";
import NodeInput from "./NodeInput.svelte";
import { getGraphManager, getGraphState } from "../graph/state.svelte.js";
type Props = {
node: NodeType;
node: NodeInstance | SerializedNode;
input: NodeInputType;
id: string;
isLast?: boolean;
@@ -18,7 +19,7 @@
let { node = $bindable(), input, id, isLast }: Props = $props();
const inputType = node?.tmp?.type?.inputs?.[id]!;
const inputType = node?.state?.type?.inputs?.[id]!;
const socketId = `${node.id}-${id}`;
@@ -37,7 +38,7 @@
});
}
const leftBump = node.tmp?.type?.inputs?.[id].internal !== true;
const leftBump = node.state?.type?.inputs?.[id].internal !== true;
const cornerBottom = isLast ? 5 : 0;
const aspectRatio = 0.5;
@@ -83,7 +84,7 @@
{/if}
</div>
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
{#if node?.state?.type?.inputs?.[id]?.internal !== true}
<div data-node-socket class="large target"></div>
<div
data-node-socket

View File

@@ -16,9 +16,6 @@ export function grid(width: number, height: number) {
graph.nodes.push({
id: i,
tmp: {
visible: false,
},
position: [x * 30, y * 40],
props: i == 0 ? { value: 0 } : { op_type: 0, a: 1, b: 0.05 },
type: i == 0 ? "max/plantarium/float" : "max/plantarium/math",
@@ -29,9 +26,6 @@ export function grid(width: number, height: number) {
graph.nodes.push({
id: amount,
tmp: {
visible: false,
},
position: [width * 30, (height - 1) * 40],
type: "max/plantarium/output",
props: {},

View File

@@ -1,8 +1,8 @@
import type { Graph, Node } from "@nodarium/types";
import type { Graph, SerializedNode } from "@nodarium/types";
export function tree(depth: number): Graph {
const nodes: Node[] = [
const nodes: SerializedNode[] = [
{
id: 0,
type: "max/plantarium/output",

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte";
import type { NodeDefinition } from "@nodarium/types";
import type { SerializedNode } from "@nodarium/types";
export let node: NodeDefinition;
const { node }: { node: SerializedNode } = $props();
let dragging = false;
let dragging = $state(false);
let nodeData = {
let nodeData = $state({
id: 0,
type: node?.id,
position: [0, 0] as [number, number],
@@ -14,14 +14,14 @@
tmp: {
type: node,
},
};
});
function handleDragStart(e: DragEvent) {
dragging = true;
const box = (e?.target as HTMLElement)?.getBoundingClientRect();
if (e.dataTransfer === null) return;
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("data/node-id", node.id);
e.dataTransfer.setData("data/node-id", node.id.toString());
if (nodeData.props) {
e.dataTransfer.setData("data/node-props", JSON.stringify(nodeData.props));
}
@@ -38,13 +38,13 @@
<div class="node-wrapper" class:dragging>
<div
on:dragend={() => {
ondragend={() => {
dragging = false;
}}
draggable={true}
role="button"
tabindex="0"
on:dragstart={handleDragStart}
ondragstart={handleDragStart}
>
<NodeHtml inView={true} position={"relative"} z={5} bind:node={nodeData} />
</div>

View File

@@ -1,6 +1,5 @@
import type {
Graph,
Node,
NodeDefinition,
NodeInput,
NodeRegistry,
@@ -14,6 +13,7 @@ import {
fastHashArrayBuffer,
type PerformanceStore,
} from "@nodarium/utils";
import type { RuntimeNode } from "./types";
const log = createLogger("runtime-executor");
log.mute();
@@ -92,18 +92,27 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
// First, lets check if all nodes have a definition
this.definitionMap = await this.getNodeDefinitions(graph);
const outputNode = graph.nodes.find((node) =>
const graphNodes = graph.nodes.map(node => {
const n = node as RuntimeNode;
n.state = {
depth: 0,
children: [],
parents: [],
inputNodes: {},
}
return n
})
const outputNode = graphNodes.find((node) =>
node.type.endsWith("/output"),
) as Node;
);
if (!outputNode) {
throw new Error("No output node found");
}
outputNode.tmp = outputNode.tmp || {};
outputNode.tmp.depth = 0;
const nodeMap = new Map<number, Node>(
graph.nodes.map((node) => [node.id, node]),
const nodeMap = new Map(
graphNodes.map((node) => [node.id, node]),
);
// loop through all edges and assign the parent and child nodes to each node
@@ -112,14 +121,9 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
const parent = nodeMap.get(parentId);
const child = nodeMap.get(childId);
if (parent && child) {
parent.tmp = parent.tmp || {};
parent.tmp.children = parent.tmp.children || [];
parent.tmp.children.push(child);
child.tmp = child.tmp || {};
child.tmp.parents = child.tmp.parents || [];
child.tmp.parents.push(parent);
child.tmp.inputNodes = child.tmp.inputNodes || {};
child.tmp.inputNodes[childInput] = parent;
parent.state.children.push(child);
child.state.parents.push(parent);
child.state.inputNodes[childInput] = parent;
}
}
@@ -130,20 +134,10 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
while (stack.length) {
const node = stack.pop();
if (!node) continue;
node.tmp = node.tmp || {};
if (node?.tmp?.depth === undefined) {
node.tmp.depth = 0;
}
if (node?.tmp?.parents !== undefined) {
for (const parent of node.tmp.parents) {
parent.tmp = parent.tmp || {};
if (parent.tmp?.depth === undefined) {
parent.tmp.depth = node.tmp.depth + 1;
stack.push(parent);
} else {
parent.tmp.depth = Math.max(parent.tmp.depth, node.tmp.depth + 1);
}
}
for (const parent of node.state.parents) {
parent.state = parent.state || {};
parent.state.depth = node.state.depth + 1;
stack.push(parent);
}
nodes.push(node);
}
@@ -175,7 +169,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
// we execute the nodes from the bottom up
const sortedNodes = nodes.sort(
(a, b) => (b.tmp?.depth || 0) - (a.tmp?.depth || 0),
(a, b) => (b.state?.depth || 0) - (a.state?.depth || 0),
);
// here we store the intermediate results of the nodes
@@ -188,7 +182,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
for (const node of sortedNodes) {
const node_type = this.definitionMap.get(node.type)!;
if (!node_type || !node.tmp || !node_type.execute) {
if (!node_type || !node.state || !node_type.execute) {
log.warn(`Node ${node.id} has no definition`);
continue;
}
@@ -208,7 +202,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
}
// check if the input is connected to another node
const inputNode = node.tmp?.inputNodes?.[key];
const inputNode = node.state.inputNodes[key];
if (inputNode) {
if (results[inputNode.id] === undefined) {
throw new Error(

View File

@@ -0,0 +1,10 @@
import type { SerializedNode } from "@nodarium/types";
type RuntimeState = {
depth: number
parents: RuntimeNode[],
children: RuntimeNode[],
inputNodes: Record<string, RuntimeNode>
}
export type RuntimeNode = SerializedNode & { state: RuntimeState }

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import type { Node, NodeInput } from "@nodarium/types";
import type { NodeInstance, NodeInput } from "@nodarium/types";
import NestedSettings from "$lib/settings/NestedSettings.svelte";
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
type Props = {
manager: GraphManager;
node: Node;
node: NodeInstance;
};
const { manager, node = $bindable() }: Props = $props();
@@ -19,19 +19,19 @@
})
.map(([key, value]) => {
//@ts-ignore
value.__node_type = node?.tmp?.type.id;
value.__node_type = node.state?.type.id;
//@ts-ignore
value.__node_input = key;
return [key, value];
}),
);
}
const nodeDefinition = filterInputs(node.tmp?.type?.inputs);
const nodeDefinition = filterInputs(node.state?.type?.inputs);
type Store = Record<string, number | number[]>;
let store = $state<Store>(createStore(node?.props, nodeDefinition));
function createStore(
props: Node["props"],
props: NodeInstance["props"],
inputs: Record<string, NodeInput>,
): Store {
const store: Store = {};

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import type { Node } from "@nodarium/types";
import type { NodeInstance } from "@nodarium/types";
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
import ActiveNodeSelected from "./ActiveNodeSelected.svelte";
type Props = {
manager: GraphManager;
node: Node | undefined;
node: NodeInstance | undefined;
};
const { manager, node }: Props = $props();

View File

@@ -2,7 +2,7 @@
import Grid from "$lib/grid";
import GraphInterface from "$lib/graph-interface";
import * as templates from "$lib/graph-templates";
import type { Graph, Node } from "@nodarium/types";
import type { Graph, NodeInstance } from "@nodarium/types";
import Viewer from "$lib/result-viewer/Viewer.svelte";
import {
appSettings,
@@ -42,7 +42,7 @@
appSettings.value.debug.useWorker ? workerRuntime : memoryRuntime,
);
let activeNode = $state<Node | undefined>(undefined);
let activeNode = $state<NodeInstance | undefined>(undefined);
let scene = $state<Group>(null!);
let graph = $state(

View File

@@ -1,4 +1,4 @@
import type { Graph, NodeDefinition, NodeType } from "./types";
import type { Graph, NodeDefinition, NodeId } 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: NodeType[]) => Promise<NodeDefinition[]>;
load: (nodeIds: NodeId[]) => 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: NodeType | string) => NodeDefinition | undefined;
getNode: (id: NodeId | string) => NodeDefinition | undefined;
/**
* Get all nodes
* @returns An array of all nodes

View File

@@ -6,10 +6,11 @@ export type {
AsyncCache,
} from "./components";
export type {
Node,
SerializedNode,
NodeInstance,
NodeDefinition,
Socket,
NodeType,
NodeId,
Edge,
Graph,
} from "./types";

View File

@@ -1,36 +1,35 @@
import { z } from "zod";
import { NodeInputSchema } from "./inputs";
export const NodeTypeSchema = z
export const NodeIdSchema = z
.string()
.regex(/^[^/]+\/[^/]+\/[^/]+$/, "Invalid NodeId format")
.transform((value) => value as `${string}/${string}/${string}`);
export type NodeType = z.infer<typeof NodeTypeSchema>;
export type NodeId = z.infer<typeof NodeIdSchema>;
export type Node = {
/**
* .tmp only exists at runtime
*/
tmp?: {
depth?: number;
mesh?: any;
parents?: Node[];
children?: Node[];
inputNodes?: Record<string, Node>;
type?: NodeDefinition;
downX?: number;
downY?: number;
x?: number;
y?: number;
ref?: HTMLElement;
visible?: boolean;
isMoving?: boolean;
};
} & z.infer<typeof NodeSchema>;
export type NodeRuntimeState = {
depth?: number;
mesh?: any;
parents?: NodeInstance[];
children?: NodeInstance[];
inputNodes?: Record<string, NodeInstance>;
type?: NodeDefinition;
downX?: number;
downY?: number;
x?: number;
y?: number;
ref?: HTMLElement;
visible?: boolean;
isMoving?: boolean;
};
export type NodeInstance = {
state: NodeRuntimeState;
} & SerializedNode;
export const NodeDefinitionSchema = z.object({
id: NodeTypeSchema,
id: NodeIdSchema,
inputs: z.record(z.string(), NodeInputSchema).optional(),
outputs: z.array(z.string()).optional(),
meta: z
@@ -43,7 +42,7 @@ export const NodeDefinitionSchema = z.object({
export const NodeSchema = z.object({
id: z.number(),
type: NodeTypeSchema,
type: NodeIdSchema,
props: z
.record(z.string(), z.union([z.number(), z.array(z.number())]))
.optional(),
@@ -56,17 +55,20 @@ export const NodeSchema = z.object({
position: z.tuple([z.number(), z.number()]),
});
export type SerializedNode = z.infer<typeof NodeSchema>;
export type NodeDefinition = z.infer<typeof NodeDefinitionSchema> & {
execute(input: Int32Array): Int32Array;
};
export type Socket = {
node: Node;
node: NodeInstance;
index: number | string;
position: [number, number];
};
export type Edge = [Node, number, Node, string];
export type Edge = [NodeInstance, number, NodeInstance, string];
export const GraphSchema = z.object({
id: z.number(),
@@ -81,4 +83,4 @@ export const GraphSchema = z.object({
edges: z.array(z.tuple([z.number(), z.number(), z.number(), z.string()])),
});
export type Graph = z.infer<typeof GraphSchema> & { nodes: Node[] };
export type Graph = z.infer<typeof GraphSchema>;

View File

@@ -1,44 +1,39 @@
<script lang="ts">
interface Props {
ctrl?: boolean;
shift?: boolean;
alt?: boolean;
key: string | string[];
}
interface Props {
ctrl?: boolean;
shift?: boolean;
alt?: boolean;
key: string | string[];
}
let {
ctrl = false,
shift = false,
alt = false,
key
}: Props = $props();
let { ctrl = false, shift = false, alt = false, key }: Props = $props();
</script>
<div class="command">
{#if ctrl}
<span>Ctrl</span>
{/if}
{#if shift}
<span>Shift</span>
{/if}
{#if alt}
<span>Alt</span>
{/if}
{key}
{#if ctrl}
<span>Ctrl</span>
{/if}
{#if shift}
<span>Shift</span>
{/if}
{#if alt}
<span>Alt</span>
{/if}
{key}
</div>
<style>
.command {
background: var(--layer-2);
padding: 0.4em;
font-size: 0.8em;
border-radius: 0.3em;
white-space: nowrap;
width: fit-content;
}
.command {
background: var(--layer-2);
padding: 0.4em;
font-size: 0.8em;
border-radius: 0.3em;
white-space: nowrap;
width: fit-content;
}
span::after {
content: " +";
opacity: 0.5;
}
span::after {
content: ' +';
opacity: 0.5;
}
</style>