refactor: move view logic inside graph.svelte
This commit is contained in:
		| @@ -17,7 +17,6 @@ | ||||
|     position[0] = camera.position.x; | ||||
|     position[1] = camera.position.z; | ||||
|     position[2] = camera.zoom; | ||||
|  | ||||
|     saveControls(); | ||||
|   } | ||||
|  | ||||
| @@ -57,6 +56,7 @@ | ||||
|  | ||||
| <T.OrthographicCamera bind:ref={camera} position.y={10} makeDefault> | ||||
|   <OrbitControls | ||||
|     args={[camera, window]} | ||||
|     bind:ref={controls} | ||||
|     enableZoom={true} | ||||
|     zoomSpeed={2} | ||||
|   | ||||
| @@ -2,15 +2,13 @@ | ||||
|   import type { Node } from "$lib/types"; | ||||
|   import NodeHeader from "./NodeHeader.svelte"; | ||||
|   import NodeParameter from "./NodeParameter.svelte"; | ||||
|   import { getGraphManager } from "./graph/context"; | ||||
|  | ||||
|   export let node: Node; | ||||
|  | ||||
|   const graph = getGraphManager(); | ||||
|  | ||||
|   export let inView = true; | ||||
|  | ||||
|   const type = graph.getNodeType(node.type); | ||||
|   export let possibleSocketIds: null | Set<string> = null; | ||||
|  | ||||
|   const type = node?.tmp?.type; | ||||
|  | ||||
|   const parameters = Object.entries(type?.inputs || {}); | ||||
| </script> | ||||
| @@ -18,7 +16,6 @@ | ||||
| <div | ||||
|   class="node" | ||||
|   class:in-view={inView} | ||||
|   class:is-moving={node?.tmp?.isMoving} | ||||
|   data-node-id={node.id} | ||||
|   style={`--nx:${node.position.x * 10}px; | ||||
|           --ny: ${node.position.y * 10}px`} | ||||
| @@ -28,9 +25,9 @@ | ||||
|   {#each parameters as [key, value], i} | ||||
|     <NodeParameter | ||||
|       {node} | ||||
|       {possibleSocketIds} | ||||
|       id={key} | ||||
|       index={i} | ||||
|       value={node?.props?.[key]} | ||||
|       input={value} | ||||
|       isLast={i == parameters.length - 1} | ||||
|     /> | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| <script lang="ts"> | ||||
|   import type { Node } from "$lib/types"; | ||||
|   import { getContext } from "svelte"; | ||||
|   import { getGraphManager, getGraphState } from "./graph/context"; | ||||
|  | ||||
|   export let node: Node; | ||||
| @@ -34,16 +35,15 @@ | ||||
|       Z`.replace(/\s+/g, " "); | ||||
|   } | ||||
|  | ||||
|   const setDownSocket = getContext("setDownSocket"); | ||||
|  | ||||
|   function handleMouseDown(event: MouseEvent) { | ||||
|     event.stopPropagation(); | ||||
|     event.preventDefault(); | ||||
|     state.setMouseDown({ | ||||
|       x: node.position.x + 5, | ||||
|       y: node.position.y + 0.625, | ||||
|     setDownSocket({ | ||||
|       node, | ||||
|       type: node.tmp?.type?.outputs?.[0] || "", | ||||
|       index: 0, | ||||
|       isInput: false, | ||||
|       position: [node.position.x + 5, node.position.y + 0.625], | ||||
|     }); | ||||
|   } | ||||
| </script> | ||||
|   | ||||
| @@ -1,17 +1,16 @@ | ||||
| <script lang="ts"> | ||||
|   import type { NodeInput } from "$lib/types"; | ||||
|   import type { Node } from "$lib/types"; | ||||
|   import { getGraphState } from "./graph/context"; | ||||
|   import { getContext } from "svelte"; | ||||
|  | ||||
|   export let node: Node; | ||||
|   export let value: unknown; | ||||
|   export let input: NodeInput; | ||||
|   export let id: string; | ||||
|   export let index: number; | ||||
|  | ||||
|   export let isLast = false; | ||||
|   export let possibleSocketIds: null | Set<string> = null; | ||||
|  | ||||
|   const state = getGraphState(); | ||||
|   export let isLast = false; | ||||
|  | ||||
|   function createPath({ depth = 8, height = 20, y = 50 } = {}) { | ||||
|     let corner = isLast ? 5 : 0; | ||||
| @@ -46,21 +45,24 @@ | ||||
|       Z`.replace(/\s+/g, " "); | ||||
|   } | ||||
|  | ||||
|   const setDownSocket = getContext("setDownSocket"); | ||||
|  | ||||
|   function handleMouseDown(ev: MouseEvent) { | ||||
|     ev.preventDefault(); | ||||
|     ev.stopPropagation(); | ||||
|     state.setMouseDown({ | ||||
|       x: node.position.x, | ||||
|       y: node.position.y + 2.5 + index * 2.5, | ||||
|     setDownSocket({ | ||||
|       node, | ||||
|       index: index, | ||||
|       type: node?.tmp?.type?.inputs?.[id].type || "", | ||||
|       isInput: true, | ||||
|       index: id, | ||||
|       position: [node.position.x, node.position.y + 2.5 + index * 2.5], | ||||
|     }); | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <div class="wrapper"> | ||||
| <div | ||||
|   class="wrapper" | ||||
|   class:disabled={possibleSocketIds && | ||||
|     !possibleSocketIds.has(`${node.id}-${id}`)} | ||||
| > | ||||
|   <div class="content"> | ||||
|     <label>{id}</label> | ||||
|  | ||||
| @@ -69,11 +71,16 @@ | ||||
|  | ||||
|   {#if node.tmp?.type?.inputs?.[id].internal !== true} | ||||
|     <div | ||||
|       class="click-target" | ||||
|       class="large target" | ||||
|       on:mousedown={handleMouseDown} | ||||
|       role="button" | ||||
|       tabindex="0" | ||||
|     /> | ||||
|     <div | ||||
|       class="small target" | ||||
|       on:mousedown={handleMouseDown} | ||||
|       role="button" | ||||
|       tabindex="0" | ||||
|       style={`background: var(--node-hovered-in-${node.tmp?.type?.inputs?.[id].type}`} | ||||
|     /> | ||||
|   {/if} | ||||
|  | ||||
| @@ -85,6 +92,7 @@ | ||||
|     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 })}"); | ||||
|     `} | ||||
|   > | ||||
| @@ -100,14 +108,24 @@ | ||||
|     transform: translateY(-0.5px); | ||||
|   } | ||||
|  | ||||
|   .click-target { | ||||
|   .target { | ||||
|     position: absolute; | ||||
|     border-radius: 50%; | ||||
|   } | ||||
|  | ||||
|   .small.target { | ||||
|     width: 6px; | ||||
|     height: 6px; | ||||
|     border-radius: 50%; | ||||
|     top: 9.5px; | ||||
|     left: -3px; | ||||
|     opacity: 0.1; | ||||
|   } | ||||
|  | ||||
|   .large.target { | ||||
|     width: 15px; | ||||
|     height: 15px; | ||||
|     top: 5px; | ||||
|     left: -7.5px; | ||||
|     cursor: unset; | ||||
|   } | ||||
|  | ||||
|   .content { | ||||
| @@ -142,7 +160,6 @@ | ||||
|   svg { | ||||
|     position: absolute; | ||||
|     box-sizing: border-box; | ||||
|     /* pointer-events: none; */ | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     overflow: visible; | ||||
| @@ -160,7 +177,11 @@ | ||||
|     d: var(--path); | ||||
|   } | ||||
|  | ||||
|   .click-target:hover + svg path { | ||||
|     d: var(--hover-path) !important; | ||||
|   :global(.hovering-sockets) .large:hover ~ svg path { | ||||
|     d: var(--hover-path); | ||||
|   } | ||||
|  | ||||
|   .disabled svg path { | ||||
|     d: var(--hover-path-disabled) !important; | ||||
|   } | ||||
| </style> | ||||
|   | ||||
| @@ -8,9 +8,7 @@ | ||||
|   export let minZoom = 4; | ||||
|   export let maxZoom = 150; | ||||
|  | ||||
|   export let cx = 0; | ||||
|   export let cy = 0; | ||||
|   export let cz = 30; | ||||
|   export let cameraPosition: [number, number, number] = [0, 1, 0]; | ||||
|  | ||||
|   export let width = globalThis?.innerWidth || 100; | ||||
|   export let height = globalThis?.innerHeight || 100; | ||||
| @@ -19,12 +17,16 @@ | ||||
|   let bh = 2; | ||||
|  | ||||
|   $: if (width && height) { | ||||
|     bw = width / cz; | ||||
|     bh = height / cz; | ||||
|     bw = width / cameraPosition[2]; | ||||
|     bh = height / cameraPosition[2]; | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <T.Group position.x={cx} position.z={cy} position.y={-1.0}> | ||||
| <T.Group | ||||
|   position.x={cameraPosition[0]} | ||||
|   position.z={cameraPosition[1]} | ||||
|   position.y={-1.0} | ||||
| > | ||||
|   <T.Mesh rotation.x={-Math.PI / 2} position.y={0.2} scale.x={bw} scale.y={bh}> | ||||
|     <T.PlaneGeometry args={[1, 1]} /> | ||||
|     <T.ShaderMaterial | ||||
| @@ -54,9 +56,9 @@ | ||||
|           value: 100, | ||||
|         }, | ||||
|       }} | ||||
|       uniforms.cx.value={cx} | ||||
|       uniforms.cy.value={cy} | ||||
|       uniforms.cz.value={cz} | ||||
|       uniforms.cx.value={cameraPosition[0]} | ||||
|       uniforms.cy.value={cameraPosition[1]} | ||||
|       uniforms.cz.value={cameraPosition[2]} | ||||
|       uniforms.width.value={width} | ||||
|       uniforms.height.value={height} | ||||
|     /> | ||||
|   | ||||
| @@ -1,12 +1,11 @@ | ||||
| import type { Vector3 } from "three"; | ||||
| import { Vector3 } from "three"; | ||||
| import { lines, points } from "./store"; | ||||
|  | ||||
| export function debugPosition(pos: Vector3) { | ||||
| export function debugPosition(x: number, y: number) { | ||||
|   points.update((p) => { | ||||
|     p.push(pos); | ||||
|     p.push(new Vector3(x, 1, y)); | ||||
|     return p; | ||||
|   }); | ||||
|  | ||||
| } | ||||
|  | ||||
| export function clear() { | ||||
|   | ||||
							
								
								
									
										8
									
								
								frontend/src/lib/components/edges/FloatingEdge.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/src/lib/components/edges/FloatingEdge.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <script lang="ts"> | ||||
|   import Edge from "./Edge.svelte"; | ||||
|  | ||||
|   export let from: { x: number; y: number }; | ||||
|   export let to: { x: number; y: number }; | ||||
| </script> | ||||
|  | ||||
| <Edge {from} {to} /> | ||||
| @@ -1,16 +1,16 @@ | ||||
| <script lang="ts"> | ||||
|   import Edge from "../Edge.svelte"; | ||||
|   import { HTML } from "@threlte/extras"; | ||||
|   import Node from "../Node.svelte"; | ||||
|   import { animate, lerp, snapToGrid } from "$lib/helpers"; | ||||
|   import Debug from "../debug/Debug.svelte"; | ||||
|   import { OrthographicCamera } from "three"; | ||||
|   import Background from "../background/Background.svelte"; | ||||
|   import type { GraphManager } from "$lib/graph-manager"; | ||||
|   import { setContext } from "svelte"; | ||||
|   import { GraphState } from "./state"; | ||||
|   import Camera from "../Camera.svelte"; | ||||
|   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"; | ||||
|  | ||||
|   export let graph: GraphManager; | ||||
|   setContext("graphManager", graph); | ||||
| @@ -18,44 +18,131 @@ | ||||
|   const nodes = graph.nodes; | ||||
|   const edges = graph.edges; | ||||
|  | ||||
|   const state = new GraphState(graph); | ||||
|   setContext("graphState", state); | ||||
|   const mouse = state.mouse; | ||||
|   const dimensions = state.dimensions; | ||||
|   const mouseDown = state.mouseDown; | ||||
|   const cameraPosition = state.cameraPosition; | ||||
|   const cameraBounds = state.cameraBounds; | ||||
|   const activeNodeId = state.activeNodeId; | ||||
|   const hoveredSocket = state.hoveredSocket; | ||||
|  | ||||
|   let camera: OrthographicCamera; | ||||
|  | ||||
|   const minZoom = 4; | ||||
|   const maxZoom = 150; | ||||
|   let mousePosition = [0, 0]; | ||||
|   let mouseDown: null | [number, number] = null; | ||||
|   let cameraPosition: [number, number, number] = [0, 1, 0]; | ||||
|   let width = 100; | ||||
|   let height = 100; | ||||
|  | ||||
|   $: edgePositions = $edges.map((edge) => { | ||||
|     const index = Object.keys(edge[2].tmp?.type?.inputs || {}).indexOf(edge[3]); | ||||
|     return [ | ||||
|       edge[0].position.x + 5, | ||||
|       edge[0].position.y + 0.625 + edge[1] * 2.5, | ||||
|       edge[2].position.x, | ||||
|       edge[2].position.y + 2.5 + index * 2.5, | ||||
|     ]; | ||||
|   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], | ||||
|   ]; | ||||
|  | ||||
|   setContext("isNodeInView", (node: NodeType) => { | ||||
|     return ( | ||||
|       node.position.x > cameraBounds[0] && | ||||
|       node.position.x < cameraBounds[1] && | ||||
|       node.position.y > cameraBounds[2] && | ||||
|       node.position.y < cameraBounds[3] | ||||
|     ); | ||||
|   }); | ||||
|  | ||||
|   function handleMouseMove(event: MouseEvent) { | ||||
|     state.setMouseFromEvent(event); | ||||
|   setContext("setDownSocket", (socket: Socket) => { | ||||
|     downSocket = socket; | ||||
|  | ||||
|     if (!$mouseDown) return; | ||||
|     if (state?.possibleSockets?.length) { | ||||
|     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]; | ||||
|           index = edge[1]; | ||||
|           position = getSocketPosition({ node, index }); | ||||
|           graph.removeEdge(edge); | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     mouseDown = position; | ||||
|     downSocket = { | ||||
|       node, | ||||
|       index, | ||||
|       position, | ||||
|     }; | ||||
|  | ||||
|     possibleSockets = graph | ||||
|       .getPossibleSockets(downSocket) | ||||
|       .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( | ||||
|     socket: Omit<Socket, "position">, | ||||
|   ): [number, number] { | ||||
|     if (typeof socket.index === "number") { | ||||
|       return [ | ||||
|         socket.node.position.x + 5, | ||||
|         socket.node.position.y + 0.625 + 2.5 * socket.index, | ||||
|       ]; | ||||
|     } else { | ||||
|       const _index = Object.keys(socket.node.tmp?.type?.inputs || {}).indexOf( | ||||
|         socket.index, | ||||
|       ); | ||||
|       return [ | ||||
|         socket.node.position.x, | ||||
|         socket.node.position.y + 2.5 + 2.5 * _index, | ||||
|       ]; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   function setMouseFromEvent(event: MouseEvent) { | ||||
|     const x = event.clientX; | ||||
|     const y = event.clientY; | ||||
|  | ||||
|     mousePosition = [ | ||||
|       cameraPosition[0] + (x - width / 2) / cameraPosition[2], | ||||
|       cameraPosition[1] + (y - height / 2) / cameraPosition[2], | ||||
|     ]; | ||||
|   } | ||||
|  | ||||
|   function handleMouseMove(event: MouseEvent) { | ||||
|     setMouseFromEvent(event); | ||||
|  | ||||
|     if (!mouseDown) return; | ||||
|  | ||||
|     if (possibleSockets?.length) { | ||||
|       let smallestDist = 1000; | ||||
|       let _socket; | ||||
|       for (const socket of state.possibleSockets) { | ||||
|         const posX = socket.position[0]; | ||||
|         const posY = socket.position[1]; | ||||
|  | ||||
|       for (const socket of possibleSockets) { | ||||
|         const dist = Math.sqrt( | ||||
|           (posX - $mouse[0]) ** 2 + (posY - $mouse[1]) ** 2, | ||||
|           (socket.position[0] - mousePosition[0]) ** 2 + | ||||
|             (socket.position[1] - mousePosition[1]) ** 2, | ||||
|         ); | ||||
|         if (dist < smallestDist) { | ||||
|           smallestDist = dist; | ||||
| @@ -64,28 +151,27 @@ | ||||
|       } | ||||
|  | ||||
|       if (_socket && smallestDist < 0.3) { | ||||
|         state.setMouse(_socket.position[0], _socket.position[1]); | ||||
|         state.hoveredSocket.set(_socket); | ||||
|         mousePosition = _socket.position; | ||||
|         hoveredSocket = _socket; | ||||
|       } else { | ||||
|         state.hoveredSocket.set(null); | ||||
|         hoveredSocket = null; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if ($activeNodeId === -1) return; | ||||
|  | ||||
|     const node = graph.getNode($activeNodeId); | ||||
|     if (activeNodeId === -1) return; | ||||
|  | ||||
|     const node = graph.getNode(activeNodeId); | ||||
|     if (!node) return; | ||||
|  | ||||
|     if (!node.tmp) node.tmp = {}; | ||||
|     node.tmp = node.tmp || {}; | ||||
|     node.tmp.isMoving = true; | ||||
|  | ||||
|     let newX = | ||||
|       (node?.tmp?.downX || 0) + | ||||
|       (event.clientX - $mouseDown.x) / $cameraPosition[2]; | ||||
|       (event.clientX - mouseDown[0]) / cameraPosition[2]; | ||||
|     let newY = | ||||
|       (node?.tmp?.downY || 0) + | ||||
|       (event.clientY - $mouseDown.y) / $cameraPosition[2]; | ||||
|       (event.clientY - mouseDown[1]) / cameraPosition[2]; | ||||
|  | ||||
|     if (event.ctrlKey) { | ||||
|       const snapLevel = getSnapLevel(); | ||||
| @@ -100,54 +186,32 @@ | ||||
|     edges.set($edges); | ||||
|   } | ||||
|  | ||||
|   function handleMouseDown(ev: MouseEvent) { | ||||
|     if ($mouseDown) return; | ||||
|   function handleMouseDown(event: MouseEvent) { | ||||
|     if (mouseDown) return; | ||||
|  | ||||
|     for (const node of ev.composedPath()) { | ||||
|     for (const node of event.composedPath()) { | ||||
|       let _activeNodeId = (node as unknown as HTMLElement)?.getAttribute?.( | ||||
|         "data-node-id", | ||||
|       )!; | ||||
|       if (_activeNodeId) { | ||||
|         $activeNodeId = parseInt(_activeNodeId, 10); | ||||
|         activeNodeId = parseInt(_activeNodeId, 10); | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     if ($activeNodeId < 0) return; | ||||
|     if (activeNodeId < 0) return; | ||||
|  | ||||
|     $mouseDown = { x: ev.clientX, y: ev.clientY }; | ||||
|     const node = graph.getNode($activeNodeId); | ||||
|     mouseDown = [event.clientX, event.clientY]; | ||||
|     const node = graph.getNode(activeNodeId); | ||||
|     if (!node) return; | ||||
|     node.tmp = node.tmp || {}; | ||||
|     node.tmp.downX = node.position.x; | ||||
|     node.tmp.downY = node.position.y; | ||||
|   } | ||||
|  | ||||
|   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 handleMouseUp(event: MouseEvent) { | ||||
|     if (event.button !== 0) return; | ||||
|  | ||||
|   function isNodeInView(node: any) { | ||||
|     return ( | ||||
|       node.position.x > $cameraBounds[0] && | ||||
|       node.position.x < $cameraBounds[1] && | ||||
|       node.position.y > $cameraBounds[2] && | ||||
|       node.position.y < $cameraBounds[3] | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   function handleMouseUp(ev: MouseEvent) { | ||||
|     if (ev.button !== 0) return; | ||||
|  | ||||
|     const node = graph.getNode($activeNodeId); | ||||
|     const node = graph.getNode(activeNodeId); | ||||
|     if (node) { | ||||
|       node.tmp = node.tmp || {}; | ||||
|       node.tmp.isMoving = false; | ||||
| @@ -157,41 +221,39 @@ | ||||
|       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) { | ||||
|           return false; | ||||
|         } | ||||
|         nodes.set($nodes); | ||||
|         edges.set($edges); | ||||
|       }); | ||||
|       nodes.set($nodes); | ||||
|       edges.set($edges); | ||||
|     } else if ($hoveredSocket && $mouseDown && $mouseDown?.node) { | ||||
|       if ($hoveredSocket.isInput) { | ||||
|         const newEdge: [NodeType, number, NodeType, string] = [ | ||||
|           $hoveredSocket.node, | ||||
|           $hoveredSocket.index || 0, | ||||
|           $mouseDown.node, | ||||
|           Object.keys($mouseDown?.node?.tmp?.type?.inputs || {})[ | ||||
|             $mouseDown?.index || 0 | ||||
|           ], | ||||
|         ]; | ||||
|         $edges = [...$edges, newEdge]; | ||||
|     } else if (hoveredSocket && downSocket) { | ||||
|       console.log({ hoveredSocket, downSocket }); | ||||
|       if ( | ||||
|         typeof hoveredSocket.index === "number" && | ||||
|         typeof downSocket.index === "string" | ||||
|       ) { | ||||
|         graph.createEdge( | ||||
|           hoveredSocket.node, | ||||
|           hoveredSocket.index || 0, | ||||
|           downSocket.node, | ||||
|           downSocket.index, | ||||
|         ); | ||||
|       } else { | ||||
|         const newEdge: [NodeType, number, NodeType, string] = [ | ||||
|           $mouseDown.node, | ||||
|           $mouseDown?.index || 0, | ||||
|           $hoveredSocket.node, | ||||
|           Object.keys($hoveredSocket.node?.tmp?.type?.inputs || {})[ | ||||
|             $hoveredSocket.index | ||||
|           ], | ||||
|         ]; | ||||
|         $edges = [...$edges, newEdge]; | ||||
|         graph.createEdge( | ||||
|           downSocket.node, | ||||
|           downSocket.index || 0, | ||||
|           hoveredSocket.node, | ||||
|           hoveredSocket.index, | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     $mouseDown = false; | ||||
|     $hoveredSocket = null; | ||||
|     $activeNodeId = -1; | ||||
|     mouseDown = null; | ||||
|     downSocket = null; | ||||
|     possibleSockets = []; | ||||
|     hoveredSocket = null; | ||||
|     activeNodeId = -1; | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| @@ -201,67 +263,30 @@ | ||||
|   on:mousedown={handleMouseDown} | ||||
| /> | ||||
|  | ||||
| <svelte:window bind:innerWidth={width} bind:innerHeight={height} /> | ||||
|  | ||||
| <Debug /> | ||||
|  | ||||
| <Camera bind:camera {maxZoom} {minZoom} bind:position={$cameraPosition} /> | ||||
| <Camera bind:camera {maxZoom} {minZoom} bind:position={cameraPosition} /> | ||||
|  | ||||
| <Background | ||||
|   cx={$cameraPosition[0]} | ||||
|   cy={$cameraPosition[1]} | ||||
|   cz={$cameraPosition[2]} | ||||
|   {maxZoom} | ||||
|   {minZoom} | ||||
|   width={$dimensions[0]} | ||||
|   height={$dimensions[1]} | ||||
| /> | ||||
| <Background {cameraPosition} {maxZoom} {minZoom} {width} {height} /> | ||||
|  | ||||
| {#if $status === "idle"} | ||||
|   {#each edgePositions as [x1, y1, x2, y2]} | ||||
|     <Edge | ||||
|       from={{ | ||||
|         x: x1, | ||||
|         y: y1, | ||||
|       }} | ||||
|       to={{ | ||||
|         x: x2, | ||||
|         y: y2, | ||||
|       }} | ||||
|   {#if downSocket} | ||||
|     <FloatingEdge | ||||
|       from={{ x: downSocket.position[0], y: downSocket.position[1] }} | ||||
|       to={{ x: mousePosition[0], y: mousePosition[1] }} | ||||
|     /> | ||||
|   {/each} | ||||
|  | ||||
|   {#if $mouseDown && $mouseDown?.node} | ||||
|     <Edge from={$mouseDown} to={{ x: $mouse[0], y: $mouse[1] }} /> | ||||
|   {/if} | ||||
|  | ||||
|   <HTML transform={false}> | ||||
|     <div | ||||
|       role="tree" | ||||
|       tabindex="0" | ||||
|       class="wrapper" | ||||
|       class:zoom-small={$cameraPosition[2] < 10} | ||||
|       style={`--cz: ${$cameraPosition[2]}; ${$mouseDown ? `--node-hovered-${$mouseDown.isInput ? "out" : "in"}-${$mouseDown.type}: red;` : ""}`} | ||||
|     > | ||||
|       {#each $nodes as node} | ||||
|         <Node {node} inView={$cameraPosition && isNodeInView(node)} /> | ||||
|       {/each} | ||||
|     </div> | ||||
|   </HTML> | ||||
|   <GraphView | ||||
|     {nodes} | ||||
|     {edges} | ||||
|     {cameraPosition} | ||||
|     {possibleSocketIds} | ||||
|     {downSocket} | ||||
|   /> | ||||
| {:else if $status === "loading"} | ||||
|   <span>Loading</span> | ||||
| {:else if $status === "error"} | ||||
|   <span>Error</span> | ||||
| {/if} | ||||
|  | ||||
| <style> | ||||
|   :global(body) { | ||||
|     overflow: hidden; | ||||
|   } | ||||
|  | ||||
|   .wrapper { | ||||
|     position: absolute; | ||||
|     z-index: 100; | ||||
|     width: 0px; | ||||
|     height: 0px; | ||||
|     transform: scale(calc(var(--cz) * 0.1)); | ||||
|   } | ||||
| </style> | ||||
|   | ||||
							
								
								
									
										72
									
								
								frontend/src/lib/components/graph/GraphView.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								frontend/src/lib/components/graph/GraphView.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| <script lang="ts"> | ||||
|   import type { Edge as EdgeType, Node as NodeType } from "$lib/types"; | ||||
|   import { HTML } from "@threlte/extras"; | ||||
|   import Edge from "../edges/Edge.svelte"; | ||||
|   import Node from "../Node.svelte"; | ||||
|   import { getContext } from "svelte"; | ||||
|   import type { Writable } from "svelte/store"; | ||||
|  | ||||
|   export let nodes: Writable<Map<number, NodeType>>; | ||||
|   export let edges: Writable<EdgeType[]>; | ||||
|  | ||||
|   export let cameraPosition = [0, 1, 0]; | ||||
|   export let downSocket: null | { node: NodeType; index: number | string } = | ||||
|     null; | ||||
|   export let possibleSocketIds: null | Set<string> = null; | ||||
|  | ||||
|   const isNodeInView = getContext<(n: NodeType) => boolean>("isNodeInView"); | ||||
|  | ||||
|   function getEdgePosition(edge: EdgeType) { | ||||
|     const index = Object.keys(edge[2].tmp?.type?.inputs || {}).indexOf(edge[3]); | ||||
|     return [ | ||||
|       edge[0].position.x + 5, | ||||
|       edge[0].position.y + 0.625 + edge[1] * 2.5, | ||||
|       edge[2].position.x, | ||||
|       edge[2].position.y + 2.5 + index * 2.5, | ||||
|     ]; | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| {#each $edges as edge} | ||||
|   {@const pos = getEdgePosition(edge)} | ||||
|   {@const [x1, y1, x2, y2] = pos} | ||||
|   <Edge | ||||
|     from={{ | ||||
|       x: x1, | ||||
|       y: y1, | ||||
|     }} | ||||
|     to={{ | ||||
|       x: x2, | ||||
|       y: y2, | ||||
|     }} | ||||
|   /> | ||||
| {/each} | ||||
|  | ||||
| <HTML transform={false}> | ||||
|   <div | ||||
|     role="tree" | ||||
|     tabindex="0" | ||||
|     class="wrapper" | ||||
|     class:zoom-small={cameraPosition[2] < 10} | ||||
|     class:hovering-sockets={downSocket} | ||||
|     style={`--cz: ${cameraPosition[2]}`} | ||||
|   > | ||||
|     {#each $nodes.values() as node} | ||||
|       <Node | ||||
|         {node} | ||||
|         inView={cameraPosition && isNodeInView(node)} | ||||
|         {possibleSocketIds} | ||||
|       /> | ||||
|     {/each} | ||||
|   </div> | ||||
| </HTML> | ||||
|  | ||||
| <style> | ||||
|   .wrapper { | ||||
|     position: absolute; | ||||
|     z-index: 100; | ||||
|     width: 0px; | ||||
|     height: 0px; | ||||
|     transform: scale(calc(var(--cz) * 0.1)); | ||||
|   } | ||||
| </style> | ||||
| @@ -1,11 +1,11 @@ | ||||
| import type { GraphManager } from "$lib/graph-manager"; | ||||
| import { getContext } from "svelte"; | ||||
| import type { GraphState } from "./graph-state"; | ||||
| import type { GraphView } from "./view"; | ||||
|  | ||||
| export function getGraphManager(): GraphManager { | ||||
|   return getContext("graphManager"); | ||||
| } | ||||
|  | ||||
| export function getGraphState(): GraphState { | ||||
| export function getGraphState(): GraphView { | ||||
|   return getContext("graphState"); | ||||
| } | ||||
|   | ||||
| @@ -1,129 +0,0 @@ | ||||
| import type { GraphManager } from "$lib/graph-manager"; | ||||
| import type { Node } from "$lib/types"; | ||||
| import { derived, get, writable, type Writable } from "svelte/store"; | ||||
| import * as debug from "../debug"; | ||||
|  | ||||
|  | ||||
| type Socket = { | ||||
|   node: Node; | ||||
|   index: number; | ||||
|   isInput: boolean; | ||||
|   type: string; | ||||
|   position: [number, number]; | ||||
| } | ||||
|  | ||||
| export class GraphState { | ||||
|  | ||||
|   activeNodeId: Writable<number> = writable(-1); | ||||
|   dimensions: Writable<[number, number]> = writable([100, 100]); | ||||
|   mouse: Writable<[number, number]> = writable([0, 0]); | ||||
|   mouseDown: Writable<false | ({ x: number, y: number } & Omit<Socket, "position">)> = writable(false); | ||||
|   cameraPosition: Writable<[number, number, number]> = writable([0, 1, 0]); | ||||
|   cameraBounds = derived([this.cameraPosition, this.dimensions], ([_cameraPosition, [width, height]]) => { | ||||
|     return [ | ||||
|       _cameraPosition[0] - width / _cameraPosition[2], | ||||
|       _cameraPosition[0] + width / _cameraPosition[2], | ||||
|       _cameraPosition[1] - height / _cameraPosition[2], | ||||
|       _cameraPosition[1] + height / _cameraPosition[2], | ||||
|     ] as const | ||||
|   }); | ||||
|  | ||||
|   possibleSockets: Socket[] = []; | ||||
|   hoveredSocket: Writable<Socket | null> = writable(null); | ||||
|  | ||||
|   constructor(private graph: GraphManager) { | ||||
|     if (globalThis?.innerWidth && globalThis?.innerHeight) { | ||||
|       this.dimensions.set([window.innerWidth, window.innerHeight]); | ||||
|       globalThis.addEventListener("resize", () => { | ||||
|         this.dimensions.set([window.innerWidth, window.innerHeight]); | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   setMouse(x: number, y: number) { | ||||
|     this.mouse.set([x, y]); | ||||
|   } | ||||
|  | ||||
|   setMouseFromEvent(event: MouseEvent) { | ||||
|     const x = event.clientX; | ||||
|     const y = event.clientY; | ||||
|  | ||||
|     const cameraPosition = get(this.cameraPosition); | ||||
|     const dimensions = get(this.dimensions); | ||||
|  | ||||
|     this.mouse.set([ | ||||
|       cameraPosition[0] + (x - dimensions[0] / 2) / cameraPosition[2], | ||||
|       cameraPosition[1] + (y - dimensions[1] / 2) / cameraPosition[2], | ||||
|     ]); | ||||
|   } | ||||
|  | ||||
|   getSocketPosition(node: Node, index: number | string) { | ||||
|     const isOutput = typeof index === "number"; | ||||
|     if (isOutput) { | ||||
|       return [node.position.x + 5, node.position.y + 0.625 + 2.5 * index] as const; | ||||
|     } else { | ||||
|       const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index); | ||||
|       return [node.position.x, node.position.y + 2.5 + 2.5 * _index] as const; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   setMouseDown(opts: ({ x: number, y: number } & Omit<Socket, "position">) | false) { | ||||
|  | ||||
|     if (!opts) { | ||||
|       this.mouseDown.set(false); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let { x, y, node, index, isInput, type } = opts; | ||||
|  | ||||
|     if (node && index !== undefined && isInput !== undefined) { | ||||
|  | ||||
|       debug.clear(); | ||||
|  | ||||
|       // remove existing edge | ||||
|       if (isInput) { | ||||
|         const edges = this.graph.getEdgesToNode(node); | ||||
|         const key = Object.keys(node.tmp?.type?.inputs || {})[index]; | ||||
|         for (const edge of edges) { | ||||
|           if (edge[3] === key) { | ||||
|             node = edge[2]; | ||||
|             index = 0; | ||||
|             const pos = this.getSocketPosition(edge[0], index); | ||||
|             x = pos[0]; | ||||
|             y = pos[1]; | ||||
|             isInput = false; | ||||
|             this.graph.removeEdge(edge); | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       this.mouseDown.set({ x, y, node, index, isInput, type }); | ||||
|  | ||||
|       this.possibleSockets = this.graph.getPossibleSockets(node, index, isInput).map(([node, index]) => { | ||||
|         if (isInput) { | ||||
|           const key = Object.keys(node.tmp?.type?.inputs || {})[index]; | ||||
|           return { | ||||
|             node, | ||||
|             index, | ||||
|             isInput, | ||||
|             type: node.tmp?.type?.inputs?.[key].type || "", | ||||
|             position: [node.position.x + 5, node.position.y + 0.625 + 2.5 * index] | ||||
|           } | ||||
|         } else { | ||||
|           return { | ||||
|             node, | ||||
|             index, | ||||
|             isInput, | ||||
|             type: node.tmp?.type?.outputs?.[index] || "", | ||||
|             position: [node.position.x, node.position.y + 2.5 + 2.5 * index] | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     console.log("possibleSockets", this.possibleSockets); | ||||
|  | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { get, writable, type Writable } from "svelte/store"; | ||||
| import type { Graph, NodeRegistry as INodeRegistry, NodeType, Node, Edge } from "./types"; | ||||
| import { writable, type Writable } from "svelte/store"; | ||||
| import type { Graph, NodeRegistry as INodeRegistry, NodeType, Node, Edge, Socket } from "./types"; | ||||
|  | ||||
| const nodeTypes: NodeType[] = [ | ||||
|   { | ||||
| @@ -38,8 +38,8 @@ export class GraphManager { | ||||
|  | ||||
|   status: Writable<"loading" | "idle" | "error"> = writable("loading"); | ||||
|  | ||||
|   private _nodes: Node[] = []; | ||||
|   nodes: Writable<Node[]> = writable([]); | ||||
|   private _nodes: Map<number, Node> = new Map(); | ||||
|   nodes: Writable<Map<number, Node>> = writable(new Map()); | ||||
|   private _edges: Edge[] = []; | ||||
|   edges: Writable<Edge[]> = writable([]); | ||||
|  | ||||
| @@ -54,10 +54,10 @@ export class GraphManager { | ||||
|  | ||||
|   async load() { | ||||
|  | ||||
|     const nodes = this.graph.nodes; | ||||
|     const nodes = new Map(this.graph.nodes.map(node => [node.id, node])); | ||||
|  | ||||
|     for (const node of nodes) { | ||||
|       const nodeType = this.getNodeType(node.type); | ||||
|     for (const node of nodes.values()) { | ||||
|       const nodeType = this.nodeRegistry.getNode(node.type); | ||||
|       if (!nodeType) { | ||||
|         console.error(`Node type not found: ${node.type}`); | ||||
|         this.status.set("error"); | ||||
| @@ -67,39 +67,110 @@ export class GraphManager { | ||||
|       node.tmp.type = nodeType; | ||||
|     } | ||||
|  | ||||
|     this.nodes.set(nodes); | ||||
|  | ||||
|     this.edges.set(this.graph.edges.map((edge) => { | ||||
|       const from = this._nodes.find((node) => node.id === edge[0]); | ||||
|       const to = this._nodes.find((node) => node.id === edge[2]); | ||||
|       if (!from || !to) return; | ||||
|       const from = nodes.get(edge[0]); | ||||
|       const to = nodes.get(edge[2]); | ||||
|       if (!from || !to) { | ||||
|         console.error("Edge references non-existing node"); | ||||
|         return; | ||||
|       }; | ||||
|       from.tmp = from.tmp || {}; | ||||
|       from.tmp.children = from.tmp.children || []; | ||||
|       from.tmp.children.push(to); | ||||
|       to.tmp = to.tmp || {}; | ||||
|       to.tmp.parents = to.tmp.parents || []; | ||||
|       to.tmp.parents.push(from); | ||||
|       return [from, edge[1], to, edge[3]] as const; | ||||
|     }) | ||||
|       .filter(Boolean) as unknown as [Node, number, Node, string][] | ||||
|     ); | ||||
|  | ||||
|     this.nodes.set(nodes); | ||||
|     console.log(this._nodes); | ||||
|  | ||||
|  | ||||
|     this.status.set("idle"); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   getNode(id: number) { | ||||
|     return this._nodes.find((node) => node.id === id); | ||||
|   getAllNodes() { | ||||
|     return Array.from(this._nodes.values()); | ||||
|   } | ||||
|  | ||||
|   getPossibleSockets(node: Node, socketIndex: number, isInput: boolean): [Node, number][] { | ||||
|   getNode(id: number) { | ||||
|     return this._nodes.get(id); | ||||
|   } | ||||
|  | ||||
|     const nodeType = this.getNodeType(node.type); | ||||
|   getChildrenOfNode(node: Node) { | ||||
|     const children = []; | ||||
|     const stack = node.tmp?.children?.slice(0); | ||||
|     while (stack?.length) { | ||||
|       const child = stack.pop(); | ||||
|       if (!child) continue; | ||||
|       children.push(child); | ||||
|       stack.push(...child.tmp?.children || []); | ||||
|     } | ||||
|     return children; | ||||
|   } | ||||
|  | ||||
|   createEdge(from: Node, fromSocket: number, to: Node, toSocket: string) { | ||||
|  | ||||
|  | ||||
|     const existingEdges = this.getEdgesToNode(to); | ||||
|  | ||||
|     // check if this exact edge already exists | ||||
|     const existingEdge = existingEdges.find(e => e[0].id === from.id && e[1] === fromSocket && e[3] === toSocket); | ||||
|     if (existingEdge) { | ||||
|       console.log("Edge already exists"); | ||||
|       console.log(existingEdge) | ||||
|       return; | ||||
|     }; | ||||
|  | ||||
|     // check if socket types match | ||||
|     const fromSocketType = from.tmp?.type?.outputs?.[fromSocket]; | ||||
|     const toSocketType = to.tmp?.type?.inputs?.[toSocket]?.type; | ||||
|  | ||||
|     if (fromSocketType !== toSocketType) { | ||||
|       console.error(`Socket types do not match: ${fromSocketType} !== ${toSocketType}`); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this.edges.update((edges) => { | ||||
|       return [...edges.filter(e => e[2].id !== to.id || e[3] !== toSocket), [from, fromSocket, to, toSocket]]; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   getParentsOfNode(node: Node) { | ||||
|     const parents = []; | ||||
|     const stack = node.tmp?.parents?.slice(0); | ||||
|     while (stack?.length) { | ||||
|       const parent = stack.pop(); | ||||
|       if (!parent) continue; | ||||
|       parents.push(parent); | ||||
|       stack.push(...parent.tmp?.parents || []); | ||||
|     } | ||||
|     return parents; | ||||
|   } | ||||
|  | ||||
|   getPossibleSockets({ node, index }: Socket): [Node, string | number][] { | ||||
|  | ||||
|     const nodeType = node?.tmp?.type; | ||||
|     if (!nodeType) return []; | ||||
|  | ||||
|     const nodes = this._nodes.filter(n => n.id !== node.id); | ||||
|  | ||||
|     const sockets: [Node, number][] = [] | ||||
|     if (isInput) { | ||||
|     const sockets: [Node, string | number][] = [] | ||||
|     // if index is a string, we are an input looking for outputs | ||||
|     if (typeof index === "string") { | ||||
|  | ||||
|       const ownType = Object.values(nodeType?.inputs || {})[socketIndex].type; | ||||
|       // filter out self and child nodes | ||||
|       const children = new Set(this.getChildrenOfNode(node).map(n => n.id)); | ||||
|       const nodes = this.getAllNodes().filter(n => n.id !== node.id && !children.has(n.id)); | ||||
|  | ||||
|       const ownType = nodeType?.inputs?.[index].type; | ||||
|  | ||||
|       for (const node of nodes) { | ||||
|         const nodeType = this.getNodeType(node.type); | ||||
|         const nodeType = node?.tmp?.type; | ||||
|         const inputs = nodeType?.outputs; | ||||
|         if (!inputs) continue; | ||||
|         for (let index = 0; index < inputs.length; index++) { | ||||
| @@ -109,31 +180,33 @@ export class GraphManager { | ||||
|         } | ||||
|       } | ||||
|  | ||||
|     } else { | ||||
|     } else if (typeof index === "number") { | ||||
|       // if index is a number, we are an output looking for inputs | ||||
|  | ||||
|       const ownType = nodeType.outputs?.[socketIndex]; | ||||
|       // filter out self and parent nodes | ||||
|       const parents = new Set(this.getParentsOfNode(node).map(n => n.id)); | ||||
|       const nodes = this.getAllNodes().filter(n => n.id !== node.id && !parents.has(n.id)); | ||||
|  | ||||
|       // get edges from this socket | ||||
|       const edges = new Map(this.getEdgesFromNode(node).filter(e => e[1] === index).map(e => [e[2].id, e[3]])); | ||||
|  | ||||
|       const ownType = nodeType.outputs?.[index]; | ||||
|  | ||||
|       for (const node of nodes) { | ||||
|         const nodeType = this.getNodeType(node.type); | ||||
|         const inputs = nodeType?.inputs; | ||||
|         const entries = Object.values(inputs || {}); | ||||
|         entries.map((input, index) => { | ||||
|           if (input.type === ownType) { | ||||
|             sockets.push([node, index]); | ||||
|         const inputs = node?.tmp?.type?.inputs; | ||||
|         if (!inputs) continue; | ||||
|         for (const key in inputs) { | ||||
|           if (inputs[key].type === ownType && edges.get(node.id) !== key) { | ||||
|             sockets.push([node, key]); | ||||
|           } | ||||
|         }); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     return sockets; | ||||
|  | ||||
|   } | ||||
|  | ||||
|   getNodeType(id: string): NodeType { | ||||
|     return this.nodeRegistry.getNode(id)!; | ||||
|   } | ||||
|  | ||||
|   removeEdge(edge: Edge) { | ||||
|     const id0 = edge[0].id; | ||||
|     const sid0 = edge[1]; | ||||
| @@ -148,8 +221,8 @@ export class GraphManager { | ||||
|     return this._edges | ||||
|       .filter((edge) => edge[2].id === node.id) | ||||
|       .map((edge) => { | ||||
|         const from = this._nodes.find((node) => node.id === edge[0].id); | ||||
|         const to = this._nodes.find((node) => node.id === edge[2].id); | ||||
|         const from = this.getNode(edge[0].id); | ||||
|         const to = this.getNode(edge[2].id); | ||||
|         if (!from || !to) return; | ||||
|         return [from, edge[1], to, edge[3]] as const; | ||||
|       }) | ||||
| @@ -158,10 +231,10 @@ export class GraphManager { | ||||
|  | ||||
|   getEdgesFromNode(node: Node) { | ||||
|     return this._edges | ||||
|       .filter((edge) => edge[0] === node.id) | ||||
|       .filter((edge) => edge[0].id === node.id) | ||||
|       .map((edge) => { | ||||
|         const from = this._nodes.find((node) => node.id === edge[0]); | ||||
|         const to = this._nodes.find((node) => node.id === edge[2]); | ||||
|         const from = this.getNode(edge[0].id); | ||||
|         const to = this.getNode(edge[2].id); | ||||
|         if (!from || !to) return; | ||||
|         return [from, edge[1], to, edge[3]] as const; | ||||
|       }) | ||||
|   | ||||
| @@ -6,6 +6,8 @@ export type Node = { | ||||
|   type: string; | ||||
|   props?: Record<string, any>, | ||||
|   tmp?: { | ||||
|     parents?: Node[], | ||||
|     children?: Node[], | ||||
|     type?: NodeType; | ||||
|     downX?: number; | ||||
|     downY?: number; | ||||
| @@ -31,6 +33,13 @@ export type NodeType = { | ||||
|   } | ||||
| } | ||||
|  | ||||
| export type Socket = { | ||||
|   node: Node; | ||||
|   index: number | string; | ||||
|   position: [number, number]; | ||||
| }; | ||||
|  | ||||
|  | ||||
| export interface NodeRegistry { | ||||
|   getNode: (id: string) => NodeType | undefined; | ||||
| } | ||||
|   | ||||
| @@ -10,26 +10,26 @@ | ||||
|   const graph = GraphManager.createEmptyGraph({ width: 12, height: 12 }); | ||||
|   graph.load(); | ||||
|  | ||||
|   onMount(async () => { | ||||
|     try { | ||||
|       const res = await invoke("greet", { name: "Dude" }); | ||||
|       console.log({ res }); | ||||
|     } catch (error) { | ||||
|       console.log(error); | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|       const res2 = await invoke("run_nodes", {}); | ||||
|       console.log({ res2 }); | ||||
|     } catch (error) { | ||||
|       console.log(error); | ||||
|     } | ||||
|   }); | ||||
|   // onMount(async () => { | ||||
|   //   try { | ||||
|   //     const res = await invoke("greet", { name: "Dude" }); | ||||
|   //     console.log({ res }); | ||||
|   //   } catch (error) { | ||||
|   //     console.log(error); | ||||
|   //   } | ||||
|   // | ||||
|   //   try { | ||||
|   //     const res2 = await invoke("run_nodes", {}); | ||||
|   //     console.log({ res2 }); | ||||
|   //   } catch (error) { | ||||
|   //     console.log(error); | ||||
|   //   } | ||||
|   // }); | ||||
| </script> | ||||
|  | ||||
| <div> | ||||
|   <Canvas shadows={false} renderMode="on-demand"> | ||||
|     <PerfMonitor /> | ||||
|   <Canvas shadows={false} renderMode="on-demand" autoRender={true}> | ||||
|     <!-- <PerfMonitor /> --> | ||||
|     <Graph {graph} /> | ||||
|   </Canvas> | ||||
| </div> | ||||
|   | ||||
| @@ -23,3 +23,7 @@ | ||||
| :root { | ||||
|   font-family: 'Fira Code', monospace; | ||||
| } | ||||
|  | ||||
| body { | ||||
|   overflow: hidden; | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user