feat: some updates
📊 Benchmark the Runtime / benchmark (pull_request) Successful in 1m13s
🚀 Lint & Test & Deploy / quality (pull_request) Failing after 44s
🚀 Lint & Test & Deploy / test-unit (pull_request) Failing after 29s
🚀 Lint & Test & Deploy / test-e2e (pull_request) Failing after 33s
🚀 Lint & Test & Deploy / deploy (pull_request) Has been skipped
📊 Benchmark the Runtime / benchmark (pull_request) Successful in 1m13s
🚀 Lint & Test & Deploy / quality (pull_request) Failing after 44s
🚀 Lint & Test & Deploy / test-unit (pull_request) Failing after 29s
🚀 Lint & Test & Deploy / test-e2e (pull_request) Failing after 33s
🚀 Lint & Test & Deploy / deploy (pull_request) Has been skipped
This commit is contained in:
@@ -19,7 +19,7 @@ import EventEmitter from './helpers/EventEmitter';
|
|||||||
import { HistoryManager } from './history-manager';
|
import { HistoryManager } from './history-manager';
|
||||||
|
|
||||||
const logger = createLogger('graph-manager');
|
const logger = createLogger('graph-manager');
|
||||||
logger.mute();
|
// logger.mute();
|
||||||
|
|
||||||
const remoteRegistry = new RemoteNodeRegistry('');
|
const remoteRegistry = new RemoteNodeRegistry('');
|
||||||
|
|
||||||
@@ -73,9 +73,22 @@ export class GraphManager extends EventEmitter<{
|
|||||||
id = $state(0);
|
id = $state(0);
|
||||||
|
|
||||||
nodes = new SvelteMap<number, NodeInstance>();
|
nodes = new SvelteMap<number, NodeInstance>();
|
||||||
|
nodeArray = $derived(Array.from(this.nodes.values()));
|
||||||
|
|
||||||
edges = $state<Edge[]>([]);
|
edges = $state<Edge[]>([]);
|
||||||
|
|
||||||
|
// Plain array — NOT $state. rootGraph items are plain-serialized (safe for structuredClone).
|
||||||
|
// savedNodes/savedEdges hold live reactive references so reactivity is preserved on exit.
|
||||||
|
graphStack: {
|
||||||
|
rootGraph: Graph;
|
||||||
|
savedNodes: Map<number, NodeInstance>;
|
||||||
|
savedEdges: Edge[];
|
||||||
|
outerGraph: Graph;
|
||||||
|
groupId: number;
|
||||||
|
nodeId: number;
|
||||||
|
cameraPosition: [number, number, number];
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
settingTypes: Record<string, NodeInput> = {};
|
settingTypes: Record<string, NodeInput> = {};
|
||||||
settings = $state<Record<string, unknown>>();
|
settings = $state<Record<string, unknown>>();
|
||||||
|
|
||||||
@@ -90,9 +103,25 @@ export class GraphManager extends EventEmitter<{
|
|||||||
});
|
});
|
||||||
|
|
||||||
history: HistoryManager = new HistoryManager();
|
history: HistoryManager = new HistoryManager();
|
||||||
|
|
||||||
|
private serializeFullGraph(): Graph {
|
||||||
|
if (this.graphStack.length === 0) return this.serialize();
|
||||||
|
let merged = this.serialize();
|
||||||
|
for (let i = this.graphStack.length - 1; i >= 0; i--) {
|
||||||
|
const { rootGraph, groupId } = this.graphStack[i];
|
||||||
|
merged = {
|
||||||
|
...rootGraph,
|
||||||
|
groups: rootGraph.groups.map(g =>
|
||||||
|
g.id === groupId ? { ...g, nodes: merged.nodes, edges: merged.edges } : g
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
execute = throttle(() => {
|
execute = throttle(() => {
|
||||||
if (this.loaded === false) return;
|
if (this.loaded === false) return;
|
||||||
this.emit('result', this.serialize());
|
this.emit('result', this.serializeFullGraph());
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
constructor(public registry: NodeRegistry) {
|
constructor(public registry: NodeRegistry) {
|
||||||
@@ -263,6 +292,28 @@ export class GraphManager extends EventEmitter<{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tryConnectToDebugNode(nodeId: number) {
|
||||||
|
const node = this.nodes.get(nodeId);
|
||||||
|
if (!node) return;
|
||||||
|
if (node.type.endsWith('/debug')) return;
|
||||||
|
if (!node.state.type?.outputs?.length) return;
|
||||||
|
let debugNode = this.nodes.values().find(n => n.type.endsWith('/debug'));
|
||||||
|
|
||||||
|
if (!debugNode) {
|
||||||
|
debugNode = this.createNode({
|
||||||
|
type: '__internal/node/debug',
|
||||||
|
position: [node.position[0] + 30, node.position[1]],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debugNode) {
|
||||||
|
this.createEdge(node, 0, debugNode, 'input');
|
||||||
|
}
|
||||||
|
|
||||||
|
return debugNode;
|
||||||
|
}
|
||||||
|
|
||||||
getEdgesBetweenNodes(nodes: NodeInstance[]): [number, number, number, string][] {
|
getEdgesBetweenNodes(nodes: NodeInstance[]): [number, number, number, string][] {
|
||||||
const edges = [];
|
const edges = [];
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
@@ -290,13 +341,11 @@ export class GraphManager extends EventEmitter<{
|
|||||||
private _init(graph: Graph) {
|
private _init(graph: Graph) {
|
||||||
const nodes = new SvelteMap(
|
const nodes = new SvelteMap(
|
||||||
graph.nodes.map((node) => {
|
graph.nodes.map((node) => {
|
||||||
const nodeType = this.registry.getNode(node.type);
|
|
||||||
const n = node as NodeInstance;
|
const n = node as NodeInstance;
|
||||||
if (nodeType) {
|
const registryType = this.registry.getNode(node.type);
|
||||||
n.state = {
|
n.state = registryType ? { type: registryType } : {};
|
||||||
type: nodeType
|
const resolvedType = this.getNodeType(n);
|
||||||
};
|
if (resolvedType) n.state = { type: resolvedType };
|
||||||
}
|
|
||||||
return [node.id, n];
|
return [node.id, n];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -436,6 +485,39 @@ export class GraphManager extends EventEmitter<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
getNodeType(node: NodeInstance) {
|
getNodeType(node: NodeInstance) {
|
||||||
|
if (!node) {
|
||||||
|
console.trace('failed to get node type');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === '__internal/group/input') {
|
||||||
|
const groupId = this.graphStack.at(-1)?.groupId;
|
||||||
|
const group = groupId !== undefined ? this.getGroup(groupId) : undefined;
|
||||||
|
if (!group) return node.state.type;
|
||||||
|
return {
|
||||||
|
id: '__internal/group/input' as NodeId,
|
||||||
|
outputs: Object.values(group.inputs ?? {}).map(i => i.type),
|
||||||
|
execute: (x: Int32Array) => x
|
||||||
|
} as NodeDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === '__internal/group/output') {
|
||||||
|
const groupId = this.graphStack.at(-1)?.groupId;
|
||||||
|
const group = groupId !== undefined ? this.getGroup(groupId) : undefined;
|
||||||
|
if (!group) return node.state.type;
|
||||||
|
return {
|
||||||
|
id: '__internal/group/output' as NodeId,
|
||||||
|
inputs: Object.fromEntries(
|
||||||
|
(group.outputs ?? []).map((
|
||||||
|
o,
|
||||||
|
i
|
||||||
|
) => [`out_${i}`, { type: o.type, label: o.label, external: true }])
|
||||||
|
),
|
||||||
|
outputs: [],
|
||||||
|
execute: (x: Int32Array) => x
|
||||||
|
} as NodeDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
// Construct the group inputs on the fly
|
// Construct the group inputs on the fly
|
||||||
if (node.type === '__internal/group/instance') {
|
if (node.type === '__internal/group/instance') {
|
||||||
const groupDefinition = this.getGroup(node.props?.groupId as number);
|
const groupDefinition = this.getGroup(node.props?.groupId as number);
|
||||||
@@ -446,15 +528,15 @@ export class GraphManager extends EventEmitter<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inputs = {
|
const inputs = {
|
||||||
|
...(node.state.type?.inputs || {}),
|
||||||
|
...groupDefinition?.inputs,
|
||||||
'groupId': {
|
'groupId': {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
label: '',
|
label: '',
|
||||||
value: node.props?.groupId,
|
value: node.props?.groupId,
|
||||||
internal: true,
|
internal: true,
|
||||||
options: this.graph.groups.map(g => g.id)
|
options: this.graph.groups.map(g => g.id)
|
||||||
},
|
}
|
||||||
...(node.state.type?.inputs || {}),
|
|
||||||
...groupDefinition?.inputs
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const groupType = {
|
const groupType = {
|
||||||
@@ -576,6 +658,58 @@ export class GraphManager extends EventEmitter<{
|
|||||||
return this.graph.groups.find(g => g.id === id);
|
return this.graph.groups.find(g => g.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isInsideGroup = $state(false);
|
||||||
|
|
||||||
|
enterGroup(nodeId: number, cameraPosition: [number, number, number]): boolean {
|
||||||
|
const groupNode = this.getNode(nodeId);
|
||||||
|
if (!groupNode || groupNode.type !== '__internal/group/instance') return false;
|
||||||
|
const groupId = groupNode.props?.groupId as number;
|
||||||
|
const group = this.getGroup(groupId);
|
||||||
|
if (!group) return false;
|
||||||
|
|
||||||
|
this.graphStack.push({
|
||||||
|
rootGraph: this.serialize(),
|
||||||
|
savedNodes: new Map(this.nodes),
|
||||||
|
savedEdges: [...this.edges],
|
||||||
|
outerGraph: this.graph,
|
||||||
|
groupId,
|
||||||
|
nodeId,
|
||||||
|
cameraPosition
|
||||||
|
});
|
||||||
|
this.graph = { ...this.graph, nodes: group.nodes, edges: group.edges };
|
||||||
|
this._init(this.graph);
|
||||||
|
this.history.reset();
|
||||||
|
this.isInsideGroup = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
exitGroup(): { camera: [number, number, number]; nodeId: number } | false {
|
||||||
|
if (!this.graphStack.length) return false;
|
||||||
|
const { savedNodes, savedEdges, outerGraph, groupId, nodeId, cameraPosition } = this.graphStack.pop()!;
|
||||||
|
const internalState = this.serialize();
|
||||||
|
|
||||||
|
// Restore live reactive nodes and edges so drag-reactivity is preserved
|
||||||
|
this.nodes.clear();
|
||||||
|
for (const [id, node] of savedNodes) {
|
||||||
|
this.nodes.set(id, node);
|
||||||
|
}
|
||||||
|
this.edges = savedEdges;
|
||||||
|
|
||||||
|
// Patch the group definition with the edited internal graph
|
||||||
|
this.graph = {
|
||||||
|
...outerGraph,
|
||||||
|
groups: (outerGraph.groups ?? []).map(g =>
|
||||||
|
g.id === groupId ? { ...g, nodes: internalState.nodes, edges: internalState.edges } : g
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
this.history.reset();
|
||||||
|
this.isInsideGroup = this.graphStack.length > 0;
|
||||||
|
this.execute();
|
||||||
|
this.save();
|
||||||
|
return { camera: cameraPosition, nodeId };
|
||||||
|
}
|
||||||
|
|
||||||
createNodeId() {
|
createNodeId() {
|
||||||
const ids = [
|
const ids = [
|
||||||
...this.nodes.keys(),
|
...this.nodes.keys(),
|
||||||
@@ -583,6 +717,8 @@ export class GraphManager extends EventEmitter<{
|
|||||||
...this.graph.groups.flatMap(g => g.nodes.map(n => n.id))
|
...this.graph.groups.flatMap(g => g.nodes.map(n => n.id))
|
||||||
];
|
];
|
||||||
|
|
||||||
|
console.log('CREATE NODE ID', ids);
|
||||||
|
|
||||||
let id = 0;
|
let id = 0;
|
||||||
while (ids.includes(id)) {
|
while (ids.includes(id)) {
|
||||||
id++;
|
id++;
|
||||||
@@ -723,7 +859,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
return [groupInputNode.id, 0, edge[2].id, edge[3]];
|
return [groupInputNode.id, 0, edge[2].id, edge[3]];
|
||||||
// Going out to the group
|
// Going out to the group
|
||||||
} else if (!ids.has(edge[2].id)) {
|
} else if (!ids.has(edge[2].id)) {
|
||||||
return [edge[0].id, edge[1], groupOutputNode.id, 'Out'];
|
return [edge[0].id, edge[1], groupOutputNode.id, 'out_0'];
|
||||||
}
|
}
|
||||||
return [edge[0].id, edge[1], edge[2].id, edge[3]];
|
return [edge[0].id, edge[1], edge[2].id, edge[3]];
|
||||||
}) as [number, number, number, string][];
|
}) as [number, number, number, string][];
|
||||||
@@ -900,8 +1036,9 @@ export class GraphManager extends EventEmitter<{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit('save', state);
|
const fullState = this.graphStack.length > 0 ? this.serializeFullGraph() : state;
|
||||||
logger.log('saving graphs', state);
|
this.emit('save', fullState);
|
||||||
|
logger.log('saving graphs', fullState);
|
||||||
}
|
}
|
||||||
|
|
||||||
getParentsOfNode(node: NodeInstance) {
|
getParentsOfNode(node: NodeInstance) {
|
||||||
|
|||||||
@@ -152,10 +152,6 @@ export class GraphState {
|
|||||||
this.edges.delete(edgeId);
|
this.edges.delete(edgeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdgeData() {
|
|
||||||
return this.edges;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNodePosition(node: NodeInstance) {
|
updateNodePosition(node: NodeInstance) {
|
||||||
if (
|
if (
|
||||||
node.state.x === node.position[0]
|
node.state.x === node.position[0]
|
||||||
@@ -190,29 +186,6 @@ export class GraphState {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
tryConnectToDebugNode(nodeId: number) {
|
|
||||||
const node = this.graph.nodes.get(nodeId);
|
|
||||||
if (!node) return;
|
|
||||||
if (node.type.endsWith('/debug')) return;
|
|
||||||
if (!node.state.type?.outputs?.length) return;
|
|
||||||
for (const _node of this.graph.nodes.values()) {
|
|
||||||
if (_node.type.endsWith('/debug')) {
|
|
||||||
this.graph.createEdge(node, 0, _node, 'input');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const debugNode = this.graph.createNode({
|
|
||||||
type: '__internal/node/debug',
|
|
||||||
position: [node.position[0] + 30, node.position[1]],
|
|
||||||
props: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (debugNode) {
|
|
||||||
this.graph.createEdge(node, 0, debugNode, 'input');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
copyNodes() {
|
copyNodes() {
|
||||||
if (this.activeNodeId === -1 && !this.selectedNodes?.size) {
|
if (this.activeNodeId === -1 && !this.selectedNodes?.size) {
|
||||||
return;
|
return;
|
||||||
@@ -362,7 +335,8 @@ export class GraphState {
|
|||||||
for (const node of this.graph.nodes.values()) {
|
for (const node of this.graph.nodes.values()) {
|
||||||
const x = node.position[0];
|
const x = node.position[0];
|
||||||
const y = node.position[1];
|
const y = node.position[1];
|
||||||
const height = getNodeHeight(this.graph.getNodeType(node)!);
|
const nodeType = this.graph.getNodeType(node);
|
||||||
|
const height = nodeType ? getNodeHeight(nodeType) : 20;
|
||||||
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;
|
||||||
break;
|
break;
|
||||||
@@ -374,6 +348,7 @@ export class GraphState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isNodeInView(node: NodeInstance) {
|
isNodeInView(node: NodeInstance) {
|
||||||
|
if (!node) return false;
|
||||||
const height = getNodeHeight(this.graph.getNodeType(node)!);
|
const height = getNodeHeight(this.graph.getNodeType(node)!);
|
||||||
const width = 20;
|
const width = 20;
|
||||||
return node.position[0] > this.cameraBounds[0] - width
|
return node.position[0] > this.cameraBounds[0] - width
|
||||||
@@ -388,8 +363,21 @@ export class GraphState {
|
|||||||
|
|
||||||
enterGroupNode() {
|
enterGroupNode() {
|
||||||
if (this.activeNodeId === -1) return;
|
if (this.activeNodeId === -1) return;
|
||||||
const selectedNode = this.graph.getNode(this.activeNodeId);
|
const node = this.graph.getNode(this.activeNodeId);
|
||||||
if (!selectedNode || selectedNode.type.startsWith('__internal/group/instance')) return;
|
if (!node || node.type !== '__internal/group/instance') return;
|
||||||
|
const ok = this.graph.enterGroup(this.activeNodeId, [...this.cameraPosition]);
|
||||||
|
if (ok) {
|
||||||
|
this.activeNodeId = -1;
|
||||||
|
this.clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exitGroupNode() {
|
||||||
|
const result = this.graph.exitGroup();
|
||||||
|
if (!result) return;
|
||||||
|
this.cameraPosition = result.camera;
|
||||||
|
this.activeNodeId = -1;
|
||||||
|
this.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
getSocketPosition(
|
getSocketPosition(
|
||||||
|
|||||||
@@ -95,8 +95,9 @@
|
|||||||
graphState.addMenuPosition = null;
|
graphState.addMenuPosition = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSocketType(node: NodeInstance, index: number | string, e: unknown): string {
|
function getSocketType(node: NodeInstance, index: number | string): string {
|
||||||
const nodeType = graph.getNodeType(node);
|
const nodeType = graph.getNodeType(node);
|
||||||
|
console.log({ nodeType, index });
|
||||||
if (typeof index === 'string') {
|
if (typeof index === 'string') {
|
||||||
return nodeType?.inputs?.[index].type || 'unknown';
|
return nodeType?.inputs?.[index].type || 'unknown';
|
||||||
}
|
}
|
||||||
@@ -169,6 +170,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if graph.status === 'idle'}
|
{#if graph.status === 'idle'}
|
||||||
|
{#if graph.isInsideGroup}
|
||||||
|
<HTML transform={false}>
|
||||||
|
<button class="exit-group" onclick={() => graphState.exitGroupNode()}>
|
||||||
|
↑ Exit Group
|
||||||
|
</button>
|
||||||
|
</HTML>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if graphState.addMenuPosition}
|
{#if graphState.addMenuPosition}
|
||||||
<AddMenu
|
<AddMenu
|
||||||
onnode={handleNodeCreation}
|
onnode={handleNodeCreation}
|
||||||
@@ -182,8 +191,8 @@
|
|||||||
{#if graphState.activeSocket}
|
{#if graphState.activeSocket}
|
||||||
<EdgeEl
|
<EdgeEl
|
||||||
z={graphState.cameraPosition[2]}
|
z={graphState.cameraPosition[2]}
|
||||||
inputType={getSocketType(graphState.activeSocket.node, graphState.activeSocket.index, 'c')}
|
inputType={getSocketType(graphState.activeSocket.node, graphState.activeSocket.index)}
|
||||||
outputType={getSocketType(graphState.activeSocket.node, graphState.activeSocket.index, 'd')}
|
outputType={getSocketType(graphState.activeSocket.node, graphState.activeSocket.index)}
|
||||||
x1={graphState.activeSocket.position[0]}
|
x1={graphState.activeSocket.position[0]}
|
||||||
y1={graphState.activeSocket.position[1]}
|
y1={graphState.activeSocket.position[1]}
|
||||||
x2={graphState.edgeEndPosition?.[0] ?? graphState.mousePosition[0]}
|
x2={graphState.edgeEndPosition?.[0] ?? graphState.mousePosition[0]}
|
||||||
@@ -196,8 +205,8 @@
|
|||||||
<EdgeEl
|
<EdgeEl
|
||||||
id={graph.getEdgeId(edge)}
|
id={graph.getEdgeId(edge)}
|
||||||
z={graphState.cameraPosition[2]}
|
z={graphState.cameraPosition[2]}
|
||||||
inputType={getSocketType(edge[0], edge[1], 'a')}
|
inputType={getSocketType(edge[0], edge[1])}
|
||||||
outputType={getSocketType(edge[2], edge[3], 'b')}
|
outputType={getSocketType(edge[2], edge[3])}
|
||||||
{x1}
|
{x1}
|
||||||
{y1}
|
{y1}
|
||||||
{x2}
|
{x2}
|
||||||
@@ -216,10 +225,10 @@
|
|||||||
style:transform={`scale(${graphState.cameraPosition[2] * 0.1})`}
|
style:transform={`scale(${graphState.cameraPosition[2] * 0.1})`}
|
||||||
class:hovering-sockets={graphState.activeSocket}
|
class:hovering-sockets={graphState.activeSocket}
|
||||||
>
|
>
|
||||||
{#each graph.getAllNodes() as node (node.id)}
|
{#each graph.nodeArray as node, index (node.id)}
|
||||||
<NodeEl
|
<NodeEl
|
||||||
{node}
|
bind:node={graph.nodeArray[index]}
|
||||||
inView={graphState.isNodeInView(node)}
|
inView={node ? graphState.isNodeInView(node) : false}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -244,6 +253,26 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.exit-group) {
|
||||||
|
position: fixed;
|
||||||
|
top: 12px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background: var(--color-layer-2);
|
||||||
|
border: 1px solid var(--stroke);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 0.85em;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.exit-group:hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ export class MouseEventManager {
|
|||||||
// if we clicked on a node
|
// if we clicked on a node
|
||||||
if (clickedNodeId !== -1) {
|
if (clickedNodeId !== -1) {
|
||||||
if (event.ctrlKey && event.shiftKey) {
|
if (event.ctrlKey && event.shiftKey) {
|
||||||
this.state.tryConnectToDebugNode(clickedNodeId);
|
this.graph.tryConnectToDebugNode(clickedNodeId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.state.activeNodeId === -1) {
|
if (this.state.activeNodeId === -1) {
|
||||||
|
|||||||
@@ -25,15 +25,12 @@ export function getParameterHeight(node: NodeDefinition, inputKey: string) {
|
|||||||
|
|
||||||
const nodeHeightCache: Record<string, number> = {};
|
const nodeHeightCache: Record<string, number> = {};
|
||||||
export function getNodeHeight(node: NodeDefinition) {
|
export function getNodeHeight(node: NodeDefinition) {
|
||||||
if (!node) {
|
if (!node || !('inputs' in node)) {
|
||||||
console.trace('Node is undefined', node);
|
return 5;
|
||||||
}
|
}
|
||||||
if (node.id in nodeHeightCache) {
|
if (node.id in nodeHeightCache) {
|
||||||
return nodeHeightCache[node.id];
|
return nodeHeightCache[node.id];
|
||||||
}
|
}
|
||||||
if (!node?.inputs) {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
let height = 5;
|
let height = 5;
|
||||||
|
|
||||||
for (const key in node.inputs) {
|
for (const key in node.inputs) {
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
|
|||||||
key: 'Escape',
|
key: 'Escape',
|
||||||
description: 'Deselect nodes',
|
description: 'Deselect nodes',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
|
if (graph.isInsideGroup) {
|
||||||
|
graphState.exitGroupNode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
graphState.activeNodeId = -1;
|
graphState.activeNodeId = -1;
|
||||||
graphState.clearSelection();
|
graphState.clearSelection();
|
||||||
graphState.edgeEndPosition = null;
|
graphState.edgeEndPosition = null;
|
||||||
@@ -64,6 +68,7 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
|
|||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: 'Tab',
|
key: 'Tab',
|
||||||
|
preventDefault: true,
|
||||||
description: 'Enter selected node group',
|
description: 'Enter selected node group',
|
||||||
callback: () => graphState.enterGroupNode()
|
callback: () => graphState.enterGroupNode()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,14 +34,14 @@
|
|||||||
|
|
||||||
const sectionHeights = $derived(
|
const sectionHeights = $derived(
|
||||||
Object
|
Object
|
||||||
.keys(nodeType.inputs || {})
|
.keys(nodeType?.inputs || {})
|
||||||
.map(key => getParameterHeight(nodeType, key) / 10)
|
.map(key => getParameterHeight(nodeType, key) / 10)
|
||||||
.filter(b => !!b)
|
.filter(b => !!b)
|
||||||
);
|
);
|
||||||
|
|
||||||
let meshRef: Mesh | undefined = $state();
|
let meshRef: Mesh | undefined = $state();
|
||||||
|
|
||||||
const height = $derived(getNodeHeight(nodeType));
|
const height = $derived(nodeType ? getNodeHeight(nodeType) : 20);
|
||||||
|
|
||||||
const zoom = $derived(graphState.cameraPosition[2]);
|
const zoom = $derived(graphState.cameraPosition[2]);
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
let { node = $bindable(), input, id, isLast }: Props = $props();
|
let { node = $bindable(), input, id, isLast }: Props = $props();
|
||||||
|
|
||||||
const nodeType = $derived(graph.getNodeType(node)!);
|
let nodeType = $derived(graph.getNodeType(node)!);
|
||||||
|
|
||||||
const inputType = $derived(nodeType.inputs?.[id]);
|
const inputType = $derived(nodeType.inputs?.[id]);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ export function grid(width: number, height: number) {
|
|||||||
const graph: Graph = {
|
const graph: Graph = {
|
||||||
id: Math.floor(Math.random() * 100000),
|
id: Math.floor(Math.random() * 100000),
|
||||||
edges: [],
|
edges: [],
|
||||||
nodes: []
|
nodes: [],
|
||||||
|
groups: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const amount = width * height;
|
const amount = width * height;
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export function tree(depth: number): Graph {
|
|||||||
return {
|
return {
|
||||||
id: Math.floor(Math.random() * 100000),
|
id: Math.floor(Math.random() * 100000),
|
||||||
nodes,
|
nodes,
|
||||||
edges
|
edges,
|
||||||
|
groups: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
// Only load non-virtual types (virtual nodes are resolved locally)
|
// Only load non-virtual types (virtual nodes are resolved locally)
|
||||||
const nonVirtualTypes = graph.nodes
|
const nonVirtualTypes = graph.nodes
|
||||||
.map(node => node.type)
|
.map(node => node.type)
|
||||||
.filter(t => !t.startsWith('__virtual/'));
|
.filter(t => !t.startsWith('__internal/'));
|
||||||
await this.registry.load(nonVirtualTypes as any);
|
await this.registry.load(nonVirtualTypes as any);
|
||||||
|
|
||||||
const typeMap = new Map<string, NodeDefinition>();
|
const typeMap = new Map<string, NodeDefinition>();
|
||||||
|
|||||||
Reference in New Issue
Block a user