feat: implement selection
This commit is contained in:
parent
9241700ada
commit
305341fdf0
@ -28,6 +28,7 @@
|
|||||||
"@sveltejs/vite-plugin-svelte": "^3.0.1",
|
"@sveltejs/vite-plugin-svelte": "^3.0.1",
|
||||||
"@tauri-apps/cli": "2.0.0-beta.3",
|
"@tauri-apps/cli": "2.0.0-beta.3",
|
||||||
"@tsconfig/svelte": "^5.0.2",
|
"@tsconfig/svelte": "^5.0.2",
|
||||||
|
"@zerodevx/svelte-json-view": "^1.0.9",
|
||||||
"histoire": "^0.17.9",
|
"histoire": "^0.17.9",
|
||||||
"internal-ip": "^7.0.0",
|
"internal-ip": "^7.0.0",
|
||||||
"svelte": "^4.2.8",
|
"svelte": "^4.2.8",
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
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 { activeNodeId, selectedNodes } from "./graph/stores";
|
||||||
|
|
||||||
export let node: Node;
|
export let node: Node;
|
||||||
export let inView = true;
|
export let inView = true;
|
||||||
|
|
||||||
export let possibleSocketIds: null | Set<string> = null;
|
|
||||||
|
|
||||||
const type = node?.tmp?.type;
|
const type = node?.tmp?.type;
|
||||||
|
|
||||||
const parameters = Object.entries(type?.inputs || {});
|
const parameters = Object.entries(type?.inputs || {});
|
||||||
@ -15,17 +14,17 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="node"
|
class="node"
|
||||||
|
class:active={$activeNodeId === node.id}
|
||||||
|
class:selected={!!$selectedNodes?.has(node.id)}
|
||||||
class:in-view={inView}
|
class:in-view={inView}
|
||||||
data-node-id={node.id}
|
data-node-id={node.id}
|
||||||
style={`--nx:${node.position.x * 10}px;
|
bind:this={node.tmp.ref}
|
||||||
--ny: ${node.position.y * 10}px`}
|
|
||||||
>
|
>
|
||||||
<NodeHeader {node} />
|
<NodeHeader {node} />
|
||||||
|
|
||||||
{#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}
|
||||||
input={value}
|
input={value}
|
||||||
@ -47,6 +46,18 @@
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 0.5em;
|
font-size: 0.5em;
|
||||||
display: none;
|
display: none;
|
||||||
|
--stroke: #777;
|
||||||
|
--stroke-width: 0.1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node.active {
|
||||||
|
--stroke: white;
|
||||||
|
--stroke-width: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node.selected {
|
||||||
|
--stroke: #f2be90;
|
||||||
|
--stroke-width: 0.2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node.in-view {
|
.node.in-view {
|
||||||
|
@ -1,41 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node } from "$lib/types";
|
import { createNodePath } from "$lib/helpers";
|
||||||
|
import type { Node, Socket } from "$lib/types";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import { getGraphManager, getGraphState } from "./graph/context";
|
|
||||||
|
|
||||||
export let node: Node;
|
export let node: Node;
|
||||||
|
|
||||||
const graph = getGraphManager();
|
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
|
||||||
const state = getGraphState();
|
|
||||||
|
|
||||||
function createPath({ depth = 8, height = 20, y = 50 } = {}) {
|
|
||||||
let corner = 10;
|
|
||||||
|
|
||||||
let right_bump = node.tmp.type.outputs.length > 0;
|
|
||||||
|
|
||||||
return `M0,100
|
|
||||||
${
|
|
||||||
corner
|
|
||||||
? ` V${corner}
|
|
||||||
Q0,0 ${corner / 4},0
|
|
||||||
H${100 - corner / 4}
|
|
||||||
Q100,0 100,${corner}
|
|
||||||
`
|
|
||||||
: ` V0
|
|
||||||
H100
|
|
||||||
`
|
|
||||||
}
|
|
||||||
V${y - height / 2}
|
|
||||||
${
|
|
||||||
right_bump
|
|
||||||
? ` C${100 - depth},${y - height / 2} ${100 - depth},${y + height / 2} 100,${y + height / 2}`
|
|
||||||
: ` H100`
|
|
||||||
}
|
|
||||||
V100
|
|
||||||
Z`.replace(/\s+/g, " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
const setDownSocket = getContext("setDownSocket");
|
|
||||||
|
|
||||||
function handleMouseDown(event: MouseEvent) {
|
function handleMouseDown(event: MouseEvent) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -46,6 +16,35 @@
|
|||||||
position: [node.position.x + 5, node.position.y + 0.625],
|
position: [node.position.x + 5, node.position.y + 0.625],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cornerTop = 10;
|
||||||
|
const rightBump = !!node?.tmp?.type?.outputs?.length;
|
||||||
|
const aspectRatio = 0.25;
|
||||||
|
|
||||||
|
const path = createNodePath({
|
||||||
|
depth: 4.5,
|
||||||
|
height: 24,
|
||||||
|
y: 50,
|
||||||
|
cornerTop,
|
||||||
|
rightBump,
|
||||||
|
aspectRatio,
|
||||||
|
});
|
||||||
|
const pathDisabled = createNodePath({
|
||||||
|
depth: 0,
|
||||||
|
height: 15,
|
||||||
|
y: 50,
|
||||||
|
cornerTop,
|
||||||
|
rightBump,
|
||||||
|
aspectRatio,
|
||||||
|
});
|
||||||
|
const pathHover = createNodePath({
|
||||||
|
depth: 6,
|
||||||
|
height: 30,
|
||||||
|
y: 50,
|
||||||
|
cornerTop,
|
||||||
|
rightBump,
|
||||||
|
aspectRatio,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper" data-node-id={node.id}>
|
<div class="wrapper" data-node-id={node.id}>
|
||||||
@ -66,8 +65,8 @@
|
|||||||
height="100"
|
height="100"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
style={`
|
style={`
|
||||||
--path: path("${createPath({ depth: 5, height: 27, y: 50 })}");
|
--path: path("${path}");
|
||||||
--hover-path: path("${createPath({ depth: 6, height: 33, y: 50 })}");
|
--hover-path: path("${pathHover}");
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
@ -116,8 +115,8 @@
|
|||||||
stroke-width: 0.2px;
|
stroke-width: 0.2px;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
fill: #131313;
|
fill: #131313;
|
||||||
stroke: #777;
|
stroke: var(--stroke);
|
||||||
stroke-width: 0.1;
|
stroke-width: var(--stroke-width);
|
||||||
d: var(--path);
|
d: var(--path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,51 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { NodeInput } from "$lib/types";
|
import type { NodeInput, Socket } from "$lib/types";
|
||||||
import type { Node } from "$lib/types";
|
import type { Node } from "$lib/types";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
import { createNodePath } from "$lib/helpers";
|
||||||
|
import { possibleSocketIds } from "./graph/stores";
|
||||||
|
|
||||||
export let node: Node;
|
export let node: Node;
|
||||||
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 possibleSocketIds: null | Set<string> = null;
|
|
||||||
|
|
||||||
export let isLast = false;
|
export let isLast = false;
|
||||||
|
|
||||||
function createPath({ depth = 8, height = 20, y = 50 } = {}) {
|
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
|
||||||
let corner = isLast ? 5 : 0;
|
|
||||||
|
|
||||||
let right_bump = false;
|
|
||||||
let left_bump = node.tmp?.type?.inputs?.[id].internal !== true;
|
|
||||||
|
|
||||||
return `M0,0
|
|
||||||
H100
|
|
||||||
V${y - height / 2}
|
|
||||||
${
|
|
||||||
right_bump
|
|
||||||
? ` C${100 - depth},${y - height / 2} ${100 - depth},${y + height / 2} 100,${y + height / 2}`
|
|
||||||
: ` H100`
|
|
||||||
}
|
|
||||||
${
|
|
||||||
corner
|
|
||||||
? ` V${100 - corner}
|
|
||||||
Q100,100 ${100 - corner / 2},100
|
|
||||||
H${corner / 2}
|
|
||||||
Q0,100 0,${100 - corner}
|
|
||||||
`
|
|
||||||
: ` V100
|
|
||||||
H0
|
|
||||||
`
|
|
||||||
}
|
|
||||||
${
|
|
||||||
left_bump
|
|
||||||
? ` V${y + height / 2} C${depth},${y + height / 2} ${depth},${y - height / 2} 0,${y - height / 2}`
|
|
||||||
: ` H0`
|
|
||||||
}
|
|
||||||
Z`.replace(/\s+/g, " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
const setDownSocket = getContext("setDownSocket");
|
|
||||||
|
|
||||||
function handleMouseDown(ev: MouseEvent) {
|
function handleMouseDown(ev: MouseEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
@ -56,12 +22,41 @@
|
|||||||
position: [node.position.x, node.position.y + 2.5 + index * 2.5],
|
position: [node.position.x, node.position.y + 2.5 + index * 2.5],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const leftBump = node.tmp?.type?.inputs?.[id].internal !== true;
|
||||||
|
const cornerBottom = isLast ? 5 : 0;
|
||||||
|
const aspectRatio = 0.5;
|
||||||
|
|
||||||
|
const path = createNodePath({
|
||||||
|
depth: 4,
|
||||||
|
height: 12,
|
||||||
|
y: 50,
|
||||||
|
cornerBottom,
|
||||||
|
leftBump,
|
||||||
|
aspectRatio,
|
||||||
|
});
|
||||||
|
const pathDisabled = createNodePath({
|
||||||
|
depth: 0,
|
||||||
|
height: 15,
|
||||||
|
y: 50,
|
||||||
|
cornerBottom,
|
||||||
|
leftBump,
|
||||||
|
aspectRatio,
|
||||||
|
});
|
||||||
|
const pathHover = createNodePath({
|
||||||
|
depth: 8,
|
||||||
|
height: 24,
|
||||||
|
y: 50,
|
||||||
|
cornerBottom,
|
||||||
|
leftBump,
|
||||||
|
aspectRatio,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
class:disabled={possibleSocketIds &&
|
class:disabled={$possibleSocketIds &&
|
||||||
!possibleSocketIds.has(`${node.id}-${id}`)}
|
!$possibleSocketIds.has(`${node.id}-${id}`)}
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<label>{id}</label>
|
<label>{id}</label>
|
||||||
@ -91,9 +86,9 @@
|
|||||||
height="100"
|
height="100"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
style={`
|
style={`
|
||||||
--path: path("${createPath({ depth: 5, height: 15, y: 50 })}");
|
--path: path("${path}");
|
||||||
--hover-path-disabled: path("${createPath({ depth: 0, height: 15, y: 50 })}");
|
--hover-path: path("${pathHover}");
|
||||||
--hover-path: path("${createPath({ depth: 8, height: 24, y: 50 })}");
|
--hover-path-disabled: path("${pathDisabled}");
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<path vector-effect="non-scaling-stroke"></path>
|
<path vector-effect="non-scaling-stroke"></path>
|
||||||
@ -111,6 +106,8 @@
|
|||||||
.target {
|
.target {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
/* background: red; */
|
||||||
|
/* opacity: 0.1; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.small.target {
|
.small.target {
|
||||||
@ -126,6 +123,11 @@
|
|||||||
top: 5px;
|
top: 5px;
|
||||||
left: -7.5px;
|
left: -7.5px;
|
||||||
cursor: unset;
|
cursor: unset;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.hovering-sockets) .large.target {
|
||||||
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@ -169,19 +171,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
svg path {
|
svg path {
|
||||||
stroke-width: 0.2px;
|
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
fill: #060606;
|
fill: #060606;
|
||||||
stroke: #777;
|
stroke: var(--stroke);
|
||||||
stroke-width: 0.1;
|
stroke-width: var(--stroke-width);
|
||||||
d: var(--path);
|
d: var(--path);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.hovering-sockets) .large:hover ~ svg path {
|
:global(.hovering-sockets) .large:hover ~ svg path {
|
||||||
d: var(--hover-path);
|
d: var(--hover-path);
|
||||||
|
fill: #131313;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.hovering-sockets) .small:hover ~ svg path {
|
||||||
|
fill: #161616;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabled svg path {
|
.disabled svg path {
|
||||||
d: var(--hover-path-disabled) !important;
|
d: var(--hover-path-disabled) !important;
|
||||||
|
fill: #060606 !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -22,3 +22,12 @@
|
|||||||
<MeshLineMaterial color="red" linewidth={1} attenuate={false} />
|
<MeshLineMaterial color="red" linewidth={1} attenuate={false} />
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
let mesh: Mesh;
|
let mesh: Mesh;
|
||||||
|
|
||||||
function update(force = false) {
|
export const update = function (force = false) {
|
||||||
if (!force) {
|
if (!force) {
|
||||||
const new_x = from.x + to.x;
|
const new_x = from.x + to.x;
|
||||||
const new_y = from.y + to.y;
|
const new_y = from.y + to.y;
|
||||||
@ -50,7 +50,7 @@
|
|||||||
points = curve.getPoints(samples).map((p) => new Vector3(p.x, 0, p.y));
|
points = curve.getPoints(samples).map((p) => new Vector3(p.x, 0, p.y));
|
||||||
// mesh.setGeometry(points);
|
// mesh.setGeometry(points);
|
||||||
// mesh.needsUpdate = true;
|
// mesh.needsUpdate = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
update();
|
update();
|
||||||
$: if (from || to) {
|
$: if (from || to) {
|
||||||
|
@ -9,8 +9,15 @@
|
|||||||
import GraphView from "./GraphView.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 FloatingEdge from "../edges/FloatingEdge.svelte";
|
||||||
import * as debug from "../debug";
|
|
||||||
import type { Socket } from "$lib/types";
|
import type { Socket } from "$lib/types";
|
||||||
|
import {
|
||||||
|
activeNodeId,
|
||||||
|
activeSocket,
|
||||||
|
hoveredSocket,
|
||||||
|
possibleSockets,
|
||||||
|
possibleSocketIds,
|
||||||
|
selectedNodes,
|
||||||
|
} from "./stores";
|
||||||
|
|
||||||
export let graph: GraphManager;
|
export let graph: GraphManager;
|
||||||
setContext("graphManager", graph);
|
setContext("graphManager", graph);
|
||||||
@ -27,39 +34,68 @@
|
|||||||
let width = 100;
|
let width = 100;
|
||||||
let height = 100;
|
let height = 100;
|
||||||
|
|
||||||
let activeNodeId = -1;
|
|
||||||
let downSocket: null | Socket = null;
|
|
||||||
let possibleSockets: Socket[] = [];
|
|
||||||
$: possibleSocketIds = possibleSockets?.length
|
|
||||||
? new Set(possibleSockets.map((s) => `${s.node.id}-${s.index}`))
|
|
||||||
: null;
|
|
||||||
let hoveredSocket: Socket | null = null;
|
|
||||||
|
|
||||||
$: cameraBounds = [
|
$: cameraBounds = [
|
||||||
cameraPosition[0] - width / cameraPosition[2],
|
cameraPosition[0] - width / cameraPosition[2] / 2,
|
||||||
cameraPosition[0] + width / cameraPosition[2],
|
cameraPosition[0] + width / cameraPosition[2] / 2,
|
||||||
cameraPosition[1] - height / cameraPosition[2],
|
cameraPosition[1] - height / cameraPosition[2] / 2,
|
||||||
cameraPosition[1] + height / cameraPosition[2],
|
cameraPosition[1] + height / cameraPosition[2] / 2,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export let debug = {};
|
||||||
|
$: debug = {
|
||||||
|
activeNodeId: $activeNodeId,
|
||||||
|
activeSocket: $activeSocket
|
||||||
|
? `${$activeSocket?.node.id}-${$activeSocket?.index}`
|
||||||
|
: null,
|
||||||
|
hoveredSocket: $hoveredSocket
|
||||||
|
? `${$hoveredSocket?.node.id}-${$hoveredSocket?.index}`
|
||||||
|
: null,
|
||||||
|
selectedNodes: [...($selectedNodes?.values() || [])],
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateNodePosition(node: NodeType) {
|
||||||
|
node.tmp = node.tmp || {};
|
||||||
|
if (node?.tmp?.ref) {
|
||||||
|
node.tmp.ref.style.setProperty("--nx", `${node.position.x * 10}px`);
|
||||||
|
node.tmp.ref.style.setProperty("--ny", `${node.position.y * 10}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeHeightCache: Record<string, number> = {};
|
||||||
|
function getNodeHeight(nodeTypeId: string) {
|
||||||
|
if (nodeTypeId in nodeHeightCache) {
|
||||||
|
return nodeHeightCache[nodeTypeId];
|
||||||
|
}
|
||||||
|
const node = graph.getNodeType(nodeTypeId);
|
||||||
|
if (!node?.inputs) {
|
||||||
|
return 1.25;
|
||||||
|
}
|
||||||
|
const height = 1.25 + 2.5 * Object.keys(node.inputs).length;
|
||||||
|
nodeHeightCache[nodeTypeId] = height;
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
setContext("isNodeInView", (node: NodeType) => {
|
setContext("isNodeInView", (node: NodeType) => {
|
||||||
|
const height = getNodeHeight(node.type);
|
||||||
|
const width = 5;
|
||||||
return (
|
return (
|
||||||
node.position.x > cameraBounds[0] &&
|
// check x-axis
|
||||||
|
node.position.x > cameraBounds[0] - width &&
|
||||||
node.position.x < cameraBounds[1] &&
|
node.position.x < cameraBounds[1] &&
|
||||||
node.position.y > cameraBounds[2] &&
|
// check y-axis
|
||||||
|
node.position.y > cameraBounds[2] - height &&
|
||||||
node.position.y < cameraBounds[3]
|
node.position.y < cameraBounds[3]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
setContext("setDownSocket", (socket: Socket) => {
|
setContext("setDownSocket", (socket: Socket) => {
|
||||||
downSocket = socket;
|
$activeSocket = socket;
|
||||||
|
|
||||||
let { node, index, position } = socket;
|
let { node, index, position } = socket;
|
||||||
|
|
||||||
// remove existing edge
|
// remove existing edge
|
||||||
if (typeof index === "string") {
|
if (typeof index === "string") {
|
||||||
const edges = graph.getEdgesToNode(node);
|
const edges = graph.getEdgesToNode(node);
|
||||||
console.log({ edges });
|
|
||||||
for (const edge of edges) {
|
for (const edge of edges) {
|
||||||
if (edge[3] === index) {
|
if (edge[3] === index) {
|
||||||
node = edge[0];
|
node = edge[0];
|
||||||
@ -72,14 +108,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
mouseDown = position;
|
mouseDown = position;
|
||||||
downSocket = {
|
$activeSocket = {
|
||||||
node,
|
node,
|
||||||
index,
|
index,
|
||||||
position,
|
position,
|
||||||
};
|
};
|
||||||
|
|
||||||
possibleSockets = graph
|
$possibleSockets = graph
|
||||||
.getPossibleSockets(downSocket)
|
.getPossibleSockets($activeSocket)
|
||||||
.map(([node, index]) => {
|
.map(([node, index]) => {
|
||||||
return {
|
return {
|
||||||
node,
|
node,
|
||||||
@ -87,6 +123,9 @@
|
|||||||
position: getSocketPosition({ node, index }),
|
position: getSocketPosition({ node, index }),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
$possibleSocketIds = new Set(
|
||||||
|
$possibleSockets.map((s) => `${s.node.id}-${s.index}`),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function getSnapLevel() {
|
function getSnapLevel() {
|
||||||
@ -136,10 +175,11 @@
|
|||||||
|
|
||||||
if (!mouseDown) return;
|
if (!mouseDown) return;
|
||||||
|
|
||||||
if (possibleSockets?.length) {
|
// we are creating a new edge here
|
||||||
|
if ($possibleSockets?.length) {
|
||||||
let smallestDist = 1000;
|
let smallestDist = 1000;
|
||||||
let _socket;
|
let _socket;
|
||||||
for (const socket of possibleSockets) {
|
for (const socket of $possibleSockets) {
|
||||||
const dist = Math.sqrt(
|
const dist = Math.sqrt(
|
||||||
(socket.position[0] - mousePosition[0]) ** 2 +
|
(socket.position[0] - mousePosition[0]) ** 2 +
|
||||||
(socket.position[1] - mousePosition[1]) ** 2,
|
(socket.position[1] - mousePosition[1]) ** 2,
|
||||||
@ -152,119 +192,236 @@
|
|||||||
|
|
||||||
if (_socket && smallestDist < 0.3) {
|
if (_socket && smallestDist < 0.3) {
|
||||||
mousePosition = _socket.position;
|
mousePosition = _socket.position;
|
||||||
hoveredSocket = _socket;
|
$hoveredSocket = _socket;
|
||||||
} else {
|
} else {
|
||||||
hoveredSocket = 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 || event.buttons !== 1) return;
|
||||||
|
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
node.tmp.isMoving = true;
|
|
||||||
|
|
||||||
let newX =
|
const oldX = node.tmp.downX || 0;
|
||||||
(node?.tmp?.downX || 0) +
|
const oldY = node.tmp.downY || 0;
|
||||||
(event.clientX - mouseDown[0]) / cameraPosition[2];
|
|
||||||
let newY =
|
let newX = oldX + (event.clientX - mouseDown[0]) / cameraPosition[2];
|
||||||
(node?.tmp?.downY || 0) +
|
let newY = oldY + (event.clientY - mouseDown[1]) / cameraPosition[2];
|
||||||
(event.clientY - mouseDown[1]) / cameraPosition[2];
|
|
||||||
|
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
const snapLevel = getSnapLevel();
|
const snapLevel = getSnapLevel();
|
||||||
newX = snapToGrid(newX, 5 / snapLevel);
|
newX = snapToGrid(newX, 5 / snapLevel);
|
||||||
newY = snapToGrid(newY, 5 / snapLevel);
|
newY = snapToGrid(newY, 5 / snapLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!node.tmp.isMoving) {
|
||||||
|
const dist = Math.sqrt((oldX - newX) ** 2 + (oldY - newY) ** 2);
|
||||||
|
if (dist > 0.2) {
|
||||||
|
node.tmp.isMoving = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vecX = oldX - newX;
|
||||||
|
const vecY = oldY - newY;
|
||||||
|
|
||||||
|
if ($selectedNodes?.size) {
|
||||||
|
for (const nodeId of $selectedNodes) {
|
||||||
|
const n = graph.getNode(nodeId);
|
||||||
|
if (!n) continue;
|
||||||
|
n.position.x = (n?.tmp?.downX || 0) - vecX;
|
||||||
|
n.position.y = (n?.tmp?.downY || 0) - vecY;
|
||||||
|
updateNodePosition(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
node.position.x = newX;
|
node.position.x = newX;
|
||||||
node.position.y = newY;
|
node.position.y = newY;
|
||||||
node.position = node.position;
|
node.position = node.position;
|
||||||
|
|
||||||
nodes.set($nodes);
|
updateNodePosition(node);
|
||||||
edges.set($edges);
|
|
||||||
|
$edges = $edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseDown(event: MouseEvent) {
|
function handleMouseDown(event: MouseEvent) {
|
||||||
if (mouseDown) return;
|
if (mouseDown) return;
|
||||||
|
|
||||||
for (const node of event.composedPath()) {
|
|
||||||
let _activeNodeId = (node as unknown as HTMLElement)?.getAttribute?.(
|
|
||||||
"data-node-id",
|
|
||||||
)!;
|
|
||||||
if (_activeNodeId) {
|
|
||||||
activeNodeId = parseInt(_activeNodeId, 10);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (activeNodeId < 0) return;
|
|
||||||
|
|
||||||
mouseDown = [event.clientX, event.clientY];
|
mouseDown = [event.clientX, event.clientY];
|
||||||
const node = graph.getNode(activeNodeId);
|
|
||||||
|
if (event.target instanceof HTMLElement && event.buttons === 1) {
|
||||||
|
const nodeElement = event.target.closest(".node");
|
||||||
|
const _activeNodeId = nodeElement?.getAttribute?.("data-node-id");
|
||||||
|
if (_activeNodeId) {
|
||||||
|
const nodeId = parseInt(_activeNodeId, 10);
|
||||||
|
if ($activeNodeId !== -1) {
|
||||||
|
// if the selected node is the same as the clicked node
|
||||||
|
if ($activeNodeId === nodeId) {
|
||||||
|
//$activeNodeId = -1;
|
||||||
|
// if the clicked node is different from the selected node and secondary
|
||||||
|
} else if (event.ctrlKey) {
|
||||||
|
$selectedNodes = $selectedNodes || new Set();
|
||||||
|
$selectedNodes.add($activeNodeId);
|
||||||
|
$selectedNodes.delete(nodeId);
|
||||||
|
$activeNodeId = nodeId;
|
||||||
|
// select the node
|
||||||
|
} else if (event.shiftKey) {
|
||||||
|
const activeNode = graph.getNode($activeNodeId);
|
||||||
|
const newNode = graph.getNode(nodeId);
|
||||||
|
if (activeNode && newNode) {
|
||||||
|
const edge = graph.getNodesBetween(activeNode, newNode);
|
||||||
|
if (edge) {
|
||||||
|
$selectedNodes = new Set(edge.map((n) => n.id));
|
||||||
|
}
|
||||||
|
$activeNodeId = nodeId;
|
||||||
|
}
|
||||||
|
} else if (!$selectedNodes?.has(nodeId)) {
|
||||||
|
$activeNodeId = nodeId;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$activeNodeId = nodeId;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$activeNodeId = -1;
|
||||||
|
$selectedNodes?.clear();
|
||||||
|
$selectedNodes = $selectedNodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
if ($selectedNodes) {
|
||||||
|
for (const nodeId of $selectedNodes) {
|
||||||
|
const n = graph.getNode(nodeId);
|
||||||
|
if (!n) continue;
|
||||||
|
n.tmp = n.tmp || {};
|
||||||
|
n.tmp.downX = n.position.x;
|
||||||
|
n.tmp.downY = n.position.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
|
if (event.key === "Delete") {
|
||||||
|
if ($activeNodeId !== -1) {
|
||||||
|
const node = graph.getNode($activeNodeId);
|
||||||
|
if (node) {
|
||||||
|
graph.removeNode(node);
|
||||||
|
$activeNodeId = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($selectedNodes) {
|
||||||
|
for (const nodeId of $selectedNodes) {
|
||||||
|
const node = graph.getNode(nodeId);
|
||||||
|
if (node) {
|
||||||
|
graph.removeNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$selectedNodes.clear();
|
||||||
|
$selectedNodes = $selectedNodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseUp(event: MouseEvent) {
|
function handleMouseUp(event: MouseEvent) {
|
||||||
if (event.button !== 0) return;
|
const activeNode = graph.getNode($activeNodeId);
|
||||||
|
|
||||||
const node = graph.getNode(activeNodeId);
|
if (event.target instanceof HTMLElement && event.button === 0) {
|
||||||
if (node) {
|
const nodeElement = event.target.closest(".node");
|
||||||
node.tmp = node.tmp || {};
|
const _activeNodeId = nodeElement?.getAttribute?.("data-node-id");
|
||||||
node.tmp.isMoving = false;
|
if (_activeNodeId) {
|
||||||
|
const nodeId = parseInt(_activeNodeId, 10);
|
||||||
|
if (activeNode) {
|
||||||
|
if (!activeNode?.tmp?.isMoving && !event.ctrlKey && !event.shiftKey) {
|
||||||
|
$selectedNodes?.clear();
|
||||||
|
$selectedNodes = $selectedNodes;
|
||||||
|
$activeNodeId = nodeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeNode?.tmp?.isMoving) {
|
||||||
|
activeNode.tmp = activeNode.tmp || {};
|
||||||
|
activeNode.tmp.isMoving = false;
|
||||||
const snapLevel = getSnapLevel();
|
const snapLevel = getSnapLevel();
|
||||||
const fx = snapToGrid(node.position.x, 5 / snapLevel);
|
const fx = snapToGrid(activeNode.position.x, 5 / snapLevel);
|
||||||
const fy = snapToGrid(node.position.y, 5 / snapLevel);
|
const fy = snapToGrid(activeNode.position.y, 5 / snapLevel);
|
||||||
|
if ($selectedNodes) {
|
||||||
|
for (const nodeId of $selectedNodes) {
|
||||||
|
const node = graph.getNode(nodeId);
|
||||||
|
if (!node) continue;
|
||||||
|
node.tmp = node.tmp || {};
|
||||||
|
node.tmp.snapX = node.position.x - (activeNode.position.x - fx);
|
||||||
|
node.tmp.snapY = node.position.y - (activeNode.position.y - fy);
|
||||||
|
}
|
||||||
|
}
|
||||||
animate(500, (a: number) => {
|
animate(500, (a: number) => {
|
||||||
node.position.x = lerp(node.position.x, fx, a);
|
activeNode.position.x = lerp(activeNode.position.x, fx, a);
|
||||||
node.position.y = lerp(node.position.y, fy, a);
|
activeNode.position.y = lerp(activeNode.position.y, fy, a);
|
||||||
nodes.set($nodes);
|
updateNodePosition(activeNode);
|
||||||
edges.set($edges);
|
|
||||||
if (node?.tmp?.isMoving) {
|
if ($selectedNodes) {
|
||||||
|
for (const nodeId of $selectedNodes) {
|
||||||
|
const node = graph.getNode(nodeId);
|
||||||
|
if (!node) continue;
|
||||||
|
node.position.x = lerp(node.position.x, node?.tmp?.snapX || 0, a);
|
||||||
|
node.position.y = lerp(node.position.y, node?.tmp?.snapY || 0, a);
|
||||||
|
updateNodePosition(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeNode?.tmp?.isMoving) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$edges = $edges;
|
||||||
});
|
});
|
||||||
} else if (hoveredSocket && downSocket) {
|
} else if ($hoveredSocket && $activeSocket) {
|
||||||
console.log({ hoveredSocket, downSocket });
|
|
||||||
if (
|
if (
|
||||||
typeof hoveredSocket.index === "number" &&
|
typeof $hoveredSocket.index === "number" &&
|
||||||
typeof downSocket.index === "string"
|
typeof $activeSocket.index === "string"
|
||||||
) {
|
) {
|
||||||
graph.createEdge(
|
graph.createEdge(
|
||||||
hoveredSocket.node,
|
$hoveredSocket.node,
|
||||||
hoveredSocket.index || 0,
|
$hoveredSocket.index || 0,
|
||||||
downSocket.node,
|
$activeSocket.node,
|
||||||
downSocket.index,
|
$activeSocket.index,
|
||||||
);
|
);
|
||||||
} else {
|
} else if (
|
||||||
|
typeof $activeSocket.index == "number" &&
|
||||||
|
typeof $hoveredSocket.index === "string"
|
||||||
|
) {
|
||||||
graph.createEdge(
|
graph.createEdge(
|
||||||
downSocket.node,
|
$activeSocket.node,
|
||||||
downSocket.index || 0,
|
$activeSocket.index || 0,
|
||||||
hoveredSocket.node,
|
$hoveredSocket.node,
|
||||||
hoveredSocket.index,
|
$hoveredSocket.index,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseDown = null;
|
mouseDown = null;
|
||||||
downSocket = null;
|
$activeSocket = null;
|
||||||
possibleSockets = [];
|
$possibleSockets = [];
|
||||||
hoveredSocket = null;
|
$possibleSocketIds = null;
|
||||||
activeNodeId = -1;
|
$hoveredSocket = null;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:document
|
<svelte:window
|
||||||
on:mousemove={handleMouseMove}
|
on:mousemove={handleMouseMove}
|
||||||
on:mouseup={handleMouseUp}
|
on:mouseup={handleMouseUp}
|
||||||
on:mousedown={handleMouseDown}
|
on:mousedown={handleMouseDown}
|
||||||
|
on:keydown={handleKeyDown}
|
||||||
|
bind:innerWidth={width}
|
||||||
|
bind:innerHeight={height}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<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} />
|
||||||
@ -272,19 +429,13 @@
|
|||||||
<Background {cameraPosition} {maxZoom} {minZoom} {width} {height} />
|
<Background {cameraPosition} {maxZoom} {minZoom} {width} {height} />
|
||||||
|
|
||||||
{#if $status === "idle"}
|
{#if $status === "idle"}
|
||||||
{#if downSocket}
|
{#if $activeSocket}
|
||||||
<FloatingEdge
|
<FloatingEdge
|
||||||
from={{ x: downSocket.position[0], y: downSocket.position[1] }}
|
from={{ x: $activeSocket.position[0], y: $activeSocket.position[1] }}
|
||||||
to={{ x: mousePosition[0], y: mousePosition[1] }}
|
to={{ x: mousePosition[0], y: mousePosition[1] }}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<GraphView
|
<GraphView {nodes} {edges} {cameraPosition} />
|
||||||
{nodes}
|
|
||||||
{edges}
|
|
||||||
{cameraPosition}
|
|
||||||
{possibleSocketIds}
|
|
||||||
{downSocket}
|
|
||||||
/>
|
|
||||||
{:else if $status === "loading"}
|
{:else if $status === "loading"}
|
||||||
<span>Loading</span>
|
<span>Loading</span>
|
||||||
{:else if $status === "error"}
|
{:else if $status === "error"}
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Edge as EdgeType, Node as NodeType } from "$lib/types";
|
import type { Edge as EdgeType, Node as NodeType, Socket } from "$lib/types";
|
||||||
import { HTML } from "@threlte/extras";
|
import { HTML } from "@threlte/extras";
|
||||||
import Edge from "../edges/Edge.svelte";
|
import Edge from "../edges/Edge.svelte";
|
||||||
import Node from "../Node.svelte";
|
import Node from "../Node.svelte";
|
||||||
import { getContext } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
|
import { activeSocket } from "./stores";
|
||||||
|
|
||||||
export let nodes: Writable<Map<number, NodeType>>;
|
export let nodes: Writable<Map<number, NodeType>>;
|
||||||
export let edges: Writable<EdgeType[]>;
|
export let edges: Writable<EdgeType[]>;
|
||||||
|
|
||||||
export let cameraPosition = [0, 1, 0];
|
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");
|
const isNodeInView = getContext<(n: NodeType) => boolean>("isNodeInView");
|
||||||
|
|
||||||
@ -25,9 +23,18 @@
|
|||||||
edge[2].position.y + 2.5 + index * 2.5,
|
edge[2].position.y + 2.5 + index * 2.5,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
for (const node of $nodes.values()) {
|
||||||
|
if (node?.tmp?.ref) {
|
||||||
|
node.tmp.ref.style.setProperty("--nx", `${node.position.x * 10}px`);
|
||||||
|
node.tmp.ref.style.setProperty("--ny", `${node.position.y * 10}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each $edges as edge}
|
{#each $edges as edge (edge[0].id + edge[2].id + edge[3])}
|
||||||
{@const pos = getEdgePosition(edge)}
|
{@const pos = getEdgePosition(edge)}
|
||||||
{@const [x1, y1, x2, y2] = pos}
|
{@const [x1, y1, x2, y2] = pos}
|
||||||
<Edge
|
<Edge
|
||||||
@ -48,15 +55,11 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
class:zoom-small={cameraPosition[2] < 10}
|
class:zoom-small={cameraPosition[2] < 10}
|
||||||
class:hovering-sockets={downSocket}
|
class:hovering-sockets={activeSocket}
|
||||||
style={`--cz: ${cameraPosition[2]}`}
|
style={`--cz: ${cameraPosition[2]}`}
|
||||||
>
|
>
|
||||||
{#each $nodes.values() as node}
|
{#each $nodes.values() as node (node.id)}
|
||||||
<Node
|
<Node {node} inView={cameraPosition && isNodeInView(node)} />
|
||||||
{node}
|
|
||||||
inView={cameraPosition && isNodeInView(node)}
|
|
||||||
{possibleSocketIds}
|
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</HTML>
|
</HTML>
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import type { GraphManager } from "$lib/graph-manager";
|
import type { GraphManager } from "$lib/graph-manager";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import type { GraphView } from "./view";
|
|
||||||
|
|
||||||
export function getGraphManager(): GraphManager {
|
export function getGraphManager(): GraphManager {
|
||||||
return getContext("graphManager");
|
return getContext("graphManager");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGraphState(): GraphView {
|
|
||||||
return getContext("graphState");
|
|
||||||
}
|
|
||||||
|
10
frontend/src/lib/components/graph/stores.ts
Normal file
10
frontend/src/lib/components/graph/stores.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { Node, Socket } from "$lib/types";
|
||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const activeNodeId: Writable<number> = writable(-1);
|
||||||
|
export const selectedNodes: Writable<Set<number> | null> = writable(null);
|
||||||
|
|
||||||
|
export const activeSocket: Writable<Socket | null> = writable(null);
|
||||||
|
export const hoveredSocket: Writable<Socket | null> = writable(null);
|
||||||
|
export const possibleSockets: Writable<Socket[]> = writable([]);
|
||||||
|
export const possibleSocketIds: Writable<Set<string> | null> = writable(null);
|
20
frontend/src/lib/elements/Details.svelte
Normal file
20
frontend/src/lib/elements/Details.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let title = "Details";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>{title}</summary>
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
details {
|
||||||
|
padding: 1em;
|
||||||
|
color: white;
|
||||||
|
outline: solid 0.1px white;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
</style>
|
@ -50,6 +50,19 @@ export class GraphManager {
|
|||||||
this.edges.subscribe((edges) => {
|
this.edges.subscribe((edges) => {
|
||||||
this._edges = edges;
|
this._edges = edges;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
globalThis["serialize"] = () => this.serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
const nodes = Array.from(this._nodes.values()).map(node => ({
|
||||||
|
id: node.id,
|
||||||
|
position: node.position,
|
||||||
|
type: node.type,
|
||||||
|
props: node.props,
|
||||||
|
}));
|
||||||
|
const edges = this._edges.map(edge => [edge[0].id, edge[1], edge[2].id, edge[3]]);
|
||||||
|
return { nodes, edges };
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
@ -87,8 +100,6 @@ export class GraphManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.nodes.set(nodes);
|
this.nodes.set(nodes);
|
||||||
console.log(this._nodes);
|
|
||||||
|
|
||||||
|
|
||||||
this.status.set("idle");
|
this.status.set("idle");
|
||||||
}
|
}
|
||||||
@ -102,6 +113,10 @@ export class GraphManager {
|
|||||||
return this._nodes.get(id);
|
return this._nodes.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNodeType(id: string) {
|
||||||
|
return this.nodeRegistry.getNode(id);
|
||||||
|
}
|
||||||
|
|
||||||
getChildrenOfNode(node: Node) {
|
getChildrenOfNode(node: Node) {
|
||||||
const children = [];
|
const children = [];
|
||||||
const stack = node.tmp?.children?.slice(0);
|
const stack = node.tmp?.children?.slice(0);
|
||||||
@ -114,8 +129,35 @@ export class GraphManager {
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
createEdge(from: Node, fromSocket: number, to: Node, toSocket: string) {
|
getNodesBetween(from: Node, to: Node): Node[] | undefined {
|
||||||
|
// < - - - - from
|
||||||
|
const toParents = this.getParentsOfNode(to);
|
||||||
|
// < - - - - from - - - - to
|
||||||
|
const fromParents = this.getParentsOfNode(from);
|
||||||
|
if (toParents.includes(from)) {
|
||||||
|
return toParents.splice(toParents.indexOf(from));
|
||||||
|
} else if (fromParents.includes(to)) {
|
||||||
|
return fromParents.splice(fromParents.indexOf(to));
|
||||||
|
} else {
|
||||||
|
// these two nodes are not connected
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateNodeParents(node: Node) {
|
||||||
|
}
|
||||||
|
|
||||||
|
removeNode(node: Node) {
|
||||||
|
const edges = this._edges.filter((edge) => edge[0].id !== node.id && edge[2].id !== node.id);
|
||||||
|
this.edges.set(edges);
|
||||||
|
|
||||||
|
this.nodes.update((nodes) => {
|
||||||
|
nodes.delete(node.id);
|
||||||
|
return nodes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createEdge(from: Node, fromSocket: number, to: Node, toSocket: string) {
|
||||||
|
|
||||||
const existingEdges = this.getEdgesToNode(to);
|
const existingEdges = this.getEdgesToNode(to);
|
||||||
|
|
||||||
@ -150,7 +192,7 @@ export class GraphManager {
|
|||||||
parents.push(parent);
|
parents.push(parent);
|
||||||
stack.push(...parent.tmp?.parents || []);
|
stack.push(...parent.tmp?.parents || []);
|
||||||
}
|
}
|
||||||
return parents;
|
return parents.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
getPossibleSockets({ node, index }: Socket): [Node, string | number][] {
|
getPossibleSockets({ node, index }: Socket): [Node, string | number][] {
|
||||||
|
@ -21,3 +21,44 @@ export function animate(duration: number, callback: (progress: number) => void |
|
|||||||
}
|
}
|
||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createNodePath({
|
||||||
|
depth = 8,
|
||||||
|
height = 20,
|
||||||
|
y = 50,
|
||||||
|
cornerTop = 0,
|
||||||
|
cornerBottom = 0,
|
||||||
|
leftBump = false,
|
||||||
|
rightBump = false,
|
||||||
|
aspectRatio = 1,
|
||||||
|
} = {}) {
|
||||||
|
return `M0,${cornerTop}
|
||||||
|
${cornerTop
|
||||||
|
? ` V${cornerTop}
|
||||||
|
Q0,0 ${cornerTop * aspectRatio},0
|
||||||
|
H${100 - cornerTop * aspectRatio}
|
||||||
|
Q100,0 100,${cornerTop}
|
||||||
|
`
|
||||||
|
: ` V0
|
||||||
|
H100
|
||||||
|
`
|
||||||
|
}
|
||||||
|
V${y - height / 2}
|
||||||
|
${rightBump
|
||||||
|
? ` C${100 - depth},${y - height / 2} ${100 - depth},${y + height / 2} 100,${y + height / 2}`
|
||||||
|
: ` H100`
|
||||||
|
}
|
||||||
|
${cornerBottom
|
||||||
|
? ` V${100 - cornerBottom}
|
||||||
|
Q100,100 ${100 - cornerBottom * aspectRatio},100
|
||||||
|
H${cornerBottom * aspectRatio}
|
||||||
|
Q0,100 0,${100 - cornerBottom}
|
||||||
|
`
|
||||||
|
: `${leftBump ? `V100 H0` : `V100`}`
|
||||||
|
}
|
||||||
|
${leftBump
|
||||||
|
? ` V${y + height / 2} C${depth},${y + height / 2} ${depth},${y - height / 2} 0,${y - height / 2}`
|
||||||
|
: ` H0`
|
||||||
|
}
|
||||||
|
Z`.replace(/\s+/g, " ");
|
||||||
|
}
|
||||||
|
@ -11,6 +11,9 @@ export type Node = {
|
|||||||
type?: NodeType;
|
type?: NodeType;
|
||||||
downX?: number;
|
downX?: number;
|
||||||
downY?: number;
|
downY?: number;
|
||||||
|
snapX?: number;
|
||||||
|
snapY?: number;
|
||||||
|
ref?: HTMLElement;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
isMoving?: boolean;
|
isMoving?: boolean;
|
||||||
},
|
},
|
||||||
|
@ -2,14 +2,17 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { PerfMonitor } from "@threlte/extras";
|
import { PerfMonitor } from "@threlte/extras";
|
||||||
|
|
||||||
import { Canvas } from "@threlte/core";
|
import { Canvas } from "@threlte/core";
|
||||||
import { GraphManager } from "$lib/graph-manager";
|
import { GraphManager } from "$lib/graph-manager";
|
||||||
import Graph from "$lib/components/graph/Graph.svelte";
|
import Graph from "$lib/components/graph/Graph.svelte";
|
||||||
|
import Details from "$lib/elements/Details.svelte";
|
||||||
|
import { JsonView } from "@zerodevx/svelte-json-view";
|
||||||
|
|
||||||
const graph = GraphManager.createEmptyGraph({ width: 12, height: 12 });
|
const graph = GraphManager.createEmptyGraph({ width: 12, height: 12 });
|
||||||
graph.load();
|
graph.load();
|
||||||
|
|
||||||
|
let debug: undefined;
|
||||||
|
|
||||||
// onMount(async () => {
|
// onMount(async () => {
|
||||||
// try {
|
// try {
|
||||||
// const res = await invoke("greet", { name: "Dude" });
|
// const res = await invoke("greet", { name: "Dude" });
|
||||||
@ -27,15 +30,28 @@
|
|||||||
// });
|
// });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class="wrapper">
|
||||||
|
<Details>
|
||||||
|
<JsonView json={debug} />
|
||||||
|
</Details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="canvas-wrapper">
|
||||||
<Canvas shadows={false} renderMode="on-demand" autoRender={true}>
|
<Canvas shadows={false} renderMode="on-demand" autoRender={true}>
|
||||||
<!-- <PerfMonitor /> -->
|
<!-- <PerfMonitor /> -->
|
||||||
<Graph {graph} />
|
<Graph {graph} bind:debug />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
.wrapper {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-wrapper {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,9 @@ importers:
|
|||||||
'@tsconfig/svelte':
|
'@tsconfig/svelte':
|
||||||
specifier: ^5.0.2
|
specifier: ^5.0.2
|
||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
|
'@zerodevx/svelte-json-view':
|
||||||
|
specifier: ^1.0.9
|
||||||
|
version: 1.0.9(svelte@4.2.12)
|
||||||
histoire:
|
histoire:
|
||||||
specifier: ^0.17.9
|
specifier: ^0.17.9
|
||||||
version: 0.17.9(vite@5.1.4)
|
version: 0.17.9(vite@5.1.4)
|
||||||
@ -1228,6 +1231,14 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@zerodevx/svelte-json-view@1.0.9(svelte@4.2.12):
|
||||||
|
resolution: {integrity: sha512-2KKxBfDxEo7lM/kJSy+m1PdLAp5Q9c5nB6OYVBg7oWPdCLXB9JVH1Ytxn2hkqTn77m9MobqGI1fz9FFOTPONfA==}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: ^3.57.0 || ^4.0.0
|
||||||
|
dependencies:
|
||||||
|
svelte: 4.2.12
|
||||||
|
dev: true
|
||||||
|
|
||||||
/abab@2.0.6:
|
/abab@2.0.6:
|
||||||
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
|
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
|
||||||
deprecated: Use your platform's native atob() and btoa() methods instead
|
deprecated: Use your platform's native atob() and btoa() methods instead
|
||||||
|
Loading…
Reference in New Issue
Block a user