feat: initial group entering ui
📊 Benchmark the Runtime / benchmark (pull_request) Successful in 1m7s
🚀 Lint & Test & Deploy / quality (pull_request) Failing after 45s
🚀 Lint & Test & Deploy / test-unit (pull_request) Failing after 30s
🚀 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 1m7s
🚀 Lint & Test & Deploy / quality (pull_request) Failing after 45s
🚀 Lint & Test & Deploy / test-unit (pull_request) Failing after 30s
🚀 Lint & Test & Deploy / test-e2e (pull_request) Failing after 33s
🚀 Lint & Test & Deploy / deploy (pull_request) Has been skipped
This commit is contained in:
@@ -76,6 +76,13 @@ export class GraphManager extends EventEmitter<{
|
||||
|
||||
edges = $state<Edge[]>([]);
|
||||
|
||||
graphStack: {
|
||||
rootGraph: Graph;
|
||||
groupId: number;
|
||||
nodeId: number;
|
||||
cameraPosition: [number, number, number];
|
||||
}[] = $state([]);
|
||||
|
||||
settingTypes: Record<string, NodeInput> = {};
|
||||
settings = $state<Record<string, unknown>>();
|
||||
|
||||
@@ -90,9 +97,25 @@ export class GraphManager extends EventEmitter<{
|
||||
});
|
||||
|
||||
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(() => {
|
||||
if (this.loaded === false) return;
|
||||
this.emit('result', this.serialize());
|
||||
this.emit('result', this.serializeFullGraph());
|
||||
}, 10);
|
||||
|
||||
constructor(public registry: NodeRegistry) {
|
||||
@@ -290,13 +313,11 @@ export class GraphManager extends EventEmitter<{
|
||||
private _init(graph: Graph) {
|
||||
const nodes = new SvelteMap(
|
||||
graph.nodes.map((node) => {
|
||||
const nodeType = this.registry.getNode(node.type);
|
||||
const n = node as NodeInstance;
|
||||
if (nodeType) {
|
||||
n.state = {
|
||||
type: nodeType
|
||||
};
|
||||
}
|
||||
const n = { ...node } as NodeInstance;
|
||||
const registryType = this.registry.getNode(node.type);
|
||||
n.state = registryType ? { type: registryType } : {};
|
||||
const resolvedType = this.getNodeType(n);
|
||||
if (resolvedType) n.state = { type: resolvedType };
|
||||
return [node.id, n];
|
||||
})
|
||||
);
|
||||
@@ -436,6 +457,31 @@ export class GraphManager extends EventEmitter<{
|
||||
}
|
||||
|
||||
getNodeType(node: NodeInstance) {
|
||||
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 }])
|
||||
),
|
||||
outputs: [],
|
||||
execute: (x: Int32Array) => x
|
||||
} as NodeDefinition;
|
||||
}
|
||||
|
||||
// Construct the group inputs on the fly
|
||||
if (node.type === '__internal/group/instance') {
|
||||
const groupDefinition = this.getGroup(node.props?.groupId as number);
|
||||
@@ -576,6 +622,41 @@ export class GraphManager extends EventEmitter<{
|
||||
return this.graph.groups.find(g => g.id === id);
|
||||
}
|
||||
|
||||
get isInsideGroup() {
|
||||
return this.graphStack.length > 0;
|
||||
}
|
||||
|
||||
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(), groupId, nodeId, cameraPosition });
|
||||
this.graph = { ...this.graph, nodes: group.nodes, edges: group.edges };
|
||||
this._init(this.graph);
|
||||
this.history.reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
exitGroup(): { camera: [number, number, number]; nodeId: number } | false {
|
||||
if (!this.graphStack.length) return false;
|
||||
const { rootGraph, groupId, nodeId, cameraPosition } = this.graphStack.pop()!;
|
||||
const internalState = this.serialize();
|
||||
const updatedRoot = {
|
||||
...rootGraph,
|
||||
groups: rootGraph.groups.map(g =>
|
||||
g.id === groupId ? { ...g, nodes: internalState.nodes, edges: internalState.edges } : g
|
||||
)
|
||||
};
|
||||
this.graph = updatedRoot;
|
||||
this._init(updatedRoot);
|
||||
this.history.reset();
|
||||
this.save();
|
||||
return { camera: cameraPosition, nodeId };
|
||||
}
|
||||
|
||||
createNodeId() {
|
||||
const ids = [
|
||||
...this.nodes.keys(),
|
||||
@@ -900,8 +981,9 @@ export class GraphManager extends EventEmitter<{
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('save', state);
|
||||
logger.log('saving graphs', state);
|
||||
const fullState = this.graphStack.length > 0 ? this.serializeFullGraph() : state;
|
||||
this.emit('save', fullState);
|
||||
logger.log('saving graphs', fullState);
|
||||
}
|
||||
|
||||
getParentsOfNode(node: NodeInstance) {
|
||||
|
||||
@@ -388,8 +388,21 @@ export class GraphState {
|
||||
|
||||
enterGroupNode() {
|
||||
if (this.activeNodeId === -1) return;
|
||||
const selectedNode = this.graph.getNode(this.activeNodeId);
|
||||
if (!selectedNode || selectedNode.type.startsWith('__internal/group/instance')) return;
|
||||
const node = this.graph.getNode(this.activeNodeId);
|
||||
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(
|
||||
|
||||
@@ -169,6 +169,12 @@
|
||||
{/if}
|
||||
|
||||
{#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}
|
||||
<AddMenu
|
||||
onnode={handleNodeCreation}
|
||||
@@ -244,6 +250,26 @@
|
||||
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 {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
|
||||
@@ -47,6 +47,10 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
|
||||
key: 'Escape',
|
||||
description: 'Deselect nodes',
|
||||
callback: () => {
|
||||
if (graph.isInsideGroup) {
|
||||
graphState.exitGroupNode();
|
||||
return;
|
||||
}
|
||||
graphState.activeNodeId = -1;
|
||||
graphState.clearSelection();
|
||||
graphState.edgeEndPosition = null;
|
||||
|
||||
@@ -4,7 +4,8 @@ export function grid(width: number, height: number) {
|
||||
const graph: Graph = {
|
||||
id: Math.floor(Math.random() * 100000),
|
||||
edges: [],
|
||||
nodes: []
|
||||
nodes: [],
|
||||
groups: []
|
||||
};
|
||||
|
||||
const amount = width * height;
|
||||
|
||||
@@ -47,6 +47,7 @@ export function tree(depth: number): Graph {
|
||||
return {
|
||||
id: Math.floor(Math.random() * 100000),
|
||||
nodes,
|
||||
edges
|
||||
edges,
|
||||
groups: []
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user