chore: rename @nodes -> @nodarium for everything
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 3m33s

This commit is contained in:
Max Richter
2025-12-01 17:03:14 +01:00
parent e5658b8a7e
commit 1ea544e765
58 changed files with 909 additions and 882 deletions

View File

@@ -1,5 +1,5 @@
{ {
"name": "@nodes/app", "name": "@nodarium/app",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
@@ -10,9 +10,9 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@nodes/registry": "link:../packages/registry", "@nodarium/registry": "link:../packages/registry",
"@nodes/ui": "link:../packages/ui", "@nodarium/ui": "link:../packages/ui",
"@nodes/utils": "link:../packages/utils", "@nodarium/utils": "link:../packages/utils",
"@sveltejs/kit": "^2.49.0", "@sveltejs/kit": "^2.49.0",
"@threlte/core": "8.3.0", "@threlte/core": "8.3.0",
"@threlte/extras": "9.7.0", "@threlte/extras": "9.7.0",
@@ -26,7 +26,7 @@
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/tabler": "^1.2.23", "@iconify-json/tabler": "^1.2.23",
"@nodes/types": "link:../packages/types", "@nodarium/types": "link:../packages/types",
"@sveltejs/adapter-static": "^3.0.10", "@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/vite-plugin-svelte": "^6.2.1", "@sveltejs/vite-plugin-svelte": "^6.2.1",
"@tsconfig/svelte": "^5.0.6", "@tsconfig/svelte": "^5.0.6",

View File

@@ -1,19 +1,12 @@
<script lang="ts"> <script lang="ts">
import { HTML } from "@threlte/extras"; import { HTML } from "@threlte/extras";
import { onMount } from "svelte"; import { onMount } from "svelte";
import type { Node, NodeType } from "@nodes/types"; import type { Node, NodeType } from "@nodarium/types";
import { getGraphState } from "./graph/state.svelte"; import { getGraphManager, getGraphState } from "../graph/state.svelte";
import { getGraphManager } from "./graph/context";
type Props = {
position: [x: number, y: number] | null;
};
const graph = getGraphManager(); const graph = getGraphManager();
const graphState = getGraphState(); const graphState = getGraphState();
let { position = $bindable() }: Props = $props();
let input: HTMLInputElement; let input: HTMLInputElement;
let value = $state<string>(); let value = $state<string>();
let activeNodeId = $state<NodeType>(); let activeNodeId = $state<NodeType>();
@@ -41,11 +34,11 @@
}); });
function handleNodeCreation(nodeType: Node["type"]) { function handleNodeCreation(nodeType: Node["type"]) {
if (!position) return; if (!graphState.addMenuPosition) return;
const newNode = graph.createNode({ const newNode = graph.createNode({
type: nodeType, type: nodeType,
position, position: graphState.addMenuPosition,
props: {}, props: {},
}); });
@@ -59,14 +52,14 @@
} }
graphState.activeSocket = null; graphState.activeSocket = null;
position = null; graphState.addMenuPosition = null;
} }
function handleKeyDown(event: KeyboardEvent) { function handleKeyDown(event: KeyboardEvent) {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
if (event.key === "Escape") { if (event.key === "Escape") {
position = null; graphState.addMenuPosition = null;
return; return;
} }
@@ -83,7 +76,7 @@
} }
if (event.key === "Enter") { if (event.key === "Enter") {
if (activeNodeId && position) { if (activeNodeId && graphState.addMenuPosition) {
handleNodeCreation(activeNodeId); handleNodeCreation(activeNodeId);
} }
return; return;
@@ -96,7 +89,11 @@
}); });
</script> </script>
<HTML position.x={position?.[0]} position.z={position?.[1]} transform={false}> <HTML
position.x={graphState.addMenuPosition?.[0]}
position.z={graphState.addMenuPosition?.[1]}
transform={false}
>
<div class="add-menu-wrapper"> <div class="add-menu-wrapper">
<div class="header"> <div class="header">
<input <input

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { NodeDefinition, NodeRegistry } from "@nodes/types"; import type { NodeDefinition, NodeRegistry } from "@nodarium/types";
import { onDestroy, onMount } from "svelte"; import { onMount } from "svelte";
let mx = $state(0); let mx = $state(0);
let my = $state(0); let my = $state(0);

View File

@@ -12,7 +12,7 @@
}); });
}); });
const lineCache = new Map<number, BufferGeometry>(); // const lineCache = new Map<number, BufferGeometry>();
const curve = new CubicBezierCurve( const curve = new CubicBezierCurve(
new Vector2(0, 0), new Vector2(0, 0),
@@ -25,7 +25,7 @@
<script lang="ts"> <script lang="ts">
import { T } from "@threlte/core"; import { T } from "@threlte/core";
import { MeshLineMaterial } from "@threlte/extras"; import { MeshLineMaterial } from "@threlte/extras";
import { BufferGeometry, MeshBasicMaterial, Vector3 } from "three"; import { Mesh, MeshBasicMaterial, Vector3 } from "three";
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js"; import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
import { Vector2 } from "three/src/math/Vector2.js"; import { Vector2 } from "three/src/math/Vector2.js";
import { createEdgeGeometry } from "./createEdgeGeometry.js"; import { createEdgeGeometry } from "./createEdgeGeometry.js";
@@ -39,7 +39,7 @@
const { from, to, z }: Props = $props(); const { from, to, z }: Props = $props();
let geometry: BufferGeometry | null = $state(null); let mesh = $state<Mesh>();
const lineColor = $derived( const lineColor = $derived(
appSettings.value.theme && colors.edge.clone().convertSRGBToLinear(), appSettings.value.theme && colors.edge.clone().convertSRGBToLinear(),
@@ -58,13 +58,6 @@
return; return;
} }
const mid = new Vector2(new_x / 2, new_y / 2);
if (lineCache.has(curveId)) {
geometry = lineCache.get(curveId)!;
return;
}
const length = Math.floor( const length = Math.floor(
Math.sqrt(Math.pow(new_x, 2) + Math.pow(new_y, 2)) / 4, Math.sqrt(Math.pow(new_x, 2) + Math.pow(new_y, 2)) / 4,
); );
@@ -72,8 +65,8 @@
const samples = Math.max(length * 16, 10); const samples = Math.max(length * 16, 10);
curve.v0.set(0, 0); curve.v0.set(0, 0);
curve.v1.set(mid.x, 0); curve.v1.set(new_x / 2, 0);
curve.v2.set(mid.x, new_y); curve.v2.set(new_x / 2, new_y);
curve.v3.set(new_x, new_y); curve.v3.set(new_x, new_y);
const points = curve const points = curve
@@ -81,8 +74,9 @@
.map((p) => new Vector3(p.x, 0, p.y)) .map((p) => new Vector3(p.x, 0, p.y))
.flat(); .flat();
geometry = createEdgeGeometry(points); if (mesh) {
lineCache.set(curveId, geometry); mesh.geometry = createEdgeGeometry(points);
}
} }
$effect(() => { $effect(() => {
@@ -112,11 +106,11 @@
<T.CircleGeometry args={[0.5, 16]} /> <T.CircleGeometry args={[0.5, 16]} />
</T.Mesh> </T.Mesh>
{#if geometry} <T.Mesh
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}> bind:ref={mesh}
<MeshLineMaterial position.x={from.x}
width={Math.max(z * 0.00012, 0.00003)} position.z={from.y}
color={lineColor} position.y={0.1}
/> >
</T.Mesh> <MeshLineMaterial width={Math.max(z * 0.00012, 0.00003)} color={lineColor} />
{/if} </T.Mesh>

View File

@@ -37,8 +37,6 @@ export function createEdgeGeometry(points: Vector3[]) {
let indices: number[] = [] let indices: number[] = []
let indicesIndex = 0 let indicesIndex = 0
for (let j = 0; j < pointCount; j++) { for (let j = 0; j < pointCount; j++) {
const c = j / points.length const c = j / points.length
counters[counterIndex + 0] = c counters[counterIndex + 0] = c
@@ -73,8 +71,6 @@ export function createEdgeGeometry(points: Vector3[]) {
geometry.setAttribute('uv', new BufferAttribute(new Float32Array(uvArray), 2)) geometry.setAttribute('uv', new BufferAttribute(new Float32Array(uvArray), 2))
geometry.setIndex(new BufferAttribute(new Uint16Array(indices), 1)) geometry.setIndex(new BufferAttribute(new Uint16Array(indices), 1))
let positions: number[] = [] let positions: number[] = []
let previous: number[] = [] let previous: number[] = []
let next: number[] = [] let next: number[] = []

View File

@@ -7,11 +7,11 @@ import type {
NodeRegistry, NodeRegistry,
NodeType, NodeType,
Socket, Socket,
} from "@nodes/types"; } from "@nodarium/types";
import { fastHashString } from "@nodes/utils"; import { fastHashString } from "@nodarium/utils";
import { SvelteMap } from "svelte/reactivity"; import { SvelteMap } from "svelte/reactivity";
import EventEmitter from "./helpers/EventEmitter"; import EventEmitter from "./helpers/EventEmitter";
import { createLogger } from "@nodes/utils"; import { createLogger } from "@nodarium/utils";
import throttle from "$lib/helpers/throttle"; import throttle from "$lib/helpers/throttle";
import { HistoryManager } from "./history-manager"; import { HistoryManager } from "./history-manager";
@@ -33,6 +33,27 @@ function areSocketsCompatible(
return inputs === output; return inputs === output;
} }
function areEdgesEqual(firstEdge: Edge, secondEdge: Edge) {
if (firstEdge[0].id !== secondEdge[0].id) {
return false;
}
if (firstEdge[1] !== secondEdge[1]) {
return false
}
if (firstEdge[2].id !== secondEdge[2].id) {
return false
}
if (firstEdge[3] !== secondEdge[3]) {
return false
}
return true
}
export class GraphManager extends EventEmitter<{ export class GraphManager extends EventEmitter<{
save: Graph; save: Graph;
result: any; result: any;
@@ -119,7 +140,7 @@ export class GraphManager extends EventEmitter<{
const n = stack.pop(); const n = stack.pop();
if (!n) continue; if (!n) continue;
nodes.add(n); nodes.add(n);
const children = this.getChildrenOfNode(n); const children = this.getChildren(n);
const parents = this.getParentsOfNode(n); const parents = this.getParentsOfNode(n);
const newNodes = [...children, ...parents].filter((n) => !nodes.has(n)); const newNodes = [...children, ...parents].filter((n) => !nodes.has(n));
stack.push(...newNodes); stack.push(...newNodes);
@@ -273,7 +294,7 @@ export class GraphManager extends EventEmitter<{
return this.registry.getNode(id); return this.registry.getNode(id);
} }
async loadNode(id: NodeType) { async loadNodeType(id: NodeType) {
await this.registry.load([id]); await this.registry.load([id]);
const nodeType = this.registry.getNode(id); const nodeType = this.registry.getNode(id);
@@ -298,12 +319,11 @@ export class GraphManager extends EventEmitter<{
} }
this.settings = settingValues; this.settings = settingValues;
console.log("GraphManager.setSettings", settingValues);
this.settingTypes = settingTypes; this.settingTypes = settingTypes;
this.emit("settings", { types: settingTypes, values: settingValues }); this.emit("settings", { types: settingTypes, values: settingValues });
} }
getChildrenOfNode(node: Node) { getChildren(node: Node) {
const children = []; const children = [];
const stack = node.tmp?.children?.slice(0); const stack = node.tmp?.children?.slice(0);
while (stack?.length) { while (stack?.length) {
@@ -321,10 +341,10 @@ export class GraphManager extends EventEmitter<{
// < - - - - from - - - - to // < - - - - from - - - - to
const fromParents = this.getParentsOfNode(from); const fromParents = this.getParentsOfNode(from);
if (toParents.includes(from)) { if (toParents.includes(from)) {
const fromChildren = this.getChildrenOfNode(from); const fromChildren = this.getChildren(from);
return toParents.filter((n) => fromChildren.includes(n)); return toParents.filter((n) => fromChildren.includes(n));
} else if (fromParents.includes(to)) { } else if (fromParents.includes(to)) {
const toChildren = this.getChildrenOfNode(to); const toChildren = this.getChildren(to);
return fromParents.filter((n) => toChildren.includes(n)); return fromParents.filter((n) => toChildren.includes(n));
} else { } else {
// these two nodes are not connected // these two nodes are not connected
@@ -386,7 +406,7 @@ export class GraphManager extends EventEmitter<{
const idMap = new Map<number, number>(); const idMap = new Map<number, number>();
nodes = nodes.map((node, i) => { nodes = nodes.map((node) => {
const id = this.createNodeId(); const id = this.createNodeId();
idMap.set(node.id, id); idMap.set(node.id, id);
const type = this.registry.getNode(node.type); const type = this.registry.getNode(node.type);
@@ -463,8 +483,6 @@ export class GraphManager extends EventEmitter<{
{ applyUpdate = true } = {}, { applyUpdate = true } = {},
): Edge | undefined { ): Edge | undefined {
console.log("Create Edge", from.type, fromSocket, to.type, toSocket)
const existingEdges = this.getEdgesToNode(to); const existingEdges = this.getEdgesToNode(to);
// check if this exact edge already exists // check if this exact edge already exists
@@ -483,8 +501,6 @@ export class GraphManager extends EventEmitter<{
toSocketType.push(...(to?.tmp?.type?.inputs?.[toSocket]?.accepts || [])); toSocketType.push(...(to?.tmp?.type?.inputs?.[toSocket]?.accepts || []));
} }
console.log({ fromSocketType, toSocket, toType: to?.tmp?.type, toSocketType });
if (!areSocketsCompatible(fromSocketType, toSocketType)) { if (!areSocketsCompatible(fromSocketType, toSocketType)) {
logger.error( logger.error(
`Socket types do not match: ${fromSocketType} !== ${toSocketType}`, `Socket types do not match: ${fromSocketType} !== ${toSocketType}`,
@@ -576,23 +592,29 @@ export class GraphManager extends EventEmitter<{
return []; return [];
} }
if (typeof socket.index === "string") { const definitions = typeof socket.index === "string"
// if index is a string, we are an input looking for outputs ? allDefinitions.filter(s => {
return allDefinitions.filter(s => {
return s.outputs?.find(_s => Object return s.outputs?.find(_s => Object
.values(nodeType?.inputs || {}) .values(nodeType?.inputs || {})
.map(s => s.type) .map(s => s.type)
.includes(_s as NodeInput["type"]) .includes(_s as NodeInput["type"])
) )
}) })
} else { : allDefinitions.filter(s => Object
// if index is a number, we are an output looking for inputs
return allDefinitions.filter(s => Object
.values(s.inputs ?? {}) .values(s.inputs ?? {})
.map(s => s.type) .find(s => {
.find(s => nodeType?.outputs?.includes(s)) if (s.hidden) return false;
) if (nodeType.outputs?.includes(s.type)) {
return true
} }
return s.accepts?.find(a => nodeType.outputs?.includes(a))
}))
console.log(definitions.map(d => Object.values(d?.inputs ?? {})))
console.log(definitions)
return definitions
} }
getPossibleSockets({ node, index }: Socket): [Node, string | number][] { getPossibleSockets({ node, index }: Socket): [Node, string | number][] {
@@ -604,7 +626,7 @@ export class GraphManager extends EventEmitter<{
// if index is a string, we are an input looking for outputs // if index is a string, we are an input looking for outputs
if (typeof index === "string") { if (typeof index === "string") {
// filter out self and child nodes // filter out self and child nodes
const children = new Set(this.getChildrenOfNode(node).map((n) => n.id)); const children = new Set(this.getChildren(node).map((n) => n.id));
const nodes = this.getAllNodes().filter( const nodes = this.getAllNodes().filter(
(n) => n.id !== node.id && !children.has(n.id), (n) => n.id !== node.id && !children.has(n.id),
); );
@@ -672,6 +694,7 @@ export class GraphManager extends EventEmitter<{
(e) => (e) =>
e[0].id === id0 && e[1] === sid0 && e[2].id === id2 && e[3] === sid2, e[0].id === id0 && e[1] === sid0 && e[2].id === id2 && e[3] === sid2,
); );
if (!_edge) return; if (!_edge) return;
edge[0].tmp = edge[0].tmp || {}; edge[0].tmp = edge[0].tmp || {};
@@ -688,13 +711,12 @@ export class GraphManager extends EventEmitter<{
); );
} }
this.edges = this.edges.filter((e) => !areEdgesEqual(e, edge));
if (applyDeletion) { if (applyDeletion) {
this.edges = this.edges.filter((e) => e !== _edge);
this.execute(); this.execute();
this.save(); this.save();
} else {
this.edges = this.edges.filter((e) => e !== _edge);
} }
} }
getEdgesToNode(node: Node) { getEdgesToNode(node: Node) {

View File

@@ -1,261 +1,75 @@
<script lang="ts"> <script lang="ts">
import type { Node, NodeType, Socket } from "@nodes/types"; import type { Edge, Node, NodeType } from "@nodarium/types";
import { GraphSchema } from "@nodes/types"; import { GraphSchema } from "@nodarium/types";
import { getContext, onMount, setContext } from "svelte"; import { onMount } from "svelte";
import type { OrthographicCamera } from "three";
import { createKeyMap } from "../../helpers/createKeyMap"; import { createKeyMap } from "../../helpers/createKeyMap";
import AddMenu from "../AddMenu.svelte"; import AddMenu from "../components/AddMenu.svelte";
import Background from "../background/Background.svelte"; import Background from "../background/Background.svelte";
import BoxSelection from "../BoxSelection.svelte"; import BoxSelection from "../components/BoxSelection.svelte";
import Camera from "../Camera.svelte"; import EdgeEl from "../edges/Edge.svelte";
import NodeEl from "../node/Node.svelte";
import Camera from "../components/Camera.svelte";
import FloatingEdge from "../edges/FloatingEdge.svelte"; import FloatingEdge from "../edges/FloatingEdge.svelte";
import { import {
animate, animate,
lerp, lerp,
snapToGrid as snapPointToGrid, snapToGrid as snapPointToGrid,
} from "../helpers/index.js"; } from "../helpers/index.js";
import GraphView from "./GraphView.svelte";
import { getGraphState } from "./state.svelte";
import { Canvas } from "@threlte/core"; import { Canvas } from "@threlte/core";
import FileSaver from "file-saver"; import HelpView from "../components/HelpView.svelte";
import HelpView from "../HelpView.svelte"; import { getGraphManager, getGraphState } from "./state.svelte";
import { getGraphManager } from "./context"; import { HTML } from "@threlte/extras";
const graph = getGraphManager();
const graphState = getGraphState();
const { const {
snapToGrid = $bindable(true), snapToGrid = $bindable(true),
showGrid = $bindable(true), showGrid = $bindable(true),
showHelp = $bindable(false), showHelp = $bindable(false),
keymap,
}: {
snapToGrid: boolean;
showGrid: boolean;
showHelp: boolean;
keymap: ReturnType<typeof createKeyMap>;
} = $props(); } = $props();
const keymap = getContext<ReturnType<typeof createKeyMap>>("keymap");
let wrapper = $state<HTMLDivElement>(null!);
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 camera = $state<OrthographicCamera>(null!);
const minZoom = 1; const minZoom = 1;
const maxZoom = 40; const maxZoom = 40;
let mousePosition = $state([0, 0]); let mouseDownNodeId = -1;
let mouseDown = $state<[number, number] | null>(null);
let mouseDownId = -1;
let boxSelection = $state(false);
const cameraDown = [0, 0]; const cameraDown = [0, 0];
let cameraPosition: [number, number, number] = $state([0, 0, 4]);
let edgeEndPosition = $state<[number, number] | null>();
let addMenuPosition = $state<[number, number] | null>(null);
let clipboard: null | {
nodes: Node[];
edges: [number, number, number, string][];
} = null;
const cameraBounds = $derived([ let isPanning = $state(false);
cameraPosition[0] - width / cameraPosition[2] / 2, let isDragging = $state(false);
cameraPosition[0] + width / cameraPosition[2] / 2, let hoveredNodeId = $state(-1);
cameraPosition[1] - height / cameraPosition[2] / 2,
cameraPosition[1] + height / cameraPosition[2] / 2, const graph = getGraphManager();
]); const graphState = getGraphState();
function setCameraTransform(
x = cameraPosition[0], function getEdgeId(edge: Edge) {
y = cameraPosition[1], return `${edge[0].id}-${edge[1]}-${edge[2].id}-${edge[3]}`;
z = cameraPosition[2],
) {
if (camera) {
camera.position.x = x;
camera.position.z = y;
camera.zoom = z;
}
cameraPosition = [x, y, z];
localStorage.setItem("cameraPosition", JSON.stringify(cameraPosition));
} }
function updateNodePosition(node: Node) { function getEdgePosition(edge: Edge) {
if (node?.tmp?.ref && node?.tmp?.mesh) { const fromNode = graph.nodes.get(edge[0].id);
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) { const toNode = graph.nodes.get(edge[2].id);
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 + 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;
}
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`);
node.tmp.mesh.position.x = node.position[0] + 10;
node.tmp.mesh.position.z =
node.position[1] + getNodeHeight(node.type) / 2;
}
}
}
setContext("updateNodePosition", updateNodePosition);
const nodeHeightCache: Record<string, number> = {}; // This check is important because nodes might not be there during some transitions.
function getNodeHeight(nodeTypeId: string) { if (!fromNode || !toNode) {
if (nodeTypeId in nodeHeightCache) { return [0, 0, 0, 0];
return nodeHeightCache[nodeTypeId];
}
const node = graph.getNodeType(nodeTypeId);
if (!node?.inputs) {
return 5;
}
const height =
5 +
10 *
Object.keys(node.inputs).filter(
(p) =>
p !== "seed" &&
node?.inputs &&
!("setting" in node?.inputs?.[p]) &&
node.inputs[p].hidden !== true,
).length;
nodeHeightCache[nodeTypeId] = height;
return height;
}
setContext("getNodeHeight", getNodeHeight);
setContext("isNodeInView", (node: Node) => {
const height = getNodeHeight(node.type);
const width = 20;
return (
node.position[0] > cameraBounds[0] - width &&
node.position[0] < cameraBounds[1] &&
node.position[1] > cameraBounds[2] - height &&
node.position[1] < cameraBounds[3]
);
});
function getNodeIdFromEvent(event: MouseEvent) {
let clickedNodeId = -1;
let mx = event.clientX - rect.x;
let my = event.clientY - rect.y;
if (event.button === 0) {
// check if the clicked element is a node
if (event.target instanceof HTMLElement) {
const nodeElement = event.target.closest(".node");
const nodeId = nodeElement?.getAttribute?.("data-node-id");
if (nodeId) {
clickedNodeId = parseInt(nodeId, 10);
}
} }
// if we do not have an active node, const pos1 = graphState.getSocketPosition(fromNode, edge[1]);
// we are going to check if we clicked on a node by coordinates const pos2 = graphState.getSocketPosition(toNode, edge[3]);
if (clickedNodeId === -1) { return [pos1[0], pos1[1], pos2[0], pos2[1]];
const [downX, downY] = projectScreenToWorld(mx, my);
for (const node of graph.nodes.values()) {
const x = node.position[0];
const y = node.position[1];
const height = getNodeHeight(node.type);
if (downX > x && downX < x + 20 && downY > y && downY < y + height) {
clickedNodeId = node.id;
break;
}
}
}
}
return clickedNodeId;
}
setContext("setDownSocket", (socket: Socket) => {
graphState.activeSocket = socket;
let { node, index, position } = socket;
// remove existing edge
if (typeof index === "string") {
const edges = graph.getEdgesToNode(node);
for (const edge of edges) {
if (edge[3] === index) {
node = edge[0];
index = edge[1];
position = getSocketPosition(node, index);
graph.removeEdge(edge);
break;
}
}
}
mouseDown = position;
graphState.activeSocket = {
node,
index,
position,
};
graphState.possibleSockets = graph
.getPossibleSockets(graphState.activeSocket)
.map(([node, index]) => {
return {
node,
index,
position: getSocketPosition(node, index),
};
});
});
function getSnapLevel() {
const z = cameraPosition[2];
if (z > 66) {
return 8;
} else if (z > 55) {
return 4;
} else if (z > 11) {
return 2;
} else {
}
return 1;
}
function getSocketPosition(
node: Node,
index: string | number,
): [number, number] {
if (typeof index === "number") {
return [
(node?.tmp?.x ?? node.position[0]) + 20,
(node?.tmp?.y ?? node.position[1]) + 2.5 + 10 * index,
];
} else {
const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index);
return [
node?.tmp?.x ?? node.position[0],
(node?.tmp?.y ?? node.position[1]) + 10 + 10 * _index,
];
}
}
setContext("getSocketPosition", getSocketPosition);
function projectScreenToWorld(x: number, y: number): [number, number] {
return [
cameraPosition[0] + (x - width / 2) / cameraPosition[2],
cameraPosition[1] + (y - height / 2) / cameraPosition[2],
];
} }
function handleMouseMove(event: MouseEvent) { function handleMouseMove(event: MouseEvent) {
let mx = event.clientX - rect.x; let mx = event.clientX - graphState.rect.x;
let my = event.clientY - rect.y; let my = event.clientY - graphState.rect.y;
mousePosition = projectScreenToWorld(mx, my); graphState.mousePosition = graphState.projectScreenToWorld(mx, my);
hoveredNodeId = getNodeIdFromEvent(event); hoveredNodeId = graphState.getNodeIdFromEvent(event);
if (!mouseDown) return; if (!graphState.mouseDown) return;
// we are creating a new edge here // we are creating a new edge here
if (graphState.activeSocket || graphState.possibleSockets?.length) { if (graphState.activeSocket || graphState.possibleSockets?.length) {
@@ -263,8 +77,8 @@
let _socket; let _socket;
for (const socket of graphState.possibleSockets) { for (const socket of graphState.possibleSockets) {
const dist = Math.sqrt( const dist = Math.sqrt(
(socket.position[0] - mousePosition[0]) ** 2 + (socket.position[0] - graphState.mousePosition[0]) ** 2 +
(socket.position[1] - mousePosition[1]) ** 2, (socket.position[1] - graphState.mousePosition[1]) ** 2,
); );
if (dist < smallestDist) { if (dist < smallestDist) {
smallestDist = dist; smallestDist = dist;
@@ -273,7 +87,7 @@
} }
if (_socket && smallestDist < 0.9) { if (_socket && smallestDist < 0.9) {
mousePosition = _socket.position; graphState.mousePosition = _socket.position;
graphState.hoveredSocket = _socket; graphState.hoveredSocket = _socket;
} else { } else {
graphState.hoveredSocket = null; graphState.hoveredSocket = null;
@@ -282,19 +96,22 @@
} }
// handle box selection // handle box selection
if (boxSelection) { if (graphState.boxSelection) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
const mouseD = projectScreenToWorld(mouseDown[0], mouseDown[1]); const mouseD = graphState.projectScreenToWorld(
const x1 = Math.min(mouseD[0], mousePosition[0]); graphState.mouseDown[0],
const x2 = Math.max(mouseD[0], mousePosition[0]); graphState.mouseDown[1],
const y1 = Math.min(mouseD[1], mousePosition[1]); );
const y2 = Math.max(mouseD[1], mousePosition[1]); const x1 = Math.min(mouseD[0], graphState.mousePosition[0]);
const x2 = Math.max(mouseD[0], graphState.mousePosition[0]);
const y1 = Math.min(mouseD[1], graphState.mousePosition[1]);
const y2 = Math.max(mouseD[1], graphState.mousePosition[1]);
for (const node of graph.nodes.values()) { for (const node of graph.nodes.values()) {
if (!node?.tmp) continue; if (!node?.tmp) continue;
const x = node.position[0]; const x = node.position[0];
const y = node.position[1]; const y = node.position[1];
const height = getNodeHeight(node.type); const height = graphState.getNodeHeight(node.type);
if (x > x1 - 20 && x < x2 && y > y1 - height && y < y2) { if (x > x1 - 20 && x < x2 && y > y1 - height && y < y2) {
graphState.selectedNodes?.add(node.id); graphState.selectedNodes?.add(node.id);
} else { } else {
@@ -305,7 +122,7 @@
} }
// here we are handling dragging of nodes // here we are handling dragging of nodes
if (graphState.activeNodeId !== -1 && mouseDownId !== -1) { if (graphState.activeNodeId !== -1 && mouseDownNodeId !== -1) {
const node = graph.getNode(graphState.activeNodeId); const node = graph.getNode(graphState.activeNodeId);
if (!node || event.buttons !== 1) return; if (!node || event.buttons !== 1) return;
@@ -314,11 +131,13 @@
const oldX = node.tmp.downX || 0; const oldX = node.tmp.downX || 0;
const oldY = node.tmp.downY || 0; const oldY = node.tmp.downY || 0;
let newX = oldX + (mx - mouseDown[0]) / cameraPosition[2]; let newX =
let newY = oldY + (my - mouseDown[1]) / cameraPosition[2]; oldX + (mx - graphState.mouseDown[0]) / graphState.cameraPosition[2];
let newY =
oldY + (my - graphState.mouseDown[1]) / graphState.cameraPosition[2];
if (event.ctrlKey) { if (event.ctrlKey) {
const snapLevel = getSnapLevel(); const snapLevel = graphState.getSnapLevel();
if (snapToGrid) { if (snapToGrid) {
newX = snapPointToGrid(newX, 5 / snapLevel); newX = snapPointToGrid(newX, 5 / snapLevel);
newY = snapPointToGrid(newY, 5 / snapLevel); newY = snapPointToGrid(newY, 5 / snapLevel);
@@ -341,31 +160,35 @@
if (!n?.tmp) continue; if (!n?.tmp) continue;
n.tmp.x = (n?.tmp?.downX || 0) - vecX; n.tmp.x = (n?.tmp?.downX || 0) - vecX;
n.tmp.y = (n?.tmp?.downY || 0) - vecY; n.tmp.y = (n?.tmp?.downY || 0) - vecY;
updateNodePosition(n); graphState.updateNodePosition(n);
} }
} }
node.tmp.x = newX; node.tmp.x = newX;
node.tmp.y = newY; node.tmp.y = newY;
updateNodePosition(node); graphState.updateNodePosition(node);
return; return;
} }
// here we are handling panning of camera // here we are handling panning of camera
isPanning = true; isPanning = true;
let newX = cameraDown[0] - (mx - mouseDown[0]) / cameraPosition[2]; let newX =
let newY = cameraDown[1] - (my - mouseDown[1]) / cameraPosition[2]; cameraDown[0] -
(mx - graphState.mouseDown[0]) / graphState.cameraPosition[2];
let newY =
cameraDown[1] -
(my - graphState.mouseDown[1]) / graphState.cameraPosition[2];
setCameraTransform(newX, newY, cameraPosition[2]); graphState.setCameraTransform(newX, newY);
} }
const zoomSpeed = 2; const zoomSpeed = 2;
function handleMouseScroll(event: WheelEvent) { function handleMouseScroll(event: WheelEvent) {
const bodyIsFocused = const bodyIsFocused =
document.activeElement === document.body || document.activeElement === document.body ||
document.activeElement === wrapper || document.activeElement === graphState.wrapper ||
document?.activeElement?.id === "graph"; document?.activeElement?.id === "graph";
if (!bodyIsFocused) return; if (!bodyIsFocused) return;
@@ -379,23 +202,30 @@
minZoom, minZoom,
Math.min( Math.min(
maxZoom, maxZoom,
isNegative ? cameraPosition[2] / delta : cameraPosition[2] * delta, isNegative
? graphState.cameraPosition[2] / delta
: graphState.cameraPosition[2] * delta,
), ),
); );
// Calculate the ratio of the new zoom to the original zoom // Calculate the ratio of the new zoom to the original zoom
const zoomRatio = newZoom / cameraPosition[2]; const zoomRatio = newZoom / graphState.cameraPosition[2];
// Update camera position and zoom level // Update camera position and zoom level
setCameraTransform( graphState.setCameraTransform(
mousePosition[0] - (mousePosition[0] - cameraPosition[0]) / zoomRatio, graphState.mousePosition[0] -
mousePosition[1] - (mousePosition[1] - cameraPosition[1]) / zoomRatio, (graphState.mousePosition[0] - graphState.cameraPosition[0]) /
zoomRatio,
graphState.mousePosition[1] -
(graphState.mousePosition[1] - graphState.cameraPosition[1]) /
zoomRatio,
newZoom, newZoom,
); );
} }
function handleMouseDown(event: MouseEvent) { function handleMouseDown(event: MouseEvent) {
if (mouseDown) return; if (graphState.mouseDown) return;
graphState.edgeEndPosition = null;
if (event.target instanceof HTMLElement) { if (event.target instanceof HTMLElement) {
if ( if (
@@ -407,15 +237,15 @@
} }
} }
let mx = event.clientX - rect.x; let mx = event.clientX - graphState.rect.x;
let my = event.clientY - rect.y; let my = event.clientY - graphState.rect.y;
mouseDown = [mx, my]; graphState.mouseDown = [mx, my];
cameraDown[0] = cameraPosition[0]; cameraDown[0] = graphState.cameraPosition[0];
cameraDown[1] = cameraPosition[1]; cameraDown[1] = graphState.cameraPosition[1];
const clickedNodeId = getNodeIdFromEvent(event); const clickedNodeId = graphState.getNodeIdFromEvent(event);
mouseDownId = clickedNodeId; mouseDownNodeId = clickedNodeId;
// if we clicked on a node // if we clicked on a node
if (clickedNodeId !== -1) { if (clickedNodeId !== -1) {
@@ -448,7 +278,7 @@
graphState.clearSelection(); graphState.clearSelection();
} }
} else if (event.ctrlKey) { } else if (event.ctrlKey) {
boxSelection = true; graphState.boxSelection = true;
} }
const node = graph.getNode(graphState.activeNodeId); const node = graph.getNode(graphState.activeNodeId);
@@ -467,243 +297,16 @@
} }
} }
edgeEndPosition = null; graphState.edgeEndPosition = null;
} }
function copyNodes() {
if (graphState.activeNodeId === -1 && !graphState.selectedNodes?.size)
return;
let _nodes = [
graphState.activeNodeId,
...(graphState.selectedNodes?.values() || []),
]
.map((id) => graph.getNode(id))
.filter(Boolean) as Node[];
const _edges = graph.getEdgesBetweenNodes(_nodes);
_nodes = $state.snapshot(
_nodes.map((_node) => ({
..._node,
tmp: {
downX: mousePosition[0] - _node.position[0],
downY: mousePosition[1] - _node.position[1],
},
})),
);
clipboard = {
nodes: _nodes,
edges: _edges,
};
}
function pasteNodes() {
if (!clipboard) return;
const _nodes = clipboard.nodes
.map((node) => {
node.tmp = node.tmp || {};
node.position[0] = mousePosition[0] - (node?.tmp?.downX || 0);
node.position[1] = mousePosition[1] - (node?.tmp?.downY || 0);
return node;
})
.filter(Boolean) as Node[];
const newNodes = graph.createGraph(_nodes, clipboard.edges);
graphState.selectedNodes.clear();
for (const node of newNodes) {
graphState.selectedNodes.add(node.id);
}
}
const isBodyFocused = () => document?.activeElement?.nodeName !== "INPUT";
keymap.addShortcut({
key: "l",
description: "Select linked nodes",
callback: () => {
const activeNode = graph.getNode(graphState.activeNodeId);
if (activeNode) {
const nodes = graph.getLinkedNodes(activeNode);
graphState.selectedNodes.clear();
for (const node of nodes) {
graphState.selectedNodes.add(node.id);
}
}
},
});
keymap.addShortcut({
key: "f",
description: "Smart Connect Nodes",
callback: () => {
const nodes = [...graphState.selectedNodes.values()]
.map((g) => graph.getNode(g))
.filter((n) => !!n);
const edge = graph.smartConnect(nodes[0], nodes[1]);
if (!edge) graph.smartConnect(nodes[1], nodes[0]);
},
});
keymap.addShortcut({
key: "?",
description: "Toggle Help",
callback: () => {
// TODO: fix this
// showHelp = !showHelp;
},
});
keymap.addShortcut({
key: "c",
ctrl: true,
description: "Copy active nodes",
callback: copyNodes,
});
keymap.addShortcut({
key: "v",
ctrl: true,
description: "Paste nodes",
callback: pasteNodes,
});
keymap.addShortcut({
key: "Escape",
description: "Deselect nodes",
callback: () => {
graphState.activeNodeId = -1;
graphState.clearSelection();
edgeEndPosition = null;
(document.activeElement as HTMLElement)?.blur();
},
});
keymap.addShortcut({
key: "A",
shift: true,
description: "Add new Node",
callback: () => {
addMenuPosition = [mousePosition[0], mousePosition[1]];
},
});
keymap.addShortcut({
key: ".",
description: "Center camera",
callback: () => {
if (!isBodyFocused()) return;
const average = [0, 0];
for (const node of graph.nodes.values()) {
average[0] += node.position[0];
average[1] += node.position[1];
}
average[0] = average[0] ? average[0] / graph.nodes.size : 0;
average[1] = average[1] ? average[1] / graph.nodes.size : 0;
const camX = cameraPosition[0];
const camY = cameraPosition[1];
const camZ = cameraPosition[2];
const ease = (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);
animate(500, (a: number) => {
setCameraTransform(
lerp(camX, average[0], ease(a)),
lerp(camY, average[1], ease(a)),
lerp(camZ, 2, ease(a)),
);
if (mouseDown) return false;
});
},
});
keymap.addShortcut({
key: "a",
ctrl: true,
preventDefault: true,
description: "Select all nodes",
callback: () => {
if (!isBodyFocused()) return;
for (const node of graph.nodes.keys()) {
graphState.selectedNodes.add(node);
}
},
});
keymap.addShortcut({
key: "z",
ctrl: true,
description: "Undo",
callback: () => {
if (!isBodyFocused()) return;
graph.undo();
for (const node of graph.nodes.values()) {
updateNodePosition(node);
}
},
});
keymap.addShortcut({
key: "y",
ctrl: true,
description: "Redo",
callback: () => {
graph.redo();
for (const node of graph.nodes.values()) {
updateNodePosition(node);
}
},
});
keymap.addShortcut({
key: "s",
ctrl: true,
description: "Save",
preventDefault: true,
callback: () => {
const state = graph.serialize();
const blob = new Blob([JSON.stringify(state)], {
type: "application/json;charset=utf-8",
});
FileSaver.saveAs(blob, "nodarium-graph.json");
},
});
keymap.addShortcut({
key: ["Delete", "Backspace", "x"],
description: "Delete selected nodes",
callback: (event) => {
if (!isBodyFocused()) return;
graph.startUndoGroup();
if (graphState.activeNodeId !== -1) {
const node = graph.getNode(graphState.activeNodeId);
if (node) {
graph.removeNode(node, { restoreEdges: event.ctrlKey });
graphState.activeNodeId = -1;
}
}
if (graphState.selectedNodes) {
for (const nodeId of graphState.selectedNodes) {
const node = graph.getNode(nodeId);
if (node) {
graph.removeNode(node, { restoreEdges: event.ctrlKey });
}
}
graphState.clearSelection();
}
graph.saveUndoGroup();
},
});
function handleMouseUp(event: MouseEvent) { function handleMouseUp(event: MouseEvent) {
isPanning = false; isPanning = false;
if (!mouseDown) return; if (!graphState.mouseDown) return;
const activeNode = graph.getNode(graphState.activeNodeId); const activeNode = graph.getNode(graphState.activeNodeId);
const clickedNodeId = getNodeIdFromEvent(event); const clickedNodeId = graphState.getNodeIdFromEvent(event);
if (clickedNodeId !== -1) { if (clickedNodeId !== -1) {
if (activeNode) { if (activeNode) {
@@ -718,7 +321,7 @@
activeNode.tmp = activeNode.tmp || {}; activeNode.tmp = activeNode.tmp || {};
activeNode.tmp.isMoving = false; activeNode.tmp.isMoving = false;
if (snapToGrid) { if (snapToGrid) {
const snapLevel = getSnapLevel(); const snapLevel = graphState.getSnapLevel();
activeNode.position[0] = snapPointToGrid( activeNode.position[0] = snapPointToGrid(
activeNode?.tmp?.x ?? activeNode.position[0], activeNode?.tmp?.x ?? activeNode.position[0],
5 / snapLevel, 5 / snapLevel,
@@ -761,7 +364,7 @@
) { ) {
node.tmp.x = lerp(node.tmp.x, node.position[0], a); node.tmp.x = lerp(node.tmp.x, node.position[0], a);
node.tmp.y = lerp(node.tmp.y, node.position[1], a); node.tmp.y = lerp(node.tmp.y, node.position[1], a);
updateNodePosition(node); graphState.updateNodePosition(node);
if (node?.tmp?.isMoving) { if (node?.tmp?.isMoving) {
return false; return false;
} }
@@ -794,12 +397,21 @@
graph.save(); graph.save();
} else if (graphState.activeSocket && event.ctrlKey) { } else if (graphState.activeSocket && event.ctrlKey) {
// Handle automatic adding of nodes on ctrl+mouseUp // Handle automatic adding of nodes on ctrl+mouseUp
edgeEndPosition = [mousePosition[0], mousePosition[1]]; graphState.edgeEndPosition = [
graphState.mousePosition[0],
graphState.mousePosition[1],
];
if (typeof graphState.activeSocket.index === "number") { if (typeof graphState.activeSocket.index === "number") {
addMenuPosition = [mousePosition[0], mousePosition[1] - 3]; graphState.addMenuPosition = [
graphState.mousePosition[0],
graphState.mousePosition[1] - 3,
];
} else { } else {
addMenuPosition = [mousePosition[0] - 20, mousePosition[1] - 3]; graphState.addMenuPosition = [
graphState.mousePosition[0] - 20,
graphState.mousePosition[1] - 3,
];
} }
return; return;
} }
@@ -807,27 +419,23 @@
// check if camera moved // check if camera moved
if ( if (
clickedNodeId === -1 && clickedNodeId === -1 &&
!boxSelection && !graphState.boxSelection &&
cameraDown[0] === cameraPosition[0] && cameraDown[0] === graphState.cameraPosition[0] &&
cameraDown[1] === cameraPosition[1] && cameraDown[1] === graphState.cameraPosition[1] &&
isBodyFocused() graphState.isBodyFocused()
) { ) {
graphState.activeNodeId = -1; graphState.activeNodeId = -1;
graphState.clearSelection(); graphState.clearSelection();
} }
mouseDown = null; graphState.mouseDown = null;
boxSelection = false; graphState.boxSelection = false;
graphState.activeSocket = null; graphState.activeSocket = null;
graphState.possibleSockets = []; graphState.possibleSockets = [];
graphState.hoveredSocket = null; graphState.hoveredSocket = null;
addMenuPosition = null; graphState.addMenuPosition = null;
} }
let isPanning = $state(false);
let isDragging = $state(false);
let hoveredNodeId = $state(-1);
function handleMouseLeave() { function handleMouseLeave() {
isDragging = false; isDragging = false;
isPanning = false; isPanning = false;
@@ -838,8 +446,8 @@
isDragging = false; isDragging = false;
if (!event.dataTransfer) return; if (!event.dataTransfer) return;
const nodeId = event.dataTransfer.getData("data/node-id") as NodeType; const nodeId = event.dataTransfer.getData("data/node-id") as NodeType;
let mx = event.clientX - rect.x; let mx = event.clientX - graphState.rect.x;
let my = event.clientY - rect.y; let my = event.clientY - graphState.rect.y;
if (nodeId) { if (nodeId) {
let nodeOffsetX = event.dataTransfer.getData("data/node-offset-x"); let nodeOffsetX = event.dataTransfer.getData("data/node-offset-x");
@@ -857,7 +465,7 @@
} catch (e) {} } catch (e) {}
} }
const pos = projectScreenToWorld(mx, my); const pos = graphState.projectScreenToWorld(mx, my);
graph.registry.load([nodeId]).then(() => { graph.registry.load([nodeId]).then(() => {
graph.createNode({ graph.createNode({
type: nodeId, type: nodeId,
@@ -878,7 +486,7 @@
graph.createNode({ graph.createNode({
type: nodeType.id, type: nodeType.id,
props: {}, props: {},
position: projectScreenToWorld(mx, my), position: graphState.projectScreenToWorld(mx, my),
}); });
} }
}; };
@@ -919,7 +527,7 @@
if (localStorage.getItem("cameraPosition")) { if (localStorage.getItem("cameraPosition")) {
const cPosition = JSON.parse(localStorage.getItem("cameraPosition")!); const cPosition = JSON.parse(localStorage.getItem("cameraPosition")!);
if (Array.isArray(cPosition)) { if (Array.isArray(cPosition)) {
setCameraTransform(cPosition[0], cPosition[1], cPosition[2]); graphState.setCameraTransform(cPosition[0], cPosition[1], cPosition[2]);
} }
} }
}); });
@@ -929,15 +537,15 @@
<div <div
onwheel={handleMouseScroll} onwheel={handleMouseScroll}
bind:this={wrapper} bind:this={graphState.wrapper}
class="graph-wrapper" class="graph-wrapper"
class:is-panning={isPanning} class:is-panning={isPanning}
class:is-hovering={hoveredNodeId !== -1} class:is-hovering={hoveredNodeId !== -1}
aria-label="Graph" aria-label="Graph"
role="button" role="button"
tabindex="0" tabindex="0"
bind:clientWidth={width} bind:clientWidth={graphState.width}
bind:clientHeight={height} bind:clientHeight={graphState.height}
ondragenter={handleDragEnter} ondragenter={handleDragEnter}
ondragover={handlerDragOver} ondragover={handlerDragOver}
ondragexit={handleDragEnd} ondragexit={handleDragEnd}
@@ -957,44 +565,90 @@
<label for="drop-zone"></label> <label for="drop-zone"></label>
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}> <Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
<Camera bind:camera position={cameraPosition} /> <Camera
bind:camera={graphState.camera}
position={graphState.cameraPosition}
/>
{#if showGrid !== false} {#if showGrid !== false}
<Background {cameraPosition} {maxZoom} {minZoom} {width} {height} /> <Background
cameraPosition={graphState.cameraPosition}
{maxZoom}
{minZoom}
width={graphState.width}
height={graphState.height}
/>
{/if} {/if}
{#if boxSelection && mouseDown} {#if graphState.boxSelection && graphState.mouseDown}
<BoxSelection <BoxSelection
{cameraPosition} cameraPosition={graphState.cameraPosition}
p1={{ p1={{
x: cameraPosition[0] + (mouseDown[0] - width / 2) / cameraPosition[2], x:
graphState.cameraPosition[0] +
(graphState.mouseDown[0] - graphState.width / 2) /
graphState.cameraPosition[2],
y: y:
cameraPosition[1] + (mouseDown[1] - height / 2) / cameraPosition[2], graphState.cameraPosition[1] +
(graphState.mouseDown[1] - graphState.height / 2) /
graphState.cameraPosition[2],
}} }}
p2={{ x: mousePosition[0], y: mousePosition[1] }} p2={{ x: graphState.mousePosition[0], y: graphState.mousePosition[1] }}
/> />
{/if} {/if}
{#if graph.status === "idle"} {#if graph.status === "idle"}
{#if addMenuPosition} {#if graphState.addMenuPosition}
<AddMenu bind:position={addMenuPosition} /> <AddMenu />
{/if} {/if}
{#if graphState.activeSocket} {#if graphState.activeSocket}
<FloatingEdge <FloatingEdge
z={cameraPosition[2]} z={graphState.cameraPosition[2]}
from={{ from={{
x: graphState.activeSocket.position[0], x: graphState.activeSocket.position[0],
y: graphState.activeSocket.position[1], y: graphState.activeSocket.position[1],
}} }}
to={{ to={{
x: edgeEndPosition?.[0] ?? mousePosition[0], x: graphState.edgeEndPosition?.[0] ?? graphState.mousePosition[0],
y: edgeEndPosition?.[1] ?? mousePosition[1], y: graphState.edgeEndPosition?.[1] ?? graphState.mousePosition[1],
}} }}
/> />
{/if} {/if}
<GraphView nodes={graph.nodes} edges={graph.edges} {cameraPosition} /> {#each graph.edges as edge (getEdgeId(edge))}
{@const [x1, y1, x2, y2] = getEdgePosition(edge)}
<EdgeEl
z={graphState.cameraPosition[2]}
from={{
x: x1,
y: y1,
}}
to={{
x: x2,
y: y2,
}}
/>
{/each}
<HTML transform={false}>
<div
role="tree"
id="graph"
tabindex="0"
class="wrapper"
style:transform={`scale(${graphState.cameraPosition[2] * 0.1})`}
class:hovering-sockets={graphState.activeSocket}
>
{#each graph.nodes.values() as node (node.id)}
<NodeEl
{node}
inView={graphState.isNodeInView(node)}
z={graphState.cameraPosition[2]}
/>
{/each}
</div>
</HTML>
{:else if graph.status === "loading"} {:else if graph.status === "loading"}
<span>Loading</span> <span>Loading</span>
{:else if graph.status === "error"} {:else if graph.status === "error"}
@@ -1015,6 +669,13 @@
height: 100%; height: 100%;
} }
.wrapper {
position: absolute;
z-index: 100;
width: 0px;
height: 0px;
}
.is-hovering { .is-hovering {
cursor: pointer; cursor: pointer;
} }

View File

@@ -1,102 +0,0 @@
<script lang="ts">
import type { Edge as EdgeType, Node as NodeType } from "@nodes/types";
import { HTML } from "@threlte/extras";
import Edge from "../edges/Edge.svelte";
import Node from "../node/Node.svelte";
import { getContext, onMount } from "svelte";
import { getGraphState } from "./state.svelte";
import { useThrelte } from "@threlte/core";
import { appSettings } from "$lib/settings/app-settings.svelte";
type Props = {
nodes: Map<number, NodeType>;
edges: EdgeType[];
cameraPosition: [number, number, number];
};
const { nodes, edges, cameraPosition = [0, 0, 4] }: Props = $props();
const { invalidate } = useThrelte();
$effect(() => {
appSettings.value.theme;
invalidate();
});
const graphState = getGraphState();
const isNodeInView = getContext<(n: NodeType) => boolean>("isNodeInView");
const getSocketPosition =
getContext<(node: NodeType, index: string | number) => [number, number]>(
"getSocketPosition",
);
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]];
}),
);
onMount(() => {
for (const node of nodes.values()) {
if (node?.tmp?.ref) {
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
}
}
});
</script>
{#each edgePositions as edge (`${edge.join("-")}`)}
{@const [x1, y1, x2, y2] = edge}
<Edge
z={cameraPosition[2]}
from={{
x: x1,
y: y1,
}}
to={{
x: x2,
y: y2,
}}
/>
{/each}
<HTML transform={false}>
<div
role="tree"
id="graph"
tabindex="0"
class="wrapper"
style:transform={`scale(${cameraPosition[2] * 0.1})`}
class:hovering-sockets={graphState.activeSocket}
>
{#each nodes.values() as node (node.id)}
<Node
{node}
inView={cameraPosition && isNodeInView(node)}
z={cameraPosition[2]}
/>
{/each}
</div>
</HTML>
<style>
.wrapper {
position: absolute;
z-index: 100;
width: 0px;
height: 0px;
}
</style>

View File

@@ -1,14 +1,10 @@
<script lang="ts"> <script lang="ts">
import type { Graph, Node, NodeRegistry } from "@nodes/types"; import type { Graph, Node, NodeRegistry } from "@nodarium/types";
import GraphEl from "./Graph.svelte"; import GraphEl from "./Graph.svelte";
import { GraphManager } from "../graph-manager.svelte"; import { GraphManager } from "../graph-manager.svelte";
import { setContext } from "svelte";
import { debounce } from "$lib/helpers";
import { createKeyMap } from "$lib/helpers/createKeyMap"; import { createKeyMap } from "$lib/helpers/createKeyMap";
import { GraphState } from "./state.svelte"; import { GraphState, setGraphManager, setGraphState } from "./state.svelte";
import { setupKeymaps } from "../keymaps";
const graphState = new GraphState();
setContext("graphState", graphState);
type Props = { type Props = {
graph: Graph; graph: Graph;
@@ -31,8 +27,8 @@
registry, registry,
settings = $bindable(), settings = $bindable(),
activeNode = $bindable(), activeNode = $bindable(),
showGrid, showGrid = $bindable(true),
snapToGrid, snapToGrid = $bindable(true),
showHelp = $bindable(false), showHelp = $bindable(false),
settingTypes = $bindable(), settingTypes = $bindable(),
onsave, onsave,
@@ -40,10 +36,14 @@
}: Props = $props(); }: Props = $props();
export const keymap = createKeyMap([]); export const keymap = createKeyMap([]);
setContext("keymap", keymap);
export const manager = new GraphManager(registry); export const manager = new GraphManager(registry);
setContext("graphManager", manager); setGraphManager(manager);
const graphState = new GraphState(manager);
setGraphState(graphState);
setupKeymaps(keymap, manager, graphState);
$effect(() => { $effect(() => {
if (graphState.activeNodeId !== -1) { if (graphState.activeNodeId !== -1) {
@@ -53,13 +53,16 @@
} }
}); });
const updateSettings = debounce((s: Record<string, any>) => { $effect(() => {
manager.setSettings(s); if (!graphState.addMenuPosition) {
}, 200); graphState.edgeEndPosition = null;
graphState.activeSocket = null;
}
});
$effect(() => { $effect(() => {
if (settingTypes && settings) { if (settingTypes && settings) {
updateSettings(settings); manager.setSettings(settings);
} }
}); });
@@ -75,4 +78,4 @@
manager.load(graph); manager.load(graph);
</script> </script>
<GraphEl bind:showGrid bind:snapToGrid bind:showHelp /> <GraphEl {keymap} bind:showGrid bind:snapToGrid bind:showHelp />

View File

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

View File

@@ -1,12 +1,59 @@
import type { Socket } from "@nodes/types"; import type { Node, Socket } from "@nodarium/types";
import { getContext } from "svelte"; import { getContext, setContext } from "svelte";
import { SvelteSet } from "svelte/reactivity"; import { SvelteSet } from "svelte/reactivity";
import type { GraphManager } from "../graph-manager.svelte";
import type { OrthographicCamera } from "three";
const graphStateKey = Symbol("graph-state");
export function getGraphState() { export function getGraphState() {
return getContext<GraphState>("graphState"); return getContext<GraphState>(graphStateKey);
}
export function setGraphState(graphState: GraphState) {
return setContext(graphStateKey, graphState)
}
const graphManagerKey = Symbol("graph-manager");
export function getGraphManager() {
return getContext<GraphManager>(graphManagerKey)
}
export function setGraphManager(manager: GraphManager) {
return setContext(graphManagerKey, manager);
} }
export class GraphState { export class GraphState {
constructor(private graph: GraphManager) { }
cameraPosition: [number, number, number] = $state([0, 0, 4]);
wrapper = $state<HTMLDivElement>(null!);
rect: DOMRect = $derived(
this.wrapper ? this.wrapper.getBoundingClientRect() : new DOMRect(0, 0, 0, 0),
);
width = $derived(this.rect?.width ?? 100);
height = $derived(this.rect?.height ?? 100);
camera = $state<OrthographicCamera>(null!);
clipboard: null | {
nodes: Node[];
edges: [number, number, number, string][];
} = null;
cameraBounds = $derived([
this.cameraPosition[0] - this.width / this.cameraPosition[2] / 2,
this.cameraPosition[0] + this.width / this.cameraPosition[2] / 2,
this.cameraPosition[1] - this.height / this.cameraPosition[2] / 2,
this.cameraPosition[1] + this.height / this.cameraPosition[2] / 2,
]);
boxSelection = $state(false);
edgeEndPosition = $state<[number, number] | null>();
addMenuPosition = $state<[number, number] | null>(null);
mousePosition = $state([0, 0]);
mouseDown = $state<[number, number] | null>(null);
activeNodeId = $state(-1); activeNodeId = $state(-1);
selectedNodes = new SvelteSet<number>(); selectedNodes = new SvelteSet<number>();
activeSocket = $state<Socket | null>(null); activeSocket = $state<Socket | null>(null);
@@ -15,7 +62,269 @@ export class GraphState {
possibleSocketIds = $derived( possibleSocketIds = $derived(
new Set(this.possibleSockets.map((s) => `${s.node.id}-${s.index}`)), new Set(this.possibleSockets.map((s) => `${s.node.id}-${s.index}`)),
); );
clearSelection() { clearSelection() {
this.selectedNodes.clear(); this.selectedNodes.clear();
} }
isBodyFocused = () => document?.activeElement?.nodeName !== "INPUT";
setCameraTransform(
x = this.cameraPosition[0],
y = this.cameraPosition[1],
z = this.cameraPosition[2],
) {
if (this.camera) {
this.camera.position.x = x;
this.camera.position.z = y;
this.camera.zoom = z;
}
this.cameraPosition = [x, y, z];
localStorage.setItem("cameraPosition", JSON.stringify(this.cameraPosition));
}
updateNodePosition(node: Node) {
if (node?.tmp?.ref && node?.tmp?.mesh) {
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
node.tmp.mesh.position.x = node.tmp.x + 10;
node.tmp.mesh.position.z = node.tmp.y + this.getNodeHeight(node.type) / 2;
if (
node.tmp.x === node.position[0] &&
node.tmp.y === node.position[1]
) {
delete node.tmp.x;
delete node.tmp.y;
}
this.graph.edges = [...this.graph.edges];
} else {
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
node.tmp.mesh.position.x = node.position[0] + 10;
node.tmp.mesh.position.z =
node.position[1] + this.getNodeHeight(node.type) / 2;
}
}
}
getSnapLevel() {
const z = this.cameraPosition[2];
if (z > 66) {
return 8;
} else if (z > 55) {
return 4;
} else if (z > 11) {
return 2;
} else {
}
return 1;
}
getSocketPosition(
node: Node,
index: string | number,
): [number, number] {
if (typeof index === "number") {
return [
(node?.tmp?.x ?? node.position[0]) + 20,
(node?.tmp?.y ?? node.position[1]) + 2.5 + 10 * index,
];
} else {
const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index);
return [
node?.tmp?.x ?? node.position[0],
(node?.tmp?.y ?? node.position[1]) + 10 + 10 * _index,
];
}
}
private nodeHeightCache: Record<string, number> = {};
getNodeHeight(nodeTypeId: string) {
if (nodeTypeId in this.nodeHeightCache) {
return this.nodeHeightCache[nodeTypeId];
}
const node = this.graph.getNodeType(nodeTypeId);
if (!node?.inputs) {
return 5;
}
const height =
5 +
10 *
Object.keys(node.inputs).filter(
(p) =>
p !== "seed" &&
node?.inputs &&
!("setting" in node?.inputs?.[p]) &&
node.inputs[p].hidden !== true,
).length;
this.nodeHeightCache[nodeTypeId] = height;
return height;
}
setNodePosition(node: Node) {
if (node?.tmp?.ref && node?.tmp?.mesh) {
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
node.tmp.mesh.position.x = node.tmp.x + 10;
node.tmp.mesh.position.z = node.tmp.y + this.getNodeHeight(node.type) / 2;
if (
node.tmp.x === node.position[0] &&
node.tmp.y === node.position[1]
) {
delete node.tmp.x;
delete node.tmp.y;
}
this.graph.edges = [...this.graph.edges];
} else {
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
node.tmp.mesh.position.x = node.position[0] + 10;
node.tmp.mesh.position.z =
node.position[1] + this.getNodeHeight(node.type) / 2;
}
}
}
copyNodes() {
if (this.activeNodeId === -1 && !this.selectedNodes?.size)
return;
let _nodes = [
this.activeNodeId,
...(this.selectedNodes?.values() || []),
]
.map((id) => this.graph.getNode(id))
.filter(Boolean) as Node[];
const _edges = this.graph.getEdgesBetweenNodes(_nodes);
_nodes = $state.snapshot(
_nodes.map((_node) => ({
..._node,
tmp: {
downX: this.mousePosition[0] - _node.position[0],
downY: this.mousePosition[1] - _node.position[1],
},
})),
);
this.clipboard = {
nodes: _nodes,
edges: _edges,
};
}
pasteNodes() {
if (!this.clipboard) return;
const _nodes = this.clipboard.nodes
.map((node) => {
node.tmp = node.tmp || {};
node.position[0] = this.mousePosition[0] - (node?.tmp?.downX || 0);
node.position[1] = this.mousePosition[1] - (node?.tmp?.downY || 0);
return node;
})
.filter(Boolean) as Node[];
const newNodes = this.graph.createGraph(_nodes, this.clipboard.edges);
this.selectedNodes.clear();
for (const node of newNodes) {
this.selectedNodes.add(node.id);
}
}
setDownSocket(socket: Socket) {
this.activeSocket = socket;
let { node, index, position } = socket;
// remove existing edge
if (typeof index === "string") {
const edges = this.graph.getEdgesToNode(node);
for (const edge of edges) {
if (edge[3] === index) {
node = edge[0];
index = edge[1];
position = this.getSocketPosition(node, index);
this.graph.removeEdge(edge);
break;
}
}
}
this.mouseDown = position;
this.activeSocket = {
node,
index,
position,
};
this.possibleSockets = this.graph
.getPossibleSockets(this.activeSocket)
.map(([node, index]) => {
return {
node,
index,
position: this.getSocketPosition(node, index),
};
});
};
projectScreenToWorld(x: number, y: number): [number, number] {
return [
this.cameraPosition[0] +
(x - this.width / 2) / this.cameraPosition[2],
this.cameraPosition[1] +
(y - this.height / 2) / this.cameraPosition[2],
];
}
getNodeIdFromEvent(event: MouseEvent) {
let clickedNodeId = -1;
let mx = event.clientX - this.rect.x;
let my = event.clientY - this.rect.y;
if (event.button === 0) {
// check if the clicked element is a node
if (event.target instanceof HTMLElement) {
const nodeElement = event.target.closest(".node");
const nodeId = nodeElement?.getAttribute?.("data-node-id");
if (nodeId) {
clickedNodeId = parseInt(nodeId, 10);
}
}
// if we do not have an active node,
// we are going to check if we clicked on a node by coordinates
if (clickedNodeId === -1) {
const [downX, downY] = this.projectScreenToWorld(mx, my);
for (const node of this.graph.nodes.values()) {
const x = node.position[0];
const y = node.position[1];
const height = this.getNodeHeight(node.type);
if (downX > x && downX < x + 20 && downY > y && downY < y + height) {
clickedNodeId = node.id;
break;
}
}
}
}
return clickedNodeId;
}
isNodeInView(node: Node) {
const height = this.getNodeHeight(node.type);
const width = 20;
return (
node.position[0] > this.cameraBounds[0] - width &&
node.position[0] < this.cameraBounds[1] &&
node.position[1] > this.cameraBounds[2] - height &&
node.position[1] < this.cameraBounds[3]
);
};
} }

View File

@@ -1,10 +1,10 @@
import { create, type Delta } from "jsondiffpatch"; import { create, type Delta } from "jsondiffpatch";
import type { Graph } from "@nodes/types"; import type { Graph } from "@nodarium/types";
import { clone } from "./helpers/index.js"; import { clone } from "./helpers/index.js";
import { createLogger } from "@nodes/utils"; import { createLogger } from "@nodarium/utils";
const diff = create({ const diff = create({
objectHash: function(obj, index) { objectHash: function (obj, index) {
if (obj === null) return obj; if (obj === null) return obj;
if ("id" in obj) return obj.id as string; if ("id" in obj) return obj.id as string;
if ("_id" in obj) return obj._id as string; if ("_id" in obj) return obj._id as string;

View File

@@ -0,0 +1,192 @@
import { animate, lerp } from "$lib/helpers";
import type { createKeyMap } from "$lib/helpers/createKeyMap";
import FileSaver from "file-saver";
import type { GraphManager } from "./graph-manager.svelte";
import type { GraphState } from "./graph/state.svelte";
type Keymap = ReturnType<typeof createKeyMap>;
export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: GraphState) {
keymap.addShortcut({
key: "l",
description: "Select linked nodes",
callback: () => {
const activeNode = graph.getNode(graphState.activeNodeId);
if (activeNode) {
const nodes = graph.getLinkedNodes(activeNode);
graphState.selectedNodes.clear();
for (const node of nodes) {
graphState.selectedNodes.add(node.id);
}
}
},
});
keymap.addShortcut({
key: "?",
description: "Toggle Help",
callback: () => {
// TODO: fix this
// showHelp = !showHelp;
},
});
keymap.addShortcut({
key: "c",
ctrl: true,
description: "Copy active nodes",
callback: graphState.copyNodes,
});
keymap.addShortcut({
key: "v",
ctrl: true,
description: "Paste nodes",
callback: graphState.pasteNodes,
});
keymap.addShortcut({
key: "Escape",
description: "Deselect nodes",
callback: () => {
graphState.activeNodeId = -1;
graphState.clearSelection();
graphState.edgeEndPosition = null;
(document.activeElement as HTMLElement)?.blur();
},
});
keymap.addShortcut({
key: "A",
shift: true,
description: "Add new Node",
callback: () => {
graphState.addMenuPosition = [graphState.mousePosition[0], graphState.mousePosition[1]];
},
});
keymap.addShortcut({
key: ".",
description: "Center camera",
callback: () => {
if (!graphState.isBodyFocused()) return;
const average = [0, 0];
for (const node of graph.nodes.values()) {
average[0] += node.position[0];
average[1] += node.position[1];
}
average[0] = average[0] ? average[0] / graph.nodes.size : 0;
average[1] = average[1] ? average[1] / graph.nodes.size : 0;
const camX = graphState.cameraPosition[0];
const camY = graphState.cameraPosition[1];
const camZ = graphState.cameraPosition[2];
const ease = (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);
animate(500, (a: number) => {
graphState.setCameraTransform(
lerp(camX, average[0], ease(a)),
lerp(camY, average[1], ease(a)),
lerp(camZ, 2, ease(a)),
);
if (graphState.mouseDown) return false;
});
},
});
keymap.addShortcut({
key: "a",
ctrl: true,
preventDefault: true,
description: "Select all nodes",
callback: () => {
if (!graphState.isBodyFocused()) return;
for (const node of graph.nodes.keys()) {
graphState.selectedNodes.add(node);
}
},
});
keymap.addShortcut({
key: "z",
ctrl: true,
description: "Undo",
callback: () => {
if (!graphState.isBodyFocused()) return;
graph.undo();
for (const node of graph.nodes.values()) {
graphState.updateNodePosition(node);
}
},
});
keymap.addShortcut({
key: "y",
ctrl: true,
description: "Redo",
callback: () => {
graph.redo();
for (const node of graph.nodes.values()) {
graphState.updateNodePosition(node);
}
},
});
keymap.addShortcut({
key: "s",
ctrl: true,
description: "Save",
preventDefault: true,
callback: () => {
const state = graph.serialize();
const blob = new Blob([JSON.stringify(state)], {
type: "application/json;charset=utf-8",
});
FileSaver.saveAs(blob, "nodarium-graph.json");
},
});
keymap.addShortcut({
key: ["Delete", "Backspace", "x"],
description: "Delete selected nodes",
callback: (event) => {
if (!graphState.isBodyFocused()) return;
graph.startUndoGroup();
if (graphState.activeNodeId !== -1) {
const node = graph.getNode(graphState.activeNodeId);
if (node) {
graph.removeNode(node, { restoreEdges: event.ctrlKey });
graphState.activeNodeId = -1;
}
}
if (graphState.selectedNodes) {
for (const nodeId of graphState.selectedNodes) {
const node = graph.getNode(nodeId);
if (node) {
graph.removeNode(node, { restoreEdges: event.ctrlKey });
}
}
graphState.clearSelection();
}
graph.saveUndoGroup();
},
});
keymap.addShortcut({
key: "f",
description: "Smart Connect Nodes",
callback: () => {
const nodes = [...graphState.selectedNodes.values()]
.map((g) => graph.getNode(g))
.filter((n) => !!n);
const edge = graph.smartConnect(nodes[0], nodes[1]);
if (!edge) graph.smartConnect(nodes[1], nodes[0]);
},
});
}

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { Node } from "@nodes/types"; import type { Node } from "@nodarium/types";
import { getContext, onMount } from "svelte"; import { onMount } from "svelte";
import { getGraphState } from "../graph/state.svelte"; import { getGraphManager, getGraphState } from "../graph/state.svelte";
import { T } from "@threlte/core"; import { T } from "@threlte/core";
import { type Mesh } from "three"; import { type Mesh } from "three";
import NodeFrag from "./Node.frag"; import NodeFrag from "./Node.frag";
@@ -10,6 +10,7 @@
import { colors } from "../graph/colors.svelte"; import { colors } from "../graph/colors.svelte";
import { appSettings } from "$lib/settings/app-settings.svelte"; import { appSettings } from "$lib/settings/app-settings.svelte";
const graph = getGraphManager();
const graphState = getGraphState(); const graphState = getGraphState();
type Props = { type Props = {
@@ -17,7 +18,7 @@
inView: boolean; inView: boolean;
z: number; z: number;
}; };
let { node = $bindable(), inView, z }: Props = $props(); let { node, inView, z }: Props = $props();
const isActive = $derived(graphState.activeNodeId === node.id); const isActive = $derived(graphState.activeNodeId === node.id);
const isSelected = $derived(graphState.selectedNodes.has(node.id)); const isSelected = $derived(graphState.selectedNodes.has(node.id));
@@ -31,14 +32,9 @@
: colors.outline; : colors.outline;
}); });
const updateNodePosition =
getContext<(n: Node) => void>("updateNodePosition");
const getNodeHeight = getContext<(n: string) => number>("getNodeHeight");
let meshRef: Mesh | undefined = $state(); let meshRef: Mesh | undefined = $state();
const height = getNodeHeight?.(node.type); const height = graphState.getNodeHeight(node.type);
$effect(() => { $effect(() => {
if (!node?.tmp) node.tmp = {}; if (!node?.tmp) node.tmp = {};
@@ -48,7 +44,7 @@
onMount(() => { onMount(() => {
if (!node.tmp) node.tmp = {}; if (!node.tmp) node.tmp = {};
node.tmp.mesh = meshRef; node.tmp.mesh = meshRef;
updateNodePosition?.(node); graphState.updateNodePosition(node);
}); });
</script> </script>
@@ -78,4 +74,4 @@
/> />
</T.Mesh> </T.Mesh>
<NodeHtml bind:node {inView} {isActive} {isSelected} {z} /> <NodeHtml {node} {inView} {isActive} {isSelected} {z} />

View File

@@ -1,11 +1,14 @@
<script lang="ts"> <script lang="ts">
import type { Node } from "@nodes/types"; import type { Node } from "@nodarium/types";
import NodeHeader from "./NodeHeader.svelte"; import NodeHeader from "./NodeHeader.svelte";
import NodeParameter from "./NodeParameter.svelte"; import NodeParameter from "./NodeParameter.svelte";
import { getContext, onMount } from "svelte"; import { onMount } from "svelte";
import { getGraphState } from "../graph/state.svelte";
let ref: HTMLDivElement; let ref: HTMLDivElement;
const graphState = getGraphState();
type Props = { type Props = {
node: Node; node: Node;
position?: "absolute" | "fixed" | "relative"; position?: "absolute" | "fixed" | "relative";
@@ -32,13 +35,10 @@
p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true, p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true,
); );
const updateNodePosition =
getContext<(n: Node) => void>("updateNodePosition");
onMount(() => { onMount(() => {
node.tmp = node.tmp || {}; node.tmp = node.tmp || {};
node.tmp.ref = ref; node.tmp.ref = ref;
updateNodePosition?.(node); graphState?.updateNodePosition(node);
}); });
</script> </script>

View File

@@ -1,23 +1,19 @@
<script lang="ts"> <script lang="ts">
import { getGraphState } from "../graph/state.svelte.js";
import { createNodePath } from "../helpers/index.js"; import { createNodePath } from "../helpers/index.js";
import type { Node, Socket } from "@nodes/types"; import type { Node } from "@nodarium/types";
import { getContext } from "svelte";
const graphState = getGraphState();
const { node }: { node: Node } = $props(); const { node }: { node: Node } = $props();
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
const getSocketPosition =
getContext<(node: Node, index: number) => [number, number]>(
"getSocketPosition",
);
function handleMouseDown(event: MouseEvent) { function handleMouseDown(event: MouseEvent) {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
setDownSocket?.({ graphState.setDownSocket?.({
node, node,
index: 0, index: 0,
position: getSocketPosition?.(node, 0), position: graphState.getSocketPosition?.(node, 0),
}); });
} }

View File

@@ -1,13 +1,14 @@
<script lang="ts"> <script lang="ts">
import type { Node, NodeInput } from "@nodes/types"; import type { Node, NodeInput } from "@nodarium/types";
import { getGraphManager } from "../graph/context.js"; import { Input } from "@nodarium/ui";
import { Input } from "@nodes/ui"; import type { GraphManager } from "../graph-manager.svelte";
type Props = { type Props = {
node: Node; node: Node;
input: NodeInput; input: NodeInput;
id: string; id: string;
elementId?: string; elementId?: string;
graph?: GraphManager;
}; };
const { const {
@@ -15,10 +16,9 @@
input, input,
id, id,
elementId = `input-${Math.random().toString(36).substring(7)}`, elementId = `input-${Math.random().toString(36).substring(7)}`,
graph,
}: Props = $props(); }: Props = $props();
const graph = getGraphManager();
function getDefaultValue() { function getDefaultValue() {
if (node?.props?.[id] !== undefined) return node?.props?.[id] as number; if (node?.props?.[id] !== undefined) return node?.props?.[id] as number;
if ("value" in input && input?.value !== undefined) if ("value" in input && input?.value !== undefined)

View File

@@ -1,14 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { import type {
NodeInput as NodeInputType, NodeInput as NodeInputType,
Socket,
Node as NodeType, Node as NodeType,
} from "@nodes/types"; } from "@nodarium/types";
import { getContext } from "svelte";
import { createNodePath } from "../helpers/index.js"; import { createNodePath } from "../helpers/index.js";
import { getGraphManager } from "../graph/context.js";
import NodeInput from "./NodeInput.svelte"; import NodeInput from "./NodeInput.svelte";
import { getGraphState } from "../graph/state.svelte.js"; import { getGraphManager, getGraphState } from "../graph/state.svelte.js";
type Props = { type Props = {
node: NodeType; node: NodeType;
@@ -17,31 +14,26 @@
isLast?: boolean; isLast?: boolean;
}; };
const graph = getGraphManager();
let { node = $bindable(), input, id, isLast }: Props = $props(); let { node = $bindable(), input, id, isLast }: Props = $props();
const inputType = node?.tmp?.type?.inputs?.[id]!; const inputType = node?.tmp?.type?.inputs?.[id]!;
const socketId = `${node.id}-${id}`; const socketId = `${node.id}-${id}`;
const graph = getGraphManager();
const graphState = getGraphState(); const graphState = getGraphState();
const graphId = graph?.id; const graphId = graph?.id;
const elementId = `input-${Math.random().toString(36).substring(7)}`; const elementId = `input-${Math.random().toString(36).substring(7)}`;
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
const getSocketPosition =
getContext<(node: NodeType, index: string) => [number, number]>(
"getSocketPosition",
);
function handleMouseDown(ev: MouseEvent) { function handleMouseDown(ev: MouseEvent) {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
setDownSocket?.({ graphState.setDownSocket({
node, node,
index: id, index: id,
position: getSocketPosition?.(node, id), position: graphState.getSocketPosition?.(node, id),
}); });
} }
@@ -87,7 +79,7 @@
<label for={elementId}>{input.label || id}</label> <label for={elementId}>{input.label || id}</label>
{/if} {/if}
{#if inputType.external !== true} {#if inputType.external !== true}
<NodeInput {elementId} bind:node {input} {id} /> <NodeInput {graph} {elementId} bind:node {input} {id} />
{/if} {/if}
</div> </div>

View File

@@ -1,20 +0,0 @@
import type { Node, NodeDefinition } from "@nodes/types";
export type GraphNode = Node & {
tmp?: {
depth?: number;
mesh?: any;
random?: number;
parents?: Node[];
children?: Node[];
inputNodes?: Record<string, Node>;
type?: NodeDefinition;
downX?: number;
downY?: number;
x?: number;
y?: number;
ref?: HTMLElement;
visible?: boolean;
isMoving?: boolean;
};
};

View File

@@ -1,4 +1,4 @@
import type { Graph } from "@nodes/types"; import type { Graph } from "@nodarium/types";
export function grid(width: number, height: number) { export function grid(width: number, height: number) {

View File

@@ -1,4 +1,4 @@
import type { Graph, Node } from "@nodes/types"; import type { Graph, Node } from "@nodarium/types";
export function tree(depth: number): Graph { export function tree(depth: number): Graph {

View File

@@ -1,4 +1,4 @@
import { createWasmWrapper } from "@nodes/utils"; import { createWasmWrapper } from "@nodarium/utils";
import fs from "fs/promises"; import fs from "fs/promises";
import path from "path"; import path from "path";

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Select } from "@nodes/ui"; import { Select } from "@nodarium/ui";
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store";
let activeStore = 0; let activeStore = 0;

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte"; import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte";
import type { NodeDefinition } from "@nodes/types"; import type { NodeDefinition } from "@nodarium/types";
export let node: NodeDefinition; export let node: NodeDefinition;

View File

@@ -2,7 +2,7 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import BreadCrumbs from "./BreadCrumbs.svelte"; import BreadCrumbs from "./BreadCrumbs.svelte";
import DraggableNode from "./DraggableNode.svelte"; import DraggableNode from "./DraggableNode.svelte";
import type { RemoteNodeRegistry } from "@nodes/registry"; import type { RemoteNodeRegistry } from "@nodarium/registry";
export let registry: RemoteNodeRegistry; export let registry: RemoteNodeRegistry;

View File

@@ -1,6 +1,3 @@
<script lang="ts">
</script>
<span class="spinner"></span> <span class="spinner"></span>
<style> <style>

View File

@@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import Monitor from "./Monitor.svelte"; import Monitor from "./Monitor.svelte";
import { humanizeNumber } from "$lib/helpers"; import { humanizeNumber } from "$lib/helpers";
import { Checkbox } from "@nodes/ui"; import { Checkbox } from "@nodarium/ui";
import localStore from "$lib/helpers/localStore"; import localStore from "$lib/helpers/localStore";
import { type PerformanceData } from "@nodes/utils"; import { type PerformanceData } from "@nodarium/utils";
import BarSplit from "./BarSplit.svelte"; import BarSplit from "./BarSplit.svelte";
export let data: PerformanceData; export let data: PerformanceData;

View File

@@ -2,7 +2,7 @@
import { humanizeDuration, humanizeNumber } from "$lib/helpers"; import { humanizeDuration, humanizeNumber } from "$lib/helpers";
import localStore from "$lib/helpers/localStore"; import localStore from "$lib/helpers/localStore";
import SmallGraph from "./SmallGraph.svelte"; import SmallGraph from "./SmallGraph.svelte";
import type { PerformanceData, PerformanceStore } from "@nodes/utils"; import type { PerformanceData, PerformanceStore } from "@nodarium/utils";
export let store: PerformanceStore; export let store: PerformanceStore;

View File

@@ -2,8 +2,8 @@
import { Canvas } from "@threlte/core"; import { Canvas } from "@threlte/core";
import Scene from "./Scene.svelte"; import Scene from "./Scene.svelte";
import { Vector3 } from "three"; import { Vector3 } from "three";
import { decodeFloat, splitNestedArray } from "@nodes/utils"; import { decodeFloat, splitNestedArray } from "@nodarium/utils";
import type { PerformanceStore } from "@nodes/utils"; import type { PerformanceStore } from "@nodarium/utils";
import { appSettings } from "$lib/settings/app-settings.svelte"; import { appSettings } from "$lib/settings/app-settings.svelte";
import SmallPerformanceViewer from "$lib/performance/SmallPerformanceViewer.svelte"; import SmallPerformanceViewer from "$lib/performance/SmallPerformanceViewer.svelte";
import { MeshMatcapMaterial, TextureLoader, type Group } from "three"; import { MeshMatcapMaterial, TextureLoader, type Group } from "three";

View File

@@ -1,4 +1,4 @@
import { fastHashArrayBuffer } from "@nodes/utils"; import { fastHashArrayBuffer } from "@nodarium/utils";
import { import {
BufferAttribute, BufferAttribute,
BufferGeometry, BufferGeometry,
@@ -206,19 +206,16 @@ export function createInstancedGeometryPool(
existingInstance && existingInstance &&
instanceCount > existingInstance.geometry.userData.count instanceCount > existingInstance.geometry.userData.count
) { ) {
console.log("recreating instance");
scene.remove(existingInstance); scene.remove(existingInstance);
instances.splice(instances.indexOf(existingInstance), 1); instances.splice(instances.indexOf(existingInstance), 1);
existingInstance = new InstancedMesh(geometry, material, instanceCount); existingInstance = new InstancedMesh(geometry, material, instanceCount);
scene.add(existingInstance); scene.add(existingInstance);
instances.push(existingInstance); instances.push(existingInstance);
} else if (!existingInstance) { } else if (!existingInstance) {
console.log("creating instance");
existingInstance = new InstancedMesh(geometry, material, instanceCount); existingInstance = new InstancedMesh(geometry, material, instanceCount);
scene.add(existingInstance); scene.add(existingInstance);
instances.push(existingInstance); instances.push(existingInstance);
} else { } else {
console.log("updating instance");
existingInstance.count = instanceCount; existingInstance.count = instanceCount;
} }

View File

@@ -1,4 +1,4 @@
import type { Graph, RuntimeExecutor } from "@nodes/types"; import type { Graph, RuntimeExecutor } from "@nodarium/types";
export class RemoteRuntimeExecutor implements RuntimeExecutor { export class RemoteRuntimeExecutor implements RuntimeExecutor {

View File

@@ -1,4 +1,4 @@
import { type SyncCache } from "@nodes/types"; import { type SyncCache } from "@nodarium/types";
export class MemoryRuntimeCache implements SyncCache { export class MemoryRuntimeCache implements SyncCache {

View File

@@ -6,14 +6,14 @@ import type {
NodeRegistry, NodeRegistry,
RuntimeExecutor, RuntimeExecutor,
SyncCache, SyncCache,
} from "@nodes/types"; } from "@nodarium/types";
import { import {
concatEncodedArrays, concatEncodedArrays,
createLogger, createLogger,
encodeFloat, encodeFloat,
fastHashArrayBuffer, fastHashArrayBuffer,
type PerformanceStore, type PerformanceStore,
} from "@nodes/utils"; } from "@nodarium/utils";
const log = createLogger("runtime-executor"); const log = createLogger("runtime-executor");
log.mute(); log.mute();

View File

@@ -1,7 +1,7 @@
import { MemoryRuntimeExecutor } from "./runtime-executor"; import { MemoryRuntimeExecutor } from "./runtime-executor";
import { RemoteNodeRegistry, IndexDBCache } from "@nodes/registry"; import { RemoteNodeRegistry, IndexDBCache } from "@nodarium/registry";
import type { Graph } from "@nodes/types"; import type { Graph } from "@nodarium/types";
import { createPerformanceStore } from "@nodes/utils"; import { createPerformanceStore } from "@nodarium/utils";
import { MemoryRuntimeCache } from "./runtime-executor-cache"; import { MemoryRuntimeCache } from "./runtime-executor-cache";
const cache = new MemoryRuntimeCache(); const cache = new MemoryRuntimeCache();

View File

@@ -1,5 +1,5 @@
/// <reference types="vite-plugin-comlink/client" /> /// <reference types="vite-plugin-comlink/client" />
import type { Graph, RuntimeExecutor } from "@nodes/types"; import type { Graph, RuntimeExecutor } from "@nodarium/types";
export class WorkerRuntimeExecutor implements RuntimeExecutor { export class WorkerRuntimeExecutor implements RuntimeExecutor {

View File

@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import NestedSettings from "./NestedSettings.svelte"; import NestedSettings from "./NestedSettings.svelte";
import { localState } from "$lib/helpers/localState.svelte"; import { localState } from "$lib/helpers/localState.svelte";
import type { NodeInput } from "@nodes/types"; import type { NodeInput } from "@nodarium/types";
import Input from "@nodes/ui"; import Input from "@nodarium/ui";
type Button = { type: "button"; callback: () => void; label?: string }; type Button = { type: "button"; callback: () => void; label?: string };

View File

@@ -1,4 +1,4 @@
import type { NodeInput } from "@nodes/types"; import type { NodeInput } from "@nodarium/types";
type Button = { type: "button"; label?: string }; type Button = { type: "button"; label?: string };

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Node, NodeInput } from "@nodes/types"; import type { Node, NodeInput } from "@nodarium/types";
import NestedSettings from "$lib/settings/NestedSettings.svelte"; import NestedSettings from "$lib/settings/NestedSettings.svelte";
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte"; import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
@@ -8,9 +8,8 @@
node: Node; node: Node;
}; };
const { manager, node }: Props = $props(); const { manager, node = $bindable() }: Props = $props();
const nodeDefinition = filterInputs(node.tmp?.type?.inputs);
function filterInputs(inputs?: Record<string, NodeInput>) { function filterInputs(inputs?: Record<string, NodeInput>) {
const _inputs = $state.snapshot(inputs); const _inputs = $state.snapshot(inputs);
return Object.fromEntries( return Object.fromEntries(
@@ -27,6 +26,7 @@
}), }),
); );
} }
const nodeDefinition = filterInputs(node.tmp?.type?.inputs);
type Store = Record<string, number | number[]>; type Store = Record<string, number | number[]>;
let store = $state<Store>(createStore(node?.props, nodeDefinition)); let store = $state<Store>(createStore(node?.props, nodeDefinition));
@@ -75,8 +75,12 @@
}); });
</script> </script>
<NestedSettings {#if Object.keys(nodeDefinition).length}
<NestedSettings
id="activeNodeSettings" id="activeNodeSettings"
bind:value={store} bind:value={store}
type={nodeDefinition} type={nodeDefinition}
/> />
{:else}
<p class="mx-4">Node has no settings</p>
{/if}

View File

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

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import localStore from "$lib/helpers/localStore"; import localStore from "$lib/helpers/localStore";
import { Integer } from "@nodes/ui"; import { Integer } from "@nodarium/ui";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { humanizeDuration } from "$lib/helpers"; import { humanizeDuration } from "$lib/helpers";
import Monitor from "$lib/performance/Monitor.svelte"; import Monitor from "$lib/performance/Monitor.svelte";

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { createKeyMap } from "$lib/helpers/createKeyMap"; import type { createKeyMap } from "$lib/helpers/createKeyMap";
import { ShortCut } from "@nodes/ui"; import { ShortCut } from "@nodarium/ui";
import { get } from "svelte/store"; import { get } from "svelte/store";
type Props = { type Props = {
@@ -11,7 +11,6 @@
}; };
let { keymaps }: Props = $props(); let { keymaps }: Props = $props();
console.log({ keymaps });
</script> </script>
<table class="wrapper"> <table class="wrapper">

View File

@@ -1,8 +1,6 @@
import type { import type {
Graph, Graph,
Node as NodeType,
NodeDefinition, NodeDefinition,
NodeInput, NodeInput,
RuntimeExecutor, } from "@nodarium/types";
} from "@nodes/types";
export type { Graph, NodeDefinition, NodeInput }; export type { Graph, NodeDefinition, NodeInput };

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import "@nodes/ui/app.css"; import "@nodarium/ui/app.css";
import "virtual:uno.css"; import "virtual:uno.css";
import "@unocss/reset/normalize.css"; import "@unocss/reset/normalize.css";
</script> </script>

View File

@@ -2,7 +2,7 @@
import Grid from "$lib/grid"; import Grid from "$lib/grid";
import GraphInterface from "$lib/graph-interface"; import GraphInterface from "$lib/graph-interface";
import * as templates from "$lib/graph-templates"; import * as templates from "$lib/graph-templates";
import type { Graph, Node } from "@nodes/types"; import type { Graph, Node } from "@nodarium/types";
import Viewer from "$lib/result-viewer/Viewer.svelte"; import Viewer from "$lib/result-viewer/Viewer.svelte";
import { import {
appSettings, appSettings,
@@ -23,8 +23,8 @@
WorkerRuntimeExecutor, WorkerRuntimeExecutor,
MemoryRuntimeExecutor, MemoryRuntimeExecutor,
} from "$lib/runtime"; } from "$lib/runtime";
import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry"; import { IndexDBCache, RemoteNodeRegistry } from "@nodarium/registry";
import { createPerformanceStore } from "@nodes/utils"; import { createPerformanceStore } from "@nodarium/utils";
import BenchmarkPanel from "$lib/sidebar/panels/BenchmarkPanel.svelte"; import BenchmarkPanel from "$lib/sidebar/panels/BenchmarkPanel.svelte";
import { debounceAsyncFunction } from "$lib/helpers"; import { debounceAsyncFunction } from "$lib/helpers";
@@ -194,7 +194,7 @@
<Keymap <Keymap
keymaps={[ keymaps={[
{ keymap: applicationKeymap, title: "Application" }, { keymap: applicationKeymap, title: "Application" },
{ keymap: graphInterface.keymap, title: "Node-Editor" }, { keymap: graphInterface?.keymap, title: "Node-Editor" },
]} ]}
/> />
</Panel> </Panel>

View File

@@ -8,5 +8,5 @@
"build:deploy": "pnpm build", "build:deploy": "pnpm build",
"dev": "pnpm -r --filter 'app' --filter './packages/node-registry' dev" "dev": "pnpm -r --filter 'app' --filter './packages/node-registry' dev"
}, },
"packageManager": "pnpm@10.23.0" "packageManager": "pnpm@10.24.0"
} }

View File

@@ -1,5 +1,5 @@
{ {
"name": "@nodes/registry", "name": "@nodarium/registry",
"version": "0.0.0", "version": "0.0.0",
"description": "", "description": "",
"main": "src/index.ts", "main": "src/index.ts",
@@ -10,8 +10,8 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@nodes/types": "link:../types", "@nodarium/types": "link:../types",
"@nodes/utils": "link:../utils", "@nodarium/utils": "link:../utils",
"idb": "^8.0.3" "idb": "^8.0.3"
} }
} }

View File

@@ -1,4 +1,4 @@
import type { AsyncCache } from '@nodes/types'; import type { AsyncCache } from '@nodarium/types';
import { openDB, type IDBPDatabase } from 'idb'; import { openDB, type IDBPDatabase } from 'idb';
export class IndexDBCache implements AsyncCache<ArrayBuffer> { export class IndexDBCache implements AsyncCache<ArrayBuffer> {

View File

@@ -3,8 +3,8 @@ import {
type AsyncCache, type AsyncCache,
type NodeDefinition, type NodeDefinition,
type NodeRegistry, type NodeRegistry,
} from "@nodes/types"; } from "@nodarium/types";
import { createLogger, createWasmWrapper } from "@nodes/utils"; import { createLogger, createWasmWrapper } from "@nodarium/utils";
const log = createLogger("node-registry"); const log = createLogger("node-registry");
log.mute(); log.mute();

View File

@@ -1,5 +1,5 @@
{ {
"name": "@nodes/types", "name": "@nodarium/types",
"version": "0.0.0", "version": "0.0.0",
"description": "", "description": "",
"main": "src/index.ts", "main": "src/index.ts",

View File

@@ -9,6 +9,9 @@ export const NodeTypeSchema = z
export type NodeType = z.infer<typeof NodeTypeSchema>; export type NodeType = z.infer<typeof NodeTypeSchema>;
export type Node = { export type Node = {
/**
* .tmp only exists at runtime
*/
tmp?: { tmp?: {
depth?: number; depth?: number;
mesh?: any; mesh?: any;

View File

@@ -1,5 +1,5 @@
{ {
"name": "@nodes/ui", "name": "@nodarium/ui",
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
@@ -53,7 +53,7 @@
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite": "^7.2.4", "vite": "^7.2.4",
"vitest": "^4.0.13", "vitest": "^4.0.13",
"@nodes/types": "link:../types" "@nodarium/types": "link:../types"
}, },
"svelte": "./dist/index.js", "svelte": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",

View File

@@ -4,7 +4,7 @@
import Integer from './elements/Integer.svelte'; import Integer from './elements/Integer.svelte';
import Select from './elements/Select.svelte'; import Select from './elements/Select.svelte';
import type { NodeInput } from '@nodes/types'; import type { NodeInput } from '@nodarium/types';
import Vec3 from './elements/Vec3.svelte'; import Vec3 from './elements/Vec3.svelte';
interface Props { interface Props {
@@ -27,4 +27,3 @@
{:else if input.type === 'vec3'} {:else if input.type === 'vec3'}
<Vec3 {id} bind:value /> <Vec3 {id} bind:value />
{/if} {/if}

View File

@@ -1,5 +1,5 @@
{ {
"name": "@nodes/utils", "name": "@nodarium/utils",
"version": "0.0.1", "version": "0.0.1",
"description": "", "description": "",
"main": "src/index.ts", "main": "src/index.ts",
@@ -10,7 +10,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@nodes/types": "link:../types" "@nodarium/types": "link:../types"
}, },
"devDependencies": { "devDependencies": {
"vite": "^7.2.4", "vite": "^7.2.4",

View File

@@ -1,5 +1,5 @@
//@ts-nocheck //@ts-nocheck
import { NodeDefinition } from "@nodes/types"; import { NodeDefinition } from "@nodarium/types";
const cachedTextDecoder = new TextDecoder("utf-8", { const cachedTextDecoder = new TextDecoder("utf-8", {
ignoreBOM: true, ignoreBOM: true,

16
pnpm-lock.yaml generated
View File

@@ -10,13 +10,13 @@ importers:
app: app:
dependencies: dependencies:
'@nodes/registry': '@nodarium/registry':
specifier: link:../packages/registry specifier: link:../packages/registry
version: link:../packages/registry version: link:../packages/registry
'@nodes/ui': '@nodarium/ui':
specifier: link:../packages/ui specifier: link:../packages/ui
version: link:../packages/ui version: link:../packages/ui
'@nodes/utils': '@nodarium/utils':
specifier: link:../packages/utils specifier: link:../packages/utils
version: link:../packages/utils version: link:../packages/utils
'@sveltejs/kit': '@sveltejs/kit':
@@ -53,7 +53,7 @@ importers:
'@iconify-json/tabler': '@iconify-json/tabler':
specifier: ^1.2.23 specifier: ^1.2.23
version: 1.2.23 version: 1.2.23
'@nodes/types': '@nodarium/types':
specifier: link:../packages/types specifier: link:../packages/types
version: link:../packages/types version: link:../packages/types
'@sveltejs/adapter-static': '@sveltejs/adapter-static':
@@ -130,10 +130,10 @@ importers:
packages/registry: packages/registry:
dependencies: dependencies:
'@nodes/types': '@nodarium/types':
specifier: link:../types specifier: link:../types
version: link:../types version: link:../types
'@nodes/utils': '@nodarium/utils':
specifier: link:../utils specifier: link:../utils
version: link:../utils version: link:../utils
idb: idb:
@@ -165,7 +165,7 @@ importers:
specifier: ^9.7.0 specifier: ^9.7.0
version: 9.7.0(@types/three@0.181.0)(svelte@5.43.14)(three@0.181.2) version: 9.7.0(@types/three@0.181.0)(svelte@5.43.14)(three@0.181.2)
devDependencies: devDependencies:
'@nodes/types': '@nodarium/types':
specifier: link:../types specifier: link:../types
version: link:../types version: link:../types
'@storybook/addon-essentials': '@storybook/addon-essentials':
@@ -240,7 +240,7 @@ importers:
packages/utils: packages/utils:
dependencies: dependencies:
'@nodes/types': '@nodarium/types':
specifier: link:../types specifier: link:../types
version: link:../types version: link:../types
devDependencies: devDependencies: