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:
@ -24,7 +24,7 @@
|
||||
.box-selection {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
border: solid 0.2px var(--outline);
|
||||
border: solid 2px var(--outline);
|
||||
border-style: dashed;
|
||||
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];
|
||||
bh = height / cameraPosition[2];
|
||||
}
|
||||
$: backgroundColor = $colors["layer-0"];
|
||||
$: lineColor = $colors["outline"];
|
||||
</script>
|
||||
|
||||
<T.Group
|
||||
@ -54,8 +52,8 @@
|
||||
},
|
||||
}}
|
||||
uniforms.camPos.value={cameraPosition}
|
||||
uniforms.backgroundColor.value={backgroundColor}
|
||||
uniforms.lineColor.value={lineColor}
|
||||
uniforms.backgroundColor.value={$colors["layer-0"]}
|
||||
uniforms.lineColor.value={$colors["outline"]}
|
||||
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
||||
uniforms.dimensions.value={[width, height]}
|
||||
/>
|
||||
|
@ -1,5 +1,9 @@
|
||||
<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 type { OrthographicCamera } from "three";
|
||||
import Background from "../background/Background.svelte";
|
||||
@ -23,20 +27,21 @@
|
||||
import AddMenu from "../AddMenu.svelte";
|
||||
import { createWasmWrapper } from "@nodes/utils";
|
||||
|
||||
export let graph: GraphManager;
|
||||
import HelpView from "../HelpView.svelte";
|
||||
|
||||
export let settings = {
|
||||
snapToGrid: true,
|
||||
showGrid: true,
|
||||
};
|
||||
export let manager: GraphManager;
|
||||
|
||||
export let snapToGrid = true;
|
||||
export let showGrid = true;
|
||||
export let showHelp = false;
|
||||
|
||||
let keymap =
|
||||
getContext<ReturnType<typeof createKeyMap>>("keymap") || createKeyMap([]);
|
||||
|
||||
setContext("graphManager", graph);
|
||||
const status = graph.status;
|
||||
const nodes = graph.nodes;
|
||||
const edges = graph.edges;
|
||||
setContext("graphManager", manager);
|
||||
const status = manager.status;
|
||||
const nodes = manager.nodes;
|
||||
const edges = manager.edges;
|
||||
|
||||
let wrapper: HTMLDivElement;
|
||||
let rect: DOMRect;
|
||||
@ -114,7 +119,7 @@
|
||||
if (nodeTypeId in nodeHeightCache) {
|
||||
return nodeHeightCache[nodeTypeId];
|
||||
}
|
||||
const node = graph.getNodeType(nodeTypeId);
|
||||
const node = manager.getNodeType(nodeTypeId);
|
||||
if (!node?.inputs) {
|
||||
return 5;
|
||||
}
|
||||
@ -185,13 +190,13 @@
|
||||
|
||||
// remove existing edge
|
||||
if (typeof index === "string") {
|
||||
const edges = graph.getEdgesToNode(node);
|
||||
const edges = manager.getEdgesToNode(node);
|
||||
for (const edge of edges) {
|
||||
if (edge[3] === index) {
|
||||
node = edge[0];
|
||||
index = edge[1];
|
||||
position = getSocketPosition(node, index);
|
||||
graph.removeEdge(edge);
|
||||
manager.removeEdge(edge);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -204,7 +209,7 @@
|
||||
position,
|
||||
};
|
||||
|
||||
$possibleSockets = graph
|
||||
$possibleSockets = manager
|
||||
.getPossibleSockets($activeSocket)
|
||||
.map(([node, index]) => {
|
||||
return {
|
||||
@ -315,7 +320,7 @@
|
||||
|
||||
// here we are handling dragging of nodes
|
||||
if ($activeNodeId !== -1 && mouseDownId !== -1) {
|
||||
const node = graph.getNode($activeNodeId);
|
||||
const node = manager.getNode($activeNodeId);
|
||||
if (!node || event.buttons !== 1) return;
|
||||
|
||||
node.tmp = node.tmp || {};
|
||||
@ -328,9 +333,9 @@
|
||||
|
||||
if (event.ctrlKey) {
|
||||
const snapLevel = getSnapLevel();
|
||||
if (settings.snapToGrid) {
|
||||
newX = snapToGrid(newX, 5 / snapLevel);
|
||||
newY = snapToGrid(newY, 5 / snapLevel);
|
||||
if (snapToGrid) {
|
||||
newX = snapPointToGrid(newX, 5 / snapLevel);
|
||||
newY = snapPointToGrid(newY, 5 / snapLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,7 +351,7 @@
|
||||
|
||||
if ($selectedNodes?.size) {
|
||||
for (const nodeId of $selectedNodes) {
|
||||
const n = graph.getNode(nodeId);
|
||||
const n = manager.getNode(nodeId);
|
||||
if (!n?.tmp) continue;
|
||||
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
||||
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
|
||||
@ -441,10 +446,10 @@
|
||||
$activeNodeId = clickedNodeId;
|
||||
// select the node
|
||||
} else if (event.shiftKey) {
|
||||
const activeNode = graph.getNode($activeNodeId);
|
||||
const newNode = graph.getNode(clickedNodeId);
|
||||
const activeNode = manager.getNode($activeNodeId);
|
||||
const newNode = manager.getNode(clickedNodeId);
|
||||
if (activeNode && newNode) {
|
||||
const edge = graph.getNodesBetween(activeNode, newNode);
|
||||
const edge = manager.getNodesBetween(activeNode, newNode);
|
||||
if (edge) {
|
||||
const selected = new Set(edge.map((n) => n.id));
|
||||
selected.add(clickedNodeId);
|
||||
@ -459,14 +464,14 @@
|
||||
} else if (event.ctrlKey) {
|
||||
boxSelection = true;
|
||||
}
|
||||
const node = graph.getNode($activeNodeId);
|
||||
const node = manager.getNode($activeNodeId);
|
||||
if (!node) return;
|
||||
node.tmp = node.tmp || {};
|
||||
node.tmp.downX = node.position[0];
|
||||
node.tmp.downY = node.position[1];
|
||||
if ($selectedNodes) {
|
||||
for (const nodeId of $selectedNodes) {
|
||||
const n = graph.getNode(nodeId);
|
||||
const n = manager.getNode(nodeId);
|
||||
if (!n) continue;
|
||||
n.tmp = n.tmp || {};
|
||||
n.tmp.downX = n.position[0];
|
||||
@ -478,10 +483,10 @@
|
||||
function copyNodes() {
|
||||
if ($activeNodeId === -1 && !$selectedNodes?.size) return;
|
||||
let _nodes = [$activeNodeId, ...($selectedNodes?.values() || [])]
|
||||
.map((id) => graph.getNode(id))
|
||||
.map((id) => manager.getNode(id))
|
||||
.filter(Boolean) as Node[];
|
||||
|
||||
const _edges = graph.getEdgesBetweenNodes(_nodes);
|
||||
const _edges = manager.getEdgesBetweenNodes(_nodes);
|
||||
|
||||
_nodes = _nodes.map((_node) => {
|
||||
const node = globalThis.structuredClone({
|
||||
@ -512,7 +517,7 @@
|
||||
})
|
||||
.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));
|
||||
}
|
||||
|
||||
@ -522,15 +527,23 @@
|
||||
key: "l",
|
||||
description: "Select linked nodes",
|
||||
callback: () => {
|
||||
const activeNode = graph.getNode($activeNodeId);
|
||||
const activeNode = manager.getNode($activeNodeId);
|
||||
if (activeNode) {
|
||||
const nodes = graph.getLinkedNodes(activeNode);
|
||||
const nodes = manager.getLinkedNodes(activeNode);
|
||||
$selectedNodes = new Set(nodes.map((n) => n.id));
|
||||
}
|
||||
console.log(activeNode);
|
||||
},
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "?",
|
||||
description: "Toggle Help",
|
||||
callback: () => {
|
||||
showHelp = !showHelp;
|
||||
},
|
||||
});
|
||||
|
||||
keymap.addShortcut({
|
||||
key: "c",
|
||||
ctrl: true,
|
||||
@ -612,7 +625,7 @@
|
||||
description: "Undo",
|
||||
callback: () => {
|
||||
if (!isBodyFocused()) return;
|
||||
graph.undo();
|
||||
manager.undo();
|
||||
for (const node of $nodes.values()) {
|
||||
updateNodePosition(node);
|
||||
}
|
||||
@ -625,7 +638,7 @@
|
||||
description: "Redo",
|
||||
callback: () => {
|
||||
if (!isBodyFocused()) return;
|
||||
graph.redo();
|
||||
manager.redo();
|
||||
for (const node of $nodes.values()) {
|
||||
updateNodePosition(node);
|
||||
}
|
||||
@ -637,30 +650,30 @@
|
||||
description: "Delete selected nodes",
|
||||
callback: (event) => {
|
||||
if (!isBodyFocused()) return;
|
||||
graph.startUndoGroup();
|
||||
manager.startUndoGroup();
|
||||
if ($activeNodeId !== -1) {
|
||||
const node = graph.getNode($activeNodeId);
|
||||
const node = manager.getNode($activeNodeId);
|
||||
if (node) {
|
||||
graph.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||
$activeNodeId = -1;
|
||||
}
|
||||
}
|
||||
if ($selectedNodes) {
|
||||
for (const nodeId of $selectedNodes) {
|
||||
const node = graph.getNode(nodeId);
|
||||
const node = manager.getNode(nodeId);
|
||||
if (node) {
|
||||
graph.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||
}
|
||||
}
|
||||
$selectedNodes.clear();
|
||||
$selectedNodes = $selectedNodes;
|
||||
}
|
||||
graph.saveUndoGroup();
|
||||
manager.saveUndoGroup();
|
||||
},
|
||||
});
|
||||
|
||||
function handleMouseUp(event: MouseEvent) {
|
||||
const activeNode = graph.getNode($activeNodeId);
|
||||
const activeNode = manager.getNode($activeNodeId);
|
||||
|
||||
const clickedNodeId = getNodeIdFromEvent(event);
|
||||
|
||||
@ -677,13 +690,13 @@
|
||||
if (activeNode?.tmp?.isMoving) {
|
||||
activeNode.tmp = activeNode.tmp || {};
|
||||
activeNode.tmp.isMoving = false;
|
||||
if (settings.snapToGrid) {
|
||||
if (snapToGrid) {
|
||||
const snapLevel = getSnapLevel();
|
||||
activeNode.position[0] = snapToGrid(
|
||||
activeNode.position[0] = snapPointToGrid(
|
||||
activeNode?.tmp?.x ?? activeNode.position[0],
|
||||
5 / snapLevel,
|
||||
);
|
||||
activeNode.position[1] = snapToGrid(
|
||||
activeNode.position[1] = snapPointToGrid(
|
||||
activeNode?.tmp?.y ?? activeNode.position[1],
|
||||
5 / snapLevel,
|
||||
);
|
||||
@ -692,7 +705,9 @@
|
||||
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1];
|
||||
}
|
||||
const nodes = [
|
||||
...[...($selectedNodes?.values() || [])].map((id) => graph.getNode(id)),
|
||||
...[...($selectedNodes?.values() || [])].map((id) =>
|
||||
manager.getNode(id),
|
||||
),
|
||||
] as NodeType[];
|
||||
|
||||
const vec = [
|
||||
@ -728,13 +743,13 @@
|
||||
|
||||
$edges = $edges;
|
||||
});
|
||||
graph.save();
|
||||
manager.save();
|
||||
} else if ($hoveredSocket && $activeSocket) {
|
||||
if (
|
||||
typeof $hoveredSocket.index === "number" &&
|
||||
typeof $activeSocket.index === "string"
|
||||
) {
|
||||
graph.createEdge(
|
||||
manager.createEdge(
|
||||
$hoveredSocket.node,
|
||||
$hoveredSocket.index || 0,
|
||||
$activeSocket.node,
|
||||
@ -744,14 +759,14 @@
|
||||
typeof $activeSocket.index == "number" &&
|
||||
typeof $hoveredSocket.index === "string"
|
||||
) {
|
||||
graph.createEdge(
|
||||
manager.createEdge(
|
||||
$activeSocket.node,
|
||||
$activeSocket.index || 0,
|
||||
$hoveredSocket.node,
|
||||
$hoveredSocket.index,
|
||||
);
|
||||
}
|
||||
graph.save();
|
||||
manager.save();
|
||||
}
|
||||
|
||||
// check if camera moved
|
||||
@ -808,8 +823,8 @@
|
||||
}
|
||||
|
||||
const pos = projectScreenToWorld(mx, my);
|
||||
graph.registry.load([nodeId]).then(() => {
|
||||
graph.createNode({
|
||||
manager.registry.load([nodeId]).then(() => {
|
||||
manager.createNode({
|
||||
type: nodeId,
|
||||
props,
|
||||
position: pos,
|
||||
@ -819,7 +834,7 @@
|
||||
const files = event.dataTransfer.files;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const buffer = e.target?.result;
|
||||
const buffer = e.target?.result as Buffer;
|
||||
if (buffer) {
|
||||
const wrapper = createWasmWrapper(buffer);
|
||||
const definition = wrapper.get_definition();
|
||||
@ -885,10 +900,13 @@
|
||||
/>
|
||||
<label for="drop-zone" />
|
||||
|
||||
{#if showHelp}
|
||||
<HelpView registry={manager.registry} />
|
||||
{/if}
|
||||
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
|
||||
<Camera bind:camera position={cameraPosition} />
|
||||
|
||||
{#if settings?.showGrid !== false}
|
||||
{#if showGrid !== false}
|
||||
<Background {cameraPosition} {maxZoom} {minZoom} {width} {height} />
|
||||
{/if}
|
||||
|
||||
@ -906,7 +924,7 @@
|
||||
|
||||
{#if $status === "idle"}
|
||||
{#if addMenuPosition}
|
||||
<AddMenu bind:position={addMenuPosition} {graph} />
|
||||
<AddMenu bind:position={addMenuPosition} graph={manager} />
|
||||
{/if}
|
||||
|
||||
{#if $activeSocket}
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
export let showGrid = false;
|
||||
export let snapToGrid = false;
|
||||
export let showHelp = false;
|
||||
|
||||
export let settingTypes = {};
|
||||
|
||||
@ -55,4 +56,4 @@
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<GraphEl graph={manager} settings={{ showGrid, snapToGrid }} />
|
||||
<GraphEl {manager} bind:showGrid bind:snapToGrid bind:showHelp />
|
||||
|
@ -47,6 +47,7 @@
|
||||
class:selected={isSelected}
|
||||
class:out-of-view={!inView}
|
||||
data-node-id={node.id}
|
||||
data-node-type={node.type}
|
||||
bind:this={ref}
|
||||
>
|
||||
<NodeHeader {node} />
|
||||
|
@ -51,7 +51,7 @@
|
||||
});
|
||||
</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">
|
||||
{node.type.split("/").pop()}
|
||||
</div>
|
||||
|
@ -73,6 +73,8 @@
|
||||
|
||||
<div
|
||||
class="wrapper"
|
||||
data-node-type={node.type}
|
||||
data-node-input={id}
|
||||
class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)}
|
||||
>
|
||||
{#key id && graphId}
|
||||
@ -87,12 +89,14 @@
|
||||
|
||||
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
|
||||
<div
|
||||
data-node-socket
|
||||
class="large target"
|
||||
on:mousedown={handleMouseDown}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
data-node-socket
|
||||
class="small target"
|
||||
on:mousedown={handleMouseDown}
|
||||
role="button"
|
||||
@ -187,6 +191,8 @@
|
||||
|
||||
.content.disabled {
|
||||
opacity: 0.2;
|
||||
}
|
||||
.content.disabled > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user