feat: add add menu
This commit is contained in:
parent
84bcfa61d8
commit
e80ecd2302
164
frontend/src/lib/components/AddMenu.svelte
Normal file
164
frontend/src/lib/components/AddMenu.svelte
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { GraphManager } from "$lib/graph-manager";
|
||||||
|
import { HTML } from "@threlte/extras";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
export let position: [x: number, y: number] | null;
|
||||||
|
|
||||||
|
export let graph: GraphManager;
|
||||||
|
|
||||||
|
let input: HTMLInputElement;
|
||||||
|
let value: string = "";
|
||||||
|
let activeNodeId: string = "";
|
||||||
|
|
||||||
|
const allNodes = graph.getNodeTypes();
|
||||||
|
|
||||||
|
function filterNodes() {
|
||||||
|
return allNodes.filter((node) => node.id.includes(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
$: nodes = value === "" ? allNodes : filterNodes();
|
||||||
|
$: if (nodes) {
|
||||||
|
if (activeNodeId === "") {
|
||||||
|
activeNodeId = nodes[0].id;
|
||||||
|
} else if (nodes.length) {
|
||||||
|
const node = nodes.find((node) => node.id === activeNodeId);
|
||||||
|
if (!node) {
|
||||||
|
activeNodeId = nodes[0].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
const value = (event.target as HTMLInputElement).value;
|
||||||
|
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
position = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "ArrowDown") {
|
||||||
|
const index = nodes.findIndex((node) => node.id === activeNodeId);
|
||||||
|
activeNodeId = nodes[(index + 1) % nodes.length].id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "ArrowUp") {
|
||||||
|
const index = nodes.findIndex((node) => node.id === activeNodeId);
|
||||||
|
activeNodeId = nodes[(index - 1 + nodes.length) % nodes.length].id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
if (activeNodeId && position) {
|
||||||
|
graph.createNode({ type: activeNodeId, position });
|
||||||
|
position = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
input.disabled = false;
|
||||||
|
setTimeout(() => input.focus(), 50);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<HTML position.x={position?.[0]} position.z={position?.[1]} transform={false}>
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<input
|
||||||
|
id="add-menu"
|
||||||
|
type="text"
|
||||||
|
aria-label="Search for a node type"
|
||||||
|
role="searchbox"
|
||||||
|
placeholder="Search..."
|
||||||
|
disabled={false}
|
||||||
|
on:keydown={handleKeyDown}
|
||||||
|
bind:value
|
||||||
|
bind:this={input}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{#each nodes as node}
|
||||||
|
<div
|
||||||
|
class="result"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="0"
|
||||||
|
aria-selected={node.id === activeNodeId}
|
||||||
|
on:keydown={(event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
if (position) {
|
||||||
|
graph.createNode({ type: node.id, position });
|
||||||
|
position = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
on:mousedown={() => {
|
||||||
|
if (position) {
|
||||||
|
graph.createNode({ type: node.id, position });
|
||||||
|
position = null;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
on:focus={() => {
|
||||||
|
activeNodeId = node.id;
|
||||||
|
}}
|
||||||
|
class:selected={node.id === activeNodeId}
|
||||||
|
on:mouseover={() => {
|
||||||
|
activeNodeId = node.id;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{node.id}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HTML>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input {
|
||||||
|
background: var(--background-color-lighter);
|
||||||
|
font-family: var(--font-family);
|
||||||
|
border: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 0.8em;
|
||||||
|
width: calc(100% - 2px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 1em;
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline: solid 2px rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
position: absolute;
|
||||||
|
background: var(--background-color);
|
||||||
|
border-radius: 7px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: solid 2px var(--background-color-lighter);
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
min-height: none;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
padding: 1em 0.9em;
|
||||||
|
border-bottom: solid 1px var(--background-color-lighter);
|
||||||
|
opacity: 0.7;
|
||||||
|
font-size: 0.9em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result[aria-selected="true"] {
|
||||||
|
background: var(--background-color-lighter);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
@ -18,6 +18,7 @@
|
|||||||
selectedNodes,
|
selectedNodes,
|
||||||
} from "./stores";
|
} from "./stores";
|
||||||
import BoxSelection from "../BoxSelection.svelte";
|
import BoxSelection from "../BoxSelection.svelte";
|
||||||
|
import AddMenu from "../AddMenu.svelte";
|
||||||
|
|
||||||
export let graph: GraphManager;
|
export let graph: GraphManager;
|
||||||
setContext("graphManager", graph);
|
setContext("graphManager", graph);
|
||||||
@ -36,6 +37,7 @@
|
|||||||
let loaded = false;
|
let loaded = false;
|
||||||
const cameraDown = [0, 0];
|
const cameraDown = [0, 0];
|
||||||
let cameraPosition: [number, number, number] = [0, 0, 4];
|
let cameraPosition: [number, number, number] = [0, 0, 4];
|
||||||
|
let addMenuPosition: [number, number] | null = null;
|
||||||
|
|
||||||
$: if (cameraPosition && loaded) {
|
$: if (cameraPosition && loaded) {
|
||||||
localStorage.setItem("cameraPosition", JSON.stringify(cameraPosition));
|
localStorage.setItem("cameraPosition", JSON.stringify(cameraPosition));
|
||||||
@ -79,16 +81,19 @@
|
|||||||
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
|
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
|
||||||
node.tmp.mesh.position.x = node.tmp.x + 10;
|
node.tmp.mesh.position.x = node.tmp.x + 10;
|
||||||
node.tmp.mesh.position.z = node.tmp.y + getNodeHeight(node.type) / 2;
|
node.tmp.mesh.position.z = node.tmp.y + getNodeHeight(node.type) / 2;
|
||||||
if (node.tmp.x === node.position.x && node.tmp.y === node.position.y) {
|
if (
|
||||||
|
node.tmp.x === node.position[0] &&
|
||||||
|
node.tmp.y === node.position[1]
|
||||||
|
) {
|
||||||
delete node.tmp.x;
|
delete node.tmp.x;
|
||||||
delete node.tmp.y;
|
delete node.tmp.y;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
node.tmp.ref.style.setProperty("--nx", `${node.position.x * 10}px`);
|
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
|
||||||
node.tmp.ref.style.setProperty("--ny", `${node.position.y * 10}px`);
|
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
|
||||||
node.tmp.mesh.position.x = node.position.x + 10;
|
node.tmp.mesh.position.x = node.position[0] + 10;
|
||||||
node.tmp.mesh.position.z =
|
node.tmp.mesh.position.z =
|
||||||
node.position.y + getNodeHeight(node.type) / 2;
|
node.position[1] + getNodeHeight(node.type) / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,10 +118,10 @@
|
|||||||
const height = getNodeHeight(node.type);
|
const height = getNodeHeight(node.type);
|
||||||
const width = 20;
|
const width = 20;
|
||||||
return (
|
return (
|
||||||
node.position.x > cameraBounds[0] - width &&
|
node.position[0] > cameraBounds[0] - width &&
|
||||||
node.position.x < cameraBounds[1] &&
|
node.position[0] < cameraBounds[1] &&
|
||||||
node.position.y > cameraBounds[2] - height &&
|
node.position[1] > cameraBounds[2] - height &&
|
||||||
node.position.y < cameraBounds[3]
|
node.position[1] < cameraBounds[3]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -141,8 +146,8 @@
|
|||||||
event.clientY,
|
event.clientY,
|
||||||
);
|
);
|
||||||
for (const node of $nodes.values()) {
|
for (const node of $nodes.values()) {
|
||||||
const x = node.position.x;
|
const x = node.position[0];
|
||||||
const y = node.position.y;
|
const y = node.position[1];
|
||||||
const height = getNodeHeight(node.type);
|
const height = getNodeHeight(node.type);
|
||||||
if (downX > x && downX < x + 20 && downY > y && downY < y + height) {
|
if (downX > x && downX < x + 20 && downY > y && downY < y + height) {
|
||||||
clickedNodeId = node.id;
|
clickedNodeId = node.id;
|
||||||
@ -213,14 +218,14 @@
|
|||||||
): [number, number] {
|
): [number, number] {
|
||||||
if (typeof index === "number") {
|
if (typeof index === "number") {
|
||||||
return [
|
return [
|
||||||
(node?.tmp?.x ?? node.position.x) + 20,
|
(node?.tmp?.x ?? node.position[0]) + 20,
|
||||||
(node?.tmp?.y ?? node.position.y) + 2.5 + 10 * index,
|
(node?.tmp?.y ?? node.position[1]) + 2.5 + 10 * index,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index);
|
const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index);
|
||||||
return [
|
return [
|
||||||
node?.tmp?.x ?? node.position.x,
|
node?.tmp?.x ?? node.position[0],
|
||||||
(node?.tmp?.y ?? node.position.y) + 10 + 10 * _index,
|
(node?.tmp?.y ?? node.position[1]) + 10 + 10 * _index,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,8 +278,8 @@
|
|||||||
const y2 = Math.max(mouseD[1], mousePosition[1]);
|
const y2 = Math.max(mouseD[1], mousePosition[1]);
|
||||||
for (const node of $nodes.values()) {
|
for (const node of $nodes.values()) {
|
||||||
if (!node?.tmp) continue;
|
if (!node?.tmp) continue;
|
||||||
const x = node.position.x;
|
const x = node.position[0];
|
||||||
const y = node.position.y;
|
const y = node.position[1];
|
||||||
const height = getNodeHeight(node.type);
|
const height = getNodeHeight(node.type);
|
||||||
if (x > x1 - 20 && x < x2 && y > y1 - height && y < y2) {
|
if (x > x1 - 20 && x < x2 && y > y1 - height && y < y2) {
|
||||||
$selectedNodes?.add(node.id);
|
$selectedNodes?.add(node.id);
|
||||||
@ -420,15 +425,15 @@
|
|||||||
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[0];
|
||||||
node.tmp.downY = node.position.y;
|
node.tmp.downY = node.position[1];
|
||||||
if ($selectedNodes) {
|
if ($selectedNodes) {
|
||||||
for (const nodeId of $selectedNodes) {
|
for (const nodeId of $selectedNodes) {
|
||||||
const n = graph.getNode(nodeId);
|
const n = graph.getNode(nodeId);
|
||||||
if (!n) continue;
|
if (!n) continue;
|
||||||
n.tmp = n.tmp || {};
|
n.tmp = n.tmp || {};
|
||||||
n.tmp.downX = n.position.x;
|
n.tmp.downX = n.position[0];
|
||||||
n.tmp.downY = n.position.y;
|
n.tmp.downY = n.position[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -438,6 +443,11 @@
|
|||||||
document.activeElement === document.body ||
|
document.activeElement === document.body ||
|
||||||
document?.activeElement?.id === "graph";
|
document?.activeElement?.id === "graph";
|
||||||
|
|
||||||
|
if (event.key === "l") {
|
||||||
|
const activeNode = graph.getNode($activeNodeId);
|
||||||
|
console.log(activeNode);
|
||||||
|
}
|
||||||
|
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
$activeNodeId = -1;
|
$activeNodeId = -1;
|
||||||
$selectedNodes?.clear();
|
$selectedNodes?.clear();
|
||||||
@ -445,14 +455,18 @@
|
|||||||
(document.activeElement as HTMLElement)?.blur();
|
(document.activeElement as HTMLElement)?.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.key === "A" && event.shiftKey) {
|
||||||
|
addMenuPosition = [mousePosition[0], mousePosition[1]];
|
||||||
|
}
|
||||||
|
|
||||||
if (event.key === ".") {
|
if (event.key === ".") {
|
||||||
const average = [0, 0];
|
const average = [0, 0];
|
||||||
for (const node of $nodes.values()) {
|
for (const node of $nodes.values()) {
|
||||||
average[0] += node.position.x;
|
average[0] += node.position[0];
|
||||||
average[1] += node.position.y;
|
average[1] += node.position[1];
|
||||||
}
|
}
|
||||||
average[0] /= $nodes.size;
|
average[0] = average[0] ? average[0] / $nodes.size : 0;
|
||||||
average[1] /= $nodes.size;
|
average[1] = average[1] ? average[1] / $nodes.size : 0;
|
||||||
|
|
||||||
const camX = cameraPosition[0];
|
const camX = cameraPosition[0];
|
||||||
const camY = cameraPosition[1];
|
const camY = cameraPosition[1];
|
||||||
@ -466,6 +480,7 @@
|
|||||||
lerp(camY, average[1], ease(a)),
|
lerp(camY, average[1], ease(a)),
|
||||||
lerp(camZ, 2, ease(a)),
|
lerp(camZ, 2, ease(a)),
|
||||||
);
|
);
|
||||||
|
if (mouseDown) return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,12 +553,12 @@
|
|||||||
activeNode.tmp = activeNode.tmp || {};
|
activeNode.tmp = activeNode.tmp || {};
|
||||||
activeNode.tmp.isMoving = false;
|
activeNode.tmp.isMoving = false;
|
||||||
const snapLevel = getSnapLevel();
|
const snapLevel = getSnapLevel();
|
||||||
activeNode.position.x = snapToGrid(
|
activeNode.position[0] = snapToGrid(
|
||||||
activeNode?.tmp?.x ?? activeNode.position.x,
|
activeNode?.tmp?.x ?? activeNode.position[0],
|
||||||
5 / snapLevel,
|
5 / snapLevel,
|
||||||
);
|
);
|
||||||
activeNode.position.y = snapToGrid(
|
activeNode.position[1] = snapToGrid(
|
||||||
activeNode?.tmp?.y ?? activeNode.position.y,
|
activeNode?.tmp?.y ?? activeNode.position[1],
|
||||||
5 / snapLevel,
|
5 / snapLevel,
|
||||||
);
|
);
|
||||||
const nodes = [
|
const nodes = [
|
||||||
@ -551,8 +566,8 @@
|
|||||||
] as NodeType[];
|
] as NodeType[];
|
||||||
|
|
||||||
const vec = [
|
const vec = [
|
||||||
activeNode.position.x - (activeNode?.tmp.x || 0),
|
activeNode.position[0] - (activeNode?.tmp.x || 0),
|
||||||
activeNode.position.y - (activeNode?.tmp.y || 0),
|
activeNode.position[1] - (activeNode?.tmp.y || 0),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
@ -560,8 +575,8 @@
|
|||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
const { x, y } = node.tmp;
|
const { x, y } = node.tmp;
|
||||||
if (x !== undefined && y !== undefined) {
|
if (x !== undefined && y !== undefined) {
|
||||||
node.position.x = x + vec[0];
|
node.position[0] = x + vec[0];
|
||||||
node.position.y = y + vec[1];
|
node.position[1] = y + vec[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes.push(activeNode);
|
nodes.push(activeNode);
|
||||||
@ -572,8 +587,8 @@
|
|||||||
node.tmp["x"] !== undefined &&
|
node.tmp["x"] !== undefined &&
|
||||||
node.tmp["y"] !== undefined
|
node.tmp["y"] !== undefined
|
||||||
) {
|
) {
|
||||||
node.tmp.x = lerp(node.tmp.x, node.position.x, a);
|
node.tmp.x = lerp(node.tmp.x, node.position[0], a);
|
||||||
node.tmp.y = lerp(node.tmp.y, node.position.y, a);
|
node.tmp.y = lerp(node.tmp.y, node.position[1], a);
|
||||||
updateNodePosition(node);
|
updateNodePosition(node);
|
||||||
if (node?.tmp?.isMoving) {
|
if (node?.tmp?.isMoving) {
|
||||||
return false;
|
return false;
|
||||||
@ -627,6 +642,7 @@
|
|||||||
$possibleSockets = [];
|
$possibleSockets = [];
|
||||||
$possibleSocketIds = null;
|
$possibleSocketIds = null;
|
||||||
$hoveredSocket = null;
|
$hoveredSocket = null;
|
||||||
|
addMenuPosition = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@ -669,12 +685,17 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $status === "idle"}
|
{#if $status === "idle"}
|
||||||
|
{#if addMenuPosition}
|
||||||
|
<AddMenu bind:position={addMenuPosition} {graph} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if $activeSocket}
|
{#if $activeSocket}
|
||||||
<FloatingEdge
|
<FloatingEdge
|
||||||
from={{ x: $activeSocket.position[0], y: $activeSocket.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}
|
||||||
|
|
||||||
{#key $graphId}
|
{#key $graphId}
|
||||||
<GraphView {nodes} {edges} {cameraPosition} />
|
<GraphView {nodes} {edges} {cameraPosition} />
|
||||||
{/key}
|
{/key}
|
||||||
|
@ -29,14 +29,14 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
for (const node of $nodes.values()) {
|
for (const node of $nodes.values()) {
|
||||||
if (node?.tmp?.ref) {
|
if (node?.tmp?.ref) {
|
||||||
node.tmp.ref.style.setProperty("--nx", `${node.position.x * 10}px`);
|
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
|
||||||
node.tmp.ref.style.setProperty("--ny", `${node.position.y * 10}px`);
|
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each $edges as edge (edge[0].id + edge[2].id + edge[3])}
|
{#each $edges as edge (`${edge[0].id}-${edge[1]}-${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
|
||||||
|
@ -11,7 +11,6 @@ export const hoveredSocket: Writable<Socket | null> = writable(null);
|
|||||||
export const possibleSockets: Writable<Socket[]> = writable([]);
|
export const possibleSockets: Writable<Socket[]> = writable([]);
|
||||||
export const possibleSocketIds: Writable<Set<string> | null> = writable(null);
|
export const possibleSocketIds: Writable<Set<string> | null> = writable(null);
|
||||||
|
|
||||||
|
|
||||||
export const colors = writable({
|
export const colors = writable({
|
||||||
backgroundColorDarker: new Color().setStyle("#101010"),
|
backgroundColorDarker: new Color().setStyle("#101010"),
|
||||||
backgroundColor: new Color().setStyle("#151515"),
|
backgroundColor: new Color().setStyle("#151515"),
|
||||||
@ -38,13 +37,7 @@ if (browser) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
globalThis["updateColors"] = updateColors;
|
|
||||||
|
|
||||||
body.addEventListener("transitionstart", () => {
|
body.addEventListener("transitionstart", () => {
|
||||||
updateColors();
|
updateColors();
|
||||||
})
|
})
|
||||||
window.onload = () => {
|
|
||||||
updateColors();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -53,8 +53,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<T.Mesh
|
<T.Mesh
|
||||||
position.x={node.position.x + 10}
|
position.x={node.position[0] + 10}
|
||||||
position.z={node.position.y + height / 2}
|
position.z={node.position[1] + height / 2}
|
||||||
position.y={0.8}
|
position.y={0.8}
|
||||||
rotation.x={-Math.PI / 2}
|
rotation.x={-Math.PI / 2}
|
||||||
bind:ref={meshRef}
|
bind:ref={meshRef}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { writable, type Writable } from "svelte/store";
|
import { writable, type Writable } from "svelte/store";
|
||||||
import { type Graph, type Node, type Edge, type Socket, type NodeRegistry, type RuntimeExecutor } from "./types";
|
import { type Graph, type Node, type Edge, type Socket, type NodeRegistry, type RuntimeExecutor, } from "./types";
|
||||||
import { HistoryManager } from "./history-manager";
|
import { HistoryManager } from "./history-manager";
|
||||||
import * as templates from "./graphs";
|
import * as templates from "./graphs";
|
||||||
import EventEmitter from "./helpers/EventEmitter";
|
import EventEmitter from "./helpers/EventEmitter";
|
||||||
@ -35,13 +35,13 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
}
|
}
|
||||||
this.inputSockets.set(s);
|
this.inputSockets.set(s);
|
||||||
});
|
});
|
||||||
this.execute = throttle(() => this._execute(), 100);
|
this.execute = throttle(() => this._execute(), 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(): Graph {
|
serialize(): Graph {
|
||||||
const nodes = Array.from(this._nodes.values()).map(node => ({
|
const nodes = Array.from(this._nodes.values()).map(node => ({
|
||||||
id: node.id,
|
id: node.id,
|
||||||
position: { x: node.position.x, y: node.position.y },
|
position: node.position,
|
||||||
type: node.type,
|
type: node.type,
|
||||||
props: node.props,
|
props: node.props,
|
||||||
}));
|
}));
|
||||||
@ -58,13 +58,18 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
console.log(`Execution took ${end - start}ms -> ${result}`);
|
console.log(`Execution took ${end - start}ms -> ${result}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNodeTypes() {
|
||||||
|
return this.nodeRegistry.getAllNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private _init(graph: Graph) {
|
private _init(graph: Graph) {
|
||||||
const nodes = new Map(graph.nodes.map(node => {
|
const nodes = new Map(graph.nodes.map(node => {
|
||||||
const nodeType = this.nodeRegistry.getNode(node.type);
|
const nodeType = this.nodeRegistry.getNode(node.type);
|
||||||
if (nodeType) {
|
if (nodeType) {
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = {
|
||||||
node.tmp.type = nodeType;
|
type: nodeType
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return [node.id, node]
|
return [node.id, node]
|
||||||
}));
|
}));
|
||||||
@ -177,6 +182,29 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
nodes.delete(node.id);
|
nodes.delete(node.id);
|
||||||
return nodes;
|
return nodes;
|
||||||
});
|
});
|
||||||
|
this.execute()
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createNodeId() {
|
||||||
|
return Math.max(...this.getAllNodes().map(n => n.id), 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
createNode({ type, position }: { type: string, position: [number, number] }) {
|
||||||
|
|
||||||
|
const nodeType = this.nodeRegistry.getNode(type);
|
||||||
|
if (!nodeType) {
|
||||||
|
console.error(`Node type not found: ${type}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node: Node = { id: this.createNodeId(), type, position, tmp: { type: nodeType } };
|
||||||
|
|
||||||
|
this.nodes.update((nodes) => {
|
||||||
|
nodes.set(node.id, node);
|
||||||
|
return nodes;
|
||||||
|
});
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +233,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
return [...edges.filter(e => e[2].id !== to.id || e[3] !== toSocket), [from, fromSocket, to, toSocket]];
|
return [...edges.filter(e => e[2].id !== to.id || e[3] !== toSocket), [from, fromSocket, to, toSocket]];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.execute();
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +245,13 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
getParentsOfNode(node: Node) {
|
getParentsOfNode(node: Node) {
|
||||||
const parents = [];
|
const parents = [];
|
||||||
const stack = node.tmp?.parents?.slice(0);
|
const stack = node.tmp?.parents?.slice(0);
|
||||||
|
|
||||||
|
|
||||||
while (stack?.length) {
|
while (stack?.length) {
|
||||||
|
if (parents.length > 1000000) {
|
||||||
|
console.log("Infinite loop detected")
|
||||||
|
break;
|
||||||
|
}
|
||||||
const parent = stack.pop();
|
const parent = stack.pop();
|
||||||
if (!parent) continue;
|
if (!parent) continue;
|
||||||
parents.push(parent);
|
parents.push(parent);
|
||||||
@ -287,6 +322,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
this.edges.update((edges) => {
|
this.edges.update((edges) => {
|
||||||
return edges.filter((e) => e[0].id !== id0 || e[1] !== sid0 || e[2].id !== id2 || e[3] !== sid2);
|
return edges.filter((e) => e[0].id !== id0 || e[1] !== sid0 || e[2].id !== id2 || e[3] !== sid2);
|
||||||
});
|
});
|
||||||
|
this.execute();
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +19,7 @@ export function grid(width: number, height: number) {
|
|||||||
tmp: {
|
tmp: {
|
||||||
visible: false,
|
visible: false,
|
||||||
},
|
},
|
||||||
position: {
|
position: [x * 30, y * 40],
|
||||||
x: x * 30,
|
|
||||||
y: y * 40,
|
|
||||||
},
|
|
||||||
props: i == 0 ? { value: 0 } : {},
|
props: i == 0 ? { value: 0 } : {},
|
||||||
type: i == 0 ? "input/float" : "math",
|
type: i == 0 ? "input/float" : "math",
|
||||||
});
|
});
|
||||||
@ -35,10 +32,7 @@ export function grid(width: number, height: number) {
|
|||||||
tmp: {
|
tmp: {
|
||||||
visible: false,
|
visible: false,
|
||||||
},
|
},
|
||||||
position: {
|
position: [width * 30, (height - 1) * 40],
|
||||||
x: width * 30,
|
|
||||||
y: (height - 1) * 40,
|
|
||||||
},
|
|
||||||
type: "output",
|
type: "output",
|
||||||
props: {},
|
props: {},
|
||||||
});
|
});
|
||||||
|
@ -6,12 +6,12 @@ export function tree(depth: number): Graph {
|
|||||||
{
|
{
|
||||||
id: 0,
|
id: 0,
|
||||||
type: "output",
|
type: "output",
|
||||||
position: { x: 0, y: 0 }
|
position: [0, 0]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
type: "math",
|
type: "math",
|
||||||
position: { x: -40, y: -10 }
|
position: [-40, -10]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -34,13 +34,13 @@ export function tree(depth: number): Graph {
|
|||||||
nodes.push({
|
nodes.push({
|
||||||
id: id0,
|
id: id0,
|
||||||
type: "math",
|
type: "math",
|
||||||
position: { x, y: y },
|
position: [x, y],
|
||||||
});
|
});
|
||||||
edges.push([id0, 0, parent, "a"]);
|
edges.push([id0, 0, parent, "a"]);
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: id1,
|
id: id1,
|
||||||
type: "math",
|
type: "math",
|
||||||
position: { x, y: y + 35 },
|
position: [x, y + 35],
|
||||||
});
|
});
|
||||||
edges.push([id1, 0, parent, "b"]);
|
edges.push([id1, 0, parent, "b"]);
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
// place files you want to import through the `$lib` alias in this folder.
|
|
@ -41,5 +41,8 @@ export class MemoryNodeRegistry implements NodeRegistry {
|
|||||||
getNode(id: string): NodeType | undefined {
|
getNode(id: string): NodeType | undefined {
|
||||||
return nodeTypes.find((nodeType) => nodeType.id === id);
|
return nodeTypes.find((nodeType) => nodeType.id === id);
|
||||||
}
|
}
|
||||||
|
getAllNodes(): NodeType[] {
|
||||||
|
return [...nodeTypes];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { NodeInput, NodeInputType } from "./inputs";
|
import type { NodeInput } from "./inputs";
|
||||||
export type { NodeInput } from "./inputs";
|
export type { NodeInput } from "./inputs";
|
||||||
|
|
||||||
export type Node = {
|
export type Node = {
|
||||||
@ -24,10 +24,7 @@ export type Node = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
lastModified?: string;
|
lastModified?: string;
|
||||||
},
|
},
|
||||||
position: {
|
position: [x: number, y: number]
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NodeType = {
|
export type NodeType = {
|
||||||
@ -49,6 +46,7 @@ export type Socket = {
|
|||||||
|
|
||||||
export interface NodeRegistry {
|
export interface NodeRegistry {
|
||||||
getNode: (id: string) => NodeType | undefined;
|
getNode: (id: string) => NodeType | undefined;
|
||||||
|
getAllNodes: () => NodeType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeExecutor {
|
export interface RuntimeExecutor {
|
||||||
|
Loading…
Reference in New Issue
Block a user