refactor: move view logic inside graph.svelte
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user