chore: make some things a bit more typesafe
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
import type { Edge, Graph, Node, NodeInput, NodeRegistry, Socket, } from "@nodes/types";
|
||||
import type {
|
||||
Edge,
|
||||
Graph,
|
||||
Node,
|
||||
NodeInput,
|
||||
NodeRegistry,
|
||||
Socket,
|
||||
} from "@nodes/types";
|
||||
import { fastHashString } from "@nodes/utils";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import EventEmitter from "./helpers/EventEmitter.js";
|
||||
@@ -10,17 +17,29 @@ const logger = createLogger("graph-manager");
|
||||
|
||||
logger.mute();
|
||||
|
||||
const clone = "structuredClone" in self ? self.structuredClone : (args: any) => JSON.parse(JSON.stringify(args));
|
||||
const clone =
|
||||
"structuredClone" in self
|
||||
? self.structuredClone
|
||||
: (args: any) => JSON.parse(JSON.stringify(args));
|
||||
|
||||
function areSocketsCompatible(output: string | undefined, inputs: string | string[] | undefined) {
|
||||
function areSocketsCompatible(
|
||||
output: string | undefined,
|
||||
inputs: string | string[] | undefined,
|
||||
) {
|
||||
if (Array.isArray(inputs) && output) {
|
||||
return inputs.includes(output);
|
||||
}
|
||||
return inputs === output;
|
||||
}
|
||||
|
||||
export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "settings": { types: Record<string, NodeInput>, values: Record<string, unknown> } }> {
|
||||
|
||||
export class GraphManager extends EventEmitter<{
|
||||
save: Graph;
|
||||
result: any;
|
||||
settings: {
|
||||
types: Record<string, NodeInput>;
|
||||
values: Record<string, unknown>;
|
||||
};
|
||||
}> {
|
||||
status: Writable<"loading" | "idle" | "error"> = writable("loading");
|
||||
loaded = false;
|
||||
|
||||
@@ -62,24 +81,32 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
}
|
||||
|
||||
serialize(): Graph {
|
||||
logger.group("serializing graph")
|
||||
const nodes = Array.from(this._nodes.values()).map(node => ({
|
||||
logger.group("serializing graph");
|
||||
const nodes = Array.from(this._nodes.values()).map((node) => ({
|
||||
id: node.id,
|
||||
position: [...node.position],
|
||||
type: node.type,
|
||||
props: node.props,
|
||||
})) as Node[];
|
||||
const edges = this._edges.map(edge => [edge[0].id, edge[1], edge[2].id, edge[3]]) as Graph["edges"];
|
||||
const serialized = { id: this.graph.id, settings: this.settings, nodes, edges };
|
||||
const edges = this._edges.map((edge) => [
|
||||
edge[0].id,
|
||||
edge[1],
|
||||
edge[2].id,
|
||||
edge[3],
|
||||
]) as Graph["edges"];
|
||||
const serialized = {
|
||||
id: this.graph.id,
|
||||
settings: this.settings,
|
||||
nodes,
|
||||
edges,
|
||||
};
|
||||
logger.groupEnd();
|
||||
|
||||
return clone(serialized);
|
||||
}
|
||||
|
||||
|
||||
private lastSettingsHash = 0;
|
||||
setSettings(settings: Record<string, unknown>) {
|
||||
|
||||
let hash = fastHashString(JSON.stringify(settings));
|
||||
if (hash === this.lastSettingsHash) return;
|
||||
this.lastSettingsHash = hash;
|
||||
@@ -89,8 +116,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
this.execute();
|
||||
}
|
||||
|
||||
|
||||
|
||||
getNodeDefinitions() {
|
||||
return this.registry.getAllNodes();
|
||||
}
|
||||
@@ -104,7 +129,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
nodes.add(n);
|
||||
const children = this.getChildrenOfNode(n);
|
||||
const parents = this.getParentsOfNode(n);
|
||||
const newNodes = [...children, ...parents].filter(n => !nodes.has(n));
|
||||
const newNodes = [...children, ...parents].filter((n) => !nodes.has(n));
|
||||
stack.push(...newNodes);
|
||||
}
|
||||
return [...nodes.values()];
|
||||
@@ -116,9 +141,16 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
const children = node.tmp?.children || [];
|
||||
for (const child of children) {
|
||||
if (nodes.includes(child)) {
|
||||
const edge = this._edges.find(e => e[0].id === node.id && e[2].id === child.id);
|
||||
const edge = this._edges.find(
|
||||
(e) => e[0].id === node.id && e[2].id === child.id,
|
||||
);
|
||||
if (edge) {
|
||||
edges.push([edge[0].id, edge[1], edge[2].id, edge[3]] as [number, number, number, string]);
|
||||
edges.push([edge[0].id, edge[1], edge[2].id, edge[3]] as [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
string,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,25 +159,26 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
return edges;
|
||||
}
|
||||
|
||||
|
||||
private _init(graph: Graph) {
|
||||
const nodes = new Map(graph.nodes.map(node => {
|
||||
const nodeType = this.registry.getNode(node.type);
|
||||
if (nodeType) {
|
||||
node.tmp = {
|
||||
random: (Math.random() - 0.5) * 2,
|
||||
type: nodeType
|
||||
};
|
||||
}
|
||||
return [node.id, node]
|
||||
}));
|
||||
const nodes = new Map(
|
||||
graph.nodes.map((node) => {
|
||||
const nodeType = this.registry.getNode(node.type);
|
||||
if (nodeType) {
|
||||
node.tmp = {
|
||||
random: (Math.random() - 0.5) * 2,
|
||||
type: nodeType,
|
||||
};
|
||||
}
|
||||
return [node.id, node];
|
||||
}),
|
||||
);
|
||||
|
||||
const edges = graph.edges.map((edge) => {
|
||||
const from = nodes.get(edge[0]);
|
||||
const to = nodes.get(edge[2]);
|
||||
if (!from || !to) {
|
||||
throw new Error("Edge references non-existing node");
|
||||
};
|
||||
}
|
||||
from.tmp = from.tmp || {};
|
||||
from.tmp.children = from.tmp.children || [];
|
||||
from.tmp.children.push(to);
|
||||
@@ -153,17 +186,15 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
to.tmp.parents = to.tmp.parents || [];
|
||||
to.tmp.parents.push(from);
|
||||
return [from, edge[1], to, edge[3]] as Edge;
|
||||
})
|
||||
});
|
||||
|
||||
this.edges.set(edges);
|
||||
this.nodes.set(nodes);
|
||||
|
||||
this.execute();
|
||||
|
||||
}
|
||||
|
||||
async load(graph: Graph) {
|
||||
|
||||
const a = performance.now();
|
||||
|
||||
this.loaded = false;
|
||||
@@ -171,7 +202,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
this.status.set("loading");
|
||||
this.id.set(graph.id);
|
||||
|
||||
const nodeIds = Array.from(new Set([...graph.nodes.map(n => n.type)]));
|
||||
const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)]));
|
||||
await this.registry.load(nodeIds);
|
||||
|
||||
for (const node of this.graph.nodes) {
|
||||
@@ -186,9 +217,12 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
node.tmp.type = nodeType;
|
||||
}
|
||||
|
||||
|
||||
// load settings
|
||||
const settingTypes: Record<string, NodeInput> = {};
|
||||
const settingTypes: Record<
|
||||
string,
|
||||
// Optional metadata to map settings to specific nodes
|
||||
NodeInput & { __node_type: string; __node_input: string }
|
||||
> = {};
|
||||
const settingValues = graph.settings || {};
|
||||
const types = this.getNodeDefinitions();
|
||||
for (const type of types) {
|
||||
@@ -196,8 +230,15 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
for (const key in type.inputs) {
|
||||
let settingId = type.inputs[key].setting;
|
||||
if (settingId) {
|
||||
settingTypes[settingId] = { __node_type: type.id, __node_input: key, ...type.inputs[key] };
|
||||
if (settingValues[settingId] === undefined && "value" in type.inputs[key]) {
|
||||
settingTypes[settingId] = {
|
||||
__node_type: type.id,
|
||||
__node_input: key,
|
||||
...type.inputs[key],
|
||||
};
|
||||
if (
|
||||
settingValues[settingId] === undefined &&
|
||||
"value" in type.inputs[key]
|
||||
) {
|
||||
settingValues[settingId] = type.inputs[key].value;
|
||||
}
|
||||
}
|
||||
@@ -220,7 +261,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
setTimeout(() => this.execute(), 100);
|
||||
}
|
||||
|
||||
|
||||
getAllNodes() {
|
||||
return Array.from(this._nodes.values());
|
||||
}
|
||||
@@ -234,7 +274,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
}
|
||||
|
||||
async loadNode(id: string) {
|
||||
|
||||
await this.registry.load([id]);
|
||||
const nodeType = this.registry.getNode(id);
|
||||
|
||||
@@ -247,7 +286,10 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
let settingId = nodeType.inputs[key].setting;
|
||||
if (settingId) {
|
||||
settingTypes[settingId] = nodeType.inputs[key];
|
||||
if (settingValues[settingId] === undefined && "value" in nodeType.inputs[key]) {
|
||||
if (
|
||||
settingValues[settingId] === undefined &&
|
||||
"value" in nodeType.inputs[key]
|
||||
) {
|
||||
settingValues[settingId] = nodeType.inputs[key].value;
|
||||
}
|
||||
}
|
||||
@@ -266,7 +308,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
const child = stack.pop();
|
||||
if (!child) continue;
|
||||
children.push(child);
|
||||
stack.push(...child.tmp?.children || []);
|
||||
stack.push(...(child.tmp?.children || []));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
@@ -278,10 +320,10 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
const fromParents = this.getParentsOfNode(from);
|
||||
if (toParents.includes(from)) {
|
||||
const fromChildren = this.getChildrenOfNode(from);
|
||||
return toParents.filter(n => fromChildren.includes(n));
|
||||
return toParents.filter((n) => fromChildren.includes(n));
|
||||
} else if (fromParents.includes(to)) {
|
||||
const toChildren = this.getChildrenOfNode(to);
|
||||
return fromParents.filter(n => toChildren.includes(n));
|
||||
return fromParents.filter((n) => toChildren.includes(n));
|
||||
} else {
|
||||
// these two nodes are not connected
|
||||
return;
|
||||
@@ -289,7 +331,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
}
|
||||
|
||||
removeNode(node: Node, { restoreEdges = false } = {}) {
|
||||
|
||||
const edgesToNode = this._edges.filter((edge) => edge[2].id === node.id);
|
||||
const edgesFromNode = this._edges.filter((edge) => edge[0].id === node.id);
|
||||
for (const edge of [...edgesToNode, ...edgesFromNode]) {
|
||||
@@ -297,15 +338,17 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
}
|
||||
|
||||
if (restoreEdges) {
|
||||
const outputSockets = edgesToNode.map(e => [e[0], e[1]] as const);
|
||||
const inputSockets = edgesFromNode.map(e => [e[2], e[3]] as const);
|
||||
const outputSockets = edgesToNode.map((e) => [e[0], e[1]] as const);
|
||||
const inputSockets = edgesFromNode.map((e) => [e[2], e[3]] as const);
|
||||
|
||||
for (const [to, toSocket] of inputSockets) {
|
||||
for (const [from, fromSocket] of outputSockets) {
|
||||
const outputType = from.tmp?.type?.outputs?.[fromSocket];
|
||||
const inputType = to?.tmp?.type?.inputs?.[toSocket]?.type;
|
||||
if (outputType === inputType) {
|
||||
this.createEdge(from, fromSocket, to, toSocket, { applyUpdate: false });
|
||||
this.createEdge(from, fromSocket, to, toSocket, {
|
||||
applyUpdate: false,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -318,7 +361,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
nodes.delete(node.id);
|
||||
return nodes;
|
||||
});
|
||||
this.execute()
|
||||
this.execute();
|
||||
this.save();
|
||||
}
|
||||
|
||||
@@ -328,7 +371,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
}
|
||||
|
||||
createGraph(nodes: Node[], edges: [number, number, number, string][]) {
|
||||
|
||||
// map old ids to new ids
|
||||
const idMap = new Map<number, number>();
|
||||
|
||||
@@ -344,9 +386,9 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
return { ...node, id, tmp: { type } };
|
||||
});
|
||||
|
||||
const _edges = edges.map(edge => {
|
||||
const from = nodes.find(n => n.id === idMap.get(edge[0]));
|
||||
const to = nodes.find(n => n.id === idMap.get(edge[2]));
|
||||
const _edges = edges.map((edge) => {
|
||||
const from = nodes.find((n) => n.id === idMap.get(edge[0]));
|
||||
const to = nodes.find((n) => n.id === idMap.get(edge[2]));
|
||||
|
||||
if (!from || !to) {
|
||||
throw new Error("Edge references non-existing node");
|
||||
@@ -375,15 +417,28 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
return nodes;
|
||||
}
|
||||
|
||||
createNode({ type, position, props = {} }: { type: Node["type"], position: Node["position"], props: Node["props"] }) {
|
||||
|
||||
createNode({
|
||||
type,
|
||||
position,
|
||||
props = {},
|
||||
}: {
|
||||
type: Node["type"];
|
||||
position: Node["position"];
|
||||
props: Node["props"];
|
||||
}) {
|
||||
const nodeType = this.registry.getNode(type);
|
||||
if (!nodeType) {
|
||||
logger.error(`Node type not found: ${type}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const node: Node = { id: this.createNodeId(), type, position, tmp: { type: nodeType }, props };
|
||||
const node: Node = {
|
||||
id: this.createNodeId(),
|
||||
type,
|
||||
position,
|
||||
tmp: { type: nodeType },
|
||||
props,
|
||||
};
|
||||
|
||||
this.nodes.update((nodes) => {
|
||||
nodes.set(node.id, node);
|
||||
@@ -393,16 +448,23 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
this.save();
|
||||
}
|
||||
|
||||
createEdge(from: Node, fromSocket: number, to: Node, toSocket: string, { applyUpdate = true } = {}) {
|
||||
|
||||
createEdge(
|
||||
from: Node,
|
||||
fromSocket: number,
|
||||
to: Node,
|
||||
toSocket: string,
|
||||
{ applyUpdate = true } = {},
|
||||
) {
|
||||
const existingEdges = this.getEdgesToNode(to);
|
||||
|
||||
// check if this exact edge already exists
|
||||
const existingEdge = existingEdges.find(e => e[0].id === from.id && e[1] === fromSocket && e[3] === toSocket);
|
||||
const existingEdge = existingEdges.find(
|
||||
(e) => e[0].id === from.id && e[1] === fromSocket && e[3] === toSocket,
|
||||
);
|
||||
if (existingEdge) {
|
||||
logger.error("Edge already exists", existingEdge);
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
// check if socket types match
|
||||
const fromSocketType = from.tmp?.type?.outputs?.[fromSocket];
|
||||
@@ -412,11 +474,15 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
}
|
||||
|
||||
if (!areSocketsCompatible(fromSocketType, toSocketType)) {
|
||||
logger.error(`Socket types do not match: ${fromSocketType} !== ${toSocketType}`);
|
||||
logger.error(
|
||||
`Socket types do not match: ${fromSocketType} !== ${toSocketType}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const edgeToBeReplaced = this._edges.find(e => e[2].id === to.id && e[3] === toSocket);
|
||||
const edgeToBeReplaced = this._edges.find(
|
||||
(e) => e[2].id === to.id && e[3] === toSocket,
|
||||
);
|
||||
if (edgeToBeReplaced) {
|
||||
this.removeEdge(edgeToBeReplaced, { applyDeletion: false });
|
||||
}
|
||||
@@ -450,14 +516,12 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
redo() {
|
||||
const nextState = this.history.redo();
|
||||
if (nextState) {
|
||||
this._init(nextState);
|
||||
this.emit("save", this.serialize());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
startUndoGroup() {
|
||||
@@ -482,30 +546,30 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
const stack = node.tmp?.parents?.slice(0);
|
||||
while (stack?.length) {
|
||||
if (parents.length > 1000000) {
|
||||
logger.warn("Infinite loop detected")
|
||||
logger.warn("Infinite loop detected");
|
||||
break;
|
||||
}
|
||||
const parent = stack.pop();
|
||||
if (!parent) continue;
|
||||
parents.push(parent);
|
||||
stack.push(...parent.tmp?.parents || []);
|
||||
stack.push(...(parent.tmp?.parents || []));
|
||||
}
|
||||
return parents.reverse();
|
||||
}
|
||||
|
||||
getPossibleSockets({ node, index }: Socket): [Node, string | number][] {
|
||||
|
||||
const nodeType = node?.tmp?.type;
|
||||
if (!nodeType) return [];
|
||||
|
||||
const sockets: [Node, string | number][] = []
|
||||
const sockets: [Node, string | number][] = [];
|
||||
|
||||
// if index is a string, we are an input looking for outputs
|
||||
if (typeof index === "string") {
|
||||
|
||||
// filter out self and child nodes
|
||||
const children = new Set(this.getChildrenOfNode(node).map(n => n.id));
|
||||
const nodes = this.getAllNodes().filter(n => n.id !== node.id && !children.has(n.id));
|
||||
const children = new Set(this.getChildrenOfNode(node).map((n) => n.id));
|
||||
const nodes = this.getAllNodes().filter(
|
||||
(n) => n.id !== node.id && !children.has(n.id),
|
||||
);
|
||||
|
||||
const ownType = nodeType?.inputs?.[index].type;
|
||||
|
||||
@@ -519,16 +583,21 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (typeof index === "number") {
|
||||
// if index is a number, we are an output looking for inputs
|
||||
|
||||
// filter out self and parent nodes
|
||||
const parents = new Set(this.getParentsOfNode(node).map(n => n.id));
|
||||
const nodes = this.getAllNodes().filter(n => n.id !== node.id && !parents.has(n.id));
|
||||
const parents = new Set(this.getParentsOfNode(node).map((n) => n.id));
|
||||
const nodes = this.getAllNodes().filter(
|
||||
(n) => n.id !== node.id && !parents.has(n.id),
|
||||
);
|
||||
|
||||
// get edges from this socket
|
||||
const edges = new Map(this.getEdgesFromNode(node).filter(e => e[1] === index).map(e => [e[2].id, e[3]]));
|
||||
const edges = new Map(
|
||||
this.getEdgesFromNode(node)
|
||||
.filter((e) => e[1] === index)
|
||||
.map((e) => [e[2].id, e[3]]),
|
||||
);
|
||||
|
||||
const ownType = nodeType.outputs?.[index];
|
||||
|
||||
@@ -536,11 +605,13 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
const inputs = node?.tmp?.type?.inputs;
|
||||
if (!inputs) continue;
|
||||
for (const key in inputs) {
|
||||
|
||||
const otherType = [inputs[key].type];
|
||||
otherType.push(...(inputs[key].accepts || []));
|
||||
|
||||
if (areSocketsCompatible(ownType, otherType) && edges.get(node.id) !== key) {
|
||||
if (
|
||||
areSocketsCompatible(ownType, otherType) &&
|
||||
edges.get(node.id) !== key
|
||||
) {
|
||||
sockets.push([node, key]);
|
||||
}
|
||||
}
|
||||
@@ -548,38 +619,46 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
}
|
||||
|
||||
return sockets;
|
||||
|
||||
}
|
||||
|
||||
removeEdge(edge: Edge, { applyDeletion = true }: { applyDeletion?: boolean } = {}) {
|
||||
removeEdge(
|
||||
edge: Edge,
|
||||
{ applyDeletion = true }: { applyDeletion?: boolean } = {},
|
||||
) {
|
||||
const id0 = edge[0].id;
|
||||
const sid0 = edge[1];
|
||||
const id2 = edge[2].id;
|
||||
const sid2 = edge[3];
|
||||
|
||||
const _edge = this._edges.find((e) => e[0].id === id0 && e[1] === sid0 && e[2].id === id2 && e[3] === sid2);
|
||||
const _edge = this._edges.find(
|
||||
(e) =>
|
||||
e[0].id === id0 && e[1] === sid0 && e[2].id === id2 && e[3] === sid2,
|
||||
);
|
||||
if (!_edge) return;
|
||||
|
||||
edge[0].tmp = edge[0].tmp || {};
|
||||
if (edge[0].tmp.children) {
|
||||
edge[0].tmp.children = edge[0].tmp.children.filter(n => n.id !== id2);
|
||||
edge[0].tmp.children = edge[0].tmp.children.filter(
|
||||
(n: Node) => n.id !== id2,
|
||||
);
|
||||
}
|
||||
|
||||
edge[2].tmp = edge[2].tmp || {};
|
||||
if (edge[2].tmp.parents) {
|
||||
edge[2].tmp.parents = edge[2].tmp.parents.filter(n => n.id !== id0);
|
||||
edge[2].tmp.parents = edge[2].tmp.parents.filter(
|
||||
(n: Node) => n.id !== id0,
|
||||
);
|
||||
}
|
||||
|
||||
if (applyDeletion) {
|
||||
this.edges.update((edges) => {
|
||||
return edges.filter(e => e !== _edge);
|
||||
return edges.filter((e) => e !== _edge);
|
||||
});
|
||||
this.execute();
|
||||
this.save();
|
||||
} else {
|
||||
this._edges = this._edges.filter(e => e !== _edge);
|
||||
this._edges = this._edges.filter((e) => e !== _edge);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getEdgesToNode(node: Node) {
|
||||
@@ -605,7 +684,4 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
})
|
||||
.filter(Boolean) as unknown as [Node, number, Node, string][];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user