@shumoku/core 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hierarchical.d.ts +35 -0
- package/dist/hierarchical.d.ts.map +1 -0
- package/dist/hierarchical.js +176 -0
- package/dist/hierarchical.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/hierarchical.ts +243 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchical sheet generation utilities
|
|
3
|
+
* Shared logic for building child sheets with export connectors
|
|
4
|
+
*/
|
|
5
|
+
import type { LayoutResult, NetworkGraph } from './models/types.js';
|
|
6
|
+
export interface SheetData {
|
|
7
|
+
graph: NetworkGraph;
|
|
8
|
+
layout: LayoutResult;
|
|
9
|
+
}
|
|
10
|
+
export interface LayoutEngine {
|
|
11
|
+
layoutAsync(graph: NetworkGraph): Promise<LayoutResult>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Check if a node is a virtual export connector
|
|
15
|
+
*/
|
|
16
|
+
export declare function isExportNode(nodeId: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Check if a link is a virtual export connector link
|
|
19
|
+
*/
|
|
20
|
+
export declare function isExportLink(linkId: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Build hierarchical sheets from a root graph
|
|
23
|
+
*
|
|
24
|
+
* Creates child sheets for each subgraph with:
|
|
25
|
+
* - Filtered nodes (only those belonging to the subgraph)
|
|
26
|
+
* - Internal links (both endpoints in subgraph)
|
|
27
|
+
* - Export connector nodes/links for boundary connections
|
|
28
|
+
*
|
|
29
|
+
* @param graph - Root network graph with subgraphs
|
|
30
|
+
* @param rootLayout - Layout result for the root graph
|
|
31
|
+
* @param layoutEngine - Engine to layout child sheets
|
|
32
|
+
* @returns Map of sheet ID to SheetData (includes 'root')
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildHierarchicalSheets(graph: NetworkGraph, rootLayout: LayoutResult, layoutEngine: LayoutEngine): Promise<Map<string, SheetData>>;
|
|
35
|
+
//# sourceMappingURL=hierarchical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchical.d.ts","sourceRoot":"","sources":["../src/hierarchical.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAQ,YAAY,EAAkB,MAAM,mBAAmB,CAAA;AAazF,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,YAAY,CAAA;IACnB,MAAM,EAAE,YAAY,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CACxD;AAgBD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEpD;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,YAAY,EACxB,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAsBjC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchical sheet generation utilities
|
|
3
|
+
* Shared logic for building child sheets with export connectors
|
|
4
|
+
*/
|
|
5
|
+
// ============================================
|
|
6
|
+
// Constants
|
|
7
|
+
// ============================================
|
|
8
|
+
const EXPORT_NODE_PREFIX = '__export_';
|
|
9
|
+
const EXPORT_LINK_PREFIX = '__export_link_';
|
|
10
|
+
// ============================================
|
|
11
|
+
// Type Guards
|
|
12
|
+
// ============================================
|
|
13
|
+
/**
|
|
14
|
+
* Check if a node is a virtual export connector
|
|
15
|
+
*/
|
|
16
|
+
export function isExportNode(nodeId) {
|
|
17
|
+
return nodeId.startsWith(EXPORT_NODE_PREFIX);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check if a link is a virtual export connector link
|
|
21
|
+
*/
|
|
22
|
+
export function isExportLink(linkId) {
|
|
23
|
+
return linkId.startsWith(EXPORT_LINK_PREFIX);
|
|
24
|
+
}
|
|
25
|
+
// ============================================
|
|
26
|
+
// Main Function
|
|
27
|
+
// ============================================
|
|
28
|
+
/**
|
|
29
|
+
* Build hierarchical sheets from a root graph
|
|
30
|
+
*
|
|
31
|
+
* Creates child sheets for each subgraph with:
|
|
32
|
+
* - Filtered nodes (only those belonging to the subgraph)
|
|
33
|
+
* - Internal links (both endpoints in subgraph)
|
|
34
|
+
* - Export connector nodes/links for boundary connections
|
|
35
|
+
*
|
|
36
|
+
* @param graph - Root network graph with subgraphs
|
|
37
|
+
* @param rootLayout - Layout result for the root graph
|
|
38
|
+
* @param layoutEngine - Engine to layout child sheets
|
|
39
|
+
* @returns Map of sheet ID to SheetData (includes 'root')
|
|
40
|
+
*/
|
|
41
|
+
export async function buildHierarchicalSheets(graph, rootLayout, layoutEngine) {
|
|
42
|
+
const sheets = new Map();
|
|
43
|
+
// Add root sheet
|
|
44
|
+
sheets.set('root', { graph, layout: rootLayout });
|
|
45
|
+
if (!graph.subgraphs || graph.subgraphs.length === 0) {
|
|
46
|
+
return sheets;
|
|
47
|
+
}
|
|
48
|
+
// Mark subgraphs as clickable
|
|
49
|
+
for (const sg of graph.subgraphs) {
|
|
50
|
+
sg.file = sg.id;
|
|
51
|
+
}
|
|
52
|
+
// Build child sheets
|
|
53
|
+
for (const sg of graph.subgraphs) {
|
|
54
|
+
const childSheet = await buildChildSheet(graph, sg, layoutEngine);
|
|
55
|
+
sheets.set(sg.id, childSheet);
|
|
56
|
+
}
|
|
57
|
+
return sheets;
|
|
58
|
+
}
|
|
59
|
+
// ============================================
|
|
60
|
+
// Internal Functions
|
|
61
|
+
// ============================================
|
|
62
|
+
async function buildChildSheet(rootGraph, subgraph, layoutEngine) {
|
|
63
|
+
// Get nodes belonging to this subgraph
|
|
64
|
+
const childNodes = rootGraph.nodes.filter((n) => n.parent === subgraph.id);
|
|
65
|
+
const childNodeIds = new Set(childNodes.map((n) => n.id));
|
|
66
|
+
// Get internal links (both endpoints in subgraph)
|
|
67
|
+
const childLinks = rootGraph.links.filter((l) => {
|
|
68
|
+
const fromNode = typeof l.from === 'string' ? l.from : l.from.node;
|
|
69
|
+
const toNode = typeof l.to === 'string' ? l.to : l.to.node;
|
|
70
|
+
return childNodeIds.has(fromNode) && childNodeIds.has(toNode);
|
|
71
|
+
});
|
|
72
|
+
// Generate export connectors for boundary connections
|
|
73
|
+
const { exportNodes, exportLinks } = generateExportConnectors(rootGraph, subgraph.id, childNodeIds);
|
|
74
|
+
// Build child graph
|
|
75
|
+
const childGraph = {
|
|
76
|
+
...rootGraph,
|
|
77
|
+
name: subgraph.label,
|
|
78
|
+
nodes: [
|
|
79
|
+
...childNodes.map((n) => ({ ...n, parent: undefined })),
|
|
80
|
+
...exportNodes,
|
|
81
|
+
],
|
|
82
|
+
links: [...childLinks, ...exportLinks],
|
|
83
|
+
subgraphs: undefined,
|
|
84
|
+
};
|
|
85
|
+
// Layout child sheet
|
|
86
|
+
const childLayout = await layoutEngine.layoutAsync(childGraph);
|
|
87
|
+
return { graph: childGraph, layout: childLayout };
|
|
88
|
+
}
|
|
89
|
+
function generateExportConnectors(rootGraph, subgraphId, childNodeIds) {
|
|
90
|
+
const exportNodes = [];
|
|
91
|
+
const exportLinks = [];
|
|
92
|
+
const exportPoints = new Map();
|
|
93
|
+
// Find boundary links
|
|
94
|
+
for (const link of rootGraph.links) {
|
|
95
|
+
const fromNode = typeof link.from === 'string' ? link.from : link.from.node;
|
|
96
|
+
const toNode = typeof link.to === 'string' ? link.to : link.to.node;
|
|
97
|
+
const fromPort = typeof link.from === 'object' ? link.from.port : undefined;
|
|
98
|
+
const toPort = typeof link.to === 'object' ? link.to.port : undefined;
|
|
99
|
+
const fromInside = childNodeIds.has(fromNode);
|
|
100
|
+
const toInside = childNodeIds.has(toNode);
|
|
101
|
+
if (fromInside && !toInside) {
|
|
102
|
+
// Outgoing connection
|
|
103
|
+
const key = `${subgraphId}:${fromNode}:${fromPort || ''}`;
|
|
104
|
+
if (!exportPoints.has(key)) {
|
|
105
|
+
const destSubgraph = findNodeSubgraph(rootGraph, toNode);
|
|
106
|
+
exportPoints.set(key, {
|
|
107
|
+
subgraphId,
|
|
108
|
+
device: fromNode,
|
|
109
|
+
port: fromPort,
|
|
110
|
+
destSubgraphLabel: destSubgraph?.label || toNode,
|
|
111
|
+
destDevice: toNode,
|
|
112
|
+
destPort: toPort,
|
|
113
|
+
isSource: true,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (!fromInside && toInside) {
|
|
118
|
+
// Incoming connection
|
|
119
|
+
const key = `${subgraphId}:${toNode}:${toPort || ''}`;
|
|
120
|
+
if (!exportPoints.has(key)) {
|
|
121
|
+
const destSubgraph = findNodeSubgraph(rootGraph, fromNode);
|
|
122
|
+
exportPoints.set(key, {
|
|
123
|
+
subgraphId,
|
|
124
|
+
device: toNode,
|
|
125
|
+
port: toPort,
|
|
126
|
+
destSubgraphLabel: destSubgraph?.label || fromNode,
|
|
127
|
+
destDevice: fromNode,
|
|
128
|
+
destPort: fromPort,
|
|
129
|
+
isSource: false,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Create export nodes and links
|
|
135
|
+
for (const [key, exportPoint] of exportPoints) {
|
|
136
|
+
const exportId = key.replace(/:/g, '_');
|
|
137
|
+
// Export node
|
|
138
|
+
exportNodes.push({
|
|
139
|
+
id: `${EXPORT_NODE_PREFIX}${exportId}`,
|
|
140
|
+
label: exportPoint.destSubgraphLabel,
|
|
141
|
+
shape: 'stadium',
|
|
142
|
+
metadata: {
|
|
143
|
+
_isExport: true,
|
|
144
|
+
_destSubgraph: exportPoint.destSubgraphLabel,
|
|
145
|
+
_destDevice: exportPoint.destDevice,
|
|
146
|
+
_destPort: exportPoint.destPort,
|
|
147
|
+
_isSource: exportPoint.isSource,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
// Export link
|
|
151
|
+
const exportNodeId = `${EXPORT_NODE_PREFIX}${exportId}`;
|
|
152
|
+
const deviceEndpoint = exportPoint.port
|
|
153
|
+
? { node: exportPoint.device, port: exportPoint.port }
|
|
154
|
+
: exportPoint.device;
|
|
155
|
+
exportLinks.push({
|
|
156
|
+
id: `${EXPORT_LINK_PREFIX}${exportId}`,
|
|
157
|
+
from: exportPoint.isSource ? deviceEndpoint : exportNodeId,
|
|
158
|
+
to: exportPoint.isSource ? exportNodeId : deviceEndpoint,
|
|
159
|
+
type: 'dashed',
|
|
160
|
+
arrow: 'forward',
|
|
161
|
+
metadata: {
|
|
162
|
+
_destSubgraphLabel: exportPoint.destSubgraphLabel,
|
|
163
|
+
_destDevice: exportPoint.destDevice,
|
|
164
|
+
_destPort: exportPoint.destPort,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return { exportNodes, exportLinks };
|
|
169
|
+
}
|
|
170
|
+
function findNodeSubgraph(graph, nodeId) {
|
|
171
|
+
const node = graph.nodes.find((n) => n.id === nodeId);
|
|
172
|
+
if (!node?.parent)
|
|
173
|
+
return undefined;
|
|
174
|
+
return graph.subgraphs?.find((s) => s.id === node.parent);
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=hierarchical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchical.js","sourceRoot":"","sources":["../src/hierarchical.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,gBAAgB,CAAA;AAE9B,cAAc,kBAAkB,CAAA;AAEhC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAGjC,eAAO,MAAM,OAAO,UAAU,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,gBAAgB,CAAA;AAE9B,cAAc,mBAAmB,CAAA;AAEjC,cAAc,kBAAkB,CAAA;AAEhC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAGjC,eAAO,MAAM,OAAO,UAAU,CAAA"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY;AACZ,cAAc,gBAAgB,CAAA;AAC9B,QAAQ;AACR,cAAc,kBAAkB,CAAA;AAChC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AAEjC,UAAU;AACV,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY;AACZ,cAAc,gBAAgB,CAAA;AAC9B,eAAe;AACf,cAAc,mBAAmB,CAAA;AACjC,QAAQ;AACR,cAAc,kBAAkB,CAAA;AAChC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AAEjC,UAAU;AACV,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchical sheet generation utilities
|
|
3
|
+
* Shared logic for building child sheets with export connectors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { LayoutResult, Link, NetworkGraph, Node, Subgraph } from './models/types.js'
|
|
7
|
+
|
|
8
|
+
// ============================================
|
|
9
|
+
// Constants
|
|
10
|
+
// ============================================
|
|
11
|
+
|
|
12
|
+
const EXPORT_NODE_PREFIX = '__export_'
|
|
13
|
+
const EXPORT_LINK_PREFIX = '__export_link_'
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// Types
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
export interface SheetData {
|
|
20
|
+
graph: NetworkGraph
|
|
21
|
+
layout: LayoutResult
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface LayoutEngine {
|
|
25
|
+
layoutAsync(graph: NetworkGraph): Promise<LayoutResult>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ExportPoint {
|
|
29
|
+
subgraphId: string
|
|
30
|
+
device: string
|
|
31
|
+
port?: string
|
|
32
|
+
destSubgraphLabel: string
|
|
33
|
+
destDevice: string
|
|
34
|
+
destPort?: string
|
|
35
|
+
isSource: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ============================================
|
|
39
|
+
// Type Guards
|
|
40
|
+
// ============================================
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if a node is a virtual export connector
|
|
44
|
+
*/
|
|
45
|
+
export function isExportNode(nodeId: string): boolean {
|
|
46
|
+
return nodeId.startsWith(EXPORT_NODE_PREFIX)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if a link is a virtual export connector link
|
|
51
|
+
*/
|
|
52
|
+
export function isExportLink(linkId: string): boolean {
|
|
53
|
+
return linkId.startsWith(EXPORT_LINK_PREFIX)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================
|
|
57
|
+
// Main Function
|
|
58
|
+
// ============================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build hierarchical sheets from a root graph
|
|
62
|
+
*
|
|
63
|
+
* Creates child sheets for each subgraph with:
|
|
64
|
+
* - Filtered nodes (only those belonging to the subgraph)
|
|
65
|
+
* - Internal links (both endpoints in subgraph)
|
|
66
|
+
* - Export connector nodes/links for boundary connections
|
|
67
|
+
*
|
|
68
|
+
* @param graph - Root network graph with subgraphs
|
|
69
|
+
* @param rootLayout - Layout result for the root graph
|
|
70
|
+
* @param layoutEngine - Engine to layout child sheets
|
|
71
|
+
* @returns Map of sheet ID to SheetData (includes 'root')
|
|
72
|
+
*/
|
|
73
|
+
export async function buildHierarchicalSheets(
|
|
74
|
+
graph: NetworkGraph,
|
|
75
|
+
rootLayout: LayoutResult,
|
|
76
|
+
layoutEngine: LayoutEngine,
|
|
77
|
+
): Promise<Map<string, SheetData>> {
|
|
78
|
+
const sheets = new Map<string, SheetData>()
|
|
79
|
+
|
|
80
|
+
// Add root sheet
|
|
81
|
+
sheets.set('root', { graph, layout: rootLayout })
|
|
82
|
+
|
|
83
|
+
if (!graph.subgraphs || graph.subgraphs.length === 0) {
|
|
84
|
+
return sheets
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Mark subgraphs as clickable
|
|
88
|
+
for (const sg of graph.subgraphs) {
|
|
89
|
+
sg.file = sg.id
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Build child sheets
|
|
93
|
+
for (const sg of graph.subgraphs) {
|
|
94
|
+
const childSheet = await buildChildSheet(graph, sg, layoutEngine)
|
|
95
|
+
sheets.set(sg.id, childSheet)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return sheets
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============================================
|
|
102
|
+
// Internal Functions
|
|
103
|
+
// ============================================
|
|
104
|
+
|
|
105
|
+
async function buildChildSheet(
|
|
106
|
+
rootGraph: NetworkGraph,
|
|
107
|
+
subgraph: Subgraph,
|
|
108
|
+
layoutEngine: LayoutEngine,
|
|
109
|
+
): Promise<SheetData> {
|
|
110
|
+
// Get nodes belonging to this subgraph
|
|
111
|
+
const childNodes = rootGraph.nodes.filter((n) => n.parent === subgraph.id)
|
|
112
|
+
const childNodeIds = new Set(childNodes.map((n) => n.id))
|
|
113
|
+
|
|
114
|
+
// Get internal links (both endpoints in subgraph)
|
|
115
|
+
const childLinks = rootGraph.links.filter((l) => {
|
|
116
|
+
const fromNode = typeof l.from === 'string' ? l.from : l.from.node
|
|
117
|
+
const toNode = typeof l.to === 'string' ? l.to : l.to.node
|
|
118
|
+
return childNodeIds.has(fromNode) && childNodeIds.has(toNode)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// Generate export connectors for boundary connections
|
|
122
|
+
const { exportNodes, exportLinks } = generateExportConnectors(
|
|
123
|
+
rootGraph,
|
|
124
|
+
subgraph.id,
|
|
125
|
+
childNodeIds,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
// Build child graph
|
|
129
|
+
const childGraph: NetworkGraph = {
|
|
130
|
+
...rootGraph,
|
|
131
|
+
name: subgraph.label,
|
|
132
|
+
nodes: [
|
|
133
|
+
...childNodes.map((n) => ({ ...n, parent: undefined })),
|
|
134
|
+
...exportNodes,
|
|
135
|
+
],
|
|
136
|
+
links: [...childLinks, ...exportLinks],
|
|
137
|
+
subgraphs: undefined,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Layout child sheet
|
|
141
|
+
const childLayout = await layoutEngine.layoutAsync(childGraph)
|
|
142
|
+
|
|
143
|
+
return { graph: childGraph, layout: childLayout }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function generateExportConnectors(
|
|
147
|
+
rootGraph: NetworkGraph,
|
|
148
|
+
subgraphId: string,
|
|
149
|
+
childNodeIds: Set<string>,
|
|
150
|
+
): { exportNodes: Node[]; exportLinks: Link[] } {
|
|
151
|
+
const exportNodes: Node[] = []
|
|
152
|
+
const exportLinks: Link[] = []
|
|
153
|
+
const exportPoints = new Map<string, ExportPoint>()
|
|
154
|
+
|
|
155
|
+
// Find boundary links
|
|
156
|
+
for (const link of rootGraph.links) {
|
|
157
|
+
const fromNode = typeof link.from === 'string' ? link.from : link.from.node
|
|
158
|
+
const toNode = typeof link.to === 'string' ? link.to : link.to.node
|
|
159
|
+
const fromPort = typeof link.from === 'object' ? link.from.port : undefined
|
|
160
|
+
const toPort = typeof link.to === 'object' ? link.to.port : undefined
|
|
161
|
+
|
|
162
|
+
const fromInside = childNodeIds.has(fromNode)
|
|
163
|
+
const toInside = childNodeIds.has(toNode)
|
|
164
|
+
|
|
165
|
+
if (fromInside && !toInside) {
|
|
166
|
+
// Outgoing connection
|
|
167
|
+
const key = `${subgraphId}:${fromNode}:${fromPort || ''}`
|
|
168
|
+
if (!exportPoints.has(key)) {
|
|
169
|
+
const destSubgraph = findNodeSubgraph(rootGraph, toNode)
|
|
170
|
+
exportPoints.set(key, {
|
|
171
|
+
subgraphId,
|
|
172
|
+
device: fromNode,
|
|
173
|
+
port: fromPort,
|
|
174
|
+
destSubgraphLabel: destSubgraph?.label || toNode,
|
|
175
|
+
destDevice: toNode,
|
|
176
|
+
destPort: toPort,
|
|
177
|
+
isSource: true,
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
} else if (!fromInside && toInside) {
|
|
181
|
+
// Incoming connection
|
|
182
|
+
const key = `${subgraphId}:${toNode}:${toPort || ''}`
|
|
183
|
+
if (!exportPoints.has(key)) {
|
|
184
|
+
const destSubgraph = findNodeSubgraph(rootGraph, fromNode)
|
|
185
|
+
exportPoints.set(key, {
|
|
186
|
+
subgraphId,
|
|
187
|
+
device: toNode,
|
|
188
|
+
port: toPort,
|
|
189
|
+
destSubgraphLabel: destSubgraph?.label || fromNode,
|
|
190
|
+
destDevice: fromNode,
|
|
191
|
+
destPort: fromPort,
|
|
192
|
+
isSource: false,
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Create export nodes and links
|
|
199
|
+
for (const [key, exportPoint] of exportPoints) {
|
|
200
|
+
const exportId = key.replace(/:/g, '_')
|
|
201
|
+
|
|
202
|
+
// Export node
|
|
203
|
+
exportNodes.push({
|
|
204
|
+
id: `${EXPORT_NODE_PREFIX}${exportId}`,
|
|
205
|
+
label: exportPoint.destSubgraphLabel,
|
|
206
|
+
shape: 'stadium',
|
|
207
|
+
metadata: {
|
|
208
|
+
_isExport: true,
|
|
209
|
+
_destSubgraph: exportPoint.destSubgraphLabel,
|
|
210
|
+
_destDevice: exportPoint.destDevice,
|
|
211
|
+
_destPort: exportPoint.destPort,
|
|
212
|
+
_isSource: exportPoint.isSource,
|
|
213
|
+
},
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// Export link
|
|
217
|
+
const exportNodeId = `${EXPORT_NODE_PREFIX}${exportId}`
|
|
218
|
+
const deviceEndpoint = exportPoint.port
|
|
219
|
+
? { node: exportPoint.device, port: exportPoint.port }
|
|
220
|
+
: exportPoint.device
|
|
221
|
+
|
|
222
|
+
exportLinks.push({
|
|
223
|
+
id: `${EXPORT_LINK_PREFIX}${exportId}`,
|
|
224
|
+
from: exportPoint.isSource ? deviceEndpoint : exportNodeId,
|
|
225
|
+
to: exportPoint.isSource ? exportNodeId : deviceEndpoint,
|
|
226
|
+
type: 'dashed',
|
|
227
|
+
arrow: 'forward',
|
|
228
|
+
metadata: {
|
|
229
|
+
_destSubgraphLabel: exportPoint.destSubgraphLabel,
|
|
230
|
+
_destDevice: exportPoint.destDevice,
|
|
231
|
+
_destPort: exportPoint.destPort,
|
|
232
|
+
},
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { exportNodes, exportLinks }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function findNodeSubgraph(graph: NetworkGraph, nodeId: string): Subgraph | undefined {
|
|
240
|
+
const node = graph.nodes.find((n) => n.id === nodeId)
|
|
241
|
+
if (!node?.parent) return undefined
|
|
242
|
+
return graph.subgraphs?.find((s) => s.id === node.parent)
|
|
243
|
+
}
|