feat: add help view
Some checks failed
Deploy to GitHub Pages / build_site (push) Failing after 1m20s
Some checks failed
Deploy to GitHub Pages / build_site (push) Failing after 1m20s
This commit is contained in:
parent
d06b33f508
commit
cafe9bff84
@ -24,7 +24,7 @@
|
|||||||
.box-selection {
|
.box-selection {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border: solid 0.2px var(--outline);
|
border: solid 2px var(--outline);
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
83
app/src/lib/graph-interface/HelpView.svelte
Normal file
83
app/src/lib/graph-interface/HelpView.svelte
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Node, NodeRegistry } from "@nodes/types";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
let mx = 0;
|
||||||
|
let my = 0;
|
||||||
|
|
||||||
|
let node: Node | undefined = undefined;
|
||||||
|
let input: string | undefined = undefined;
|
||||||
|
|
||||||
|
let wrapper: HTMLDivElement;
|
||||||
|
|
||||||
|
export let registry: NodeRegistry;
|
||||||
|
|
||||||
|
function handleMouseOver(ev: MouseEvent) {
|
||||||
|
let target = ev.target as HTMLElement | null;
|
||||||
|
mx = ev.clientX;
|
||||||
|
my = ev.clientY;
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
const closest = target.closest("[data-node-type]");
|
||||||
|
|
||||||
|
if (!closest) {
|
||||||
|
node = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nodeType = closest.getAttribute("data-node-type");
|
||||||
|
let nodeInput = closest.getAttribute("data-node-input");
|
||||||
|
|
||||||
|
if (!nodeType) {
|
||||||
|
node = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node = registry.getNode(nodeType);
|
||||||
|
input = nodeInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
wrapper?.parentElement?.setAttribute("style", "cursor:help !important");
|
||||||
|
return () => {
|
||||||
|
wrapper?.parentElement?.style.removeProperty("cursor");
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:mousemove={handleMouseOver} />
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="help-wrapper"
|
||||||
|
class:visible={node}
|
||||||
|
style="--my:{my}px; --mx:{mx}px;"
|
||||||
|
bind:this={wrapper}
|
||||||
|
>
|
||||||
|
{#if node}
|
||||||
|
{#if input}
|
||||||
|
{input}
|
||||||
|
{:else}
|
||||||
|
{node.id}
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.help-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translate(var(--mx), var(--my));
|
||||||
|
background: red;
|
||||||
|
padding: 10px;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 1px solid black;
|
||||||
|
z-index: 1000;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
@ -21,8 +21,6 @@
|
|||||||
bw = width / cameraPosition[2];
|
bw = width / cameraPosition[2];
|
||||||
bh = height / cameraPosition[2];
|
bh = height / cameraPosition[2];
|
||||||
}
|
}
|
||||||
$: backgroundColor = $colors["layer-0"];
|
|
||||||
$: lineColor = $colors["outline"];
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<T.Group
|
<T.Group
|
||||||
@ -54,8 +52,8 @@
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
uniforms.camPos.value={cameraPosition}
|
uniforms.camPos.value={cameraPosition}
|
||||||
uniforms.backgroundColor.value={backgroundColor}
|
uniforms.backgroundColor.value={$colors["layer-0"]}
|
||||||
uniforms.lineColor.value={lineColor}
|
uniforms.lineColor.value={$colors["outline"]}
|
||||||
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
||||||
uniforms.dimensions.value={[width, height]}
|
uniforms.dimensions.value={[width, height]}
|
||||||
/>
|
/>
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { animate, lerp, snapToGrid } from "../helpers/index.js";
|
import {
|
||||||
|
animate,
|
||||||
|
lerp,
|
||||||
|
snapToGrid as snapPointToGrid,
|
||||||
|
} from "../helpers/index.js";
|
||||||
import { Canvas } from "@threlte/core";
|
import { Canvas } from "@threlte/core";
|
||||||
import type { OrthographicCamera } from "three";
|
import type { OrthographicCamera } from "three";
|
||||||
import Background from "../background/Background.svelte";
|
import Background from "../background/Background.svelte";
|
||||||
@ -23,20 +27,21 @@
|
|||||||
import AddMenu from "../AddMenu.svelte";
|
import AddMenu from "../AddMenu.svelte";
|
||||||
import { createWasmWrapper } from "@nodes/utils";
|
import { createWasmWrapper } from "@nodes/utils";
|
||||||
|
|
||||||
export let graph: GraphManager;
|
import HelpView from "../HelpView.svelte";
|
||||||
|
|
||||||
export let settings = {
|
export let manager: GraphManager;
|
||||||
snapToGrid: true,
|
|
||||||
showGrid: true,
|
export let snapToGrid = true;
|
||||||
};
|
export let showGrid = true;
|
||||||
|
export let showHelp = false;
|
||||||
|
|
||||||
let keymap =
|
let keymap =
|
||||||
getContext<ReturnType<typeof createKeyMap>>("keymap") || createKeyMap([]);
|
getContext<ReturnType<typeof createKeyMap>>("keymap") || createKeyMap([]);
|
||||||
|
|
||||||
setContext("graphManager", graph);
|
setContext("graphManager", manager);
|
||||||
const status = graph.status;
|
const status = manager.status;
|
||||||
const nodes = graph.nodes;
|
const nodes = manager.nodes;
|
||||||
const edges = graph.edges;
|
const edges = manager.edges;
|
||||||
|
|
||||||
let wrapper: HTMLDivElement;
|
let wrapper: HTMLDivElement;
|
||||||
let rect: DOMRect;
|
let rect: DOMRect;
|
||||||
@ -114,7 +119,7 @@
|
|||||||
if (nodeTypeId in nodeHeightCache) {
|
if (nodeTypeId in nodeHeightCache) {
|
||||||
return nodeHeightCache[nodeTypeId];
|
return nodeHeightCache[nodeTypeId];
|
||||||
}
|
}
|
||||||
const node = graph.getNodeType(nodeTypeId);
|
const node = manager.getNodeType(nodeTypeId);
|
||||||
if (!node?.inputs) {
|
if (!node?.inputs) {
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
@ -185,13 +190,13 @@
|
|||||||
|
|
||||||
// remove existing edge
|
// remove existing edge
|
||||||
if (typeof index === "string") {
|
if (typeof index === "string") {
|
||||||
const edges = graph.getEdgesToNode(node);
|
const edges = manager.getEdgesToNode(node);
|
||||||
for (const edge of edges) {
|
for (const edge of edges) {
|
||||||
if (edge[3] === index) {
|
if (edge[3] === index) {
|
||||||
node = edge[0];
|
node = edge[0];
|
||||||
index = edge[1];
|
index = edge[1];
|
||||||
position = getSocketPosition(node, index);
|
position = getSocketPosition(node, index);
|
||||||
graph.removeEdge(edge);
|
manager.removeEdge(edge);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,7 +209,7 @@
|
|||||||
position,
|
position,
|
||||||
};
|
};
|
||||||
|
|
||||||
$possibleSockets = graph
|
$possibleSockets = manager
|
||||||
.getPossibleSockets($activeSocket)
|
.getPossibleSockets($activeSocket)
|
||||||
.map(([node, index]) => {
|
.map(([node, index]) => {
|
||||||
return {
|
return {
|
||||||
@ -315,7 +320,7 @@
|
|||||||
|
|
||||||
// here we are handling dragging of nodes
|
// here we are handling dragging of nodes
|
||||||
if ($activeNodeId !== -1 && mouseDownId !== -1) {
|
if ($activeNodeId !== -1 && mouseDownId !== -1) {
|
||||||
const node = graph.getNode($activeNodeId);
|
const node = manager.getNode($activeNodeId);
|
||||||
if (!node || event.buttons !== 1) return;
|
if (!node || event.buttons !== 1) return;
|
||||||
|
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
@ -328,9 +333,9 @@
|
|||||||
|
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
const snapLevel = getSnapLevel();
|
const snapLevel = getSnapLevel();
|
||||||
if (settings.snapToGrid) {
|
if (snapToGrid) {
|
||||||
newX = snapToGrid(newX, 5 / snapLevel);
|
newX = snapPointToGrid(newX, 5 / snapLevel);
|
||||||
newY = snapToGrid(newY, 5 / snapLevel);
|
newY = snapPointToGrid(newY, 5 / snapLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,7 +351,7 @@
|
|||||||
|
|
||||||
if ($selectedNodes?.size) {
|
if ($selectedNodes?.size) {
|
||||||
for (const nodeId of $selectedNodes) {
|
for (const nodeId of $selectedNodes) {
|
||||||
const n = graph.getNode(nodeId);
|
const n = manager.getNode(nodeId);
|
||||||
if (!n?.tmp) continue;
|
if (!n?.tmp) continue;
|
||||||
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
||||||
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
|
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
|
||||||
@ -441,10 +446,10 @@
|
|||||||
$activeNodeId = clickedNodeId;
|
$activeNodeId = clickedNodeId;
|
||||||
// select the node
|
// select the node
|
||||||
} else if (event.shiftKey) {
|
} else if (event.shiftKey) {
|
||||||
const activeNode = graph.getNode($activeNodeId);
|
const activeNode = manager.getNode($activeNodeId);
|
||||||
const newNode = graph.getNode(clickedNodeId);
|
const newNode = manager.getNode(clickedNodeId);
|
||||||
if (activeNode && newNode) {
|
if (activeNode && newNode) {
|
||||||
const edge = graph.getNodesBetween(activeNode, newNode);
|
const edge = manager.getNodesBetween(activeNode, newNode);
|
||||||
if (edge) {
|
if (edge) {
|
||||||
const selected = new Set(edge.map((n) => n.id));
|
const selected = new Set(edge.map((n) => n.id));
|
||||||
selected.add(clickedNodeId);
|
selected.add(clickedNodeId);
|
||||||
@ -459,14 +464,14 @@
|
|||||||
} else if (event.ctrlKey) {
|
} else if (event.ctrlKey) {
|
||||||
boxSelection = true;
|
boxSelection = true;
|
||||||
}
|
}
|
||||||
const node = graph.getNode($activeNodeId);
|
const node = manager.getNode($activeNodeId);
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
node.tmp.downX = node.position[0];
|
node.tmp.downX = node.position[0];
|
||||||
node.tmp.downY = node.position[1];
|
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 = manager.getNode(nodeId);
|
||||||
if (!n) continue;
|
if (!n) continue;
|
||||||
n.tmp = n.tmp || {};
|
n.tmp = n.tmp || {};
|
||||||
n.tmp.downX = n.position[0];
|
n.tmp.downX = n.position[0];
|
||||||
@ -478,10 +483,10 @@
|
|||||||
function copyNodes() {
|
function copyNodes() {
|
||||||
if ($activeNodeId === -1 && !$selectedNodes?.size) return;
|
if ($activeNodeId === -1 && !$selectedNodes?.size) return;
|
||||||
let _nodes = [$activeNodeId, ...($selectedNodes?.values() || [])]
|
let _nodes = [$activeNodeId, ...($selectedNodes?.values() || [])]
|
||||||
.map((id) => graph.getNode(id))
|
.map((id) => manager.getNode(id))
|
||||||
.filter(Boolean) as Node[];
|
.filter(Boolean) as Node[];
|
||||||
|
|
||||||
const _edges = graph.getEdgesBetweenNodes(_nodes);
|
const _edges = manager.getEdgesBetweenNodes(_nodes);
|
||||||
|
|
||||||
_nodes = _nodes.map((_node) => {
|
_nodes = _nodes.map((_node) => {
|
||||||
const node = globalThis.structuredClone({
|
const node = globalThis.structuredClone({
|
||||||
@ -512,7 +517,7 @@
|
|||||||
})
|
})
|
||||||
.filter(Boolean) as Node[];
|
.filter(Boolean) as Node[];
|
||||||
|
|
||||||
const newNodes = graph.createGraph(_nodes, clipboard.edges);
|
const newNodes = manager.createGraph(_nodes, clipboard.edges);
|
||||||
$selectedNodes = new Set(newNodes.map((n) => n.id));
|
$selectedNodes = new Set(newNodes.map((n) => n.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,15 +527,23 @@
|
|||||||
key: "l",
|
key: "l",
|
||||||
description: "Select linked nodes",
|
description: "Select linked nodes",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const activeNode = graph.getNode($activeNodeId);
|
const activeNode = manager.getNode($activeNodeId);
|
||||||
if (activeNode) {
|
if (activeNode) {
|
||||||
const nodes = graph.getLinkedNodes(activeNode);
|
const nodes = manager.getLinkedNodes(activeNode);
|
||||||
$selectedNodes = new Set(nodes.map((n) => n.id));
|
$selectedNodes = new Set(nodes.map((n) => n.id));
|
||||||
}
|
}
|
||||||
console.log(activeNode);
|
console.log(activeNode);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "?",
|
||||||
|
description: "Toggle Help",
|
||||||
|
callback: () => {
|
||||||
|
showHelp = !showHelp;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "c",
|
key: "c",
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
@ -612,7 +625,7 @@
|
|||||||
description: "Undo",
|
description: "Undo",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (!isBodyFocused()) return;
|
if (!isBodyFocused()) return;
|
||||||
graph.undo();
|
manager.undo();
|
||||||
for (const node of $nodes.values()) {
|
for (const node of $nodes.values()) {
|
||||||
updateNodePosition(node);
|
updateNodePosition(node);
|
||||||
}
|
}
|
||||||
@ -625,7 +638,7 @@
|
|||||||
description: "Redo",
|
description: "Redo",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (!isBodyFocused()) return;
|
if (!isBodyFocused()) return;
|
||||||
graph.redo();
|
manager.redo();
|
||||||
for (const node of $nodes.values()) {
|
for (const node of $nodes.values()) {
|
||||||
updateNodePosition(node);
|
updateNodePosition(node);
|
||||||
}
|
}
|
||||||
@ -637,30 +650,30 @@
|
|||||||
description: "Delete selected nodes",
|
description: "Delete selected nodes",
|
||||||
callback: (event) => {
|
callback: (event) => {
|
||||||
if (!isBodyFocused()) return;
|
if (!isBodyFocused()) return;
|
||||||
graph.startUndoGroup();
|
manager.startUndoGroup();
|
||||||
if ($activeNodeId !== -1) {
|
if ($activeNodeId !== -1) {
|
||||||
const node = graph.getNode($activeNodeId);
|
const node = manager.getNode($activeNodeId);
|
||||||
if (node) {
|
if (node) {
|
||||||
graph.removeNode(node, { restoreEdges: event.ctrlKey });
|
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||||
$activeNodeId = -1;
|
$activeNodeId = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($selectedNodes) {
|
if ($selectedNodes) {
|
||||||
for (const nodeId of $selectedNodes) {
|
for (const nodeId of $selectedNodes) {
|
||||||
const node = graph.getNode(nodeId);
|
const node = manager.getNode(nodeId);
|
||||||
if (node) {
|
if (node) {
|
||||||
graph.removeNode(node, { restoreEdges: event.ctrlKey });
|
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$selectedNodes.clear();
|
$selectedNodes.clear();
|
||||||
$selectedNodes = $selectedNodes;
|
$selectedNodes = $selectedNodes;
|
||||||
}
|
}
|
||||||
graph.saveUndoGroup();
|
manager.saveUndoGroup();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleMouseUp(event: MouseEvent) {
|
function handleMouseUp(event: MouseEvent) {
|
||||||
const activeNode = graph.getNode($activeNodeId);
|
const activeNode = manager.getNode($activeNodeId);
|
||||||
|
|
||||||
const clickedNodeId = getNodeIdFromEvent(event);
|
const clickedNodeId = getNodeIdFromEvent(event);
|
||||||
|
|
||||||
@ -677,13 +690,13 @@
|
|||||||
if (activeNode?.tmp?.isMoving) {
|
if (activeNode?.tmp?.isMoving) {
|
||||||
activeNode.tmp = activeNode.tmp || {};
|
activeNode.tmp = activeNode.tmp || {};
|
||||||
activeNode.tmp.isMoving = false;
|
activeNode.tmp.isMoving = false;
|
||||||
if (settings.snapToGrid) {
|
if (snapToGrid) {
|
||||||
const snapLevel = getSnapLevel();
|
const snapLevel = getSnapLevel();
|
||||||
activeNode.position[0] = snapToGrid(
|
activeNode.position[0] = snapPointToGrid(
|
||||||
activeNode?.tmp?.x ?? activeNode.position[0],
|
activeNode?.tmp?.x ?? activeNode.position[0],
|
||||||
5 / snapLevel,
|
5 / snapLevel,
|
||||||
);
|
);
|
||||||
activeNode.position[1] = snapToGrid(
|
activeNode.position[1] = snapPointToGrid(
|
||||||
activeNode?.tmp?.y ?? activeNode.position[1],
|
activeNode?.tmp?.y ?? activeNode.position[1],
|
||||||
5 / snapLevel,
|
5 / snapLevel,
|
||||||
);
|
);
|
||||||
@ -692,7 +705,9 @@
|
|||||||
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1];
|
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1];
|
||||||
}
|
}
|
||||||
const nodes = [
|
const nodes = [
|
||||||
...[...($selectedNodes?.values() || [])].map((id) => graph.getNode(id)),
|
...[...($selectedNodes?.values() || [])].map((id) =>
|
||||||
|
manager.getNode(id),
|
||||||
|
),
|
||||||
] as NodeType[];
|
] as NodeType[];
|
||||||
|
|
||||||
const vec = [
|
const vec = [
|
||||||
@ -728,13 +743,13 @@
|
|||||||
|
|
||||||
$edges = $edges;
|
$edges = $edges;
|
||||||
});
|
});
|
||||||
graph.save();
|
manager.save();
|
||||||
} else if ($hoveredSocket && $activeSocket) {
|
} else if ($hoveredSocket && $activeSocket) {
|
||||||
if (
|
if (
|
||||||
typeof $hoveredSocket.index === "number" &&
|
typeof $hoveredSocket.index === "number" &&
|
||||||
typeof $activeSocket.index === "string"
|
typeof $activeSocket.index === "string"
|
||||||
) {
|
) {
|
||||||
graph.createEdge(
|
manager.createEdge(
|
||||||
$hoveredSocket.node,
|
$hoveredSocket.node,
|
||||||
$hoveredSocket.index || 0,
|
$hoveredSocket.index || 0,
|
||||||
$activeSocket.node,
|
$activeSocket.node,
|
||||||
@ -744,14 +759,14 @@
|
|||||||
typeof $activeSocket.index == "number" &&
|
typeof $activeSocket.index == "number" &&
|
||||||
typeof $hoveredSocket.index === "string"
|
typeof $hoveredSocket.index === "string"
|
||||||
) {
|
) {
|
||||||
graph.createEdge(
|
manager.createEdge(
|
||||||
$activeSocket.node,
|
$activeSocket.node,
|
||||||
$activeSocket.index || 0,
|
$activeSocket.index || 0,
|
||||||
$hoveredSocket.node,
|
$hoveredSocket.node,
|
||||||
$hoveredSocket.index,
|
$hoveredSocket.index,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
graph.save();
|
manager.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if camera moved
|
// check if camera moved
|
||||||
@ -808,8 +823,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pos = projectScreenToWorld(mx, my);
|
const pos = projectScreenToWorld(mx, my);
|
||||||
graph.registry.load([nodeId]).then(() => {
|
manager.registry.load([nodeId]).then(() => {
|
||||||
graph.createNode({
|
manager.createNode({
|
||||||
type: nodeId,
|
type: nodeId,
|
||||||
props,
|
props,
|
||||||
position: pos,
|
position: pos,
|
||||||
@ -819,7 +834,7 @@
|
|||||||
const files = event.dataTransfer.files;
|
const files = event.dataTransfer.files;
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const buffer = e.target?.result;
|
const buffer = e.target?.result as Buffer;
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
const wrapper = createWasmWrapper(buffer);
|
const wrapper = createWasmWrapper(buffer);
|
||||||
const definition = wrapper.get_definition();
|
const definition = wrapper.get_definition();
|
||||||
@ -885,10 +900,13 @@
|
|||||||
/>
|
/>
|
||||||
<label for="drop-zone" />
|
<label for="drop-zone" />
|
||||||
|
|
||||||
|
{#if showHelp}
|
||||||
|
<HelpView registry={manager.registry} />
|
||||||
|
{/if}
|
||||||
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
|
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
|
||||||
<Camera bind:camera position={cameraPosition} />
|
<Camera bind:camera position={cameraPosition} />
|
||||||
|
|
||||||
{#if settings?.showGrid !== false}
|
{#if showGrid !== false}
|
||||||
<Background {cameraPosition} {maxZoom} {minZoom} {width} {height} />
|
<Background {cameraPosition} {maxZoom} {minZoom} {width} {height} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -906,7 +924,7 @@
|
|||||||
|
|
||||||
{#if $status === "idle"}
|
{#if $status === "idle"}
|
||||||
{#if addMenuPosition}
|
{#if addMenuPosition}
|
||||||
<AddMenu bind:position={addMenuPosition} {graph} />
|
<AddMenu bind:position={addMenuPosition} graph={manager} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $activeSocket}
|
{#if $activeSocket}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
export let showGrid = false;
|
export let showGrid = false;
|
||||||
export let snapToGrid = false;
|
export let snapToGrid = false;
|
||||||
|
export let showHelp = false;
|
||||||
|
|
||||||
export let settingTypes = {};
|
export let settingTypes = {};
|
||||||
|
|
||||||
@ -55,4 +56,4 @@
|
|||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<GraphEl graph={manager} settings={{ showGrid, snapToGrid }} />
|
<GraphEl {manager} bind:showGrid bind:snapToGrid bind:showHelp />
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
class:selected={isSelected}
|
class:selected={isSelected}
|
||||||
class:out-of-view={!inView}
|
class:out-of-view={!inView}
|
||||||
data-node-id={node.id}
|
data-node-id={node.id}
|
||||||
|
data-node-type={node.type}
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
>
|
>
|
||||||
<NodeHeader {node} />
|
<NodeHeader {node} />
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper" data-node-id={node.id}>
|
<div class="wrapper" data-node-id={node.id} data-node-type={node.type}>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{node.type.split("/").pop()}
|
{node.type.split("/").pop()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,6 +73,8 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
|
data-node-type={node.type}
|
||||||
|
data-node-input={id}
|
||||||
class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)}
|
class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)}
|
||||||
>
|
>
|
||||||
{#key id && graphId}
|
{#key id && graphId}
|
||||||
@ -87,12 +89,14 @@
|
|||||||
|
|
||||||
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
|
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
|
||||||
<div
|
<div
|
||||||
|
data-node-socket
|
||||||
class="large target"
|
class="large target"
|
||||||
on:mousedown={handleMouseDown}
|
on:mousedown={handleMouseDown}
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
data-node-socket
|
||||||
class="small target"
|
class="small target"
|
||||||
on:mousedown={handleMouseDown}
|
on:mousedown={handleMouseDown}
|
||||||
role="button"
|
role="button"
|
||||||
@ -187,6 +191,8 @@
|
|||||||
|
|
||||||
.content.disabled {
|
.content.disabled {
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
.content.disabled > * {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,10 +10,12 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
status: "loading" | "ready" | "error" = "loading";
|
status: "loading" | "ready" | "error" = "loading";
|
||||||
private nodes: Map<string, NodeDefinition> = new Map();
|
private nodes: Map<string, NodeDefinition> = new Map();
|
||||||
|
|
||||||
|
fetch: typeof fetch = globalThis.fetch.bind(globalThis);
|
||||||
|
|
||||||
constructor(private url: string) { }
|
constructor(private url: string) { }
|
||||||
|
|
||||||
async fetchUsers() {
|
async fetchUsers() {
|
||||||
const response = await fetch(`${this.url}/nodes/users.json`);
|
const response = await this.fetch(`${this.url}/nodes/users.json`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to load users`);
|
throw new Error(`Failed to load users`);
|
||||||
}
|
}
|
||||||
@ -21,7 +23,7 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fetchUser(userId: `${string}`) {
|
async fetchUser(userId: `${string}`) {
|
||||||
const response = await fetch(`${this.url}/nodes/${userId}.json`);
|
const response = await this.fetch(`${this.url}/nodes/${userId}.json`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to load user ${userId}`);
|
throw new Error(`Failed to load user ${userId}`);
|
||||||
}
|
}
|
||||||
@ -29,23 +31,15 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fetchCollection(userCollectionId: `${string}/${string}`) {
|
async fetchCollection(userCollectionId: `${string}/${string}`) {
|
||||||
const response = await fetch(`${this.url}/nodes/${userCollectionId}.json`);
|
const response = await this.fetch(`${this.url}/nodes/${userCollectionId}.json`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to load collection ${userCollectionId}`);
|
throw new Error(`Failed to load collection ${userCollectionId}`);
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchNode(nodeId: `${string}/${string}/${string}`) {
|
|
||||||
const response = await fetch(`${this.url}/nodes/${nodeId}.wasm`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to load node wasm ${nodeId}`);
|
|
||||||
}
|
|
||||||
return response.arrayBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) {
|
async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) {
|
||||||
const response = await fetch(`${this.url}/nodes/${nodeId}.json`);
|
const response = await this.fetch(`${this.url}/nodes/${nodeId}.json`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to load node definition ${nodeId}`);
|
throw new Error(`Failed to load node definition ${nodeId}`);
|
||||||
}
|
}
|
||||||
@ -58,18 +52,22 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
const nodes = await Promise.all(nodeIds.map(async id => {
|
const nodes = await Promise.all(nodeIds.map(async id => {
|
||||||
|
|
||||||
if (this.nodes.has(id)) {
|
if (this.nodes.has(id)) {
|
||||||
return this.nodes.get(id);
|
return this.nodes.get(id)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
const wasmResponse = await this.fetchNode(id);
|
const response = await this.fetch(`${this.url}/nodes/${id}.wasm`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to load node wasm ${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
const wrapper = createWasmWrapper(wasmResponse);
|
const wasmBuffer = await response.arrayBuffer();
|
||||||
|
|
||||||
|
const wrapper = createWasmWrapper(wasmBuffer);
|
||||||
|
|
||||||
const definition = wrapper.get_definition();
|
const definition = wrapper.get_definition();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...definition,
|
...definition,
|
||||||
id,
|
|
||||||
execute: wrapper.execute
|
execute: wrapper.execute
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
@ -80,7 +78,10 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
|
|
||||||
const duration = performance.now() - a;
|
const duration = performance.now() - a;
|
||||||
|
|
||||||
log.log("loaded nodes in", duration, "ms");
|
log.group("loaded nodes in", duration, "ms");
|
||||||
|
log.info(nodeIds);
|
||||||
|
log.info(nodes);
|
||||||
|
log.groupEnd();
|
||||||
this.status = "ready";
|
this.status = "ready";
|
||||||
|
|
||||||
return nodes
|
return nodes
|
||||||
|
18
app/src/lib/remote-runtime-executor.ts
Normal file
18
app/src/lib/remote-runtime-executor.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { Graph, RuntimeExecutor } from "@nodes/types";
|
||||||
|
|
||||||
|
export class RemoteRuntimeExecutor implements RuntimeExecutor {
|
||||||
|
|
||||||
|
constructor(private url: string) { }
|
||||||
|
|
||||||
|
async execute(graph: Graph, settings: Record<string, any>): Promise<Int32Array> {
|
||||||
|
|
||||||
|
const res = await fetch(this.url, { method: "POST", body: JSON.stringify({ graph, settings }) });
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Failed to execute graph`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Int32Array(await res.arrayBuffer());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -47,12 +47,14 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
|
|
||||||
constructor(private registry: NodeRegistry, private cache?: RuntimeCache) { }
|
constructor(private registry: NodeRegistry, private cache?: RuntimeCache) { }
|
||||||
|
|
||||||
private getNodeDefinitions(graph: Graph) {
|
private async getNodeDefinitions(graph: Graph) {
|
||||||
|
|
||||||
if (this.registry.status !== "ready") {
|
if (this.registry.status !== "ready") {
|
||||||
throw new Error("Node registry is not ready");
|
throw new Error("Node registry is not ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.registry.load(graph.nodes.map(node => node.type));
|
||||||
|
|
||||||
const typeMap = new Map<string, NodeDefinition>();
|
const typeMap = new Map<string, NodeDefinition>();
|
||||||
for (const node of graph.nodes) {
|
for (const node of graph.nodes) {
|
||||||
if (!typeMap.has(node.type)) {
|
if (!typeMap.has(node.type)) {
|
||||||
@ -65,10 +67,10 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
return typeMap;
|
return typeMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private addMetaData(graph: Graph) {
|
private async addMetaData(graph: Graph) {
|
||||||
|
|
||||||
// First, lets check if all nodes have a definition
|
// First, lets check if all nodes have a definition
|
||||||
this.definitionMap = this.getNodeDefinitions(graph);
|
this.definitionMap = await this.getNodeDefinitions(graph);
|
||||||
|
|
||||||
const outputNode = graph.nodes.find(node => node.type.endsWith("/output"));
|
const outputNode = graph.nodes.find(node => node.type.endsWith("/output"));
|
||||||
if (!outputNode) {
|
if (!outputNode) {
|
||||||
@ -124,7 +126,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
return [outputNode, nodes] as const;
|
return [outputNode, nodes] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(graph: Graph, settings: Record<string, unknown>) {
|
async execute(graph: Graph, settings: Record<string, unknown>) {
|
||||||
|
|
||||||
this.perf?.startRun();
|
this.perf?.startRun();
|
||||||
|
|
||||||
@ -133,7 +135,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
let a = performance.now();
|
let a = performance.now();
|
||||||
|
|
||||||
// Then we add some metadata to the graph
|
// Then we add some metadata to the graph
|
||||||
const [outputNode, nodes] = this.addMetaData(graph);
|
const [outputNode, nodes] = await this.addMetaData(graph);
|
||||||
let b = performance.now();
|
let b = performance.now();
|
||||||
|
|
||||||
this.perf?.addPoint("metadata", b - a);
|
this.perf?.addPoint("metadata", b - a);
|
||||||
|
@ -5,6 +5,7 @@ export const AppSettings = localStore("node-settings", {
|
|||||||
showGrid: true,
|
showGrid: true,
|
||||||
showNodeGrid: true,
|
showNodeGrid: true,
|
||||||
snapToGrid: true,
|
snapToGrid: true,
|
||||||
|
showHelp: false,
|
||||||
wireframe: false,
|
wireframe: false,
|
||||||
showIndices: false,
|
showIndices: false,
|
||||||
showVertices: false,
|
showVertices: false,
|
||||||
@ -55,6 +56,11 @@ export const AppSettingTypes = {
|
|||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Snap to Grid",
|
label: "Snap to Grid",
|
||||||
value: true
|
value: true
|
||||||
|
},
|
||||||
|
showHelp: {
|
||||||
|
type: "boolean",
|
||||||
|
label: "Show Help",
|
||||||
|
value: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
debug: {
|
debug: {
|
||||||
|
@ -26,9 +26,11 @@
|
|||||||
import NestedSettings from "$lib/settings/panels/NestedSettings.svelte";
|
import NestedSettings from "$lib/settings/panels/NestedSettings.svelte";
|
||||||
import { createPerformanceStore } from "$lib/performance";
|
import { createPerformanceStore } from "$lib/performance";
|
||||||
import { type PerformanceData } from "$lib/performance/store";
|
import { type PerformanceData } from "$lib/performance/store";
|
||||||
|
import { RemoteRuntimeExecutor } from "$lib/remote-runtime-executor";
|
||||||
|
|
||||||
const nodeRegistry = new RemoteNodeRegistry("");
|
const nodeRegistry = new RemoteNodeRegistry("");
|
||||||
const workerRuntime = new WorkerRuntimeExecutor();
|
const workerRuntime = new WorkerRuntimeExecutor();
|
||||||
|
// const remoteRuntime = new RemoteRuntimeExecutor("/runtime");
|
||||||
|
|
||||||
let performanceData: PerformanceData;
|
let performanceData: PerformanceData;
|
||||||
let viewerPerformance = createPerformanceStore();
|
let viewerPerformance = createPerformanceStore();
|
||||||
@ -44,6 +46,8 @@
|
|||||||
? JSON.parse(localStorage.getItem("graph")!)
|
? JSON.parse(localStorage.getItem("graph")!)
|
||||||
: templates.plant;
|
: templates.plant;
|
||||||
|
|
||||||
|
console.log({ graph });
|
||||||
|
|
||||||
let manager: GraphManager;
|
let manager: GraphManager;
|
||||||
let managerStatus: Writable<"loading" | "error" | "idle">;
|
let managerStatus: Writable<"loading" | "error" | "idle">;
|
||||||
$: if (manager) {
|
$: if (manager) {
|
||||||
@ -75,6 +79,7 @@
|
|||||||
isWorking = true;
|
isWorking = true;
|
||||||
try {
|
try {
|
||||||
let a = performance.now();
|
let a = performance.now();
|
||||||
|
// res = await remoteRuntime.execute(_graph, _settings);
|
||||||
res = await workerRuntime.execute(_graph, _settings);
|
res = await workerRuntime.execute(_graph, _settings);
|
||||||
let b = performance.now();
|
let b = performance.now();
|
||||||
let perfData = await workerRuntime.getPerformanceData();
|
let perfData = await workerRuntime.getPerformanceData();
|
||||||
@ -120,9 +125,9 @@
|
|||||||
<Grid.Row>
|
<Grid.Row>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
<Viewer
|
<Viewer
|
||||||
centerCamera={$AppSettings.centerCamera}
|
|
||||||
result={res}
|
result={res}
|
||||||
perf={viewerPerformance}
|
perf={viewerPerformance}
|
||||||
|
centerCamera={$AppSettings.centerCamera}
|
||||||
/>
|
/>
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
@ -133,8 +138,9 @@
|
|||||||
bind:manager
|
bind:manager
|
||||||
bind:activeNode
|
bind:activeNode
|
||||||
bind:keymap
|
bind:keymap
|
||||||
showGrid={$AppSettings?.showNodeGrid}
|
showGrid={$AppSettings.showNodeGrid}
|
||||||
snapToGrid={$AppSettings?.snapToGrid}
|
snapToGrid={$AppSettings.snapToGrid}
|
||||||
|
bind:showHelp={$AppSettings.showHelp}
|
||||||
bind:settings={graphSettings}
|
bind:settings={graphSettings}
|
||||||
bind:settingTypes={graphSettingTypes}
|
bind:settingTypes={graphSettingTypes}
|
||||||
on:result={(ev) => handleResult(ev.detail, $graphSettings)}
|
on:result={(ev) => handleResult(ev.detail, $graphSettings)}
|
||||||
|
29
app/src/routes/runtime/+server.ts
Normal file
29
app/src/routes/runtime/+server.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import type { RequestHandler } from "./$types";
|
||||||
|
import { MemoryRuntimeExecutor } from "$lib/runtime-executor";
|
||||||
|
import { RemoteNodeRegistry } from "$lib/node-registry-client";
|
||||||
|
import { createPerformanceStore } from "$lib/performance";
|
||||||
|
|
||||||
|
const registry = new RemoteNodeRegistry("");
|
||||||
|
const runtime = new MemoryRuntimeExecutor(registry);
|
||||||
|
const performanceStore = createPerformanceStore();
|
||||||
|
runtime.perf = performanceStore;
|
||||||
|
|
||||||
|
|
||||||
|
export const POST: RequestHandler = async ({ request, fetch }) => {
|
||||||
|
|
||||||
|
const { graph, settings } = await request.json();
|
||||||
|
|
||||||
|
registry.fetch = fetch;
|
||||||
|
|
||||||
|
await registry.load(graph.nodes.map(node => node.type))
|
||||||
|
|
||||||
|
const res = await runtime.execute(graph, settings);
|
||||||
|
|
||||||
|
let headers: Record<string, string> = { "Content-Type": "application/octet-stream" };
|
||||||
|
if (runtime.perf) {
|
||||||
|
headers["performance"] = JSON.stringify(runtime.perf.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(res, { headers });
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"id": "max/plantarium/branches",
|
"id": "max/plantarium/branch",
|
||||||
"outputs": [
|
"outputs": [
|
||||||
"path"
|
"path"
|
||||||
],
|
],
|
||||||
|
@ -13,13 +13,13 @@ export interface NodeRegistry {
|
|||||||
* @throws An error if the nodes could not be loaded
|
* @throws An error if the nodes could not be loaded
|
||||||
* @remarks This method should be called before calling getNode or getAllNodes
|
* @remarks This method should be called before calling getNode or getAllNodes
|
||||||
*/
|
*/
|
||||||
load: (nodeIds: NodeId[]) => Promise<NodeDefinition[]>;
|
load: (nodeIds: (NodeId | string)[]) => Promise<NodeDefinition[]>;
|
||||||
/**
|
/**
|
||||||
* Get a node by id
|
* Get a node by id
|
||||||
* @param id - The id of the node to get
|
* @param id - The id of the node to get
|
||||||
* @returns The node with the given id, or undefined if no such node exists
|
* @returns The node with the given id, or undefined if no such node exists
|
||||||
*/
|
*/
|
||||||
getNode: (id: NodeId) => NodeDefinition | undefined;
|
getNode: (id: NodeId | string) => NodeDefinition | undefined;
|
||||||
/**
|
/**
|
||||||
* Get all nodes
|
* Get all nodes
|
||||||
* @returns An array of all nodes
|
* @returns An array of all nodes
|
||||||
@ -33,7 +33,7 @@ export interface RuntimeExecutor {
|
|||||||
* @param graph - The graph to execute
|
* @param graph - The graph to execute
|
||||||
* @returns The result of the execution
|
* @returns The result of the execution
|
||||||
*/
|
*/
|
||||||
execute: (graph: Graph, settings: Record<string, unknown>) => Int32Array;
|
execute: (graph: Graph, settings: Record<string, unknown>) => Promise<Int32Array>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeCache {
|
export interface RuntimeCache {
|
||||||
|
Loading…
Reference in New Issue
Block a user