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