chore: rename @nodes -> @nodarium for everything
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 3m33s
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 3m33s
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@nodes/app",
|
||||
"name": "@nodarium/app",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
@@ -10,9 +10,9 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodes/registry": "link:../packages/registry",
|
||||
"@nodes/ui": "link:../packages/ui",
|
||||
"@nodes/utils": "link:../packages/utils",
|
||||
"@nodarium/registry": "link:../packages/registry",
|
||||
"@nodarium/ui": "link:../packages/ui",
|
||||
"@nodarium/utils": "link:../packages/utils",
|
||||
"@sveltejs/kit": "^2.49.0",
|
||||
"@threlte/core": "8.3.0",
|
||||
"@threlte/extras": "9.7.0",
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/tabler": "^1.2.23",
|
||||
"@nodes/types": "link:../packages/types",
|
||||
"@nodarium/types": "link:../packages/types",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tsconfig/svelte": "^5.0.6",
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { HTML } from "@threlte/extras";
|
||||
import { onMount } from "svelte";
|
||||
import type { Node, NodeType } from "@nodes/types";
|
||||
import { getGraphState } from "./graph/state.svelte";
|
||||
import { getGraphManager } from "./graph/context";
|
||||
|
||||
type Props = {
|
||||
position: [x: number, y: number] | null;
|
||||
};
|
||||
import type { Node, NodeType } from "@nodarium/types";
|
||||
import { getGraphManager, getGraphState } from "../graph/state.svelte";
|
||||
|
||||
const graph = getGraphManager();
|
||||
const graphState = getGraphState();
|
||||
|
||||
let { position = $bindable() }: Props = $props();
|
||||
|
||||
let input: HTMLInputElement;
|
||||
let value = $state<string>();
|
||||
let activeNodeId = $state<NodeType>();
|
||||
@@ -41,11 +34,11 @@
|
||||
});
|
||||
|
||||
function handleNodeCreation(nodeType: Node["type"]) {
|
||||
if (!position) return;
|
||||
if (!graphState.addMenuPosition) return;
|
||||
|
||||
const newNode = graph.createNode({
|
||||
type: nodeType,
|
||||
position,
|
||||
position: graphState.addMenuPosition,
|
||||
props: {},
|
||||
});
|
||||
|
||||
@@ -59,14 +52,14 @@
|
||||
}
|
||||
|
||||
graphState.activeSocket = null;
|
||||
position = null;
|
||||
graphState.addMenuPosition = null;
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
if (event.key === "Escape") {
|
||||
position = null;
|
||||
graphState.addMenuPosition = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,7 +76,7 @@
|
||||
}
|
||||
|
||||
if (event.key === "Enter") {
|
||||
if (activeNodeId && position) {
|
||||
if (activeNodeId && graphState.addMenuPosition) {
|
||||
handleNodeCreation(activeNodeId);
|
||||
}
|
||||
return;
|
||||
@@ -96,7 +89,11 @@
|
||||
});
|
||||
</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="header">
|
||||
<input
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { NodeDefinition, NodeRegistry } from "@nodes/types";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import type { NodeDefinition, NodeRegistry } from "@nodarium/types";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let mx = $state(0);
|
||||
let my = $state(0);
|
||||
@@ -12,7 +12,7 @@
|
||||
});
|
||||
});
|
||||
|
||||
const lineCache = new Map<number, BufferGeometry>();
|
||||
// const lineCache = new Map<number, BufferGeometry>();
|
||||
|
||||
const curve = new CubicBezierCurve(
|
||||
new Vector2(0, 0),
|
||||
@@ -25,7 +25,7 @@
|
||||
<script lang="ts">
|
||||
import { T } from "@threlte/core";
|
||||
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 { Vector2 } from "three/src/math/Vector2.js";
|
||||
import { createEdgeGeometry } from "./createEdgeGeometry.js";
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
const { from, to, z }: Props = $props();
|
||||
|
||||
let geometry: BufferGeometry | null = $state(null);
|
||||
let mesh = $state<Mesh>();
|
||||
|
||||
const lineColor = $derived(
|
||||
appSettings.value.theme && colors.edge.clone().convertSRGBToLinear(),
|
||||
@@ -58,13 +58,6 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const mid = new Vector2(new_x / 2, new_y / 2);
|
||||
|
||||
if (lineCache.has(curveId)) {
|
||||
geometry = lineCache.get(curveId)!;
|
||||
return;
|
||||
}
|
||||
|
||||
const length = Math.floor(
|
||||
Math.sqrt(Math.pow(new_x, 2) + Math.pow(new_y, 2)) / 4,
|
||||
);
|
||||
@@ -72,8 +65,8 @@
|
||||
const samples = Math.max(length * 16, 10);
|
||||
|
||||
curve.v0.set(0, 0);
|
||||
curve.v1.set(mid.x, 0);
|
||||
curve.v2.set(mid.x, new_y);
|
||||
curve.v1.set(new_x / 2, 0);
|
||||
curve.v2.set(new_x / 2, new_y);
|
||||
curve.v3.set(new_x, new_y);
|
||||
|
||||
const points = curve
|
||||
@@ -81,8 +74,9 @@
|
||||
.map((p) => new Vector3(p.x, 0, p.y))
|
||||
.flat();
|
||||
|
||||
geometry = createEdgeGeometry(points);
|
||||
lineCache.set(curveId, geometry);
|
||||
if (mesh) {
|
||||
mesh.geometry = createEdgeGeometry(points);
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
@@ -112,11 +106,11 @@
|
||||
<T.CircleGeometry args={[0.5, 16]} />
|
||||
</T.Mesh>
|
||||
|
||||
{#if geometry}
|
||||
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}>
|
||||
<MeshLineMaterial
|
||||
width={Math.max(z * 0.00012, 0.00003)}
|
||||
color={lineColor}
|
||||
/>
|
||||
<T.Mesh
|
||||
bind:ref={mesh}
|
||||
position.x={from.x}
|
||||
position.z={from.y}
|
||||
position.y={0.1}
|
||||
>
|
||||
<MeshLineMaterial width={Math.max(z * 0.00012, 0.00003)} color={lineColor} />
|
||||
</T.Mesh>
|
||||
{/if}
|
||||
|
||||
@@ -37,8 +37,6 @@ export function createEdgeGeometry(points: Vector3[]) {
|
||||
let indices: number[] = []
|
||||
let indicesIndex = 0
|
||||
|
||||
|
||||
|
||||
for (let j = 0; j < pointCount; j++) {
|
||||
const c = j / points.length
|
||||
counters[counterIndex + 0] = c
|
||||
@@ -73,8 +71,6 @@ export function createEdgeGeometry(points: Vector3[]) {
|
||||
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[] = []
|
||||
|
||||
@@ -7,11 +7,11 @@ import type {
|
||||
NodeRegistry,
|
||||
NodeType,
|
||||
Socket,
|
||||
} from "@nodes/types";
|
||||
import { fastHashString } from "@nodes/utils";
|
||||
} from "@nodarium/types";
|
||||
import { fastHashString } from "@nodarium/utils";
|
||||
import { SvelteMap } from "svelte/reactivity";
|
||||
import EventEmitter from "./helpers/EventEmitter";
|
||||
import { createLogger } from "@nodes/utils";
|
||||
import { createLogger } from "@nodarium/utils";
|
||||
import throttle from "$lib/helpers/throttle";
|
||||
import { HistoryManager } from "./history-manager";
|
||||
|
||||
@@ -33,6 +33,27 @@ function areSocketsCompatible(
|
||||
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<{
|
||||
save: Graph;
|
||||
result: any;
|
||||
@@ -119,7 +140,7 @@ export class GraphManager extends EventEmitter<{
|
||||
const n = stack.pop();
|
||||
if (!n) continue;
|
||||
nodes.add(n);
|
||||
const children = this.getChildrenOfNode(n);
|
||||
const children = this.getChildren(n);
|
||||
const parents = this.getParentsOfNode(n);
|
||||
const newNodes = [...children, ...parents].filter((n) => !nodes.has(n));
|
||||
stack.push(...newNodes);
|
||||
@@ -273,7 +294,7 @@ export class GraphManager extends EventEmitter<{
|
||||
return this.registry.getNode(id);
|
||||
}
|
||||
|
||||
async loadNode(id: NodeType) {
|
||||
async loadNodeType(id: NodeType) {
|
||||
await this.registry.load([id]);
|
||||
const nodeType = this.registry.getNode(id);
|
||||
|
||||
@@ -298,12 +319,11 @@ export class GraphManager extends EventEmitter<{
|
||||
}
|
||||
|
||||
this.settings = settingValues;
|
||||
console.log("GraphManager.setSettings", settingValues);
|
||||
this.settingTypes = settingTypes;
|
||||
this.emit("settings", { types: settingTypes, values: settingValues });
|
||||
}
|
||||
|
||||
getChildrenOfNode(node: Node) {
|
||||
getChildren(node: Node) {
|
||||
const children = [];
|
||||
const stack = node.tmp?.children?.slice(0);
|
||||
while (stack?.length) {
|
||||
@@ -321,10 +341,10 @@ export class GraphManager extends EventEmitter<{
|
||||
// < - - - - from - - - - to
|
||||
const fromParents = this.getParentsOfNode(from);
|
||||
if (toParents.includes(from)) {
|
||||
const fromChildren = this.getChildrenOfNode(from);
|
||||
const fromChildren = this.getChildren(from);
|
||||
return toParents.filter((n) => fromChildren.includes(n));
|
||||
} else if (fromParents.includes(to)) {
|
||||
const toChildren = this.getChildrenOfNode(to);
|
||||
const toChildren = this.getChildren(to);
|
||||
return fromParents.filter((n) => toChildren.includes(n));
|
||||
} else {
|
||||
// these two nodes are not connected
|
||||
@@ -386,7 +406,7 @@ export class GraphManager extends EventEmitter<{
|
||||
const idMap = new Map<number, number>();
|
||||
|
||||
|
||||
nodes = nodes.map((node, i) => {
|
||||
nodes = nodes.map((node) => {
|
||||
const id = this.createNodeId();
|
||||
idMap.set(node.id, id);
|
||||
const type = this.registry.getNode(node.type);
|
||||
@@ -463,8 +483,6 @@ export class GraphManager extends EventEmitter<{
|
||||
{ applyUpdate = true } = {},
|
||||
): Edge | undefined {
|
||||
|
||||
console.log("Create Edge", from.type, fromSocket, to.type, toSocket)
|
||||
|
||||
const existingEdges = this.getEdgesToNode(to);
|
||||
|
||||
// check if this exact edge already exists
|
||||
@@ -483,8 +501,6 @@ export class GraphManager extends EventEmitter<{
|
||||
toSocketType.push(...(to?.tmp?.type?.inputs?.[toSocket]?.accepts || []));
|
||||
}
|
||||
|
||||
console.log({ fromSocketType, toSocket, toType: to?.tmp?.type, toSocketType });
|
||||
|
||||
if (!areSocketsCompatible(fromSocketType, toSocketType)) {
|
||||
logger.error(
|
||||
`Socket types do not match: ${fromSocketType} !== ${toSocketType}`,
|
||||
@@ -576,23 +592,29 @@ export class GraphManager extends EventEmitter<{
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof socket.index === "string") {
|
||||
// if index is a string, we are an input looking for outputs
|
||||
return allDefinitions.filter(s => {
|
||||
const definitions = typeof socket.index === "string"
|
||||
? allDefinitions.filter(s => {
|
||||
return s.outputs?.find(_s => Object
|
||||
.values(nodeType?.inputs || {})
|
||||
.map(s => s.type)
|
||||
.includes(_s as NodeInput["type"])
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// if index is a number, we are an output looking for inputs
|
||||
return allDefinitions.filter(s => Object
|
||||
: allDefinitions.filter(s => Object
|
||||
.values(s.inputs ?? {})
|
||||
.map(s => s.type)
|
||||
.find(s => nodeType?.outputs?.includes(s))
|
||||
)
|
||||
.find(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][] {
|
||||
@@ -604,7 +626,7 @@ export class GraphManager extends EventEmitter<{
|
||||
// if index is a string, we are an input looking for outputs
|
||||
if (typeof index === "string") {
|
||||
// filter out self and child nodes
|
||||
const children = new Set(this.getChildrenOfNode(node).map((n) => n.id));
|
||||
const children = new Set(this.getChildren(node).map((n) => n.id));
|
||||
const nodes = this.getAllNodes().filter(
|
||||
(n) => n.id !== node.id && !children.has(n.id),
|
||||
);
|
||||
@@ -672,6 +694,7 @@ export class GraphManager extends EventEmitter<{
|
||||
(e) =>
|
||||
e[0].id === id0 && e[1] === sid0 && e[2].id === id2 && e[3] === sid2,
|
||||
);
|
||||
|
||||
if (!_edge) return;
|
||||
|
||||
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) {
|
||||
this.edges = this.edges.filter((e) => e !== _edge);
|
||||
this.execute();
|
||||
this.save();
|
||||
} else {
|
||||
this.edges = this.edges.filter((e) => e !== _edge);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getEdgesToNode(node: Node) {
|
||||
|
||||
@@ -1,261 +1,75 @@
|
||||
<script lang="ts">
|
||||
import type { Node, NodeType, Socket } from "@nodes/types";
|
||||
import { GraphSchema } from "@nodes/types";
|
||||
import { getContext, onMount, setContext } from "svelte";
|
||||
import type { OrthographicCamera } from "three";
|
||||
import type { Edge, Node, NodeType } from "@nodarium/types";
|
||||
import { GraphSchema } from "@nodarium/types";
|
||||
import { onMount } from "svelte";
|
||||
import { createKeyMap } from "../../helpers/createKeyMap";
|
||||
import AddMenu from "../AddMenu.svelte";
|
||||
import AddMenu from "../components/AddMenu.svelte";
|
||||
import Background from "../background/Background.svelte";
|
||||
import BoxSelection from "../BoxSelection.svelte";
|
||||
import Camera from "../Camera.svelte";
|
||||
import BoxSelection from "../components/BoxSelection.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 {
|
||||
animate,
|
||||
lerp,
|
||||
snapToGrid as snapPointToGrid,
|
||||
} from "../helpers/index.js";
|
||||
import GraphView from "./GraphView.svelte";
|
||||
import { getGraphState } from "./state.svelte";
|
||||
|
||||
import { Canvas } from "@threlte/core";
|
||||
import FileSaver from "file-saver";
|
||||
import HelpView from "../HelpView.svelte";
|
||||
import { getGraphManager } from "./context";
|
||||
|
||||
const graph = getGraphManager();
|
||||
const graphState = getGraphState();
|
||||
import HelpView from "../components/HelpView.svelte";
|
||||
import { getGraphManager, getGraphState } from "./state.svelte";
|
||||
import { HTML } from "@threlte/extras";
|
||||
|
||||
const {
|
||||
snapToGrid = $bindable(true),
|
||||
showGrid = $bindable(true),
|
||||
showHelp = $bindable(false),
|
||||
keymap,
|
||||
}: {
|
||||
snapToGrid: boolean;
|
||||
showGrid: boolean;
|
||||
showHelp: boolean;
|
||||
keymap: ReturnType<typeof createKeyMap>;
|
||||
} = $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 maxZoom = 40;
|
||||
let mousePosition = $state([0, 0]);
|
||||
let mouseDown = $state<[number, number] | null>(null);
|
||||
let mouseDownId = -1;
|
||||
let boxSelection = $state(false);
|
||||
let mouseDownNodeId = -1;
|
||||
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([
|
||||
cameraPosition[0] - width / cameraPosition[2] / 2,
|
||||
cameraPosition[0] + width / cameraPosition[2] / 2,
|
||||
cameraPosition[1] - height / cameraPosition[2] / 2,
|
||||
cameraPosition[1] + height / cameraPosition[2] / 2,
|
||||
]);
|
||||
function setCameraTransform(
|
||||
x = cameraPosition[0],
|
||||
y = cameraPosition[1],
|
||||
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));
|
||||
let isPanning = $state(false);
|
||||
let isDragging = $state(false);
|
||||
let hoveredNodeId = $state(-1);
|
||||
|
||||
const graph = getGraphManager();
|
||||
const graphState = getGraphState();
|
||||
|
||||
function getEdgeId(edge: Edge) {
|
||||
return `${edge[0].id}-${edge[1]}-${edge[2].id}-${edge[3]}`;
|
||||
}
|
||||
|
||||
function updateNodePosition(node: Node) {
|
||||
if (node?.tmp?.ref && node?.tmp?.mesh) {
|
||||
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
|
||||
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
|
||||
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);
|
||||
function getEdgePosition(edge: Edge) {
|
||||
const fromNode = graph.nodes.get(edge[0].id);
|
||||
const toNode = graph.nodes.get(edge[2].id);
|
||||
|
||||
const nodeHeightCache: Record<string, number> = {};
|
||||
function getNodeHeight(nodeTypeId: string) {
|
||||
if (nodeTypeId in nodeHeightCache) {
|
||||
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);
|
||||
}
|
||||
// This check is important because nodes might not be there during some transitions.
|
||||
if (!fromNode || !toNode) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
|
||||
// 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] = 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],
|
||||
];
|
||||
const pos1 = graphState.getSocketPosition(fromNode, edge[1]);
|
||||
const pos2 = graphState.getSocketPosition(toNode, edge[3]);
|
||||
return [pos1[0], pos1[1], pos2[0], pos2[1]];
|
||||
}
|
||||
|
||||
function handleMouseMove(event: MouseEvent) {
|
||||
let mx = event.clientX - rect.x;
|
||||
let my = event.clientY - rect.y;
|
||||
let mx = event.clientX - graphState.rect.x;
|
||||
let my = event.clientY - graphState.rect.y;
|
||||
|
||||
mousePosition = projectScreenToWorld(mx, my);
|
||||
hoveredNodeId = getNodeIdFromEvent(event);
|
||||
graphState.mousePosition = graphState.projectScreenToWorld(mx, my);
|
||||
hoveredNodeId = graphState.getNodeIdFromEvent(event);
|
||||
|
||||
if (!mouseDown) return;
|
||||
if (!graphState.mouseDown) return;
|
||||
|
||||
// we are creating a new edge here
|
||||
if (graphState.activeSocket || graphState.possibleSockets?.length) {
|
||||
@@ -263,8 +77,8 @@
|
||||
let _socket;
|
||||
for (const socket of graphState.possibleSockets) {
|
||||
const dist = Math.sqrt(
|
||||
(socket.position[0] - mousePosition[0]) ** 2 +
|
||||
(socket.position[1] - mousePosition[1]) ** 2,
|
||||
(socket.position[0] - graphState.mousePosition[0]) ** 2 +
|
||||
(socket.position[1] - graphState.mousePosition[1]) ** 2,
|
||||
);
|
||||
if (dist < smallestDist) {
|
||||
smallestDist = dist;
|
||||
@@ -273,7 +87,7 @@
|
||||
}
|
||||
|
||||
if (_socket && smallestDist < 0.9) {
|
||||
mousePosition = _socket.position;
|
||||
graphState.mousePosition = _socket.position;
|
||||
graphState.hoveredSocket = _socket;
|
||||
} else {
|
||||
graphState.hoveredSocket = null;
|
||||
@@ -282,19 +96,22 @@
|
||||
}
|
||||
|
||||
// handle box selection
|
||||
if (boxSelection) {
|
||||
if (graphState.boxSelection) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const mouseD = projectScreenToWorld(mouseDown[0], mouseDown[1]);
|
||||
const x1 = Math.min(mouseD[0], mousePosition[0]);
|
||||
const x2 = Math.max(mouseD[0], mousePosition[0]);
|
||||
const y1 = Math.min(mouseD[1], mousePosition[1]);
|
||||
const y2 = Math.max(mouseD[1], mousePosition[1]);
|
||||
const mouseD = graphState.projectScreenToWorld(
|
||||
graphState.mouseDown[0],
|
||||
graphState.mouseDown[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()) {
|
||||
if (!node?.tmp) continue;
|
||||
const x = node.position[0];
|
||||
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) {
|
||||
graphState.selectedNodes?.add(node.id);
|
||||
} else {
|
||||
@@ -305,7 +122,7 @@
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (!node || event.buttons !== 1) return;
|
||||
|
||||
@@ -314,11 +131,13 @@
|
||||
const oldX = node.tmp.downX || 0;
|
||||
const oldY = node.tmp.downY || 0;
|
||||
|
||||
let newX = oldX + (mx - mouseDown[0]) / cameraPosition[2];
|
||||
let newY = oldY + (my - mouseDown[1]) / cameraPosition[2];
|
||||
let newX =
|
||||
oldX + (mx - graphState.mouseDown[0]) / graphState.cameraPosition[2];
|
||||
let newY =
|
||||
oldY + (my - graphState.mouseDown[1]) / graphState.cameraPosition[2];
|
||||
|
||||
if (event.ctrlKey) {
|
||||
const snapLevel = getSnapLevel();
|
||||
const snapLevel = graphState.getSnapLevel();
|
||||
if (snapToGrid) {
|
||||
newX = snapPointToGrid(newX, 5 / snapLevel);
|
||||
newY = snapPointToGrid(newY, 5 / snapLevel);
|
||||
@@ -341,31 +160,35 @@
|
||||
if (!n?.tmp) continue;
|
||||
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
||||
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
|
||||
updateNodePosition(n);
|
||||
graphState.updateNodePosition(n);
|
||||
}
|
||||
}
|
||||
|
||||
node.tmp.x = newX;
|
||||
node.tmp.y = newY;
|
||||
|
||||
updateNodePosition(node);
|
||||
graphState.updateNodePosition(node);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// here we are handling panning of camera
|
||||
isPanning = true;
|
||||
let newX = cameraDown[0] - (mx - mouseDown[0]) / cameraPosition[2];
|
||||
let newY = cameraDown[1] - (my - mouseDown[1]) / cameraPosition[2];
|
||||
let newX =
|
||||
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;
|
||||
function handleMouseScroll(event: WheelEvent) {
|
||||
const bodyIsFocused =
|
||||
document.activeElement === document.body ||
|
||||
document.activeElement === wrapper ||
|
||||
document.activeElement === graphState.wrapper ||
|
||||
document?.activeElement?.id === "graph";
|
||||
if (!bodyIsFocused) return;
|
||||
|
||||
@@ -379,23 +202,30 @@
|
||||
minZoom,
|
||||
Math.min(
|
||||
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
|
||||
const zoomRatio = newZoom / cameraPosition[2];
|
||||
const zoomRatio = newZoom / graphState.cameraPosition[2];
|
||||
|
||||
// Update camera position and zoom level
|
||||
setCameraTransform(
|
||||
mousePosition[0] - (mousePosition[0] - cameraPosition[0]) / zoomRatio,
|
||||
mousePosition[1] - (mousePosition[1] - cameraPosition[1]) / zoomRatio,
|
||||
graphState.setCameraTransform(
|
||||
graphState.mousePosition[0] -
|
||||
(graphState.mousePosition[0] - graphState.cameraPosition[0]) /
|
||||
zoomRatio,
|
||||
graphState.mousePosition[1] -
|
||||
(graphState.mousePosition[1] - graphState.cameraPosition[1]) /
|
||||
zoomRatio,
|
||||
newZoom,
|
||||
);
|
||||
}
|
||||
|
||||
function handleMouseDown(event: MouseEvent) {
|
||||
if (mouseDown) return;
|
||||
if (graphState.mouseDown) return;
|
||||
graphState.edgeEndPosition = null;
|
||||
|
||||
if (event.target instanceof HTMLElement) {
|
||||
if (
|
||||
@@ -407,15 +237,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
let mx = event.clientX - rect.x;
|
||||
let my = event.clientY - rect.y;
|
||||
let mx = event.clientX - graphState.rect.x;
|
||||
let my = event.clientY - graphState.rect.y;
|
||||
|
||||
mouseDown = [mx, my];
|
||||
cameraDown[0] = cameraPosition[0];
|
||||
cameraDown[1] = cameraPosition[1];
|
||||
graphState.mouseDown = [mx, my];
|
||||
cameraDown[0] = graphState.cameraPosition[0];
|
||||
cameraDown[1] = graphState.cameraPosition[1];
|
||||
|
||||
const clickedNodeId = getNodeIdFromEvent(event);
|
||||
mouseDownId = clickedNodeId;
|
||||
const clickedNodeId = graphState.getNodeIdFromEvent(event);
|
||||
mouseDownNodeId = clickedNodeId;
|
||||
|
||||
// if we clicked on a node
|
||||
if (clickedNodeId !== -1) {
|
||||
@@ -448,7 +278,7 @@
|
||||
graphState.clearSelection();
|
||||
}
|
||||
} else if (event.ctrlKey) {
|
||||
boxSelection = true;
|
||||
graphState.boxSelection = true;
|
||||
}
|
||||
|
||||
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) {
|
||||
isPanning = false;
|
||||
if (!mouseDown) return;
|
||||
if (!graphState.mouseDown) return;
|
||||
|
||||
const activeNode = graph.getNode(graphState.activeNodeId);
|
||||
|
||||
const clickedNodeId = getNodeIdFromEvent(event);
|
||||
const clickedNodeId = graphState.getNodeIdFromEvent(event);
|
||||
|
||||
if (clickedNodeId !== -1) {
|
||||
if (activeNode) {
|
||||
@@ -718,7 +321,7 @@
|
||||
activeNode.tmp = activeNode.tmp || {};
|
||||
activeNode.tmp.isMoving = false;
|
||||
if (snapToGrid) {
|
||||
const snapLevel = getSnapLevel();
|
||||
const snapLevel = graphState.getSnapLevel();
|
||||
activeNode.position[0] = snapPointToGrid(
|
||||
activeNode?.tmp?.x ?? activeNode.position[0],
|
||||
5 / snapLevel,
|
||||
@@ -761,7 +364,7 @@
|
||||
) {
|
||||
node.tmp.x = lerp(node.tmp.x, node.position[0], a);
|
||||
node.tmp.y = lerp(node.tmp.y, node.position[1], a);
|
||||
updateNodePosition(node);
|
||||
graphState.updateNodePosition(node);
|
||||
if (node?.tmp?.isMoving) {
|
||||
return false;
|
||||
}
|
||||
@@ -794,12 +397,21 @@
|
||||
graph.save();
|
||||
} else if (graphState.activeSocket && event.ctrlKey) {
|
||||
// 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") {
|
||||
addMenuPosition = [mousePosition[0], mousePosition[1] - 3];
|
||||
graphState.addMenuPosition = [
|
||||
graphState.mousePosition[0],
|
||||
graphState.mousePosition[1] - 3,
|
||||
];
|
||||
} else {
|
||||
addMenuPosition = [mousePosition[0] - 20, mousePosition[1] - 3];
|
||||
graphState.addMenuPosition = [
|
||||
graphState.mousePosition[0] - 20,
|
||||
graphState.mousePosition[1] - 3,
|
||||
];
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -807,27 +419,23 @@
|
||||
// check if camera moved
|
||||
if (
|
||||
clickedNodeId === -1 &&
|
||||
!boxSelection &&
|
||||
cameraDown[0] === cameraPosition[0] &&
|
||||
cameraDown[1] === cameraPosition[1] &&
|
||||
isBodyFocused()
|
||||
!graphState.boxSelection &&
|
||||
cameraDown[0] === graphState.cameraPosition[0] &&
|
||||
cameraDown[1] === graphState.cameraPosition[1] &&
|
||||
graphState.isBodyFocused()
|
||||
) {
|
||||
graphState.activeNodeId = -1;
|
||||
graphState.clearSelection();
|
||||
}
|
||||
|
||||
mouseDown = null;
|
||||
boxSelection = false;
|
||||
graphState.mouseDown = null;
|
||||
graphState.boxSelection = false;
|
||||
graphState.activeSocket = null;
|
||||
graphState.possibleSockets = [];
|
||||
graphState.hoveredSocket = null;
|
||||
addMenuPosition = null;
|
||||
graphState.addMenuPosition = null;
|
||||
}
|
||||
|
||||
let isPanning = $state(false);
|
||||
let isDragging = $state(false);
|
||||
let hoveredNodeId = $state(-1);
|
||||
|
||||
function handleMouseLeave() {
|
||||
isDragging = false;
|
||||
isPanning = false;
|
||||
@@ -838,8 +446,8 @@
|
||||
isDragging = false;
|
||||
if (!event.dataTransfer) return;
|
||||
const nodeId = event.dataTransfer.getData("data/node-id") as NodeType;
|
||||
let mx = event.clientX - rect.x;
|
||||
let my = event.clientY - rect.y;
|
||||
let mx = event.clientX - graphState.rect.x;
|
||||
let my = event.clientY - graphState.rect.y;
|
||||
|
||||
if (nodeId) {
|
||||
let nodeOffsetX = event.dataTransfer.getData("data/node-offset-x");
|
||||
@@ -857,7 +465,7 @@
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const pos = projectScreenToWorld(mx, my);
|
||||
const pos = graphState.projectScreenToWorld(mx, my);
|
||||
graph.registry.load([nodeId]).then(() => {
|
||||
graph.createNode({
|
||||
type: nodeId,
|
||||
@@ -878,7 +486,7 @@
|
||||
graph.createNode({
|
||||
type: nodeType.id,
|
||||
props: {},
|
||||
position: projectScreenToWorld(mx, my),
|
||||
position: graphState.projectScreenToWorld(mx, my),
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -919,7 +527,7 @@
|
||||
if (localStorage.getItem("cameraPosition")) {
|
||||
const cPosition = JSON.parse(localStorage.getItem("cameraPosition")!);
|
||||
if (Array.isArray(cPosition)) {
|
||||
setCameraTransform(cPosition[0], cPosition[1], cPosition[2]);
|
||||
graphState.setCameraTransform(cPosition[0], cPosition[1], cPosition[2]);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -929,15 +537,15 @@
|
||||
|
||||
<div
|
||||
onwheel={handleMouseScroll}
|
||||
bind:this={wrapper}
|
||||
bind:this={graphState.wrapper}
|
||||
class="graph-wrapper"
|
||||
class:is-panning={isPanning}
|
||||
class:is-hovering={hoveredNodeId !== -1}
|
||||
aria-label="Graph"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
bind:clientWidth={width}
|
||||
bind:clientHeight={height}
|
||||
bind:clientWidth={graphState.width}
|
||||
bind:clientHeight={graphState.height}
|
||||
ondragenter={handleDragEnter}
|
||||
ondragover={handlerDragOver}
|
||||
ondragexit={handleDragEnd}
|
||||
@@ -957,44 +565,90 @@
|
||||
<label for="drop-zone"></label>
|
||||
|
||||
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
|
||||
<Camera bind:camera position={cameraPosition} />
|
||||
<Camera
|
||||
bind:camera={graphState.camera}
|
||||
position={graphState.cameraPosition}
|
||||
/>
|
||||
|
||||
{#if showGrid !== false}
|
||||
<Background {cameraPosition} {maxZoom} {minZoom} {width} {height} />
|
||||
<Background
|
||||
cameraPosition={graphState.cameraPosition}
|
||||
{maxZoom}
|
||||
{minZoom}
|
||||
width={graphState.width}
|
||||
height={graphState.height}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if boxSelection && mouseDown}
|
||||
{#if graphState.boxSelection && graphState.mouseDown}
|
||||
<BoxSelection
|
||||
{cameraPosition}
|
||||
cameraPosition={graphState.cameraPosition}
|
||||
p1={{
|
||||
x: cameraPosition[0] + (mouseDown[0] - width / 2) / cameraPosition[2],
|
||||
x:
|
||||
graphState.cameraPosition[0] +
|
||||
(graphState.mouseDown[0] - graphState.width / 2) /
|
||||
graphState.cameraPosition[2],
|
||||
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 graph.status === "idle"}
|
||||
{#if addMenuPosition}
|
||||
<AddMenu bind:position={addMenuPosition} />
|
||||
{#if graphState.addMenuPosition}
|
||||
<AddMenu />
|
||||
{/if}
|
||||
|
||||
{#if graphState.activeSocket}
|
||||
<FloatingEdge
|
||||
z={cameraPosition[2]}
|
||||
z={graphState.cameraPosition[2]}
|
||||
from={{
|
||||
x: graphState.activeSocket.position[0],
|
||||
y: graphState.activeSocket.position[1],
|
||||
}}
|
||||
to={{
|
||||
x: edgeEndPosition?.[0] ?? mousePosition[0],
|
||||
y: edgeEndPosition?.[1] ?? mousePosition[1],
|
||||
x: graphState.edgeEndPosition?.[0] ?? graphState.mousePosition[0],
|
||||
y: graphState.edgeEndPosition?.[1] ?? graphState.mousePosition[1],
|
||||
}}
|
||||
/>
|
||||
{/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"}
|
||||
<span>Loading</span>
|
||||
{:else if graph.status === "error"}
|
||||
@@ -1015,6 +669,13 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.is-hovering {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -1,14 +1,10 @@
|
||||
<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 { GraphManager } from "../graph-manager.svelte";
|
||||
import { setContext } from "svelte";
|
||||
import { debounce } from "$lib/helpers";
|
||||
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||
import { GraphState } from "./state.svelte";
|
||||
|
||||
const graphState = new GraphState();
|
||||
setContext("graphState", graphState);
|
||||
import { GraphState, setGraphManager, setGraphState } from "./state.svelte";
|
||||
import { setupKeymaps } from "../keymaps";
|
||||
|
||||
type Props = {
|
||||
graph: Graph;
|
||||
@@ -31,8 +27,8 @@
|
||||
registry,
|
||||
settings = $bindable(),
|
||||
activeNode = $bindable(),
|
||||
showGrid,
|
||||
snapToGrid,
|
||||
showGrid = $bindable(true),
|
||||
snapToGrid = $bindable(true),
|
||||
showHelp = $bindable(false),
|
||||
settingTypes = $bindable(),
|
||||
onsave,
|
||||
@@ -40,10 +36,14 @@
|
||||
}: Props = $props();
|
||||
|
||||
export const keymap = createKeyMap([]);
|
||||
setContext("keymap", keymap);
|
||||
|
||||
export const manager = new GraphManager(registry);
|
||||
setContext("graphManager", manager);
|
||||
setGraphManager(manager);
|
||||
|
||||
const graphState = new GraphState(manager);
|
||||
setGraphState(graphState);
|
||||
|
||||
setupKeymaps(keymap, manager, graphState);
|
||||
|
||||
$effect(() => {
|
||||
if (graphState.activeNodeId !== -1) {
|
||||
@@ -53,13 +53,16 @@
|
||||
}
|
||||
});
|
||||
|
||||
const updateSettings = debounce((s: Record<string, any>) => {
|
||||
manager.setSettings(s);
|
||||
}, 200);
|
||||
$effect(() => {
|
||||
if (!graphState.addMenuPosition) {
|
||||
graphState.edgeEndPosition = null;
|
||||
graphState.activeSocket = null;
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (settingTypes && settings) {
|
||||
updateSettings(settings);
|
||||
manager.setSettings(settings);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -75,4 +78,4 @@
|
||||
manager.load(graph);
|
||||
</script>
|
||||
|
||||
<GraphEl bind:showGrid bind:snapToGrid bind:showHelp />
|
||||
<GraphEl {keymap} bind:showGrid bind:snapToGrid bind:showHelp />
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { GraphManager } from "../graph-manager.svelte";
|
||||
import { getContext } from "svelte";
|
||||
|
||||
export function getGraphManager(): GraphManager {
|
||||
return getContext("graphManager");
|
||||
}
|
||||
@@ -1,12 +1,59 @@
|
||||
import type { Socket } from "@nodes/types";
|
||||
import { getContext } from "svelte";
|
||||
import type { Node, Socket } from "@nodarium/types";
|
||||
import { getContext, setContext } from "svelte";
|
||||
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() {
|
||||
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 {
|
||||
|
||||
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);
|
||||
selectedNodes = new SvelteSet<number>();
|
||||
activeSocket = $state<Socket | null>(null);
|
||||
@@ -15,7 +62,269 @@ export class GraphState {
|
||||
possibleSocketIds = $derived(
|
||||
new Set(this.possibleSockets.map((s) => `${s.node.id}-${s.index}`)),
|
||||
);
|
||||
|
||||
clearSelection() {
|
||||
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]
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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 { createLogger } from "@nodes/utils";
|
||||
import { createLogger } from "@nodarium/utils";
|
||||
|
||||
const diff = create({
|
||||
objectHash: function (obj, index) {
|
||||
|
||||
192
app/src/lib/graph-interface/keymaps.ts
Normal file
192
app/src/lib/graph-interface/keymaps.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { animate, lerp } from "$lib/helpers";
|
||||
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||
import FileSaver from "file-saver";
|
||||
import type { GraphManager } from "./graph-manager.svelte";
|
||||
import type { GraphState } from "./graph/state.svelte";
|
||||
|
||||
type Keymap = ReturnType<typeof createKeyMap>;
|
||||
export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: GraphState) {
|
||||
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "l",
|
||||
description: "Select linked nodes",
|
||||
callback: () => {
|
||||
const activeNode = graph.getNode(graphState.activeNodeId);
|
||||
if (activeNode) {
|
||||
const nodes = graph.getLinkedNodes(activeNode);
|
||||
graphState.selectedNodes.clear();
|
||||
for (const node of nodes) {
|
||||
graphState.selectedNodes.add(node.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "?",
|
||||
description: "Toggle Help",
|
||||
callback: () => {
|
||||
// TODO: fix this
|
||||
// showHelp = !showHelp;
|
||||
},
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "c",
|
||||
ctrl: true,
|
||||
description: "Copy active nodes",
|
||||
callback: graphState.copyNodes,
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "v",
|
||||
ctrl: true,
|
||||
description: "Paste nodes",
|
||||
callback: graphState.pasteNodes,
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "Escape",
|
||||
description: "Deselect nodes",
|
||||
callback: () => {
|
||||
graphState.activeNodeId = -1;
|
||||
graphState.clearSelection();
|
||||
graphState.edgeEndPosition = null;
|
||||
(document.activeElement as HTMLElement)?.blur();
|
||||
},
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "A",
|
||||
shift: true,
|
||||
description: "Add new Node",
|
||||
callback: () => {
|
||||
graphState.addMenuPosition = [graphState.mousePosition[0], graphState.mousePosition[1]];
|
||||
},
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: ".",
|
||||
description: "Center camera",
|
||||
callback: () => {
|
||||
if (!graphState.isBodyFocused()) return;
|
||||
|
||||
const average = [0, 0];
|
||||
for (const node of graph.nodes.values()) {
|
||||
average[0] += node.position[0];
|
||||
average[1] += node.position[1];
|
||||
}
|
||||
average[0] = average[0] ? average[0] / graph.nodes.size : 0;
|
||||
average[1] = average[1] ? average[1] / graph.nodes.size : 0;
|
||||
|
||||
const camX = graphState.cameraPosition[0];
|
||||
const camY = graphState.cameraPosition[1];
|
||||
const camZ = graphState.cameraPosition[2];
|
||||
|
||||
const ease = (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);
|
||||
|
||||
animate(500, (a: number) => {
|
||||
graphState.setCameraTransform(
|
||||
lerp(camX, average[0], ease(a)),
|
||||
lerp(camY, average[1], ease(a)),
|
||||
lerp(camZ, 2, ease(a)),
|
||||
);
|
||||
if (graphState.mouseDown) return false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "a",
|
||||
ctrl: true,
|
||||
preventDefault: true,
|
||||
description: "Select all nodes",
|
||||
callback: () => {
|
||||
if (!graphState.isBodyFocused()) return;
|
||||
for (const node of graph.nodes.keys()) {
|
||||
graphState.selectedNodes.add(node);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "z",
|
||||
ctrl: true,
|
||||
description: "Undo",
|
||||
callback: () => {
|
||||
if (!graphState.isBodyFocused()) return;
|
||||
graph.undo();
|
||||
for (const node of graph.nodes.values()) {
|
||||
graphState.updateNodePosition(node);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "y",
|
||||
ctrl: true,
|
||||
description: "Redo",
|
||||
callback: () => {
|
||||
graph.redo();
|
||||
for (const node of graph.nodes.values()) {
|
||||
graphState.updateNodePosition(node);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "s",
|
||||
ctrl: true,
|
||||
description: "Save",
|
||||
preventDefault: true,
|
||||
callback: () => {
|
||||
const state = graph.serialize();
|
||||
const blob = new Blob([JSON.stringify(state)], {
|
||||
type: "application/json;charset=utf-8",
|
||||
});
|
||||
FileSaver.saveAs(blob, "nodarium-graph.json");
|
||||
},
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: ["Delete", "Backspace", "x"],
|
||||
description: "Delete selected nodes",
|
||||
callback: (event) => {
|
||||
if (!graphState.isBodyFocused()) return;
|
||||
graph.startUndoGroup();
|
||||
if (graphState.activeNodeId !== -1) {
|
||||
const node = graph.getNode(graphState.activeNodeId);
|
||||
if (node) {
|
||||
graph.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||
graphState.activeNodeId = -1;
|
||||
}
|
||||
}
|
||||
if (graphState.selectedNodes) {
|
||||
for (const nodeId of graphState.selectedNodes) {
|
||||
const node = graph.getNode(nodeId);
|
||||
if (node) {
|
||||
graph.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||
}
|
||||
}
|
||||
graphState.clearSelection();
|
||||
}
|
||||
graph.saveUndoGroup();
|
||||
},
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "f",
|
||||
description: "Smart Connect Nodes",
|
||||
callback: () => {
|
||||
const nodes = [...graphState.selectedNodes.values()]
|
||||
.map((g) => graph.getNode(g))
|
||||
.filter((n) => !!n);
|
||||
const edge = graph.smartConnect(nodes[0], nodes[1]);
|
||||
if (!edge) graph.smartConnect(nodes[1], nodes[0]);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { Node } from "@nodes/types";
|
||||
import { getContext, onMount } from "svelte";
|
||||
import { getGraphState } from "../graph/state.svelte";
|
||||
import type { Node } from "@nodarium/types";
|
||||
import { onMount } from "svelte";
|
||||
import { getGraphManager, getGraphState } from "../graph/state.svelte";
|
||||
import { T } from "@threlte/core";
|
||||
import { type Mesh } from "three";
|
||||
import NodeFrag from "./Node.frag";
|
||||
@@ -10,6 +10,7 @@
|
||||
import { colors } from "../graph/colors.svelte";
|
||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||
|
||||
const graph = getGraphManager();
|
||||
const graphState = getGraphState();
|
||||
|
||||
type Props = {
|
||||
@@ -17,7 +18,7 @@
|
||||
inView: boolean;
|
||||
z: number;
|
||||
};
|
||||
let { node = $bindable(), inView, z }: Props = $props();
|
||||
let { node, inView, z }: Props = $props();
|
||||
|
||||
const isActive = $derived(graphState.activeNodeId === node.id);
|
||||
const isSelected = $derived(graphState.selectedNodes.has(node.id));
|
||||
@@ -31,14 +32,9 @@
|
||||
: colors.outline;
|
||||
});
|
||||
|
||||
const updateNodePosition =
|
||||
getContext<(n: Node) => void>("updateNodePosition");
|
||||
|
||||
const getNodeHeight = getContext<(n: string) => number>("getNodeHeight");
|
||||
|
||||
let meshRef: Mesh | undefined = $state();
|
||||
|
||||
const height = getNodeHeight?.(node.type);
|
||||
const height = graphState.getNodeHeight(node.type);
|
||||
|
||||
$effect(() => {
|
||||
if (!node?.tmp) node.tmp = {};
|
||||
@@ -48,7 +44,7 @@
|
||||
onMount(() => {
|
||||
if (!node.tmp) node.tmp = {};
|
||||
node.tmp.mesh = meshRef;
|
||||
updateNodePosition?.(node);
|
||||
graphState.updateNodePosition(node);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -78,4 +74,4 @@
|
||||
/>
|
||||
</T.Mesh>
|
||||
|
||||
<NodeHtml bind:node {inView} {isActive} {isSelected} {z} />
|
||||
<NodeHtml {node} {inView} {isActive} {isSelected} {z} />
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { Node } from "@nodes/types";
|
||||
import type { Node } from "@nodarium/types";
|
||||
import NodeHeader from "./NodeHeader.svelte";
|
||||
import NodeParameter from "./NodeParameter.svelte";
|
||||
import { getContext, onMount } from "svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { getGraphState } from "../graph/state.svelte";
|
||||
|
||||
let ref: HTMLDivElement;
|
||||
|
||||
const graphState = getGraphState();
|
||||
|
||||
type Props = {
|
||||
node: Node;
|
||||
position?: "absolute" | "fixed" | "relative";
|
||||
@@ -32,13 +35,10 @@
|
||||
p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true,
|
||||
);
|
||||
|
||||
const updateNodePosition =
|
||||
getContext<(n: Node) => void>("updateNodePosition");
|
||||
|
||||
onMount(() => {
|
||||
node.tmp = node.tmp || {};
|
||||
node.tmp.ref = ref;
|
||||
updateNodePosition?.(node);
|
||||
graphState?.updateNodePosition(node);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { getGraphState } from "../graph/state.svelte.js";
|
||||
import { createNodePath } from "../helpers/index.js";
|
||||
import type { Node, Socket } from "@nodes/types";
|
||||
import { getContext } from "svelte";
|
||||
import type { Node } from "@nodarium/types";
|
||||
|
||||
const graphState = getGraphState();
|
||||
|
||||
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) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
setDownSocket?.({
|
||||
graphState.setDownSocket?.({
|
||||
node,
|
||||
index: 0,
|
||||
position: getSocketPosition?.(node, 0),
|
||||
position: graphState.getSocketPosition?.(node, 0),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { Node, NodeInput } from "@nodes/types";
|
||||
import { getGraphManager } from "../graph/context.js";
|
||||
import { Input } from "@nodes/ui";
|
||||
import type { Node, NodeInput } from "@nodarium/types";
|
||||
import { Input } from "@nodarium/ui";
|
||||
import type { GraphManager } from "../graph-manager.svelte";
|
||||
|
||||
type Props = {
|
||||
node: Node;
|
||||
input: NodeInput;
|
||||
id: string;
|
||||
elementId?: string;
|
||||
graph?: GraphManager;
|
||||
};
|
||||
|
||||
const {
|
||||
@@ -15,10 +16,9 @@
|
||||
input,
|
||||
id,
|
||||
elementId = `input-${Math.random().toString(36).substring(7)}`,
|
||||
graph,
|
||||
}: Props = $props();
|
||||
|
||||
const graph = getGraphManager();
|
||||
|
||||
function getDefaultValue() {
|
||||
if (node?.props?.[id] !== undefined) return node?.props?.[id] as number;
|
||||
if ("value" in input && input?.value !== undefined)
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type {
|
||||
NodeInput as NodeInputType,
|
||||
Socket,
|
||||
Node as NodeType,
|
||||
} from "@nodes/types";
|
||||
import { getContext } from "svelte";
|
||||
} from "@nodarium/types";
|
||||
import { createNodePath } from "../helpers/index.js";
|
||||
import { getGraphManager } from "../graph/context.js";
|
||||
import NodeInput from "./NodeInput.svelte";
|
||||
import { getGraphState } from "../graph/state.svelte.js";
|
||||
import { getGraphManager, getGraphState } from "../graph/state.svelte.js";
|
||||
|
||||
type Props = {
|
||||
node: NodeType;
|
||||
@@ -17,31 +14,26 @@
|
||||
isLast?: boolean;
|
||||
};
|
||||
|
||||
const graph = getGraphManager();
|
||||
|
||||
let { node = $bindable(), input, id, isLast }: Props = $props();
|
||||
|
||||
const inputType = node?.tmp?.type?.inputs?.[id]!;
|
||||
|
||||
const socketId = `${node.id}-${id}`;
|
||||
|
||||
const graph = getGraphManager();
|
||||
const graphState = getGraphState();
|
||||
const graphId = graph?.id;
|
||||
|
||||
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) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setDownSocket?.({
|
||||
graphState.setDownSocket({
|
||||
node,
|
||||
index: id,
|
||||
position: getSocketPosition?.(node, id),
|
||||
position: graphState.getSocketPosition?.(node, id),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,7 +79,7 @@
|
||||
<label for={elementId}>{input.label || id}</label>
|
||||
{/if}
|
||||
{#if inputType.external !== true}
|
||||
<NodeInput {elementId} bind:node {input} {id} />
|
||||
<NodeInput {graph} {elementId} bind:node {input} {id} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Graph } from "@nodes/types";
|
||||
import type { Graph } from "@nodarium/types";
|
||||
|
||||
export function grid(width: number, height: number) {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Graph, Node } from "@nodes/types";
|
||||
import type { Graph, Node } from "@nodarium/types";
|
||||
|
||||
export function tree(depth: number): Graph {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createWasmWrapper } from "@nodes/utils";
|
||||
import { createWasmWrapper } from "@nodarium/utils";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Select } from "@nodes/ui";
|
||||
import { Select } from "@nodarium/ui";
|
||||
import type { Writable } from "svelte/store";
|
||||
|
||||
let activeStore = 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
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;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { writable } from "svelte/store";
|
||||
import BreadCrumbs from "./BreadCrumbs.svelte";
|
||||
import DraggableNode from "./DraggableNode.svelte";
|
||||
import type { RemoteNodeRegistry } from "@nodes/registry";
|
||||
import type { RemoteNodeRegistry } from "@nodarium/registry";
|
||||
|
||||
export let registry: RemoteNodeRegistry;
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<span class="spinner"></span>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Monitor from "./Monitor.svelte";
|
||||
import { humanizeNumber } from "$lib/helpers";
|
||||
import { Checkbox } from "@nodes/ui";
|
||||
import { Checkbox } from "@nodarium/ui";
|
||||
import localStore from "$lib/helpers/localStore";
|
||||
import { type PerformanceData } from "@nodes/utils";
|
||||
import { type PerformanceData } from "@nodarium/utils";
|
||||
import BarSplit from "./BarSplit.svelte";
|
||||
|
||||
export let data: PerformanceData;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { humanizeDuration, humanizeNumber } from "$lib/helpers";
|
||||
import localStore from "$lib/helpers/localStore";
|
||||
import SmallGraph from "./SmallGraph.svelte";
|
||||
import type { PerformanceData, PerformanceStore } from "@nodes/utils";
|
||||
import type { PerformanceData, PerformanceStore } from "@nodarium/utils";
|
||||
|
||||
export let store: PerformanceStore;
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { Canvas } from "@threlte/core";
|
||||
import Scene from "./Scene.svelte";
|
||||
import { Vector3 } from "three";
|
||||
import { decodeFloat, splitNestedArray } from "@nodes/utils";
|
||||
import type { PerformanceStore } from "@nodes/utils";
|
||||
import { decodeFloat, splitNestedArray } from "@nodarium/utils";
|
||||
import type { PerformanceStore } from "@nodarium/utils";
|
||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||
import SmallPerformanceViewer from "$lib/performance/SmallPerformanceViewer.svelte";
|
||||
import { MeshMatcapMaterial, TextureLoader, type Group } from "three";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fastHashArrayBuffer } from "@nodes/utils";
|
||||
import { fastHashArrayBuffer } from "@nodarium/utils";
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
@@ -206,19 +206,16 @@ export function createInstancedGeometryPool(
|
||||
existingInstance &&
|
||||
instanceCount > existingInstance.geometry.userData.count
|
||||
) {
|
||||
console.log("recreating instance");
|
||||
scene.remove(existingInstance);
|
||||
instances.splice(instances.indexOf(existingInstance), 1);
|
||||
existingInstance = new InstancedMesh(geometry, material, instanceCount);
|
||||
scene.add(existingInstance);
|
||||
instances.push(existingInstance);
|
||||
} else if (!existingInstance) {
|
||||
console.log("creating instance");
|
||||
existingInstance = new InstancedMesh(geometry, material, instanceCount);
|
||||
scene.add(existingInstance);
|
||||
instances.push(existingInstance);
|
||||
} else {
|
||||
console.log("updating instance");
|
||||
existingInstance.count = instanceCount;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Graph, RuntimeExecutor } from "@nodes/types";
|
||||
import type { Graph, RuntimeExecutor } from "@nodarium/types";
|
||||
|
||||
export class RemoteRuntimeExecutor implements RuntimeExecutor {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type SyncCache } from "@nodes/types";
|
||||
import { type SyncCache } from "@nodarium/types";
|
||||
|
||||
export class MemoryRuntimeCache implements SyncCache {
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@ import type {
|
||||
NodeRegistry,
|
||||
RuntimeExecutor,
|
||||
SyncCache,
|
||||
} from "@nodes/types";
|
||||
} from "@nodarium/types";
|
||||
import {
|
||||
concatEncodedArrays,
|
||||
createLogger,
|
||||
encodeFloat,
|
||||
fastHashArrayBuffer,
|
||||
type PerformanceStore,
|
||||
} from "@nodes/utils";
|
||||
} from "@nodarium/utils";
|
||||
|
||||
const log = createLogger("runtime-executor");
|
||||
log.mute();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MemoryRuntimeExecutor } from "./runtime-executor";
|
||||
import { RemoteNodeRegistry, IndexDBCache } from "@nodes/registry";
|
||||
import type { Graph } from "@nodes/types";
|
||||
import { createPerformanceStore } from "@nodes/utils";
|
||||
import { RemoteNodeRegistry, IndexDBCache } from "@nodarium/registry";
|
||||
import type { Graph } from "@nodarium/types";
|
||||
import { createPerformanceStore } from "@nodarium/utils";
|
||||
import { MemoryRuntimeCache } from "./runtime-executor-cache";
|
||||
|
||||
const cache = new MemoryRuntimeCache();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/// <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 {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import NestedSettings from "./NestedSettings.svelte";
|
||||
import { localState } from "$lib/helpers/localState.svelte";
|
||||
import type { NodeInput } from "@nodes/types";
|
||||
import Input from "@nodes/ui";
|
||||
import type { NodeInput } from "@nodarium/types";
|
||||
import Input from "@nodarium/ui";
|
||||
|
||||
type Button = { type: "button"; callback: () => void; label?: string };
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NodeInput } from "@nodes/types";
|
||||
import type { NodeInput } from "@nodarium/types";
|
||||
|
||||
type Button = { type: "button"; label?: string };
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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 type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
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>) {
|
||||
const _inputs = $state.snapshot(inputs);
|
||||
return Object.fromEntries(
|
||||
@@ -27,6 +26,7 @@
|
||||
}),
|
||||
);
|
||||
}
|
||||
const nodeDefinition = filterInputs(node.tmp?.type?.inputs);
|
||||
|
||||
type Store = Record<string, number | number[]>;
|
||||
let store = $state<Store>(createStore(node?.props, nodeDefinition));
|
||||
@@ -75,8 +75,12 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if Object.keys(nodeDefinition).length}
|
||||
<NestedSettings
|
||||
id="activeNodeSettings"
|
||||
bind:value={store}
|
||||
type={nodeDefinition}
|
||||
/>
|
||||
{:else}
|
||||
<p class="mx-4">Node has no settings</p>
|
||||
{/if}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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 ActiveNodeSelected from "./ActiveNodeSelected.svelte";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import localStore from "$lib/helpers/localStore";
|
||||
import { Integer } from "@nodes/ui";
|
||||
import { Integer } from "@nodarium/ui";
|
||||
import { writable } from "svelte/store";
|
||||
import { humanizeDuration } from "$lib/helpers";
|
||||
import Monitor from "$lib/performance/Monitor.svelte";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||
import { ShortCut } from "@nodes/ui";
|
||||
import { ShortCut } from "@nodarium/ui";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
type Props = {
|
||||
@@ -11,7 +11,6 @@
|
||||
};
|
||||
|
||||
let { keymaps }: Props = $props();
|
||||
console.log({ keymaps });
|
||||
</script>
|
||||
|
||||
<table class="wrapper">
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import type {
|
||||
Graph,
|
||||
Node as NodeType,
|
||||
NodeDefinition,
|
||||
NodeInput,
|
||||
RuntimeExecutor,
|
||||
} from "@nodes/types";
|
||||
} from "@nodarium/types";
|
||||
export type { Graph, NodeDefinition, NodeInput };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import "@nodes/ui/app.css";
|
||||
import "@nodarium/ui/app.css";
|
||||
import "virtual:uno.css";
|
||||
import "@unocss/reset/normalize.css";
|
||||
</script>
|
||||
|
||||
@@ -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 "@nodes/types";
|
||||
import type { Graph, Node } from "@nodarium/types";
|
||||
import Viewer from "$lib/result-viewer/Viewer.svelte";
|
||||
import {
|
||||
appSettings,
|
||||
@@ -23,8 +23,8 @@
|
||||
WorkerRuntimeExecutor,
|
||||
MemoryRuntimeExecutor,
|
||||
} from "$lib/runtime";
|
||||
import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry";
|
||||
import { createPerformanceStore } from "@nodes/utils";
|
||||
import { IndexDBCache, RemoteNodeRegistry } from "@nodarium/registry";
|
||||
import { createPerformanceStore } from "@nodarium/utils";
|
||||
import BenchmarkPanel from "$lib/sidebar/panels/BenchmarkPanel.svelte";
|
||||
import { debounceAsyncFunction } from "$lib/helpers";
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
<Keymap
|
||||
keymaps={[
|
||||
{ keymap: applicationKeymap, title: "Application" },
|
||||
{ keymap: graphInterface.keymap, title: "Node-Editor" },
|
||||
{ keymap: graphInterface?.keymap, title: "Node-Editor" },
|
||||
]}
|
||||
/>
|
||||
</Panel>
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"build:deploy": "pnpm build",
|
||||
"dev": "pnpm -r --filter 'app' --filter './packages/node-registry' dev"
|
||||
},
|
||||
"packageManager": "pnpm@10.23.0"
|
||||
"packageManager": "pnpm@10.24.0"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@nodes/registry",
|
||||
"name": "@nodarium/registry",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "src/index.ts",
|
||||
@@ -10,8 +10,8 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@nodes/types": "link:../types",
|
||||
"@nodes/utils": "link:../utils",
|
||||
"@nodarium/types": "link:../types",
|
||||
"@nodarium/utils": "link:../utils",
|
||||
"idb": "^8.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AsyncCache } from '@nodes/types';
|
||||
import type { AsyncCache } from '@nodarium/types';
|
||||
import { openDB, type IDBPDatabase } from 'idb';
|
||||
|
||||
export class IndexDBCache implements AsyncCache<ArrayBuffer> {
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
type AsyncCache,
|
||||
type NodeDefinition,
|
||||
type NodeRegistry,
|
||||
} from "@nodes/types";
|
||||
import { createLogger, createWasmWrapper } from "@nodes/utils";
|
||||
} from "@nodarium/types";
|
||||
import { createLogger, createWasmWrapper } from "@nodarium/utils";
|
||||
|
||||
const log = createLogger("node-registry");
|
||||
log.mute();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@nodes/types",
|
||||
"name": "@nodarium/types",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -9,6 +9,9 @@ export const NodeTypeSchema = z
|
||||
export type NodeType = z.infer<typeof NodeTypeSchema>;
|
||||
|
||||
export type Node = {
|
||||
/**
|
||||
* .tmp only exists at runtime
|
||||
*/
|
||||
tmp?: {
|
||||
depth?: number;
|
||||
mesh?: any;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@nodes/ui",
|
||||
"name": "@nodarium/ui",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -53,7 +53,7 @@
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.2.4",
|
||||
"vitest": "^4.0.13",
|
||||
"@nodes/types": "link:../types"
|
||||
"@nodarium/types": "link:../types"
|
||||
},
|
||||
"svelte": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import Integer from './elements/Integer.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';
|
||||
|
||||
interface Props {
|
||||
@@ -27,4 +27,3 @@
|
||||
{:else if input.type === 'vec3'}
|
||||
<Vec3 {id} bind:value />
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@nodes/utils",
|
||||
"name": "@nodarium/utils",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "src/index.ts",
|
||||
@@ -10,7 +10,7 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@nodes/types": "link:../types"
|
||||
"@nodarium/types": "link:../types"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^7.2.4",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//@ts-nocheck
|
||||
import { NodeDefinition } from "@nodes/types";
|
||||
import { NodeDefinition } from "@nodarium/types";
|
||||
|
||||
const cachedTextDecoder = new TextDecoder("utf-8", {
|
||||
ignoreBOM: true,
|
||||
|
||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -10,13 +10,13 @@ importers:
|
||||
|
||||
app:
|
||||
dependencies:
|
||||
'@nodes/registry':
|
||||
'@nodarium/registry':
|
||||
specifier: link:../packages/registry
|
||||
version: link:../packages/registry
|
||||
'@nodes/ui':
|
||||
'@nodarium/ui':
|
||||
specifier: link:../packages/ui
|
||||
version: link:../packages/ui
|
||||
'@nodes/utils':
|
||||
'@nodarium/utils':
|
||||
specifier: link:../packages/utils
|
||||
version: link:../packages/utils
|
||||
'@sveltejs/kit':
|
||||
@@ -53,7 +53,7 @@ importers:
|
||||
'@iconify-json/tabler':
|
||||
specifier: ^1.2.23
|
||||
version: 1.2.23
|
||||
'@nodes/types':
|
||||
'@nodarium/types':
|
||||
specifier: link:../packages/types
|
||||
version: link:../packages/types
|
||||
'@sveltejs/adapter-static':
|
||||
@@ -130,10 +130,10 @@ importers:
|
||||
|
||||
packages/registry:
|
||||
dependencies:
|
||||
'@nodes/types':
|
||||
'@nodarium/types':
|
||||
specifier: link:../types
|
||||
version: link:../types
|
||||
'@nodes/utils':
|
||||
'@nodarium/utils':
|
||||
specifier: link:../utils
|
||||
version: link:../utils
|
||||
idb:
|
||||
@@ -165,7 +165,7 @@ importers:
|
||||
specifier: ^9.7.0
|
||||
version: 9.7.0(@types/three@0.181.0)(svelte@5.43.14)(three@0.181.2)
|
||||
devDependencies:
|
||||
'@nodes/types':
|
||||
'@nodarium/types':
|
||||
specifier: link:../types
|
||||
version: link:../types
|
||||
'@storybook/addon-essentials':
|
||||
@@ -240,7 +240,7 @@ importers:
|
||||
|
||||
packages/utils:
|
||||
dependencies:
|
||||
'@nodes/types':
|
||||
'@nodarium/types':
|
||||
specifier: link:../types
|
||||
version: link:../types
|
||||
devDependencies:
|
||||
|
||||
Reference in New Issue
Block a user