From 305341fdf0a1140799d7ca6025994d153e5154c9 Mon Sep 17 00:00:00 2001 From: Max Richter Date: Wed, 13 Mar 2024 14:30:30 +0100 Subject: [PATCH] feat: implement selection --- frontend/package.json | 1 + frontend/src/lib/components/Node.svelte | 21 +- frontend/src/lib/components/NodeHeader.svelte | 73 ++-- .../src/lib/components/NodeParameter.svelte | 99 +++--- .../src/lib/components/debug/Debug.svelte | 9 + frontend/src/lib/components/edges/Edge.svelte | 4 +- .../src/lib/components/graph/Graph.svelte | 323 +++++++++++++----- .../src/lib/components/graph/GraphView.svelte | 29 +- frontend/src/lib/components/graph/context.ts | 5 - frontend/src/lib/components/graph/stores.ts | 10 + frontend/src/lib/elements/Details.svelte | 20 ++ frontend/src/lib/graph-manager.ts | 50 ++- frontend/src/lib/helpers.ts | 41 +++ frontend/src/lib/types/index.ts | 3 + frontend/src/routes/+page.svelte | 24 +- pnpm-lock.yaml | 11 + 16 files changed, 521 insertions(+), 202 deletions(-) create mode 100644 frontend/src/lib/components/graph/stores.ts create mode 100644 frontend/src/lib/elements/Details.svelte diff --git a/frontend/package.json b/frontend/package.json index ff24d6c..09cb505 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,6 +28,7 @@ "@sveltejs/vite-plugin-svelte": "^3.0.1", "@tauri-apps/cli": "2.0.0-beta.3", "@tsconfig/svelte": "^5.0.2", + "@zerodevx/svelte-json-view": "^1.0.9", "histoire": "^0.17.9", "internal-ip": "^7.0.0", "svelte": "^4.2.8", diff --git a/frontend/src/lib/components/Node.svelte b/frontend/src/lib/components/Node.svelte index fa0f091..78ea74b 100644 --- a/frontend/src/lib/components/Node.svelte +++ b/frontend/src/lib/components/Node.svelte @@ -2,12 +2,11 @@ import type { Node } from "$lib/types"; import NodeHeader from "./NodeHeader.svelte"; import NodeParameter from "./NodeParameter.svelte"; + import { activeNodeId, selectedNodes } from "./graph/stores"; export let node: Node; export let inView = true; - export let possibleSocketIds: null | Set = null; - const type = node?.tmp?.type; const parameters = Object.entries(type?.inputs || {}); @@ -15,17 +14,17 @@
{#each parameters as [key, value], i} - import type { Node } from "$lib/types"; + import { createNodePath } from "$lib/helpers"; + import type { Node, Socket } from "$lib/types"; import { getContext } from "svelte"; - import { getGraphManager, getGraphState } from "./graph/context"; export let node: Node; - const graph = getGraphManager(); - const state = getGraphState(); - - function createPath({ depth = 8, height = 20, y = 50 } = {}) { - let corner = 10; - - let right_bump = node.tmp.type.outputs.length > 0; - - return `M0,100 - ${ - corner - ? ` V${corner} - Q0,0 ${corner / 4},0 - H${100 - corner / 4} - Q100,0 100,${corner} - ` - : ` V0 - H100 - ` - } - V${y - height / 2} - ${ - right_bump - ? ` C${100 - depth},${y - height / 2} ${100 - depth},${y + height / 2} 100,${y + height / 2}` - : ` H100` - } - V100 - Z`.replace(/\s+/g, " "); - } - - const setDownSocket = getContext("setDownSocket"); + const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket"); function handleMouseDown(event: MouseEvent) { event.stopPropagation(); @@ -46,6 +16,35 @@ position: [node.position.x + 5, node.position.y + 0.625], }); } + + const cornerTop = 10; + const rightBump = !!node?.tmp?.type?.outputs?.length; + const aspectRatio = 0.25; + + const path = createNodePath({ + depth: 4.5, + height: 24, + y: 50, + cornerTop, + rightBump, + aspectRatio, + }); + const pathDisabled = createNodePath({ + depth: 0, + height: 15, + y: 50, + cornerTop, + rightBump, + aspectRatio, + }); + const pathHover = createNodePath({ + depth: 6, + height: 30, + y: 50, + cornerTop, + rightBump, + aspectRatio, + });
@@ -66,8 +65,8 @@ height="100" preserveAspectRatio="none" style={` - --path: path("${createPath({ depth: 5, height: 27, y: 50 })}"); - --hover-path: path("${createPath({ depth: 6, height: 33, y: 50 })}"); + --path: path("${path}"); + --hover-path: path("${pathHover}"); `} > - import type { NodeInput } from "$lib/types"; + import type { NodeInput, Socket } from "$lib/types"; import type { Node } from "$lib/types"; import { getContext } from "svelte"; + import { createNodePath } from "$lib/helpers"; + import { possibleSocketIds } from "./graph/stores"; export let node: Node; export let input: NodeInput; export let id: string; export let index: number; - - export let possibleSocketIds: null | Set = null; - export let isLast = false; - function createPath({ depth = 8, height = 20, y = 50 } = {}) { - let corner = isLast ? 5 : 0; - - let right_bump = false; - let left_bump = node.tmp?.type?.inputs?.[id].internal !== true; - - return `M0,0 - H100 - V${y - height / 2} - ${ - right_bump - ? ` C${100 - depth},${y - height / 2} ${100 - depth},${y + height / 2} 100,${y + height / 2}` - : ` H100` - } - ${ - corner - ? ` V${100 - corner} - Q100,100 ${100 - corner / 2},100 - H${corner / 2} - Q0,100 0,${100 - corner} - ` - : ` V100 - H0 - ` - } - ${ - left_bump - ? ` V${y + height / 2} C${depth},${y + height / 2} ${depth},${y - height / 2} 0,${y - height / 2}` - : ` H0` - } - Z`.replace(/\s+/g, " "); - } - - const setDownSocket = getContext("setDownSocket"); + const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket"); function handleMouseDown(ev: MouseEvent) { ev.preventDefault(); @@ -56,12 +22,41 @@ position: [node.position.x, node.position.y + 2.5 + index * 2.5], }); } + + const leftBump = node.tmp?.type?.inputs?.[id].internal !== true; + const cornerBottom = isLast ? 5 : 0; + const aspectRatio = 0.5; + + const path = createNodePath({ + depth: 4, + height: 12, + y: 50, + cornerBottom, + leftBump, + aspectRatio, + }); + const pathDisabled = createNodePath({ + depth: 0, + height: 15, + y: 50, + cornerBottom, + leftBump, + aspectRatio, + }); + const pathHover = createNodePath({ + depth: 8, + height: 24, + y: 50, + cornerBottom, + leftBump, + aspectRatio, + });
@@ -91,9 +86,9 @@ height="100" preserveAspectRatio="none" style={` - --path: path("${createPath({ depth: 5, height: 15, y: 50 })}"); - --hover-path-disabled: path("${createPath({ depth: 0, height: 15, y: 50 })}"); - --hover-path: path("${createPath({ depth: 8, height: 24, y: 50 })}"); + --path: path("${path}"); + --hover-path: path("${pathHover}"); + --hover-path-disabled: path("${pathDisabled}"); `} > @@ -111,6 +106,8 @@ .target { position: absolute; border-radius: 50%; + /* background: red; */ + /* opacity: 0.1; */ } .small.target { @@ -126,6 +123,11 @@ top: 5px; left: -7.5px; cursor: unset; + pointer-events: none; + } + + :global(.hovering-sockets) .large.target { + pointer-events: all; } .content { @@ -169,19 +171,24 @@ } svg path { - stroke-width: 0.2px; transition: 0.2s; fill: #060606; - stroke: #777; - stroke-width: 0.1; + stroke: var(--stroke); + stroke-width: var(--stroke-width); d: var(--path); } :global(.hovering-sockets) .large:hover ~ svg path { d: var(--hover-path); + fill: #131313; + } + + :global(.hovering-sockets) .small:hover ~ svg path { + fill: #161616; } .disabled svg path { d: var(--hover-path-disabled) !important; + fill: #060606 !important; } diff --git a/frontend/src/lib/components/debug/Debug.svelte b/frontend/src/lib/components/debug/Debug.svelte index 94686a6..d53a791 100644 --- a/frontend/src/lib/components/debug/Debug.svelte +++ b/frontend/src/lib/components/debug/Debug.svelte @@ -22,3 +22,12 @@ {/each} + + diff --git a/frontend/src/lib/components/edges/Edge.svelte b/frontend/src/lib/components/edges/Edge.svelte index f6a713e..fa154e2 100644 --- a/frontend/src/lib/components/edges/Edge.svelte +++ b/frontend/src/lib/components/edges/Edge.svelte @@ -22,7 +22,7 @@ let mesh: Mesh; - function update(force = false) { + export const update = function (force = false) { if (!force) { const new_x = from.x + to.x; const new_y = from.y + to.y; @@ -50,7 +50,7 @@ points = curve.getPoints(samples).map((p) => new Vector3(p.x, 0, p.y)); // mesh.setGeometry(points); // mesh.needsUpdate = true; - } + }; update(); $: if (from || to) { diff --git a/frontend/src/lib/components/graph/Graph.svelte b/frontend/src/lib/components/graph/Graph.svelte index b262182..13ab29b 100644 --- a/frontend/src/lib/components/graph/Graph.svelte +++ b/frontend/src/lib/components/graph/Graph.svelte @@ -9,8 +9,15 @@ import GraphView from "./GraphView.svelte"; import type { Node as NodeType } from "$lib/types"; import FloatingEdge from "../edges/FloatingEdge.svelte"; - import * as debug from "../debug"; import type { Socket } from "$lib/types"; + import { + activeNodeId, + activeSocket, + hoveredSocket, + possibleSockets, + possibleSocketIds, + selectedNodes, + } from "./stores"; export let graph: GraphManager; setContext("graphManager", graph); @@ -27,39 +34,68 @@ let width = 100; let height = 100; - let activeNodeId = -1; - let downSocket: null | Socket = null; - let possibleSockets: Socket[] = []; - $: possibleSocketIds = possibleSockets?.length - ? new Set(possibleSockets.map((s) => `${s.node.id}-${s.index}`)) - : null; - let hoveredSocket: Socket | null = null; - $: cameraBounds = [ - cameraPosition[0] - width / cameraPosition[2], - cameraPosition[0] + width / cameraPosition[2], - cameraPosition[1] - height / cameraPosition[2], - cameraPosition[1] + height / cameraPosition[2], + cameraPosition[0] - width / cameraPosition[2] / 2, + cameraPosition[0] + width / cameraPosition[2] / 2, + cameraPosition[1] - height / cameraPosition[2] / 2, + cameraPosition[1] + height / cameraPosition[2] / 2, ]; + export let debug = {}; + $: debug = { + activeNodeId: $activeNodeId, + activeSocket: $activeSocket + ? `${$activeSocket?.node.id}-${$activeSocket?.index}` + : null, + hoveredSocket: $hoveredSocket + ? `${$hoveredSocket?.node.id}-${$hoveredSocket?.index}` + : null, + selectedNodes: [...($selectedNodes?.values() || [])], + }; + + function updateNodePosition(node: NodeType) { + node.tmp = node.tmp || {}; + 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`); + } + } + + const nodeHeightCache: Record = {}; + function getNodeHeight(nodeTypeId: string) { + if (nodeTypeId in nodeHeightCache) { + return nodeHeightCache[nodeTypeId]; + } + const node = graph.getNodeType(nodeTypeId); + if (!node?.inputs) { + return 1.25; + } + const height = 1.25 + 2.5 * Object.keys(node.inputs).length; + nodeHeightCache[nodeTypeId] = height; + return height; + } + setContext("isNodeInView", (node: NodeType) => { + const height = getNodeHeight(node.type); + const width = 5; return ( - node.position.x > cameraBounds[0] && + // check x-axis + node.position.x > cameraBounds[0] - width && node.position.x < cameraBounds[1] && - node.position.y > cameraBounds[2] && + // check y-axis + node.position.y > cameraBounds[2] - height && node.position.y < cameraBounds[3] ); }); setContext("setDownSocket", (socket: Socket) => { - downSocket = socket; + $activeSocket = socket; let { node, index, position } = socket; // remove existing edge if (typeof index === "string") { const edges = graph.getEdgesToNode(node); - console.log({ edges }); for (const edge of edges) { if (edge[3] === index) { node = edge[0]; @@ -72,14 +108,14 @@ } mouseDown = position; - downSocket = { + $activeSocket = { node, index, position, }; - possibleSockets = graph - .getPossibleSockets(downSocket) + $possibleSockets = graph + .getPossibleSockets($activeSocket) .map(([node, index]) => { return { node, @@ -87,6 +123,9 @@ position: getSocketPosition({ node, index }), }; }); + $possibleSocketIds = new Set( + $possibleSockets.map((s) => `${s.node.id}-${s.index}`), + ); }); function getSnapLevel() { @@ -136,10 +175,11 @@ if (!mouseDown) return; - if (possibleSockets?.length) { + // we are creating a new edge here + if ($possibleSockets?.length) { let smallestDist = 1000; let _socket; - for (const socket of possibleSockets) { + for (const socket of $possibleSockets) { const dist = Math.sqrt( (socket.position[0] - mousePosition[0]) ** 2 + (socket.position[1] - mousePosition[1]) ** 2, @@ -152,119 +192,236 @@ if (_socket && smallestDist < 0.3) { mousePosition = _socket.position; - hoveredSocket = _socket; + $hoveredSocket = _socket; } else { - hoveredSocket = null; + $hoveredSocket = null; } } - if (activeNodeId === -1) return; + if ($activeNodeId === -1) return; - const node = graph.getNode(activeNodeId); - if (!node) return; + const node = graph.getNode($activeNodeId); + if (!node || event.buttons !== 1) return; node.tmp = node.tmp || {}; - node.tmp.isMoving = true; - let newX = - (node?.tmp?.downX || 0) + - (event.clientX - mouseDown[0]) / cameraPosition[2]; - let newY = - (node?.tmp?.downY || 0) + - (event.clientY - mouseDown[1]) / cameraPosition[2]; + const oldX = node.tmp.downX || 0; + const oldY = node.tmp.downY || 0; + + let newX = oldX + (event.clientX - mouseDown[0]) / cameraPosition[2]; + let newY = oldY + (event.clientY - mouseDown[1]) / cameraPosition[2]; if (event.ctrlKey) { const snapLevel = getSnapLevel(); newX = snapToGrid(newX, 5 / snapLevel); newY = snapToGrid(newY, 5 / snapLevel); } + + if (!node.tmp.isMoving) { + const dist = Math.sqrt((oldX - newX) ** 2 + (oldY - newY) ** 2); + if (dist > 0.2) { + node.tmp.isMoving = true; + } + } + + const vecX = oldX - newX; + const vecY = oldY - newY; + + if ($selectedNodes?.size) { + for (const nodeId of $selectedNodes) { + const n = graph.getNode(nodeId); + if (!n) continue; + n.position.x = (n?.tmp?.downX || 0) - vecX; + n.position.y = (n?.tmp?.downY || 0) - vecY; + updateNodePosition(n); + } + } + node.position.x = newX; node.position.y = newY; node.position = node.position; - nodes.set($nodes); - edges.set($edges); + updateNodePosition(node); + + $edges = $edges; } function handleMouseDown(event: MouseEvent) { if (mouseDown) return; + mouseDown = [event.clientX, event.clientY]; - for (const node of event.composedPath()) { - let _activeNodeId = (node as unknown as HTMLElement)?.getAttribute?.( - "data-node-id", - )!; + if (event.target instanceof HTMLElement && event.buttons === 1) { + const nodeElement = event.target.closest(".node"); + const _activeNodeId = nodeElement?.getAttribute?.("data-node-id"); if (_activeNodeId) { - activeNodeId = parseInt(_activeNodeId, 10); - break; + const nodeId = parseInt(_activeNodeId, 10); + if ($activeNodeId !== -1) { + // if the selected node is the same as the clicked node + if ($activeNodeId === nodeId) { + //$activeNodeId = -1; + // if the clicked node is different from the selected node and secondary + } else if (event.ctrlKey) { + $selectedNodes = $selectedNodes || new Set(); + $selectedNodes.add($activeNodeId); + $selectedNodes.delete(nodeId); + $activeNodeId = nodeId; + // select the node + } else if (event.shiftKey) { + const activeNode = graph.getNode($activeNodeId); + const newNode = graph.getNode(nodeId); + if (activeNode && newNode) { + const edge = graph.getNodesBetween(activeNode, newNode); + if (edge) { + $selectedNodes = new Set(edge.map((n) => n.id)); + } + $activeNodeId = nodeId; + } + } else if (!$selectedNodes?.has(nodeId)) { + $activeNodeId = nodeId; + } + } else { + $activeNodeId = nodeId; + } + } else { + $activeNodeId = -1; + $selectedNodes?.clear(); + $selectedNodes = $selectedNodes; } } - if (activeNodeId < 0) return; - mouseDown = [event.clientX, event.clientY]; - const node = graph.getNode(activeNodeId); + const node = graph.getNode($activeNodeId); if (!node) return; node.tmp = node.tmp || {}; node.tmp.downX = node.position.x; node.tmp.downY = node.position.y; + 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; + } + } + } + + function handleKeyDown(event: KeyboardEvent) { + if (event.key === "Delete") { + if ($activeNodeId !== -1) { + const node = graph.getNode($activeNodeId); + if (node) { + graph.removeNode(node); + $activeNodeId = -1; + } + } + if ($selectedNodes) { + for (const nodeId of $selectedNodes) { + const node = graph.getNode(nodeId); + if (node) { + graph.removeNode(node); + } + } + $selectedNodes.clear(); + $selectedNodes = $selectedNodes; + } + } } function handleMouseUp(event: MouseEvent) { - if (event.button !== 0) return; + const activeNode = graph.getNode($activeNodeId); - const node = graph.getNode(activeNodeId); - if (node) { - node.tmp = node.tmp || {}; - node.tmp.isMoving = false; + if (event.target instanceof HTMLElement && event.button === 0) { + const nodeElement = event.target.closest(".node"); + const _activeNodeId = nodeElement?.getAttribute?.("data-node-id"); + if (_activeNodeId) { + const nodeId = parseInt(_activeNodeId, 10); + if (activeNode) { + if (!activeNode?.tmp?.isMoving && !event.ctrlKey && !event.shiftKey) { + $selectedNodes?.clear(); + $selectedNodes = $selectedNodes; + $activeNodeId = nodeId; + } + } + } + } + + if (activeNode?.tmp?.isMoving) { + activeNode.tmp = activeNode.tmp || {}; + activeNode.tmp.isMoving = false; const snapLevel = getSnapLevel(); - const fx = snapToGrid(node.position.x, 5 / snapLevel); - const fy = snapToGrid(node.position.y, 5 / snapLevel); + const fx = snapToGrid(activeNode.position.x, 5 / snapLevel); + const fy = snapToGrid(activeNode.position.y, 5 / snapLevel); + if ($selectedNodes) { + for (const nodeId of $selectedNodes) { + const node = graph.getNode(nodeId); + if (!node) continue; + node.tmp = node.tmp || {}; + node.tmp.snapX = node.position.x - (activeNode.position.x - fx); + node.tmp.snapY = node.position.y - (activeNode.position.y - fy); + } + } animate(500, (a: number) => { - node.position.x = lerp(node.position.x, fx, a); - node.position.y = lerp(node.position.y, fy, a); - nodes.set($nodes); - edges.set($edges); - if (node?.tmp?.isMoving) { + activeNode.position.x = lerp(activeNode.position.x, fx, a); + activeNode.position.y = lerp(activeNode.position.y, fy, a); + updateNodePosition(activeNode); + + if ($selectedNodes) { + for (const nodeId of $selectedNodes) { + const node = graph.getNode(nodeId); + if (!node) continue; + node.position.x = lerp(node.position.x, node?.tmp?.snapX || 0, a); + node.position.y = lerp(node.position.y, node?.tmp?.snapY || 0, a); + updateNodePosition(node); + } + } + + if (activeNode?.tmp?.isMoving) { return false; } + + $edges = $edges; }); - } else if (hoveredSocket && downSocket) { - console.log({ hoveredSocket, downSocket }); + } else if ($hoveredSocket && $activeSocket) { if ( - typeof hoveredSocket.index === "number" && - typeof downSocket.index === "string" + typeof $hoveredSocket.index === "number" && + typeof $activeSocket.index === "string" ) { graph.createEdge( - hoveredSocket.node, - hoveredSocket.index || 0, - downSocket.node, - downSocket.index, + $hoveredSocket.node, + $hoveredSocket.index || 0, + $activeSocket.node, + $activeSocket.index, ); - } else { + } else if ( + typeof $activeSocket.index == "number" && + typeof $hoveredSocket.index === "string" + ) { graph.createEdge( - downSocket.node, - downSocket.index || 0, - hoveredSocket.node, - hoveredSocket.index, + $activeSocket.node, + $activeSocket.index || 0, + $hoveredSocket.node, + $hoveredSocket.index, ); } } mouseDown = null; - downSocket = null; - possibleSockets = []; - hoveredSocket = null; - activeNodeId = -1; + $activeSocket = null; + $possibleSockets = []; + $possibleSocketIds = null; + $hoveredSocket = null; } - - - @@ -272,19 +429,13 @@ {#if $status === "idle"} - {#if downSocket} + {#if $activeSocket} {/if} - + {:else if $status === "loading"} Loading {:else if $status === "error"} diff --git a/frontend/src/lib/components/graph/GraphView.svelte b/frontend/src/lib/components/graph/GraphView.svelte index cdaf228..4b02c40 100644 --- a/frontend/src/lib/components/graph/GraphView.svelte +++ b/frontend/src/lib/components/graph/GraphView.svelte @@ -1,18 +1,16 @@ -{#each $edges as edge} +{#each $edges as edge (edge[0].id + edge[2].id + edge[3])} {@const pos = getEdgePosition(edge)} {@const [x1, y1, x2, y2] = pos} - {#each $nodes.values() as node} - + {#each $nodes.values() as node (node.id)} + {/each}
diff --git a/frontend/src/lib/components/graph/context.ts b/frontend/src/lib/components/graph/context.ts index 20e5666..b0e904a 100644 --- a/frontend/src/lib/components/graph/context.ts +++ b/frontend/src/lib/components/graph/context.ts @@ -1,11 +1,6 @@ import type { GraphManager } from "$lib/graph-manager"; import { getContext } from "svelte"; -import type { GraphView } from "./view"; export function getGraphManager(): GraphManager { return getContext("graphManager"); } - -export function getGraphState(): GraphView { - return getContext("graphState"); -} diff --git a/frontend/src/lib/components/graph/stores.ts b/frontend/src/lib/components/graph/stores.ts new file mode 100644 index 0000000..c46cb30 --- /dev/null +++ b/frontend/src/lib/components/graph/stores.ts @@ -0,0 +1,10 @@ +import type { Node, Socket } from "$lib/types"; +import { writable, type Writable } from "svelte/store"; + +export const activeNodeId: Writable = writable(-1); +export const selectedNodes: Writable | null> = writable(null); + +export const activeSocket: Writable = writable(null); +export const hoveredSocket: Writable = writable(null); +export const possibleSockets: Writable = writable([]); +export const possibleSocketIds: Writable | null> = writable(null); diff --git a/frontend/src/lib/elements/Details.svelte b/frontend/src/lib/elements/Details.svelte new file mode 100644 index 0000000..2d16aad --- /dev/null +++ b/frontend/src/lib/elements/Details.svelte @@ -0,0 +1,20 @@ + + +
+ {title} + + +
+ + diff --git a/frontend/src/lib/graph-manager.ts b/frontend/src/lib/graph-manager.ts index b75fe7a..53240a9 100644 --- a/frontend/src/lib/graph-manager.ts +++ b/frontend/src/lib/graph-manager.ts @@ -50,6 +50,19 @@ export class GraphManager { this.edges.subscribe((edges) => { this._edges = edges; }); + + globalThis["serialize"] = () => this.serialize(); + } + + serialize() { + const nodes = Array.from(this._nodes.values()).map(node => ({ + id: node.id, + position: node.position, + type: node.type, + props: node.props, + })); + const edges = this._edges.map(edge => [edge[0].id, edge[1], edge[2].id, edge[3]]); + return { nodes, edges }; } async load() { @@ -87,8 +100,6 @@ export class GraphManager { ); this.nodes.set(nodes); - console.log(this._nodes); - this.status.set("idle"); } @@ -102,6 +113,10 @@ export class GraphManager { return this._nodes.get(id); } + getNodeType(id: string) { + return this.nodeRegistry.getNode(id); + } + getChildrenOfNode(node: Node) { const children = []; const stack = node.tmp?.children?.slice(0); @@ -114,8 +129,35 @@ export class GraphManager { return children; } - createEdge(from: Node, fromSocket: number, to: Node, toSocket: string) { + getNodesBetween(from: Node, to: Node): Node[] | undefined { + // < - - - - from + const toParents = this.getParentsOfNode(to); + // < - - - - from - - - - to + const fromParents = this.getParentsOfNode(from); + if (toParents.includes(from)) { + return toParents.splice(toParents.indexOf(from)); + } else if (fromParents.includes(to)) { + return fromParents.splice(fromParents.indexOf(to)); + } else { + // these two nodes are not connected + return; + } + } + private updateNodeParents(node: Node) { + } + + removeNode(node: Node) { + const edges = this._edges.filter((edge) => edge[0].id !== node.id && edge[2].id !== node.id); + this.edges.set(edges); + + this.nodes.update((nodes) => { + nodes.delete(node.id); + return nodes; + }); + } + + createEdge(from: Node, fromSocket: number, to: Node, toSocket: string) { const existingEdges = this.getEdgesToNode(to); @@ -150,7 +192,7 @@ export class GraphManager { parents.push(parent); stack.push(...parent.tmp?.parents || []); } - return parents; + return parents.reverse(); } getPossibleSockets({ node, index }: Socket): [Node, string | number][] { diff --git a/frontend/src/lib/helpers.ts b/frontend/src/lib/helpers.ts index d3d34b0..9c53461 100644 --- a/frontend/src/lib/helpers.ts +++ b/frontend/src/lib/helpers.ts @@ -21,3 +21,44 @@ export function animate(duration: number, callback: (progress: number) => void | } requestAnimationFrame(loop); } + +export function createNodePath({ + depth = 8, + height = 20, + y = 50, + cornerTop = 0, + cornerBottom = 0, + leftBump = false, + rightBump = false, + aspectRatio = 1, +} = {}) { + return `M0,${cornerTop} + ${cornerTop + ? ` V${cornerTop} + Q0,0 ${cornerTop * aspectRatio},0 + H${100 - cornerTop * aspectRatio} + Q100,0 100,${cornerTop} + ` + : ` V0 + H100 + ` + } + V${y - height / 2} + ${rightBump + ? ` C${100 - depth},${y - height / 2} ${100 - depth},${y + height / 2} 100,${y + height / 2}` + : ` H100` + } + ${cornerBottom + ? ` V${100 - cornerBottom} + Q100,100 ${100 - cornerBottom * aspectRatio},100 + H${cornerBottom * aspectRatio} + Q0,100 0,${100 - cornerBottom} + ` + : `${leftBump ? `V100 H0` : `V100`}` + } + ${leftBump + ? ` V${y + height / 2} C${depth},${y + height / 2} ${depth},${y - height / 2} 0,${y - height / 2}` + : ` H0` + } + Z`.replace(/\s+/g, " "); + } diff --git a/frontend/src/lib/types/index.ts b/frontend/src/lib/types/index.ts index 70d9f2f..10afc26 100644 --- a/frontend/src/lib/types/index.ts +++ b/frontend/src/lib/types/index.ts @@ -11,6 +11,9 @@ export type Node = { type?: NodeType; downX?: number; downY?: number; + snapX?: number; + snapY?: number; + ref?: HTMLElement; visible?: boolean; isMoving?: boolean; }, diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index bf10d1d..80a15c5 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -2,14 +2,17 @@ import { invoke } from "@tauri-apps/api/core"; import { onMount } from "svelte"; import { PerfMonitor } from "@threlte/extras"; - import { Canvas } from "@threlte/core"; import { GraphManager } from "$lib/graph-manager"; import Graph from "$lib/components/graph/Graph.svelte"; + import Details from "$lib/elements/Details.svelte"; + import { JsonView } from "@zerodevx/svelte-json-view"; const graph = GraphManager.createEmptyGraph({ width: 12, height: 12 }); graph.load(); + let debug: undefined; + // onMount(async () => { // try { // const res = await invoke("greet", { name: "Dude" }); @@ -27,15 +30,28 @@ // }); -
+
+
+ +
+
+ +
- +