feat: ungroup nodes
📊 Benchmark the Runtime / benchmark (pull_request) Successful in 1m45s
🚀 Lint & Test & Deploy / quality (pull_request) Failing after 1m7s
🚀 Lint & Test & Deploy / test-unit (pull_request) Successful in 35s
🚀 Lint & Test & Deploy / test-e2e (pull_request) Successful in 2m4s
🚀 Lint & Test & Deploy / deploy (pull_request) Has been skipped
📊 Benchmark the Runtime / benchmark (pull_request) Successful in 1m45s
🚀 Lint & Test & Deploy / quality (pull_request) Failing after 1m7s
🚀 Lint & Test & Deploy / test-unit (pull_request) Successful in 35s
🚀 Lint & Test & Deploy / test-e2e (pull_request) Successful in 2m4s
🚀 Lint & Test & Deploy / deploy (pull_request) Has been skipped
This commit is contained in:
@@ -474,8 +474,37 @@ export class GraphManager extends EventEmitter<{
|
|||||||
|
|
||||||
// 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 groupId = node.props?.groupId as number;
|
||||||
|
if (!groupId) {
|
||||||
|
return {
|
||||||
|
...node.state.type,
|
||||||
|
meta: {
|
||||||
|
title: 'Group',
|
||||||
|
...node?.state?.type?.meta || {}
|
||||||
|
},
|
||||||
|
inputs: {
|
||||||
|
'groupId': {
|
||||||
|
type: 'select',
|
||||||
|
label: '',
|
||||||
|
value: this.groups[0].id,
|
||||||
|
internal: true,
|
||||||
|
options: this.groups.map((g) => ({
|
||||||
|
value: g.id,
|
||||||
|
label: g.name || `Group#${g.id}`
|
||||||
|
})).filter((g) => {
|
||||||
|
const activeIds = new SvelteSet([
|
||||||
|
...this.parentStack.filter(e => e.id !== this.id).map(e => e.id),
|
||||||
|
...(this.currentGroupId !== null ? [this.currentGroupId] : [])
|
||||||
|
]);
|
||||||
|
return !activeIds.has(g.value);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
outputs: []
|
||||||
|
} as NodeDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupDefinition = this.getGroup(node.props?.groupId as number);
|
||||||
if (!groupDefinition) {
|
if (!groupDefinition) {
|
||||||
log.error(`Group not found: ${node.props?.groupId}`);
|
log.error(`Group not found: ${node.props?.groupId}`);
|
||||||
return;
|
return;
|
||||||
@@ -834,8 +863,9 @@ export class GraphManager extends EventEmitter<{
|
|||||||
|
|
||||||
const inputs: Record<string, NodeInput> = {};
|
const inputs: Record<string, NodeInput> = {};
|
||||||
[...groupInputs.values()].forEach((edge, i) => {
|
[...groupInputs.values()].forEach((edge, i) => {
|
||||||
|
const internalInputDef = edge[2].state.type?.inputs?.[edge[3]];
|
||||||
const input = {
|
const input = {
|
||||||
label: `Input ${i}`,
|
label: internalInputDef?.label ?? edge[3],
|
||||||
type: edge[0].state.type?.outputs?.[edge[1]] || '*'
|
type: edge[0].state.type?.outputs?.[edge[1]] || '*'
|
||||||
};
|
};
|
||||||
inputs[`input_${i}`] = input as NodeInput;
|
inputs[`input_${i}`] = input as NodeInput;
|
||||||
@@ -844,8 +874,9 @@ export class GraphManager extends EventEmitter<{
|
|||||||
const outputs = [];
|
const outputs = [];
|
||||||
if (groupOutputs.size) {
|
if (groupOutputs.size) {
|
||||||
const edge = groupOutputs.values().next().value!;
|
const edge = groupOutputs.values().next().value!;
|
||||||
|
const outputType = edge[0].state.type?.outputs?.[edge[1]] || '*';
|
||||||
outputs.push({
|
outputs.push({
|
||||||
label: `Output`,
|
label: outputType === '*' ? 'Output' : outputType.charAt(0).toUpperCase() + outputType.slice(1),
|
||||||
type: edge[2].state.type?.inputs?.[edge[3]].type || '*'
|
type: edge[2].state.type?.inputs?.[edge[3]].type || '*'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -963,6 +994,8 @@ export class GraphManager extends EventEmitter<{
|
|||||||
const group = this.getGroup(groupId);
|
const group = this.getGroup(groupId);
|
||||||
if (!group) return false;
|
if (!group) return false;
|
||||||
|
|
||||||
|
log.log('ungrouping node', { groupId, group });
|
||||||
|
|
||||||
this.startUndoGroup();
|
this.startUndoGroup();
|
||||||
|
|
||||||
const edgesToGroup = this.edges.filter(e => e[2].id === nodeId);
|
const edgesToGroup = this.edges.filter(e => e[2].id === nodeId);
|
||||||
@@ -980,8 +1013,12 @@ export class GraphManager extends EventEmitter<{
|
|||||||
centerX += n.position[0];
|
centerX += n.position[0];
|
||||||
centerY += n.position[1];
|
centerY += n.position[1];
|
||||||
}
|
}
|
||||||
const offsetX = internalNodes.length ? groupNode.position[0] - centerX / internalNodes.length : 0;
|
const offsetX = internalNodes.length
|
||||||
const offsetY = internalNodes.length ? groupNode.position[1] - centerY / internalNodes.length : 0;
|
? groupNode.position[0] - centerX / internalNodes.length
|
||||||
|
: 0;
|
||||||
|
const offsetY = internalNodes.length
|
||||||
|
? groupNode.position[1] - centerY / internalNodes.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
// Allocate new IDs that don't collide with anything in the current graph
|
// Allocate new IDs that don't collide with anything in the current graph
|
||||||
const usedIds = new SvelteSet<number>([
|
const usedIds = new SvelteSet<number>([
|
||||||
@@ -997,7 +1034,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Map old internal IDs (including boundary nodes) to fresh IDs
|
// Map old internal IDs (including boundary nodes) to fresh IDs
|
||||||
const idMap = new Map<number, number>();
|
const idMap = new SvelteMap<number, number>();
|
||||||
for (const n of group.nodes) {
|
for (const n of group.nodes) {
|
||||||
idMap.set(n.id, nextId());
|
idMap.set(n.id, nextId());
|
||||||
}
|
}
|
||||||
@@ -1020,7 +1057,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// input_X socket on the group node → the external source that was feeding it
|
// input_X socket on the group node → the external source that was feeding it
|
||||||
const inputIdxToExternal = new Map<number, { node: NodeInstance; socket: number }>();
|
const inputIdxToExternal = new SvelteMap<number, { node: NodeInstance; socket: number }>();
|
||||||
for (const edge of edgesToGroup) {
|
for (const edge of edgesToGroup) {
|
||||||
const match = (edge[3] as string).match(/^input_(\d+)$/);
|
const match = (edge[3] as string).match(/^input_(\d+)$/);
|
||||||
if (match) inputIdxToExternal.set(parseInt(match[1]), { node: edge[0], socket: edge[1] });
|
if (match) inputIdxToExternal.set(parseInt(match[1]), { node: edge[0], socket: edge[1] });
|
||||||
|
|||||||
@@ -213,6 +213,10 @@ export class GraphState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unGroupSelectedNodes() {
|
||||||
|
return this.graph.ungroupNode(this.activeNodeId);
|
||||||
|
}
|
||||||
|
|
||||||
groupSelectedNodes() {
|
groupSelectedNodes() {
|
||||||
return this.graph.groupNodes([...this.selectedNodes.keys(), this.activeNodeId]);
|
return this.graph.groupNodes([...this.selectedNodes.keys(), this.activeNodeId]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,14 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
|
|||||||
callback: () => graphState.groupSelectedNodes()
|
callback: () => graphState.groupSelectedNodes()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: 'g',
|
||||||
|
alt: true,
|
||||||
|
preventDefault: true,
|
||||||
|
description: 'Ungroup selected nodes',
|
||||||
|
callback: () => graphState.unGroupSelectedNodes()
|
||||||
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: 'Tab',
|
key: 'Tab',
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ export const groupNode = {
|
|||||||
id: '__internal/group/instance',
|
id: '__internal/group/instance',
|
||||||
meta: { title: 'Group' },
|
meta: { title: 'Group' },
|
||||||
inputs: {
|
inputs: {
|
||||||
input: {
|
groupId: {
|
||||||
|
label: '',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
values: []
|
values: []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { debounceAsyncFunction } from '$lib/helpers';
|
import { debounceAsyncFunction } from '$lib/helpers';
|
||||||
import { createKeyMap } from '$lib/helpers/createKeyMap';
|
import { createKeyMap } from '$lib/helpers/createKeyMap';
|
||||||
import { debugNode } from '$lib/node-registry/debugNode';
|
import { debugNode } from '$lib/node-registry/debugNode';
|
||||||
|
import { groupNode } from '$lib/node-registry/groupNode.js';
|
||||||
import { IndexDBCache, RemoteNodeRegistry } from '$lib/node-registry/index';
|
import { IndexDBCache, RemoteNodeRegistry } from '$lib/node-registry/index';
|
||||||
import NodeStore from '$lib/node-store/NodeStore.svelte';
|
import NodeStore from '$lib/node-store/NodeStore.svelte';
|
||||||
import PerformanceViewer from '$lib/performance/PerformanceViewer.svelte';
|
import PerformanceViewer from '$lib/performance/PerformanceViewer.svelte';
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
|
|
||||||
const registryCache = new IndexDBCache('node-registry');
|
const registryCache = new IndexDBCache('node-registry');
|
||||||
|
|
||||||
const nodeRegistry = new RemoteNodeRegistry('', registryCache, [debugNode]);
|
const nodeRegistry = new RemoteNodeRegistry('', registryCache, [debugNode, groupNode]);
|
||||||
const workerRuntime = new WorkerRuntimeExecutor();
|
const workerRuntime = new WorkerRuntimeExecutor();
|
||||||
const runtimeCache = new MemoryRuntimeCache();
|
const runtimeCache = new MemoryRuntimeCache();
|
||||||
const memoryRuntime = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
|
const memoryRuntime = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
|
||||||
|
|||||||
Reference in New Issue
Block a user