@nexus-ioc/graph-analyzer 0.1.0
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/CHANGELOG.md +112 -0
- package/LICENSE +21 -0
- package/README.md +742 -0
- package/dist/analyzer/circular-dependency-detector.d.ts +94 -0
- package/dist/analyzer/circular-dependency-detector.js +203 -0
- package/dist/analyzer/circular-dependency-detector.js.map +1 -0
- package/dist/analyzer/module-depth-analyzer.d.ts +117 -0
- package/dist/analyzer/module-depth-analyzer.js +221 -0
- package/dist/analyzer/module-depth-analyzer.js.map +1 -0
- package/dist/analyzer/provider-scope-analyzer.d.ts +91 -0
- package/dist/analyzer/provider-scope-analyzer.js +137 -0
- package/dist/analyzer/provider-scope-analyzer.js.map +1 -0
- package/dist/analyzer/unused-provider-detector.d.ts +73 -0
- package/dist/analyzer/unused-provider-detector.js +147 -0
- package/dist/analyzer/unused-provider-detector.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +537 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/config-loader.d.ts +27 -0
- package/dist/config/config-loader.js +205 -0
- package/dist/config/config-loader.js.map +1 -0
- package/dist/config/config-schema.d.ts +83 -0
- package/dist/config/config-schema.js +97 -0
- package/dist/config/config-schema.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/graph-output.interface.d.ts +126 -0
- package/dist/interfaces/graph-output.interface.js +3 -0
- package/dist/interfaces/graph-output.interface.js.map +1 -0
- package/dist/interfaces/providers.interface.d.ts +10 -0
- package/dist/interfaces/providers.interface.js +3 -0
- package/dist/interfaces/providers.interface.js.map +1 -0
- package/dist/parser/dependency-extractor.d.ts +88 -0
- package/dist/parser/dependency-extractor.js +334 -0
- package/dist/parser/dependency-extractor.js.map +1 -0
- package/dist/parser/parse-entry-file.d.ts +15 -0
- package/dist/parser/parse-entry-file.js +46 -0
- package/dist/parser/parse-entry-file.js.map +1 -0
- package/dist/parser/parse-imports.d.ts +15 -0
- package/dist/parser/parse-imports.js +65 -0
- package/dist/parser/parse-imports.js.map +1 -0
- package/dist/parser/parse-ns-module.d.ts +39 -0
- package/dist/parser/parse-ns-module.js +122 -0
- package/dist/parser/parse-ns-module.js.map +1 -0
- package/dist/parser/parse-provider.d.ts +2 -0
- package/dist/parser/parse-provider.js +7 -0
- package/dist/parser/parse-provider.js.map +1 -0
- package/dist/parser/parse-ts-config.d.ts +10 -0
- package/dist/parser/parse-ts-config.js +40 -0
- package/dist/parser/parse-ts-config.js.map +1 -0
- package/dist/parser/parser-rec.d.ts +1 -0
- package/dist/parser/parser-rec.js +52 -0
- package/dist/parser/parser-rec.js.map +1 -0
- package/dist/parser/parser.d.ts +1 -0
- package/dist/parser/parser.js +61 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/parser/provider/class-parser.d.ts +20 -0
- package/dist/parser/provider/class-parser.js +137 -0
- package/dist/parser/provider/class-parser.js.map +1 -0
- package/dist/parser/provider/providers-parser.d.ts +9 -0
- package/dist/parser/provider/providers-parser.js +32 -0
- package/dist/parser/provider/providers-parser.js.map +1 -0
- package/dist/parser/provider/use-class-parser.d.ts +25 -0
- package/dist/parser/provider/use-class-parser.js +161 -0
- package/dist/parser/provider/use-class-parser.js.map +1 -0
- package/dist/parser/provider/use-factory-parser.d.ts +15 -0
- package/dist/parser/provider/use-factory-parser.js +53 -0
- package/dist/parser/provider/use-factory-parser.js.map +1 -0
- package/dist/parser/provider/use-value-parser.d.ts +13 -0
- package/dist/parser/provider/use-value-parser.js +41 -0
- package/dist/parser/provider/use-value-parser.js.map +1 -0
- package/dist/parser/rules.d.ts +10 -0
- package/dist/parser/rules.js +37 -0
- package/dist/parser/rules.js.map +1 -0
- package/dist/utils/error-formatter.d.ts +27 -0
- package/dist/utils/error-formatter.js +177 -0
- package/dist/utils/error-formatter.js.map +1 -0
- package/dist/utils/validator.d.ts +45 -0
- package/dist/utils/validator.js +252 -0
- package/dist/utils/validator.js.map +1 -0
- package/dist/visualize/generator.d.ts +26 -0
- package/dist/visualize/generator.js +75 -0
- package/dist/visualize/generator.js.map +1 -0
- package/dist/visualize/graph-analyzer.d.ts +126 -0
- package/dist/visualize/graph-analyzer.js +199 -0
- package/dist/visualize/graph-analyzer.js.map +1 -0
- package/dist/visualize/html-generator.d.ts +71 -0
- package/dist/visualize/html-generator.js +1312 -0
- package/dist/visualize/html-generator.js.map +1 -0
- package/dist/visualize/json-formatter.d.ts +106 -0
- package/dist/visualize/json-formatter.js +252 -0
- package/dist/visualize/json-formatter.js.map +1 -0
- package/docs/API_REFERENCE.md +747 -0
- package/docs/ARCHITECTURE.md +486 -0
- package/docs/CLI_GUIDE.md +576 -0
- package/docs/HTML_VISUALIZATION.md +365 -0
- package/package.json +85 -0
|
@@ -0,0 +1,1312 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HtmlGenerator = void 0;
|
|
4
|
+
const json_formatter_1 = require("./json-formatter");
|
|
5
|
+
/**
|
|
6
|
+
* Generates interactive HTML visualizations of dependency graphs
|
|
7
|
+
*
|
|
8
|
+
* Creates a self-contained HTML file with an interactive graph using Cytoscape.js.
|
|
9
|
+
* The graph includes modules and providers with clickable nodes, zoom/pan capabilities,
|
|
10
|
+
* and detailed information panels.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const generator = new HtmlGenerator(modulesGraph, 'src/main.ts', {
|
|
15
|
+
* ideProtocol: 'vscode',
|
|
16
|
+
* title: 'My App Dependencies',
|
|
17
|
+
* darkTheme: true
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* const html = generator.generate();
|
|
21
|
+
* fs.writeFileSync('graph.html', html);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
class HtmlGenerator {
|
|
25
|
+
constructor(graph, entryPoint, options = {}) {
|
|
26
|
+
this.graph = graph;
|
|
27
|
+
this.entryPoint = entryPoint;
|
|
28
|
+
this.options = options;
|
|
29
|
+
this.formatter = new json_formatter_1.JsonFormatter(graph, entryPoint);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate the complete HTML document
|
|
33
|
+
*
|
|
34
|
+
* @returns Complete HTML string ready to be written to a file
|
|
35
|
+
*/
|
|
36
|
+
generate() {
|
|
37
|
+
const graphData = this.formatter.format();
|
|
38
|
+
const cytoscapeData = this.convertToCytoscapeFormat(graphData);
|
|
39
|
+
return this.generateHtmlTemplate(graphData, cytoscapeData);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Convert GraphOutput to Cytoscape.js format with compound nodes
|
|
43
|
+
*/
|
|
44
|
+
convertToCytoscapeFormat(graphData) {
|
|
45
|
+
const nodes = [];
|
|
46
|
+
const edges = [];
|
|
47
|
+
// Create a set of existing module names for validation
|
|
48
|
+
const existingModules = new Set(graphData.modules.map((module) => module.name));
|
|
49
|
+
// Add module nodes (these will be parent/compound nodes)
|
|
50
|
+
for (const module of graphData.modules) {
|
|
51
|
+
nodes.push({
|
|
52
|
+
data: {
|
|
53
|
+
id: `module-${module.name}`,
|
|
54
|
+
label: module.name,
|
|
55
|
+
type: "module",
|
|
56
|
+
path: module.path,
|
|
57
|
+
isGlobal: module.isGlobal,
|
|
58
|
+
imports: module.imports,
|
|
59
|
+
exports: module.exports,
|
|
60
|
+
providers: module.providers,
|
|
61
|
+
},
|
|
62
|
+
classes: ["module", module.isGlobal ? "global" : ""],
|
|
63
|
+
});
|
|
64
|
+
// Add edges for module imports (only for modules that exist in the graph)
|
|
65
|
+
for (const importedModule of module.imports) {
|
|
66
|
+
// Only create edge if the target module exists in the graph
|
|
67
|
+
if (existingModules.has(importedModule)) {
|
|
68
|
+
edges.push({
|
|
69
|
+
data: {
|
|
70
|
+
id: `edge-${module.name}-${importedModule}`,
|
|
71
|
+
source: `module-${module.name}`,
|
|
72
|
+
target: `module-${importedModule}`,
|
|
73
|
+
type: "import",
|
|
74
|
+
},
|
|
75
|
+
classes: ["import"],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Create a map of provider tokens to their full node IDs for dependency resolution
|
|
81
|
+
const providerMap = new Map();
|
|
82
|
+
for (const provider of graphData.providers) {
|
|
83
|
+
const nodeId = `provider-${provider.module}-${provider.token}`;
|
|
84
|
+
providerMap.set(provider.token, nodeId);
|
|
85
|
+
}
|
|
86
|
+
// Create a map of module names to their node IDs for export resolution
|
|
87
|
+
const moduleMap = new Map();
|
|
88
|
+
for (const module of graphData.modules) {
|
|
89
|
+
const nodeId = `module-${module.name}`;
|
|
90
|
+
moduleMap.set(module.name, nodeId);
|
|
91
|
+
}
|
|
92
|
+
// Add provider nodes as children of their parent modules
|
|
93
|
+
for (const provider of graphData.providers) {
|
|
94
|
+
const nodeId = `provider-${provider.module}-${provider.token}`;
|
|
95
|
+
// Check if provider has dependencies without explicit @Inject decorators
|
|
96
|
+
const hasMissingDecorators = provider.dependencies?.some((dep) => dep.hasExplicitDecorator === false) ?? false;
|
|
97
|
+
const classes = ["provider", provider.type.toLowerCase()];
|
|
98
|
+
if (hasMissingDecorators) {
|
|
99
|
+
classes.push("missing-decorator");
|
|
100
|
+
}
|
|
101
|
+
nodes.push({
|
|
102
|
+
data: {
|
|
103
|
+
id: nodeId,
|
|
104
|
+
label: provider.token,
|
|
105
|
+
type: "provider",
|
|
106
|
+
providerType: provider.type,
|
|
107
|
+
module: provider.module,
|
|
108
|
+
scope: provider.scope,
|
|
109
|
+
dependencies: provider.dependencies,
|
|
110
|
+
value: provider.value,
|
|
111
|
+
factory: provider.factory,
|
|
112
|
+
useClass: provider.useClass,
|
|
113
|
+
hasMissingDecorators,
|
|
114
|
+
// Set parent to create compound node structure
|
|
115
|
+
parent: `module-${provider.module}`,
|
|
116
|
+
},
|
|
117
|
+
classes,
|
|
118
|
+
});
|
|
119
|
+
// Note: We no longer need "provides" edges since providers are children of modules
|
|
120
|
+
// Add edges for provider dependencies (only for providers that exist in the graph)
|
|
121
|
+
if (provider.dependencies && provider.dependencies.length > 0) {
|
|
122
|
+
for (const dep of provider.dependencies) {
|
|
123
|
+
if (dep.token) {
|
|
124
|
+
// Find the provider node for this dependency
|
|
125
|
+
const depNodeId = providerMap.get(dep.token);
|
|
126
|
+
// Only create edge if the target provider exists in the graph
|
|
127
|
+
if (depNodeId) {
|
|
128
|
+
edges.push({
|
|
129
|
+
data: {
|
|
130
|
+
id: `edge-${nodeId}-${depNodeId}`,
|
|
131
|
+
source: nodeId,
|
|
132
|
+
target: depNodeId,
|
|
133
|
+
type: "dependency",
|
|
134
|
+
optional: dep.optional,
|
|
135
|
+
},
|
|
136
|
+
classes: ["dependency", dep.optional ? "optional" : ""],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Convert Maps to plain objects for JSON serialization
|
|
144
|
+
const providerMapObject = {};
|
|
145
|
+
providerMap.forEach((nodeId, token) => {
|
|
146
|
+
providerMapObject[token] = nodeId;
|
|
147
|
+
});
|
|
148
|
+
const moduleMapObject = {};
|
|
149
|
+
moduleMap.forEach((nodeId, name) => {
|
|
150
|
+
moduleMapObject[name] = nodeId;
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
nodes,
|
|
154
|
+
edges,
|
|
155
|
+
providerMap: providerMapObject,
|
|
156
|
+
moduleMap: moduleMapObject,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get IDE URL for a file path
|
|
161
|
+
*/
|
|
162
|
+
getIdeUrl(filePath, line = 1, column = 1) {
|
|
163
|
+
const protocol = this.options.ideProtocol || "vscode";
|
|
164
|
+
if (this.options.customIdeUrl) {
|
|
165
|
+
return this.options.customIdeUrl
|
|
166
|
+
.replace("{path}", filePath)
|
|
167
|
+
.replace("{line}", String(line))
|
|
168
|
+
.replace("{column}", String(column));
|
|
169
|
+
}
|
|
170
|
+
switch (protocol) {
|
|
171
|
+
case "vscode":
|
|
172
|
+
return `vscode://file/${filePath}:${line}:${column}`;
|
|
173
|
+
case "webstorm":
|
|
174
|
+
case "idea":
|
|
175
|
+
return `idea://open?file=${filePath}&line=${line}&column=${column}`;
|
|
176
|
+
default:
|
|
177
|
+
return `vscode://file/${filePath}:${line}:${column}`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Generate warning section for providers with missing @Inject decorators
|
|
182
|
+
*/
|
|
183
|
+
generateMissingDecoratorsWarning(graphData) {
|
|
184
|
+
const providersWithMissingDecorators = graphData.providers.filter((provider) => provider.dependencies?.some((dep) => dep.hasExplicitDecorator === false));
|
|
185
|
+
if (providersWithMissingDecorators.length === 0) {
|
|
186
|
+
return "";
|
|
187
|
+
}
|
|
188
|
+
const providersList = providersWithMissingDecorators
|
|
189
|
+
.map((provider) => {
|
|
190
|
+
const missingDeps = provider.dependencies?.filter((dep) => dep.hasExplicitDecorator === false) || [];
|
|
191
|
+
return `<li><strong>${provider.token}</strong> (${provider.module}): ${missingDeps.map((d) => d.token).join(", ")}</li>`;
|
|
192
|
+
})
|
|
193
|
+
.join("");
|
|
194
|
+
return `
|
|
195
|
+
<div style="background: #fef2f2; border-left: 4px solid #ef4444; border-radius: 8px; padding: 1.25rem 1.5rem; margin-top: 1.25rem; box-shadow: 0 2px 8px rgba(239, 68, 68, 0.15);">
|
|
196
|
+
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem;">
|
|
197
|
+
<span style="font-size: 1.75rem;">⚠️</span>
|
|
198
|
+
<strong style="color: #dc2626; font-size: 1.15rem; font-weight: 700;">Missing @Inject Decorators Detected</strong>
|
|
199
|
+
</div>
|
|
200
|
+
<p style="color: #991b1b; margin-bottom: 1rem; font-size: 0.95rem; line-height: 1.6;">
|
|
201
|
+
<strong>${providersWithMissingDecorators.length}</strong> provider(s) have dependencies without explicit @Inject decorators.
|
|
202
|
+
This may cause runtime errors in production. Affected providers:
|
|
203
|
+
</p>
|
|
204
|
+
<ul style="color: #991b1b; font-size: 0.9rem; margin-left: 1.5rem; line-height: 1.8;">
|
|
205
|
+
${providersList}
|
|
206
|
+
</ul>
|
|
207
|
+
</div>
|
|
208
|
+
`;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Generate the complete HTML template
|
|
212
|
+
*/
|
|
213
|
+
generateHtmlTemplate(graphData, cytoscapeData) {
|
|
214
|
+
const title = this.options.title || "Dependency Graph";
|
|
215
|
+
const isDark = this.options.darkTheme || false;
|
|
216
|
+
return `<!DOCTYPE html>
|
|
217
|
+
<html lang="en">
|
|
218
|
+
<head>
|
|
219
|
+
<meta charset="UTF-8">
|
|
220
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
221
|
+
<title>${title}</title>
|
|
222
|
+
<script src="https://unpkg.com/cytoscape@3.28.1/dist/cytoscape.min.js"></script>
|
|
223
|
+
<style>
|
|
224
|
+
${this.generateStyles(isDark)}
|
|
225
|
+
</style>
|
|
226
|
+
</head>
|
|
227
|
+
<body>
|
|
228
|
+
<div class="container">
|
|
229
|
+
<header>
|
|
230
|
+
<h1>${title}</h1>
|
|
231
|
+
<div class="metadata">
|
|
232
|
+
<span>Entry Point: <code>${graphData.metadata.entryPoint}</code></span>
|
|
233
|
+
<span>Modules: <strong>${graphData.metadata.totalModules}</strong></span>
|
|
234
|
+
<span>Providers: <strong>${graphData.metadata.totalProviders}</strong></span>
|
|
235
|
+
<span>Analyzed: ${new Date(graphData.metadata.analyzedAt).toLocaleString()}</span>
|
|
236
|
+
</div>
|
|
237
|
+
${this.generateMissingDecoratorsWarning(graphData)}
|
|
238
|
+
</header>
|
|
239
|
+
|
|
240
|
+
<div class="controls">
|
|
241
|
+
<div class="control-group">
|
|
242
|
+
<label>View Mode:</label>
|
|
243
|
+
<button id="view-all" class="active">All</button>
|
|
244
|
+
<button id="view-modules">Modules Only</button>
|
|
245
|
+
<button id="view-providers">Providers Only</button>
|
|
246
|
+
</div>
|
|
247
|
+
<div class="control-group">
|
|
248
|
+
<label>Search:</label>
|
|
249
|
+
<input type="text" id="search" placeholder="Search modules or providers..." />
|
|
250
|
+
</div>
|
|
251
|
+
<div class="control-group">
|
|
252
|
+
<button id="reset-zoom">Reset View</button>
|
|
253
|
+
<button id="fit-graph">Fit to Screen</button>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<div class="main-content">
|
|
258
|
+
<div id="cy"></div>
|
|
259
|
+
<div id="info-panel" class="info-panel">
|
|
260
|
+
<h3>Node Information</h3>
|
|
261
|
+
<p class="placeholder">Click on a node to see details</p>
|
|
262
|
+
<div id="node-details" style="display: none;"></div>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<script>
|
|
268
|
+
${this.generateScript(graphData, cytoscapeData)}
|
|
269
|
+
</script>
|
|
270
|
+
</body>
|
|
271
|
+
</html>`;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Generate CSS styles
|
|
275
|
+
*/
|
|
276
|
+
generateStyles(isDark) {
|
|
277
|
+
// Enhanced color palette for better accessibility and visual appeal
|
|
278
|
+
const bgColor = isDark ? "#1a1a1a" : "#f8f9fa";
|
|
279
|
+
const textColor = isDark ? "#e0e0e0" : "#2c3e50";
|
|
280
|
+
const borderColor = isDark ? "#404040" : "#dee2e6";
|
|
281
|
+
const panelBg = isDark ? "#242424" : "#ffffff";
|
|
282
|
+
const accentColor = isDark ? "#3b82f6" : "#2563eb";
|
|
283
|
+
const accentHover = isDark ? "#60a5fa" : "#1d4ed8";
|
|
284
|
+
const mutedText = isDark ? "#9ca3af" : "#6b7280";
|
|
285
|
+
const cardBg = isDark ? "#2d2d2d" : "#ffffff";
|
|
286
|
+
const shadowColor = isDark ? "rgba(0, 0, 0, 0.5)" : "rgba(0, 0, 0, 0.1)";
|
|
287
|
+
return `
|
|
288
|
+
* {
|
|
289
|
+
margin: 0;
|
|
290
|
+
padding: 0;
|
|
291
|
+
box-sizing: border-box;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
body {
|
|
295
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
|
|
296
|
+
background: ${bgColor};
|
|
297
|
+
color: ${textColor};
|
|
298
|
+
overflow: hidden;
|
|
299
|
+
line-height: 1.6;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.container {
|
|
303
|
+
display: flex;
|
|
304
|
+
flex-direction: column;
|
|
305
|
+
height: 100vh;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
header {
|
|
309
|
+
padding: 1.5rem 2rem;
|
|
310
|
+
border-bottom: 2px solid ${borderColor};
|
|
311
|
+
background: ${panelBg};
|
|
312
|
+
box-shadow: 0 2px 8px ${shadowColor};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
header h1 {
|
|
316
|
+
font-size: 1.75rem;
|
|
317
|
+
font-weight: 700;
|
|
318
|
+
margin-bottom: 0.75rem;
|
|
319
|
+
color: ${textColor};
|
|
320
|
+
letter-spacing: -0.025em;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.metadata {
|
|
324
|
+
display: flex;
|
|
325
|
+
gap: 2rem;
|
|
326
|
+
font-size: 0.9rem;
|
|
327
|
+
color: ${mutedText};
|
|
328
|
+
flex-wrap: wrap;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.metadata span {
|
|
332
|
+
display: flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
gap: 0.5rem;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.metadata strong {
|
|
338
|
+
color: ${accentColor};
|
|
339
|
+
font-weight: 600;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.metadata code {
|
|
343
|
+
background: ${isDark ? "#1a1a1a" : "#f1f3f5"};
|
|
344
|
+
padding: 0.25rem 0.5rem;
|
|
345
|
+
border-radius: 4px;
|
|
346
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Courier New', monospace;
|
|
347
|
+
font-size: 0.85rem;
|
|
348
|
+
border: 1px solid ${borderColor};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.controls {
|
|
352
|
+
padding: 1.25rem 2rem;
|
|
353
|
+
border-bottom: 1px solid ${borderColor};
|
|
354
|
+
display: flex;
|
|
355
|
+
gap: 2.5rem;
|
|
356
|
+
align-items: center;
|
|
357
|
+
background: ${panelBg};
|
|
358
|
+
flex-wrap: wrap;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.control-group {
|
|
362
|
+
display: flex;
|
|
363
|
+
gap: 0.75rem;
|
|
364
|
+
align-items: center;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.control-group label {
|
|
368
|
+
font-weight: 600;
|
|
369
|
+
font-size: 0.9rem;
|
|
370
|
+
color: ${mutedText};
|
|
371
|
+
text-transform: uppercase;
|
|
372
|
+
letter-spacing: 0.05em;
|
|
373
|
+
font-size: 0.75rem;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
button {
|
|
377
|
+
padding: 0.625rem 1.25rem;
|
|
378
|
+
border: 1px solid ${borderColor};
|
|
379
|
+
background: ${cardBg};
|
|
380
|
+
color: ${textColor};
|
|
381
|
+
border-radius: 6px;
|
|
382
|
+
cursor: pointer;
|
|
383
|
+
font-size: 0.9rem;
|
|
384
|
+
font-weight: 500;
|
|
385
|
+
transition: all 0.2s ease;
|
|
386
|
+
box-shadow: 0 1px 3px ${shadowColor};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
button:hover {
|
|
390
|
+
background: ${isDark ? "#3a3a3a" : "#f1f3f5"};
|
|
391
|
+
transform: translateY(-1px);
|
|
392
|
+
box-shadow: 0 2px 6px ${shadowColor};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
button:active {
|
|
396
|
+
transform: translateY(0);
|
|
397
|
+
box-shadow: 0 1px 2px ${shadowColor};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
button.active {
|
|
401
|
+
background: ${accentColor};
|
|
402
|
+
color: white;
|
|
403
|
+
border-color: ${accentColor};
|
|
404
|
+
box-shadow: 0 2px 6px ${isDark ? "rgba(59, 130, 246, 0.4)" : "rgba(37, 99, 235, 0.3)"};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
button.active:hover {
|
|
408
|
+
background: ${accentHover};
|
|
409
|
+
border-color: ${accentHover};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
input[type="text"] {
|
|
413
|
+
padding: 0.625rem 1rem;
|
|
414
|
+
border: 1px solid ${borderColor};
|
|
415
|
+
background: ${cardBg};
|
|
416
|
+
color: ${textColor};
|
|
417
|
+
border-radius: 6px;
|
|
418
|
+
font-size: 0.9rem;
|
|
419
|
+
min-width: 300px;
|
|
420
|
+
transition: all 0.2s ease;
|
|
421
|
+
box-shadow: 0 1px 3px ${shadowColor};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
input[type="text"]:focus {
|
|
425
|
+
outline: none;
|
|
426
|
+
border-color: ${accentColor};
|
|
427
|
+
box-shadow: 0 0 0 3px ${isDark ? "rgba(59, 130, 246, 0.2)" : "rgba(37, 99, 235, 0.1)"};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
input[type="text"]::placeholder {
|
|
431
|
+
color: ${mutedText};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.main-content {
|
|
435
|
+
display: flex;
|
|
436
|
+
flex: 1;
|
|
437
|
+
overflow: hidden;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
#cy {
|
|
441
|
+
flex: 1;
|
|
442
|
+
background: ${bgColor};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.info-panel {
|
|
446
|
+
width: 380px;
|
|
447
|
+
border-left: 2px solid ${borderColor};
|
|
448
|
+
background: ${panelBg};
|
|
449
|
+
padding: 2rem;
|
|
450
|
+
overflow-y: auto;
|
|
451
|
+
box-shadow: -2px 0 8px ${shadowColor};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.info-panel h3 {
|
|
455
|
+
margin-bottom: 1.5rem;
|
|
456
|
+
font-size: 1.25rem;
|
|
457
|
+
font-weight: 700;
|
|
458
|
+
color: ${textColor};
|
|
459
|
+
letter-spacing: -0.025em;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.info-panel .placeholder {
|
|
463
|
+
color: ${mutedText};
|
|
464
|
+
font-style: italic;
|
|
465
|
+
text-align: center;
|
|
466
|
+
padding: 2rem 0;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.info-item {
|
|
470
|
+
margin-bottom: 1.5rem;
|
|
471
|
+
padding-bottom: 1.5rem;
|
|
472
|
+
border-bottom: 1px solid ${borderColor};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.info-item:last-child {
|
|
476
|
+
border-bottom: none;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.info-item label {
|
|
480
|
+
display: block;
|
|
481
|
+
font-weight: 700;
|
|
482
|
+
margin-bottom: 0.5rem;
|
|
483
|
+
font-size: 0.85rem;
|
|
484
|
+
color: ${mutedText};
|
|
485
|
+
text-transform: uppercase;
|
|
486
|
+
letter-spacing: 0.05em;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.info-item .value {
|
|
490
|
+
font-size: 1rem;
|
|
491
|
+
line-height: 1.6;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.info-item code {
|
|
495
|
+
background: ${isDark ? "#1a1a1a" : "#f1f3f5"};
|
|
496
|
+
padding: 0.25rem 0.5rem;
|
|
497
|
+
border-radius: 4px;
|
|
498
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Courier New', monospace;
|
|
499
|
+
font-size: 0.875rem;
|
|
500
|
+
border: 1px solid ${borderColor};
|
|
501
|
+
word-break: break-all;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.info-item a {
|
|
505
|
+
color: ${accentColor};
|
|
506
|
+
text-decoration: none;
|
|
507
|
+
font-weight: 500;
|
|
508
|
+
transition: color 0.2s ease;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.info-item a:hover {
|
|
512
|
+
color: ${accentHover};
|
|
513
|
+
text-decoration: underline;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.badge {
|
|
517
|
+
display: inline-block;
|
|
518
|
+
padding: 0.375rem 0.75rem;
|
|
519
|
+
border-radius: 6px;
|
|
520
|
+
font-size: 0.75rem;
|
|
521
|
+
font-weight: 700;
|
|
522
|
+
margin-right: 0.5rem;
|
|
523
|
+
text-transform: uppercase;
|
|
524
|
+
letter-spacing: 0.05em;
|
|
525
|
+
box-shadow: 0 1px 3px ${shadowColor};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.badge.module { background: #3b82f6; color: white; }
|
|
529
|
+
.badge.provider { background: #10b981; color: white; }
|
|
530
|
+
.badge.class { background: #3b82f6; color: white; }
|
|
531
|
+
.badge.usevalue { background: #10b981; color: white; }
|
|
532
|
+
.badge.usefactory { background: #f59e0b; color: white; }
|
|
533
|
+
.badge.useclass { background: #eab308; color: #1a1a1a; }
|
|
534
|
+
.badge.global { background: #a855f7; color: white; }
|
|
535
|
+
|
|
536
|
+
.dependency-list {
|
|
537
|
+
list-style: none;
|
|
538
|
+
padding-left: 0;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.dependency-list li {
|
|
542
|
+
padding: 0.5rem 0;
|
|
543
|
+
font-size: 0.9rem;
|
|
544
|
+
line-height: 1.6;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.dependency-list li::before {
|
|
548
|
+
content: "→ ";
|
|
549
|
+
color: ${accentColor};
|
|
550
|
+
font-weight: bold;
|
|
551
|
+
margin-right: 0.25rem;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.node-link {
|
|
555
|
+
color: ${accentColor};
|
|
556
|
+
text-decoration: none;
|
|
557
|
+
cursor: pointer;
|
|
558
|
+
transition: all 0.2s ease;
|
|
559
|
+
font-weight: 500;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.node-link:hover {
|
|
563
|
+
color: ${accentHover};
|
|
564
|
+
text-decoration: underline;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.node-link code {
|
|
568
|
+
color: inherit;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/* Scrollbar styling */
|
|
572
|
+
.info-panel::-webkit-scrollbar {
|
|
573
|
+
width: 8px;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.info-panel::-webkit-scrollbar-track {
|
|
577
|
+
background: ${isDark ? "#1a1a1a" : "#f1f3f5"};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.info-panel::-webkit-scrollbar-thumb {
|
|
581
|
+
background: ${isDark ? "#404040" : "#cbd5e0"};
|
|
582
|
+
border-radius: 4px;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.info-panel::-webkit-scrollbar-thumb:hover {
|
|
586
|
+
background: ${isDark ? "#505050" : "#a0aec0"};
|
|
587
|
+
}
|
|
588
|
+
`;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Generate JavaScript code
|
|
592
|
+
*/
|
|
593
|
+
generateScript(_graphData, cytoscapeData) {
|
|
594
|
+
const ideUrlTemplate = this.getIdeUrl("{path}", 1, 1);
|
|
595
|
+
return `
|
|
596
|
+
const graphData = ${JSON.stringify({ nodes: cytoscapeData.nodes, edges: cytoscapeData.edges }, null, 2)};
|
|
597
|
+
const providerMap = ${JSON.stringify(cytoscapeData.providerMap, null, 2)};
|
|
598
|
+
const moduleMap = ${JSON.stringify(cytoscapeData.moduleMap, null, 2)};
|
|
599
|
+
|
|
600
|
+
// Initialize Cytoscape
|
|
601
|
+
const cy = cytoscape({
|
|
602
|
+
container: document.getElementById('cy'),
|
|
603
|
+
elements: graphData,
|
|
604
|
+
|
|
605
|
+
// User interaction options
|
|
606
|
+
userPanningEnabled: true,
|
|
607
|
+
userZoomingEnabled: true,
|
|
608
|
+
boxSelectionEnabled: false,
|
|
609
|
+
|
|
610
|
+
// Panning configuration - require actual drag movement
|
|
611
|
+
panningEnabled: true,
|
|
612
|
+
|
|
613
|
+
style: [
|
|
614
|
+
{
|
|
615
|
+
selector: 'node',
|
|
616
|
+
style: {
|
|
617
|
+
'label': 'data(label)',
|
|
618
|
+
'text-valign': 'center',
|
|
619
|
+
'text-halign': 'center',
|
|
620
|
+
'font-size': '13px',
|
|
621
|
+
'font-weight': '700',
|
|
622
|
+
'color': '#1a1a1a',
|
|
623
|
+
'text-outline-width': 3,
|
|
624
|
+
'text-outline-color': '#ffffff',
|
|
625
|
+
'width': '90px',
|
|
626
|
+
'height': '90px',
|
|
627
|
+
'border-width': 3,
|
|
628
|
+
'border-color': '#64748b',
|
|
629
|
+
'text-wrap': 'wrap',
|
|
630
|
+
'text-max-width': '80px'
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
// Parent/compound nodes (modules containing providers)
|
|
635
|
+
selector: ':parent',
|
|
636
|
+
style: {
|
|
637
|
+
'text-valign': 'top',
|
|
638
|
+
'text-halign': 'center',
|
|
639
|
+
'text-margin-y': -12,
|
|
640
|
+
'background-opacity': 0.08,
|
|
641
|
+
'background-color': '#3b82f6',
|
|
642
|
+
'border-width': 3,
|
|
643
|
+
'border-color': '#2563eb',
|
|
644
|
+
'border-opacity': 0.6,
|
|
645
|
+
'padding': '15px',
|
|
646
|
+
'shape': 'roundrectangle',
|
|
647
|
+
'font-size': '15px',
|
|
648
|
+
'font-weight': 'bold',
|
|
649
|
+
'compound-sizing-wrt-labels': 'include',
|
|
650
|
+
'min-width': '100px',
|
|
651
|
+
'min-height': '80px',
|
|
652
|
+
'color': '#1e40af',
|
|
653
|
+
'text-outline-width': 2,
|
|
654
|
+
'text-outline-color': '#ffffff'
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
selector: ':parent.global',
|
|
659
|
+
style: {
|
|
660
|
+
'background-color': '#a855f7',
|
|
661
|
+
'border-color': '#9333ea',
|
|
662
|
+
'border-width': 4,
|
|
663
|
+
'color': '#7e22ce'
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
// Module nodes without children (standalone modules)
|
|
668
|
+
selector: 'node.module',
|
|
669
|
+
style: {
|
|
670
|
+
'background-color': '#3b82f6',
|
|
671
|
+
'border-color': '#2563eb',
|
|
672
|
+
'shape': 'roundrectangle',
|
|
673
|
+
'width': '140px',
|
|
674
|
+
'height': '70px',
|
|
675
|
+
'color': '#ffffff',
|
|
676
|
+
'text-outline-width': 0,
|
|
677
|
+
'font-size': '14px'
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
selector: 'node.module.global',
|
|
682
|
+
style: {
|
|
683
|
+
'background-color': '#a855f7',
|
|
684
|
+
'border-width': 4,
|
|
685
|
+
'border-color': '#9333ea'
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
selector: 'node.provider',
|
|
690
|
+
style: {
|
|
691
|
+
'shape': 'ellipse',
|
|
692
|
+
'width': '95px',
|
|
693
|
+
'height': '95px',
|
|
694
|
+
'font-size': '12px',
|
|
695
|
+
'text-wrap': 'wrap',
|
|
696
|
+
'text-max-width': '85px'
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
selector: 'node.provider.class',
|
|
701
|
+
style: {
|
|
702
|
+
'background-color': '#10b981',
|
|
703
|
+
'border-color': '#059669',
|
|
704
|
+
'border-width': 3,
|
|
705
|
+
'color': '#ffffff',
|
|
706
|
+
'text-outline-width': 0
|
|
707
|
+
}
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
selector: 'node.provider.usevalue',
|
|
711
|
+
style: {
|
|
712
|
+
'background-color': '#10b981',
|
|
713
|
+
'border-color': '#059669',
|
|
714
|
+
'border-width': 3,
|
|
715
|
+
'color': '#ffffff',
|
|
716
|
+
'text-outline-width': 0
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
selector: 'node.provider.usefactory',
|
|
721
|
+
style: {
|
|
722
|
+
'background-color': '#f59e0b',
|
|
723
|
+
'border-color': '#d97706',
|
|
724
|
+
'border-width': 3,
|
|
725
|
+
'color': '#ffffff',
|
|
726
|
+
'text-outline-width': 0
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
selector: 'node.provider.useclass',
|
|
731
|
+
style: {
|
|
732
|
+
'background-color': '#eab308',
|
|
733
|
+
'border-color': '#ca8a04',
|
|
734
|
+
'border-width': 3,
|
|
735
|
+
'color': '#1a1a1a',
|
|
736
|
+
'text-outline-width': 2,
|
|
737
|
+
'text-outline-color': '#ffffff'
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
// Providers with missing @Inject decorators (error state)
|
|
742
|
+
selector: 'node.provider.missing-decorator',
|
|
743
|
+
style: {
|
|
744
|
+
'border-width': 4,
|
|
745
|
+
'border-color': '#ef4444',
|
|
746
|
+
'border-style': 'double'
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
selector: 'edge',
|
|
751
|
+
style: {
|
|
752
|
+
'width': 2.5,
|
|
753
|
+
'curve-style': 'bezier',
|
|
754
|
+
'target-arrow-shape': 'triangle',
|
|
755
|
+
'arrow-scale': 1.8,
|
|
756
|
+
'opacity': 0.85
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
selector: 'edge.import',
|
|
761
|
+
style: {
|
|
762
|
+
'line-color': '#64748b',
|
|
763
|
+
'target-arrow-color': '#64748b',
|
|
764
|
+
'width': 2.5
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
selector: 'edge.provides',
|
|
769
|
+
style: {
|
|
770
|
+
'line-color': '#10b981',
|
|
771
|
+
'target-arrow-color': '#10b981',
|
|
772
|
+
'line-style': 'dashed',
|
|
773
|
+
'width': 2.5
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
selector: 'edge.dependency',
|
|
778
|
+
style: {
|
|
779
|
+
'line-color': '#f59e0b',
|
|
780
|
+
'target-arrow-color': '#f59e0b',
|
|
781
|
+
'width': 3
|
|
782
|
+
}
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
selector: 'edge.dependency.optional',
|
|
786
|
+
style: {
|
|
787
|
+
'line-style': 'dotted',
|
|
788
|
+
'opacity': 0.6
|
|
789
|
+
}
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
selector: 'edge.dimmed',
|
|
793
|
+
style: {
|
|
794
|
+
'opacity': 0.1,
|
|
795
|
+
'z-index': 0
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
selector: 'node.dimmed',
|
|
800
|
+
style: {
|
|
801
|
+
'opacity': 0.3
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
selector: 'node.highlighted',
|
|
806
|
+
style: {
|
|
807
|
+
'border-width': 6,
|
|
808
|
+
'border-color': '#FF8C00', // Bright orange for maximum visibility
|
|
809
|
+
'background-color': '#FFD700', // Bright gold/yellow
|
|
810
|
+
'opacity': 1,
|
|
811
|
+
'z-index': 999, // Bring to front
|
|
812
|
+
'text-outline-color': '#000',
|
|
813
|
+
'text-outline-width': 2,
|
|
814
|
+
'font-weight': 'bold',
|
|
815
|
+
'font-size': '14px'
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
selector: ':parent.highlighted',
|
|
820
|
+
style: {
|
|
821
|
+
'border-width': 8,
|
|
822
|
+
'border-color': '#FF8C00', // Bright orange
|
|
823
|
+
'background-opacity': 0.2,
|
|
824
|
+
'background-color': '#FFD700',
|
|
825
|
+
'opacity': 1,
|
|
826
|
+
'z-index': 998
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
selector: 'edge.highlighted',
|
|
831
|
+
style: {
|
|
832
|
+
'width': 6, // Thicker edges for better visibility
|
|
833
|
+
'line-color': '#FF8C00', // Bright orange
|
|
834
|
+
'target-arrow-color': '#FF8C00',
|
|
835
|
+
'opacity': 1,
|
|
836
|
+
'z-index': 999,
|
|
837
|
+
'arrow-scale': 2 // Larger arrows
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
],
|
|
841
|
+
layout: {
|
|
842
|
+
name: 'cose',
|
|
843
|
+
animate: true,
|
|
844
|
+
animationDuration: 600,
|
|
845
|
+
animationEasing: 'ease-out',
|
|
846
|
+
nodeRepulsion: 10000,
|
|
847
|
+
idealEdgeLength: 120,
|
|
848
|
+
edgeElasticity: 120,
|
|
849
|
+
nestingFactor: 1.8,
|
|
850
|
+
gravity: 0.8,
|
|
851
|
+
numIter: 1200,
|
|
852
|
+
initialTemp: 250,
|
|
853
|
+
coolingFactor: 0.95,
|
|
854
|
+
minTemp: 1.0,
|
|
855
|
+
// Compound node specific options
|
|
856
|
+
componentSpacing: 120,
|
|
857
|
+
nodeOverlap: 15,
|
|
858
|
+
padding: 25
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// Hide dependency edges by default (show only module structure)
|
|
863
|
+
cy.edges('.dependency').hide();
|
|
864
|
+
|
|
865
|
+
// Fix for unwanted panning on click: Track mouse movement to distinguish click from drag
|
|
866
|
+
let mouseDownPos = null;
|
|
867
|
+
let isDragging = false;
|
|
868
|
+
const DRAG_THRESHOLD = 5; // pixels - minimum movement to be considered a drag
|
|
869
|
+
|
|
870
|
+
cy.on('mousedown', function(evt) {
|
|
871
|
+
if (evt.target === cy) {
|
|
872
|
+
// Store initial mouse position when clicking on background
|
|
873
|
+
mouseDownPos = { x: evt.position.x, y: evt.position.y };
|
|
874
|
+
isDragging = false;
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
cy.on('mousemove', function(evt) {
|
|
879
|
+
if (mouseDownPos) {
|
|
880
|
+
// Calculate distance moved
|
|
881
|
+
const dx = evt.position.x - mouseDownPos.x;
|
|
882
|
+
const dy = evt.position.y - mouseDownPos.y;
|
|
883
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
884
|
+
|
|
885
|
+
// If moved more than threshold, it's a drag
|
|
886
|
+
if (distance > DRAG_THRESHOLD) {
|
|
887
|
+
isDragging = true;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
cy.on('mouseup', function(evt) {
|
|
893
|
+
mouseDownPos = null;
|
|
894
|
+
isDragging = false;
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
// Track currently selected node for edge filtering
|
|
898
|
+
let selectedNode = null;
|
|
899
|
+
|
|
900
|
+
// Node click handler
|
|
901
|
+
cy.on('tap', 'node', function(evt) {
|
|
902
|
+
const node = evt.target;
|
|
903
|
+
const data = node.data();
|
|
904
|
+
|
|
905
|
+
// Store the selected node
|
|
906
|
+
selectedNode = node;
|
|
907
|
+
|
|
908
|
+
// Clear previous highlights and dimming
|
|
909
|
+
cy.elements().removeClass('highlighted dimmed');
|
|
910
|
+
|
|
911
|
+
if (data.type === 'module') {
|
|
912
|
+
// MODULE SELECTION: Show only edges related to this module
|
|
913
|
+
|
|
914
|
+
// Get all edges connected to this module
|
|
915
|
+
const moduleEdges = node.connectedEdges('.import');
|
|
916
|
+
|
|
917
|
+
// Hide all edges first, then show only the module's edges
|
|
918
|
+
cy.edges().hide();
|
|
919
|
+
|
|
920
|
+
// Show and highlight only the module's import/export edges
|
|
921
|
+
moduleEdges.show().addClass('highlighted');
|
|
922
|
+
|
|
923
|
+
// Highlight the selected module
|
|
924
|
+
node.addClass('highlighted');
|
|
925
|
+
|
|
926
|
+
// Highlight connected modules
|
|
927
|
+
moduleEdges.connectedNodes('[type="module"]').addClass('highlighted');
|
|
928
|
+
|
|
929
|
+
// Dim all other nodes except highlighted ones
|
|
930
|
+
cy.nodes().not('.highlighted').addClass('dimmed');
|
|
931
|
+
|
|
932
|
+
} else if (data.type === 'provider') {
|
|
933
|
+
// PROVIDER SELECTION: Show only edges related to this provider
|
|
934
|
+
|
|
935
|
+
// Get all dependency edges connected to this provider
|
|
936
|
+
const providerEdges = node.connectedEdges('.dependency');
|
|
937
|
+
|
|
938
|
+
// Hide all edges first, then show only the provider's edges
|
|
939
|
+
cy.edges().hide();
|
|
940
|
+
|
|
941
|
+
// Show and highlight only the provider's dependency edges
|
|
942
|
+
providerEdges.show().addClass('highlighted');
|
|
943
|
+
|
|
944
|
+
// Highlight the selected provider
|
|
945
|
+
node.addClass('highlighted');
|
|
946
|
+
|
|
947
|
+
// Highlight connected providers
|
|
948
|
+
providerEdges.connectedNodes('[type="provider"]').addClass('highlighted');
|
|
949
|
+
|
|
950
|
+
// Dim all other nodes except highlighted ones
|
|
951
|
+
cy.nodes().not('.highlighted').addClass('dimmed');
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Show node details
|
|
955
|
+
showNodeDetails(data);
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
// Canvas click handler for deselection
|
|
959
|
+
cy.on('tap', function(evt) {
|
|
960
|
+
// Check if the tap was on the background (not on a node or edge)
|
|
961
|
+
// AND it wasn't a drag operation
|
|
962
|
+
if (evt.target === cy && !isDragging) {
|
|
963
|
+
// Reset selection
|
|
964
|
+
selectedNode = null;
|
|
965
|
+
|
|
966
|
+
// Clear all highlights and dimming
|
|
967
|
+
cy.elements().removeClass('highlighted dimmed');
|
|
968
|
+
|
|
969
|
+
// Restore default view: show all module edges, hide dependency edges
|
|
970
|
+
cy.edges('.import').show();
|
|
971
|
+
cy.edges('.dependency').hide();
|
|
972
|
+
|
|
973
|
+
// Ensure all nodes are visible
|
|
974
|
+
cy.nodes().show();
|
|
975
|
+
|
|
976
|
+
// Hide details panel or show default message
|
|
977
|
+
document.getElementById('details').innerHTML = '<p style="color: #999; text-align: center; padding: 20px;">Click on a node to see details</p>';
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
// Node hover handler
|
|
982
|
+
cy.on('mouseover', 'node', function(evt) {
|
|
983
|
+
const node = evt.target;
|
|
984
|
+
node.style('cursor', 'pointer');
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
// View mode controls
|
|
988
|
+
document.getElementById('view-all').addEventListener('click', function() {
|
|
989
|
+
setActiveButton(this);
|
|
990
|
+
// Reset selection
|
|
991
|
+
selectedNode = null;
|
|
992
|
+
// Clear highlights and dimming
|
|
993
|
+
cy.elements().removeClass('highlighted dimmed');
|
|
994
|
+
// Show all nodes
|
|
995
|
+
cy.nodes().show();
|
|
996
|
+
// Show import edges, hide dependency edges (default view)
|
|
997
|
+
cy.edges('.import').show();
|
|
998
|
+
cy.edges('.dependency').hide();
|
|
999
|
+
cy.fit();
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
document.getElementById('view-modules').addEventListener('click', function() {
|
|
1003
|
+
setActiveButton(this);
|
|
1004
|
+
// Reset selection
|
|
1005
|
+
selectedNode = null;
|
|
1006
|
+
// Clear highlights and dimming
|
|
1007
|
+
cy.elements().removeClass('highlighted dimmed');
|
|
1008
|
+
// Hide provider nodes (children) but keep parent modules visible
|
|
1009
|
+
cy.nodes('[type="provider"]').hide();
|
|
1010
|
+
cy.edges('.dependency').hide();
|
|
1011
|
+
// Show parent modules and import edges
|
|
1012
|
+
cy.nodes(':parent').show();
|
|
1013
|
+
cy.nodes('[type="module"]').show();
|
|
1014
|
+
cy.edges('.import').show();
|
|
1015
|
+
cy.fit();
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
document.getElementById('view-providers').addEventListener('click', function() {
|
|
1019
|
+
setActiveButton(this);
|
|
1020
|
+
// Reset selection
|
|
1021
|
+
selectedNode = null;
|
|
1022
|
+
// Clear highlights and dimming
|
|
1023
|
+
cy.elements().removeClass('highlighted dimmed');
|
|
1024
|
+
// Show parent modules (to see grouping) and provider nodes
|
|
1025
|
+
cy.nodes(':parent').show();
|
|
1026
|
+
cy.nodes('[type="provider"]').show();
|
|
1027
|
+
// Show all dependency edges in this view
|
|
1028
|
+
cy.edges('.dependency').show();
|
|
1029
|
+
// Hide import edges but keep module containers visible for context
|
|
1030
|
+
cy.edges('.import').hide();
|
|
1031
|
+
cy.fit();
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
// Reset zoom
|
|
1035
|
+
document.getElementById('reset-zoom').addEventListener('click', function() {
|
|
1036
|
+
cy.zoom(1);
|
|
1037
|
+
cy.center();
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
// Fit graph
|
|
1041
|
+
document.getElementById('fit-graph').addEventListener('click', function() {
|
|
1042
|
+
cy.fit();
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
// Search functionality
|
|
1046
|
+
document.getElementById('search').addEventListener('input', function(e) {
|
|
1047
|
+
const searchTerm = e.target.value.toLowerCase();
|
|
1048
|
+
|
|
1049
|
+
if (!searchTerm) {
|
|
1050
|
+
// Reset to default view
|
|
1051
|
+
selectedNode = null;
|
|
1052
|
+
cy.nodes().show();
|
|
1053
|
+
cy.edges('.import').show();
|
|
1054
|
+
cy.edges('.dependency').hide();
|
|
1055
|
+
cy.elements().removeClass('highlighted dimmed');
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
cy.elements().removeClass('highlighted dimmed');
|
|
1060
|
+
|
|
1061
|
+
const matchingNodes = cy.nodes().filter(function(node) {
|
|
1062
|
+
const label = node.data('label').toLowerCase();
|
|
1063
|
+
return label.includes(searchTerm);
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
if (matchingNodes.length > 0) {
|
|
1067
|
+
matchingNodes.addClass('highlighted');
|
|
1068
|
+
cy.fit(matchingNodes, 50);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
function setActiveButton(button) {
|
|
1073
|
+
document.querySelectorAll('.control-group button').forEach(btn => {
|
|
1074
|
+
btn.classList.remove('active');
|
|
1075
|
+
});
|
|
1076
|
+
button.classList.add('active');
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function navigateToNode(nodeId) {
|
|
1080
|
+
const node = cy.getElementById(nodeId);
|
|
1081
|
+
if (node.length > 0) {
|
|
1082
|
+
const data = node.data();
|
|
1083
|
+
|
|
1084
|
+
// Store the selected node
|
|
1085
|
+
selectedNode = node;
|
|
1086
|
+
|
|
1087
|
+
// Clear previous highlights and dimming
|
|
1088
|
+
cy.elements().removeClass('highlighted dimmed');
|
|
1089
|
+
|
|
1090
|
+
if (data.type === 'module') {
|
|
1091
|
+
// MODULE SELECTION: Show only edges related to this module
|
|
1092
|
+
|
|
1093
|
+
// Get all edges connected to this module
|
|
1094
|
+
const moduleEdges = node.connectedEdges('.import');
|
|
1095
|
+
|
|
1096
|
+
// Hide all edges first, then show only the module's edges
|
|
1097
|
+
cy.edges().hide();
|
|
1098
|
+
|
|
1099
|
+
// Show and highlight only the module's import/export edges
|
|
1100
|
+
moduleEdges.show().addClass('highlighted');
|
|
1101
|
+
|
|
1102
|
+
// Highlight the selected module
|
|
1103
|
+
node.addClass('highlighted');
|
|
1104
|
+
|
|
1105
|
+
// Highlight connected modules
|
|
1106
|
+
moduleEdges.connectedNodes('[type="module"]').addClass('highlighted');
|
|
1107
|
+
|
|
1108
|
+
// Dim all other nodes except highlighted ones
|
|
1109
|
+
cy.nodes().not('.highlighted').addClass('dimmed');
|
|
1110
|
+
|
|
1111
|
+
} else if (data.type === 'provider') {
|
|
1112
|
+
// PROVIDER SELECTION: Show only edges related to this provider
|
|
1113
|
+
|
|
1114
|
+
// Get all dependency edges connected to this provider
|
|
1115
|
+
const providerEdges = node.connectedEdges('.dependency');
|
|
1116
|
+
|
|
1117
|
+
// Hide all edges first, then show only the provider's edges
|
|
1118
|
+
cy.edges().hide();
|
|
1119
|
+
|
|
1120
|
+
// Show and highlight only the provider's dependency edges
|
|
1121
|
+
providerEdges.show().addClass('highlighted');
|
|
1122
|
+
|
|
1123
|
+
// Highlight the selected provider
|
|
1124
|
+
node.addClass('highlighted');
|
|
1125
|
+
|
|
1126
|
+
// Highlight connected providers
|
|
1127
|
+
providerEdges.connectedNodes('[type="provider"]').addClass('highlighted');
|
|
1128
|
+
|
|
1129
|
+
// Dim all other nodes except highlighted ones
|
|
1130
|
+
cy.nodes().not('.highlighted').addClass('dimmed');
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Animate pan and zoom to the node
|
|
1134
|
+
cy.animate({
|
|
1135
|
+
center: { eles: node },
|
|
1136
|
+
zoom: 1.5,
|
|
1137
|
+
duration: 500,
|
|
1138
|
+
easing: 'ease-in-out-cubic'
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
// Show node details
|
|
1142
|
+
showNodeDetails(data);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function showNodeDetails(data) {
|
|
1147
|
+
const placeholder = document.querySelector('.placeholder');
|
|
1148
|
+
const detailsDiv = document.getElementById('node-details');
|
|
1149
|
+
|
|
1150
|
+
placeholder.style.display = 'none';
|
|
1151
|
+
detailsDiv.style.display = 'block';
|
|
1152
|
+
|
|
1153
|
+
let html = '';
|
|
1154
|
+
|
|
1155
|
+
if (data.type === 'module') {
|
|
1156
|
+
html = \`
|
|
1157
|
+
<div class="info-item">
|
|
1158
|
+
<label>Type</label>
|
|
1159
|
+
<div class="value">
|
|
1160
|
+
<span class="badge module">Module</span>
|
|
1161
|
+
\${data.isGlobal ? '<span class="badge global">Global</span>' : ''}
|
|
1162
|
+
</div>
|
|
1163
|
+
</div>
|
|
1164
|
+
<div class="info-item">
|
|
1165
|
+
<label>Name</label>
|
|
1166
|
+
<div class="value"><code>\${data.label}</code></div>
|
|
1167
|
+
</div>
|
|
1168
|
+
<div class="info-item">
|
|
1169
|
+
<label>File Path</label>
|
|
1170
|
+
<div class="value">
|
|
1171
|
+
<a href="${ideUrlTemplate.replace("{path}", "' + data.path + '")}" target="_blank">
|
|
1172
|
+
\${data.path}
|
|
1173
|
+
</a>
|
|
1174
|
+
</div>
|
|
1175
|
+
</div>
|
|
1176
|
+
<div class="info-item">
|
|
1177
|
+
<label>Imports (\${data.imports.length})</label>
|
|
1178
|
+
<ul class="dependency-list">
|
|
1179
|
+
\${data.imports.map(imp => {
|
|
1180
|
+
const nodeId = 'module-' + imp;
|
|
1181
|
+
const exists = cy.getElementById(nodeId).length > 0;
|
|
1182
|
+
return exists
|
|
1183
|
+
? '<li><a href="#" class="node-link" data-node-id="' + nodeId + '">' + imp + '</a></li>'
|
|
1184
|
+
: '<li>' + imp + '</li>';
|
|
1185
|
+
}).join('')}
|
|
1186
|
+
</ul>
|
|
1187
|
+
</div>
|
|
1188
|
+
<div class="info-item">
|
|
1189
|
+
<label>Providers (\${data.providers.length})</label>
|
|
1190
|
+
<ul class="dependency-list">
|
|
1191
|
+
\${data.providers.map(prov => {
|
|
1192
|
+
// Use providerMap to get the correct node ID
|
|
1193
|
+
const nodeId = providerMap[prov];
|
|
1194
|
+
const exists = nodeId && cy.getElementById(nodeId).length > 0;
|
|
1195
|
+
return exists
|
|
1196
|
+
? '<li><a href="#" class="node-link" data-node-id="' + nodeId + '">' + prov + '</a></li>'
|
|
1197
|
+
: '<li>' + prov + '</li>';
|
|
1198
|
+
}).join('')}
|
|
1199
|
+
</ul>
|
|
1200
|
+
</div>
|
|
1201
|
+
\${data.exports.length > 0 ? \`
|
|
1202
|
+
<div class="info-item">
|
|
1203
|
+
<label>Exports (\${data.exports.length})</label>
|
|
1204
|
+
<ul class="dependency-list">
|
|
1205
|
+
\${data.exports.map(exp => {
|
|
1206
|
+
// Check if export is a provider or a module
|
|
1207
|
+
let nodeId = providerMap[exp] || moduleMap[exp];
|
|
1208
|
+
const exists = nodeId && cy.getElementById(nodeId).length > 0;
|
|
1209
|
+
return exists
|
|
1210
|
+
? '<li><a href="#" class="node-link" data-node-id="' + nodeId + '">' + exp + '</a></li>'
|
|
1211
|
+
: '<li>' + exp + '</li>';
|
|
1212
|
+
}).join('')}
|
|
1213
|
+
</ul>
|
|
1214
|
+
</div>
|
|
1215
|
+
\` : ''}
|
|
1216
|
+
\`;
|
|
1217
|
+
} else if (data.type === 'provider') {
|
|
1218
|
+
html = \`
|
|
1219
|
+
<div class="info-item">
|
|
1220
|
+
<label>Type</label>
|
|
1221
|
+
<div class="value">
|
|
1222
|
+
<span class="badge provider">Provider</span>
|
|
1223
|
+
<span class="badge \${data.providerType.toLowerCase()}">\${data.providerType}</span>
|
|
1224
|
+
</div>
|
|
1225
|
+
</div>
|
|
1226
|
+
<div class="info-item">
|
|
1227
|
+
<label>Token</label>
|
|
1228
|
+
<div class="value"><code>\${data.label}</code></div>
|
|
1229
|
+
</div>
|
|
1230
|
+
<div class="info-item">
|
|
1231
|
+
<label>Module</label>
|
|
1232
|
+
<div class="value">
|
|
1233
|
+
\${(() => {
|
|
1234
|
+
const nodeId = 'module-' + data.module;
|
|
1235
|
+
const exists = cy.getElementById(nodeId).length > 0;
|
|
1236
|
+
return exists
|
|
1237
|
+
? '<a href="#" class="node-link" data-node-id="' + nodeId + '"><code>' + data.module + '</code></a>'
|
|
1238
|
+
: '<code>' + data.module + '</code>';
|
|
1239
|
+
})()}
|
|
1240
|
+
</div>
|
|
1241
|
+
</div>
|
|
1242
|
+
\${data.scope ? \`
|
|
1243
|
+
<div class="info-item">
|
|
1244
|
+
<label>Scope</label>
|
|
1245
|
+
<div class="value">\${data.scope}</div>
|
|
1246
|
+
</div>
|
|
1247
|
+
\` : ''}
|
|
1248
|
+
\${data.value !== undefined ? \`
|
|
1249
|
+
<div class="info-item">
|
|
1250
|
+
<label>Value</label>
|
|
1251
|
+
<div class="value"><code>\${data.value}</code></div>
|
|
1252
|
+
</div>
|
|
1253
|
+
\` : ''}
|
|
1254
|
+
\${data.factory ? \`
|
|
1255
|
+
<div class="info-item">
|
|
1256
|
+
<label>Factory</label>
|
|
1257
|
+
<div class="value"><code>\${data.factory}</code></div>
|
|
1258
|
+
</div>
|
|
1259
|
+
\` : ''}
|
|
1260
|
+
\${data.useClass ? \`
|
|
1261
|
+
<div class="info-item">
|
|
1262
|
+
<label>Use Class</label>
|
|
1263
|
+
<div class="value"><code>\${data.useClass}</code></div>
|
|
1264
|
+
</div>
|
|
1265
|
+
\` : ''}
|
|
1266
|
+
\${data.hasMissingDecorators ? \`
|
|
1267
|
+
<div class="info-item" style="background: #ffebee; padding: 0.75rem; border-left: 3px solid #f44336; margin-bottom: 1rem;">
|
|
1268
|
+
<label style="color: #c62828; font-weight: bold;">⚠️ Missing @Inject Decorators</label>
|
|
1269
|
+
<div class="value" style="color: #c62828; font-size: 0.875rem;">
|
|
1270
|
+
Some dependencies are missing explicit @Inject decorators. This may cause runtime errors.
|
|
1271
|
+
</div>
|
|
1272
|
+
</div>
|
|
1273
|
+
\` : ''}
|
|
1274
|
+
\${data.dependencies && data.dependencies.length > 0 ? \`
|
|
1275
|
+
<div class="info-item">
|
|
1276
|
+
<label>Dependencies (\${data.dependencies.length})</label>
|
|
1277
|
+
<ul class="dependency-list">
|
|
1278
|
+
\${data.dependencies.map(dep => {
|
|
1279
|
+
const token = dep.token || 'Unknown';
|
|
1280
|
+
// Use providerMap to get the correct node ID
|
|
1281
|
+
const nodeId = providerMap[token];
|
|
1282
|
+
const exists = nodeId && cy.getElementById(nodeId).length > 0;
|
|
1283
|
+
const link = exists
|
|
1284
|
+
? '<a href="#" class="node-link" data-node-id="' + nodeId + '">' + token + '</a>'
|
|
1285
|
+
: token;
|
|
1286
|
+
const warning = dep.hasExplicitDecorator === false
|
|
1287
|
+
? ' <span style="color: #f44336; font-weight: bold;">⚠️ Missing @Inject</span>'
|
|
1288
|
+
: '';
|
|
1289
|
+
return '<li>' + link + (dep.optional ? ' <em>(optional)</em>' : '') + warning + '</li>';
|
|
1290
|
+
}).join('')}
|
|
1291
|
+
</ul>
|
|
1292
|
+
</div>
|
|
1293
|
+
\` : ''}
|
|
1294
|
+
\`;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
detailsDiv.innerHTML = html;
|
|
1298
|
+
|
|
1299
|
+
// Attach click handlers to node links
|
|
1300
|
+
detailsDiv.querySelectorAll('.node-link').forEach(link => {
|
|
1301
|
+
link.addEventListener('click', function(e) {
|
|
1302
|
+
e.preventDefault();
|
|
1303
|
+
const nodeId = this.getAttribute('data-node-id');
|
|
1304
|
+
navigateToNode(nodeId);
|
|
1305
|
+
});
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
`;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
exports.HtmlGenerator = HtmlGenerator;
|
|
1312
|
+
//# sourceMappingURL=html-generator.js.map
|