diff --git a/frontend/src/lib/components/AddMenu.svelte b/frontend/src/lib/components/AddMenu.svelte new file mode 100644 index 0000000..981cb7a --- /dev/null +++ b/frontend/src/lib/components/AddMenu.svelte @@ -0,0 +1,164 @@ + + + +
+
+ +
+ +
+ {#each nodes as node} +
{ + if (event.key === "Enter") { + if (position) { + graph.createNode({ type: node.id, position }); + position = null; + } + } + }} + on:mousedown={() => { + if (position) { + graph.createNode({ type: node.id, position }); + position = null; + } + }} + on:focus={() => { + activeNodeId = node.id; + }} + class:selected={node.id === activeNodeId} + on:mouseover={() => { + activeNodeId = node.id; + }} + > + {node.id} +
+ {/each} +
+
+ + + diff --git a/frontend/src/lib/components/graph/Graph.svelte b/frontend/src/lib/components/graph/Graph.svelte index 72bf29b..ca1a10e 100644 --- a/frontend/src/lib/components/graph/Graph.svelte +++ b/frontend/src/lib/components/graph/Graph.svelte @@ -18,6 +18,7 @@ selectedNodes, } from "./stores"; import BoxSelection from "../BoxSelection.svelte"; + import AddMenu from "../AddMenu.svelte"; export let graph: GraphManager; setContext("graphManager", graph); @@ -36,6 +37,7 @@ let loaded = false; const cameraDown = [0, 0]; let cameraPosition: [number, number, number] = [0, 0, 4]; + let addMenuPosition: [number, number] | null = null; $: if (cameraPosition && loaded) { localStorage.setItem("cameraPosition", JSON.stringify(cameraPosition)); @@ -79,16 +81,19 @@ 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.x && node.tmp.y === node.position.y) { + if ( + node.tmp.x === node.position[0] && + node.tmp.y === node.position[1] + ) { delete node.tmp.x; delete node.tmp.y; } } else { - node.tmp.ref.style.setProperty("--nx", `${node.position.x * 10}px`); - node.tmp.ref.style.setProperty("--ny", `${node.position.y * 10}px`); - node.tmp.mesh.position.x = node.position.x + 10; + 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.y + getNodeHeight(node.type) / 2; + node.position[1] + getNodeHeight(node.type) / 2; } } } @@ -113,10 +118,10 @@ const height = getNodeHeight(node.type); const width = 20; return ( - node.position.x > cameraBounds[0] - width && - node.position.x < cameraBounds[1] && - node.position.y > cameraBounds[2] - height && - node.position.y < cameraBounds[3] + node.position[0] > cameraBounds[0] - width && + node.position[0] < cameraBounds[1] && + node.position[1] > cameraBounds[2] - height && + node.position[1] < cameraBounds[3] ); }); @@ -141,8 +146,8 @@ event.clientY, ); for (const node of $nodes.values()) { - const x = node.position.x; - const y = node.position.y; + 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; @@ -213,14 +218,14 @@ ): [number, number] { if (typeof index === "number") { return [ - (node?.tmp?.x ?? node.position.x) + 20, - (node?.tmp?.y ?? node.position.y) + 2.5 + 10 * index, + (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.x, - (node?.tmp?.y ?? node.position.y) + 10 + 10 * _index, + node?.tmp?.x ?? node.position[0], + (node?.tmp?.y ?? node.position[1]) + 10 + 10 * _index, ]; } } @@ -273,8 +278,8 @@ const y2 = Math.max(mouseD[1], mousePosition[1]); for (const node of $nodes.values()) { if (!node?.tmp) continue; - const x = node.position.x; - const y = node.position.y; + const x = node.position[0]; + const y = node.position[1]; const height = getNodeHeight(node.type); if (x > x1 - 20 && x < x2 && y > y1 - height && y < y2) { $selectedNodes?.add(node.id); @@ -420,15 +425,15 @@ const node = graph.getNode($activeNodeId); if (!node) return; node.tmp = node.tmp || {}; - node.tmp.downX = node.position.x; - node.tmp.downY = node.position.y; + node.tmp.downX = node.position[0]; + node.tmp.downY = node.position[1]; if ($selectedNodes) { for (const nodeId of $selectedNodes) { const n = graph.getNode(nodeId); if (!n) continue; n.tmp = n.tmp || {}; - n.tmp.downX = n.position.x; - n.tmp.downY = n.position.y; + n.tmp.downX = n.position[0]; + n.tmp.downY = n.position[1]; } } } @@ -438,6 +443,11 @@ document.activeElement === document.body || document?.activeElement?.id === "graph"; + if (event.key === "l") { + const activeNode = graph.getNode($activeNodeId); + console.log(activeNode); + } + if (event.key === "Escape") { $activeNodeId = -1; $selectedNodes?.clear(); @@ -445,14 +455,18 @@ (document.activeElement as HTMLElement)?.blur(); } + if (event.key === "A" && event.shiftKey) { + addMenuPosition = [mousePosition[0], mousePosition[1]]; + } + if (event.key === ".") { const average = [0, 0]; for (const node of $nodes.values()) { - average[0] += node.position.x; - average[1] += node.position.y; + average[0] += node.position[0]; + average[1] += node.position[1]; } - average[0] /= $nodes.size; - average[1] /= $nodes.size; + average[0] = average[0] ? average[0] / $nodes.size : 0; + average[1] = average[1] ? average[1] / $nodes.size : 0; const camX = cameraPosition[0]; const camY = cameraPosition[1]; @@ -466,6 +480,7 @@ lerp(camY, average[1], ease(a)), lerp(camZ, 2, ease(a)), ); + if (mouseDown) return false; }); } @@ -538,12 +553,12 @@ activeNode.tmp = activeNode.tmp || {}; activeNode.tmp.isMoving = false; const snapLevel = getSnapLevel(); - activeNode.position.x = snapToGrid( - activeNode?.tmp?.x ?? activeNode.position.x, + activeNode.position[0] = snapToGrid( + activeNode?.tmp?.x ?? activeNode.position[0], 5 / snapLevel, ); - activeNode.position.y = snapToGrid( - activeNode?.tmp?.y ?? activeNode.position.y, + activeNode.position[1] = snapToGrid( + activeNode?.tmp?.y ?? activeNode.position[1], 5 / snapLevel, ); const nodes = [ @@ -551,8 +566,8 @@ ] as NodeType[]; const vec = [ - activeNode.position.x - (activeNode?.tmp.x || 0), - activeNode.position.y - (activeNode?.tmp.y || 0), + activeNode.position[0] - (activeNode?.tmp.x || 0), + activeNode.position[1] - (activeNode?.tmp.y || 0), ]; for (const node of nodes) { @@ -560,8 +575,8 @@ node.tmp = node.tmp || {}; const { x, y } = node.tmp; if (x !== undefined && y !== undefined) { - node.position.x = x + vec[0]; - node.position.y = y + vec[1]; + node.position[0] = x + vec[0]; + node.position[1] = y + vec[1]; } } nodes.push(activeNode); @@ -572,8 +587,8 @@ node.tmp["x"] !== undefined && node.tmp["y"] !== undefined ) { - node.tmp.x = lerp(node.tmp.x, node.position.x, a); - node.tmp.y = lerp(node.tmp.y, node.position.y, a); + node.tmp.x = lerp(node.tmp.x, node.position[0], a); + node.tmp.y = lerp(node.tmp.y, node.position[1], a); updateNodePosition(node); if (node?.tmp?.isMoving) { return false; @@ -627,6 +642,7 @@ $possibleSockets = []; $possibleSocketIds = null; $hoveredSocket = null; + addMenuPosition = null; } onMount(() => { @@ -669,12 +685,17 @@ {/if} {#if $status === "idle"} + {#if addMenuPosition} + + {/if} + {#if $activeSocket} {/if} + {#key $graphId} {/key} diff --git a/frontend/src/lib/components/graph/GraphView.svelte b/frontend/src/lib/components/graph/GraphView.svelte index 54e8dd9..2fdc6ac 100644 --- a/frontend/src/lib/components/graph/GraphView.svelte +++ b/frontend/src/lib/components/graph/GraphView.svelte @@ -29,14 +29,14 @@ onMount(() => { for (const node of $nodes.values()) { if (node?.tmp?.ref) { - node.tmp.ref.style.setProperty("--nx", `${node.position.x * 10}px`); - node.tmp.ref.style.setProperty("--ny", `${node.position.y * 10}px`); + node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`); + node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`); } } }); -{#each $edges as edge (edge[0].id + edge[2].id + edge[3])} +{#each $edges as edge (`${edge[0].id}-${edge[1]}-${edge[2].id}-${edge[3]}`)} {@const pos = getEdgePosition(edge)} {@const [x1, y1, x2, y2] = pos} = writable(null); export const possibleSockets: Writable = writable([]); export const possibleSocketIds: Writable | null> = writable(null); - export const colors = writable({ backgroundColorDarker: new Color().setStyle("#101010"), backgroundColor: new Color().setStyle("#151515"), @@ -38,13 +37,7 @@ if (browser) { } - - globalThis["updateColors"] = updateColors; - body.addEventListener("transitionstart", () => { updateColors(); }) - window.onload = () => { - updateColors(); - } } diff --git a/frontend/src/lib/components/node/Node.svelte b/frontend/src/lib/components/node/Node.svelte index ac91e3d..03eb15f 100644 --- a/frontend/src/lib/components/node/Node.svelte +++ b/frontend/src/lib/components/node/Node.svelte @@ -53,8 +53,8 @@ { } this.inputSockets.set(s); }); - this.execute = throttle(() => this._execute(), 100); + this.execute = throttle(() => this._execute(), 50); } serialize(): Graph { const nodes = Array.from(this._nodes.values()).map(node => ({ id: node.id, - position: { x: node.position.x, y: node.position.y }, + position: node.position, type: node.type, props: node.props, })); @@ -58,13 +58,18 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> { console.log(`Execution took ${end - start}ms -> ${result}`); } + getNodeTypes() { + return this.nodeRegistry.getAllNodes(); + } + private _init(graph: Graph) { const nodes = new Map(graph.nodes.map(node => { const nodeType = this.nodeRegistry.getNode(node.type); if (nodeType) { - node.tmp = node.tmp || {}; - node.tmp.type = nodeType; + node.tmp = { + type: nodeType + }; } return [node.id, node] })); @@ -177,6 +182,29 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> { nodes.delete(node.id); return nodes; }); + this.execute() + this.save(); + } + + private createNodeId() { + return Math.max(...this.getAllNodes().map(n => n.id), 0) + 1; + } + + createNode({ type, position }: { type: string, position: [number, number] }) { + + const nodeType = this.nodeRegistry.getNode(type); + if (!nodeType) { + console.error(`Node type not found: ${type}`); + return; + } + + const node: Node = { id: this.createNodeId(), type, position, tmp: { type: nodeType } }; + + this.nodes.update((nodes) => { + nodes.set(node.id, node); + return nodes; + }); + this.save(); } @@ -205,6 +233,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> { return [...edges.filter(e => e[2].id !== to.id || e[3] !== toSocket), [from, fromSocket, to, toSocket]]; }); + this.execute(); this.save(); } @@ -216,7 +245,13 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> { getParentsOfNode(node: Node) { const parents = []; const stack = node.tmp?.parents?.slice(0); + + while (stack?.length) { + if (parents.length > 1000000) { + console.log("Infinite loop detected") + break; + } const parent = stack.pop(); if (!parent) continue; parents.push(parent); @@ -287,6 +322,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> { this.edges.update((edges) => { return edges.filter((e) => e[0].id !== id0 || e[1] !== sid0 || e[2].id !== id2 || e[3] !== sid2); }); + this.execute(); this.save(); } diff --git a/frontend/src/lib/graphs/grid.ts b/frontend/src/lib/graphs/grid.ts index c1e0f75..fbff83a 100644 --- a/frontend/src/lib/graphs/grid.ts +++ b/frontend/src/lib/graphs/grid.ts @@ -19,10 +19,7 @@ export function grid(width: number, height: number) { tmp: { visible: false, }, - position: { - x: x * 30, - y: y * 40, - }, + position: [x * 30, y * 40], props: i == 0 ? { value: 0 } : {}, type: i == 0 ? "input/float" : "math", }); @@ -35,10 +32,7 @@ export function grid(width: number, height: number) { tmp: { visible: false, }, - position: { - x: width * 30, - y: (height - 1) * 40, - }, + position: [width * 30, (height - 1) * 40], type: "output", props: {}, }); diff --git a/frontend/src/lib/graphs/tree.ts b/frontend/src/lib/graphs/tree.ts index 87690d2..a07bd3e 100644 --- a/frontend/src/lib/graphs/tree.ts +++ b/frontend/src/lib/graphs/tree.ts @@ -6,12 +6,12 @@ export function tree(depth: number): Graph { { id: 0, type: "output", - position: { x: 0, y: 0 } + position: [0, 0] }, { id: 1, type: "math", - position: { x: -40, y: -10 } + position: [-40, -10] } ] @@ -34,13 +34,13 @@ export function tree(depth: number): Graph { nodes.push({ id: id0, type: "math", - position: { x, y: y }, + position: [x, y], }); edges.push([id0, 0, parent, "a"]); nodes.push({ id: id1, type: "math", - position: { x, y: y + 35 }, + position: [x, y + 35], }); edges.push([id1, 0, parent, "b"]); } diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts deleted file mode 100644 index 856f2b6..0000000 --- a/frontend/src/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -// place files you want to import through the `$lib` alias in this folder. diff --git a/frontend/src/lib/node-registry.ts b/frontend/src/lib/node-registry.ts index babc5c5..ed39b06 100644 --- a/frontend/src/lib/node-registry.ts +++ b/frontend/src/lib/node-registry.ts @@ -41,5 +41,8 @@ export class MemoryNodeRegistry implements NodeRegistry { getNode(id: string): NodeType | undefined { return nodeTypes.find((nodeType) => nodeType.id === id); } + getAllNodes(): NodeType[] { + return [...nodeTypes]; + } } diff --git a/frontend/src/lib/types/index.ts b/frontend/src/lib/types/index.ts index 077897a..3233762 100644 --- a/frontend/src/lib/types/index.ts +++ b/frontend/src/lib/types/index.ts @@ -1,4 +1,4 @@ -import type { NodeInput, NodeInputType } from "./inputs"; +import type { NodeInput } from "./inputs"; export type { NodeInput } from "./inputs"; export type Node = { @@ -24,10 +24,7 @@ export type Node = { title?: string; lastModified?: string; }, - position: { - x: number; - y: number; - } + position: [x: number, y: number] } export type NodeType = { @@ -49,6 +46,7 @@ export type Socket = { export interface NodeRegistry { getNode: (id: string) => NodeType | undefined; + getAllNodes: () => NodeType[]; } export interface RuntimeExecutor {