62 lines
4.9 KiB
TypeScript
62 lines
4.9 KiB
TypeScript
import type { CanvasDocument } from "./canvas.ts";
|
|
export function subgraphParents(documents: Map<string, CanvasDocument>) {
|
|
const parents = new Map<string, { boardId: string; frameId: string }>();
|
|
for (const [boardId, document] of documents) for (const node of document.nodes) if (node.data.subgraph) parents.set(node.data.subgraph.boardId, { boardId, frameId: node.id });
|
|
return parents;
|
|
}
|
|
export function subgraphMoveParticipants(documents: Map<string, CanvasDocument>, parent: string, source: string, target: string, ids: string[]) {
|
|
const participants = new Set([parent, source, target]);
|
|
const parents = subgraphParents(documents);
|
|
const addDescendants = (board: string, seen = new Set<string>()) => {
|
|
if (seen.has(board)) throw new Error("subgraph_cycle"); seen.add(board); participants.add(board);
|
|
for (const node of documents.get(board)?.nodes || []) if (node.data.subgraph) addDescendants(node.data.subgraph.boardId, seen);
|
|
};
|
|
for (const node of documents.get(source)?.nodes || []) if (ids.includes(node.id) && node.data.subgraph) addDescendants(node.data.subgraph.boardId);
|
|
const ancestor = parents.get(parent);
|
|
if (ancestor) {
|
|
const frame = documents.get(ancestor.boardId)?.nodes.find((node) => node.id === ancestor.frameId);
|
|
const affected = frame?.data.ports?.some((port) => {
|
|
if (source === parent) return ids.includes(port.nodeId);
|
|
const inner = documents.get(parent)?.nodes.find((node) => node.id === port.nodeId);
|
|
return inner?.data.subgraph?.boardId === source && inner.data.ports?.some((p) => `port:${p.id}` === port.handle && p.direction === port.direction && ids.includes(p.nodeId));
|
|
});
|
|
if (affected) participants.add(ancestor.boardId);
|
|
}
|
|
return [...participants];
|
|
}
|
|
export function subgraphMovePath(documents: Map<string, CanvasDocument>, source: string, target: string) {
|
|
const parents = subgraphParents(documents);
|
|
const chain = (board: string) => { const result = [board]; while (parents.has(board)) { board = parents.get(board)!.boardId; if (result.includes(board)) throw new Error("subgraph_cycle"); result.push(board); } return result; };
|
|
const from = chain(source), to = chain(target), common = from.find((id) => to.includes(id));
|
|
if (!common) throw new Error("ownership_conflict");
|
|
const path: { parent: string; frame: string; source: string; target: string }[] = [];
|
|
for (let i = 0; from[i] !== common; i++) { const relation = parents.get(from[i])!; path.push({ parent: relation.boardId, frame: relation.frameId, source: from[i], target: relation.boardId }); }
|
|
for (let i = to.indexOf(common) - 1; i >= 0; i--) { const relation = parents.get(to[i])!; path.push({ parent: relation.boardId, frame: relation.frameId, source: relation.boardId, target: to[i] }); }
|
|
return path;
|
|
}
|
|
export function exposedConnection(documents: Map<string, CanvasDocument>, source: { boardId: string; id: string; handle?: string | null }, target: { boardId: string; id: string; handle?: string | null }) {
|
|
const parents = subgraphParents(documents);
|
|
const ancestors = (id: string) => { const result = [id]; while (parents.has(id)) { id = parents.get(id)!.boardId; if (result.includes(id)) throw new Error("subgraph_cycle"); result.push(id); } return result; };
|
|
const owner = ancestors(source.boardId).find((id) => ancestors(target.boardId).includes(id));
|
|
if (!owner) throw new Error("ownership_conflict");
|
|
const expose = (endpoint: typeof source, direction: "input" | "output") => {
|
|
let { boardId, id, handle } = endpoint;
|
|
while (boardId !== owner) {
|
|
const parent = parents.get(boardId)!;
|
|
const frame = documents.get(parent.boardId)?.nodes.find((node) => node.id === parent.frameId);
|
|
const port = frame?.data.ports?.find((port) => port.nodeId === id && port.handle === handle && port.direction === direction);
|
|
if (!port) throw new Error("boundary_port_required");
|
|
id = parent.frameId; handle = `port:${port.id}`; boardId = parent.boardId;
|
|
}
|
|
return { id, handle };
|
|
};
|
|
return { boardId: owner, source: expose(source, "output"), target: expose(target, "input") };
|
|
}
|
|
export function connectionTransactionPlan(documents: Map<string, CanvasDocument>, source: { boardId: string; id: string; handle: string }, target: { boardId: string; id: string; handle: string }) {
|
|
const parents = subgraphParents(documents);
|
|
const path = (id: string) => { const result = [id]; while (parents.has(id)) { id = parents.get(id)!.boardId; if (result.includes(id)) throw new Error("subgraph_cycle"); result.push(id); } return result; };
|
|
const from = path(source.boardId), to = path(target.boardId), owner = from.find((id) => to.includes(id));
|
|
if (!owner) throw new Error("ownership_conflict");
|
|
return { boardId: owner, participants: [...new Set([...from.slice(0, from.indexOf(owner) + 1), ...to.slice(0, to.indexOf(owner) + 1)])], source: { board_id: source.boardId, node_id: source.id, handle: source.handle }, target: { board_id: target.boardId, node_id: target.id, handle: target.handle } };
|
|
}
|