Skip to content

Commit

Permalink
Subgraph Interface Nodes positioning :
Browse files Browse the repository at this point in the history
- position IO Nodes of a Subgraph nicely on Subgraph creation
– store IO nodes as part of Subgraph template to retain their actual position on Subgraph save
  • Loading branch information
yojeek committed Nov 1, 2023
1 parent 8d9b36d commit 03fbde0
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 45 deletions.
15 changes: 8 additions & 7 deletions packages/renderer-vue/src/graph/saveSubgraph.command.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Ref } from "vue";
import { Connection, Graph, IGraphInterface } from "@baklavajs/core";
import type { ICommand, ICommandHandler } from "../commands";
import { SUBGRAPH_INPUT_NODE_TYPE, SUBGRAPH_OUTPUT_NODE_TYPE, InputNode, OutputNode } from "./subgraphInterfaceNodes";
import {
SUBGRAPH_INPUT_NODE_TYPE,
SUBGRAPH_OUTPUT_NODE_TYPE,
SubgraphOutputNode, SubgraphInputNode
} from "./subgraphInterfaceNodes";

export const SAVE_SUBGRAPH_COMMAND = "SAVE_SUBGRAPH";
export type SaveSubgraphCommand = ICommand<void>;
Expand All @@ -17,7 +21,7 @@ export function registerSaveSubgraphCommand(displayedGraph: Ref<Graph>, handler:
const interfaceConnections: Connection[] = [];

const inputs: IGraphInterface[] = [];
const inputNodes = graph.nodes.filter((n) => n.type === SUBGRAPH_INPUT_NODE_TYPE) as InputNode[];
const inputNodes = graph.nodes.filter((n) => n.type === SUBGRAPH_INPUT_NODE_TYPE) as unknown as SubgraphInputNode[];
for (const n of inputNodes) {
const connections = graph.connections.filter((c) => c.from === n.outputs.placeholder);
connections.forEach((c) => {
Expand All @@ -31,7 +35,7 @@ export function registerSaveSubgraphCommand(displayedGraph: Ref<Graph>, handler:
}

const outputs: IGraphInterface[] = [];
const outputNodes = graph.nodes.filter((n) => n.type === SUBGRAPH_OUTPUT_NODE_TYPE) as OutputNode[];
const outputNodes = graph.nodes.filter((n) => n.type === SUBGRAPH_OUTPUT_NODE_TYPE) as unknown as SubgraphOutputNode[];
for (const n of outputNodes) {
const connections = graph.connections.filter((c) => c.to === n.inputs.placeholder);
connections.forEach((c) => {
Expand All @@ -45,15 +49,12 @@ export function registerSaveSubgraphCommand(displayedGraph: Ref<Graph>, handler:
}

const innerConnections = graph.connections.filter((c) => !interfaceConnections.includes(c));
const nodes = graph.nodes.filter(
(n) => n.type !== SUBGRAPH_INPUT_NODE_TYPE && n.type !== SUBGRAPH_OUTPUT_NODE_TYPE,
);

graph.template.update({
inputs,
outputs,
connections: innerConnections.map((c) => ({ id: c.id, from: c.from.id, to: c.to.id })),
nodes: nodes.map((n) => n.save()),
nodes: graph.nodes.map((n) => n.save()),
// will be ignored in the update method but still providing them to make TypeScript happy
panning: graph.panning,
scaling: graph.scaling,
Expand Down
93 changes: 67 additions & 26 deletions packages/renderer-vue/src/graph/subgraphInterfaceNodes.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,80 @@
import { v4 as uuidv4 } from "uuid";
import { defineNode, NodeInstanceOf, NodeInterface } from "@baklavajs/core";
import { INodeState, NodeInstanceOf, NodeInterface, Node } from "@baklavajs/core";
import { TextInputInterface } from "../nodeinterfaces";

export const SUBGRAPH_INPUT_NODE_TYPE = "__baklava_SubgraphInputNode";
export const SUBGRAPH_OUTPUT_NODE_TYPE = "__baklava_SubgraphOutputNode";

export interface ISubgraphInterfaceState<I, O> extends INodeState<I, O> {
graphInterfaceId: string;
}

abstract class SubgraphInterfaceNode<I, O> extends Node<I, O> implements ISubgraphInterfaceNode {
public graphInterfaceId: string;

constructor() {
super();
this.graphInterfaceId = uuidv4();
}

onPlaced() {
super.onPlaced();

this.initializeIo();
}

save(): ISubgraphInterfaceState<I, O> {
return {
...super.save(),
graphInterfaceId: this.graphInterfaceId,
};
}

load(state: ISubgraphInterfaceState<I, O>) {
super.load(state as INodeState<I, O>);
this.graphInterfaceId = state.graphInterfaceId;
}
}

export interface ISubgraphInterfaceNode {
graphInterfaceId: string;
}

export const SubgraphInputNode = defineNode({
type: SUBGRAPH_INPUT_NODE_TYPE,
title: "Subgraph Input",
inputs: {
name: () => new TextInputInterface("Name", "Input").setPort(false),
},
outputs: {
placeholder: () => new NodeInterface("Connection", undefined),
},
onCreate() {
(this as unknown as ISubgraphInterfaceNode).graphInterfaceId = uuidv4();
},
});

export const SubgraphOutputNode = defineNode({
type: SUBGRAPH_OUTPUT_NODE_TYPE,
title: "Subgraph Output",
inputs: {
name: () => new TextInputInterface("Name", "Output").setPort(false),
placeholder: () => new NodeInterface("Connection", undefined),
},
onCreate() {
(this as unknown as ISubgraphInterfaceNode).graphInterfaceId = uuidv4();
},
});
interface SubgraphInputNodeInputs {
name: string;
}

interface SubgraphInputNodeOutputs {
placeholder: string;
}

export class SubgraphInputNode extends SubgraphInterfaceNode<SubgraphInputNodeInputs, SubgraphInputNodeOutputs> implements ISubgraphInterfaceNode{
public readonly type = SUBGRAPH_INPUT_NODE_TYPE;
_title = "Subgraph Input";
public inputs = {
name: new TextInputInterface("Name", "Input").setPort(false),
};
public outputs = {
placeholder: new NodeInterface("Connection", ''),
};
}

interface SubgraphOutputNodeInputs {
name: string;
placeholder: string;
}

interface SubgraphOutputNodeOutputs {}

export class SubgraphOutputNode extends SubgraphInterfaceNode<SubgraphOutputNodeInputs, SubgraphOutputNodeOutputs> implements ISubgraphInterfaceNode{
public readonly type = SUBGRAPH_OUTPUT_NODE_TYPE;
_title = "Subgraph Output";
public inputs = {
name: new TextInputInterface("Name", "Output").setPort(false),
placeholder: new NodeInterface("Connection", ''),
};
public outputs = {};
}

export type InputNode = NodeInstanceOf<typeof SubgraphInputNode> & ISubgraphInterfaceNode;
export type OutputNode = NodeInstanceOf<typeof SubgraphOutputNode> & ISubgraphInterfaceNode;
62 changes: 50 additions & 12 deletions packages/renderer-vue/src/graph/switchGraph.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Ref } from "vue";
import { Editor, Graph, GraphTemplate } from "@baklavajs/core";
import { InputNode, OutputNode, SubgraphInputNode, SubgraphOutputNode } from "./subgraphInterfaceNodes";
import { AbstractNode, Editor, Graph, GraphTemplate } from "@baklavajs/core";
import { SubgraphInputNode, SubgraphOutputNode } from "./subgraphInterfaceNodes";

export type SwitchGraph = (newGraph: Graph | GraphTemplate) => void;

Expand All @@ -21,12 +21,39 @@ export function useSwitchGraph(editor: Ref<Editor>, displayedGraph: Ref<Graph>)
newGraphInstance = new Graph(editor.value);
newGraph.createGraph(newGraphInstance);

// get bounding box of all nodes to place the interface nodes in a nice way
const xRight = newGraphInstance.nodes.reduce((acc: number, cur: AbstractNode) => {
const x = cur.position.x;
return x < acc ? x : acc;
}, Infinity);

const yTop = newGraphInstance.nodes.reduce((acc: number, cur: AbstractNode) => {
const y = cur.position.y;
return y < acc ? y : acc;
}, Infinity);

const xLeft = newGraphInstance.nodes.reduce((acc: number, cur: AbstractNode) => {
const x = cur.position.x + cur.width;
return x > acc ? x : acc;
}, -Infinity);

// create interface nodes
newGraphInstance.inputs.forEach((input) => {
const node = new SubgraphInputNode() as InputNode;
node.inputs.name.value = input.name;
node.graphInterfaceId = input.id;
newGraphInstance.addNode(node);
newGraphInstance.inputs.forEach((input, idx) => {
let node = newGraphInstance.nodes.find(
(n) => n instanceof SubgraphInputNode && n.graphInterfaceId === input.id,
) as SubgraphInputNode;

if (!node) {
node = new SubgraphInputNode();
node.inputs.name.value = input.name;
node.graphInterfaceId = input.id;
node.position = {
x: xRight - 300,
y: yTop + idx * 200
};
newGraphInstance.addNode(node);
}

const targetInterface = newGraphInstance.findNodeInterface(input.nodeInterfaceId);
if (!targetInterface) {
console.warn(`Could not find target interface ${input.nodeInterfaceId} for subgraph input node`);
Expand All @@ -35,11 +62,22 @@ export function useSwitchGraph(editor: Ref<Editor>, displayedGraph: Ref<Graph>)
newGraphInstance.addConnection(node.outputs.placeholder, targetInterface);
});

newGraphInstance.outputs.forEach((output) => {
const node = new SubgraphOutputNode() as OutputNode;
node.inputs.name.value = output.name;
node.graphInterfaceId = output.id;
newGraphInstance.addNode(node);
newGraphInstance.outputs.forEach((output, index) => {
let node = newGraphInstance.nodes.find(
(n) => n instanceof SubgraphOutputNode && n.graphInterfaceId === output.id,
) as SubgraphOutputNode;

if (!node) {
node = new SubgraphOutputNode();
node.inputs.name.value = output.name;
node.graphInterfaceId = output.id;
node.position = {
x: xLeft + 100,
y: yTop + index * 200
};
newGraphInstance.addNode(node);
}

const targetInterface = newGraphInstance.findNodeInterface(output.nodeInterfaceId);
if (!targetInterface) {
console.warn(`Could not find target interface ${output.nodeInterfaceId} for subgraph input node`);
Expand Down

0 comments on commit 03fbde0

Please sign in to comment.