engin 0.0.20__py3-none-any.whl → 0.1.0__py3-none-any.whl

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.
engin/_cli/_graph.html CHANGED
@@ -1,78 +1,883 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
- <style>
4
- #mermaid-container {
5
- width: 100%;
6
- height: 100%;
7
- overflow: auto; /* Enables scrolling */
8
- border: 1px solid #ddd;
9
- cursor: grab;
10
- position: relative;
11
- white-space: nowrap; /* Prevents wrapping */
12
- }
13
-
14
- #mermaid-content {
15
- width: max-content; /* Ensures content can expand */
16
- height: max-content;
17
- }
18
- </style>
19
- <script type="module">
20
- import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
21
- let config = { flowchart: { useMaxWidth: false, htmlLabels: true, defaultRenderer: "elk" } };
22
- mermaid.initialize(config);
23
-
24
- // Drag-to-Move Functionality
25
- const container = document.getElementById("mermaid-container");
26
-
27
- let isDragging = false;
28
- let startX, startY, scrollLeft, scrollTop;
29
-
30
- container.addEventListener("pointerdown", (e) => {
31
- isDragging = true;
32
- startX = e.clientX;
33
- startY = e.clientY;
34
- scrollLeft = container.scrollLeft;
35
- scrollTop = container.scrollTop;
36
- container.style.cursor = "grabbing";
37
- });
38
-
39
- container.addEventListener("pointermove", (e) => {
40
- if (!isDragging) return;
41
- const x = e.clientX - startX;
42
- const y = e.clientY - startY;
43
- container.scrollLeft = scrollLeft - x;
44
- container.scrollTop = scrollTop - y;
45
- });
46
-
47
- container.addEventListener("pointerup", () => {
48
- isDragging = false;
49
- container.style.cursor = "grab";
50
- });
51
-
52
- container.addEventListener("pointerleave", () => {
53
- isDragging = false;
54
- container.style.cursor = "grab";
55
- });
56
- </script>
57
- <body>
58
- <div style="border-style:outset">
59
- <p>LEGEND</p>
60
- <pre class="mermaid" id="legend">
61
- graph LR
62
- %%LEGEND%%
63
- classDef b0 fill:#7fc97f;
64
- classDef external stroke-dasharray: 5 5;
65
- </pre>
3
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
4
+ <head>
5
+ <title>Engin Dependency Graph</title>
6
+ <style>
7
+ body {
8
+ font-family: Arial, sans-serif;
9
+ margin: 0;
10
+ padding: 0;
11
+ background-color: #f5f5f5;
12
+ }
13
+
14
+ .controls {
15
+ background: white;
16
+ padding: 15px;
17
+ border-bottom: 2px solid #ddd;
18
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
19
+ display: flex;
20
+ gap: 20px;
21
+ align-items: center;
22
+ flex-wrap: wrap;
23
+ }
24
+
25
+ .control-group {
26
+ display: flex;
27
+ align-items: center;
28
+ gap: 8px;
29
+ }
30
+
31
+ .control-group label {
32
+ font-weight: bold;
33
+ color: #333;
34
+ }
35
+
36
+ .control-group input[type="checkbox"] {
37
+ transform: scale(1.2);
38
+ }
39
+
40
+ .control-group button {
41
+ padding: 8px 16px;
42
+ background: #007acc;
43
+ color: white;
44
+ border: none;
45
+ border-radius: 4px;
46
+ cursor: pointer;
47
+ font-size: 14px;
48
+ }
49
+
50
+ .control-group button:hover {
51
+ background: #005c99;
52
+ }
53
+
54
+ .stats {
55
+ margin-left: auto;
56
+ color: #666;
57
+ font-size: 14px;
58
+ }
59
+
60
+ #mermaid-container {
61
+ width: 100%;
62
+ height: calc(100vh - 200px);
63
+ overflow: auto;
64
+ background: white;
65
+ cursor: grab;
66
+ position: relative;
67
+ }
68
+
69
+ #mermaid-container:active {
70
+ cursor: grabbing;
71
+ }
72
+
73
+ #mermaid-content {
74
+ width: max-content;
75
+ height: max-content;
76
+ min-width: 100%;
77
+ min-height: 100%;
78
+ }
79
+
80
+ .legend-container {
81
+ background: white;
82
+ padding: 15px;
83
+ border-top: 1px solid #ddd;
84
+ overflow-x: auto;
85
+ }
86
+
87
+ .legend-container h3 {
88
+ margin: 0 0 10px 0;
89
+ color: #333;
90
+ }
91
+
92
+ .loading {
93
+ display: flex;
94
+ justify-content: center;
95
+ align-items: center;
96
+ height: 200px;
97
+ font-size: 18px;
98
+ color: #666;
99
+ }
100
+
101
+ .hidden {
102
+ display: none;
103
+ }
104
+
105
+ .error {
106
+ color: red;
107
+ background: #ffebee;
108
+ padding: 10px;
109
+ border-radius: 4px;
110
+ margin: 10px;
111
+ }
112
+
113
+ /* Tooltip styles */
114
+ .node-tooltip {
115
+ position: absolute;
116
+ background: rgba(0, 0, 0, 0.9);
117
+ color: white;
118
+ padding: 12px;
119
+ border-radius: 6px;
120
+ font-size: 12px;
121
+ max-width: 300px;
122
+ z-index: 1000;
123
+ pointer-events: none;
124
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
125
+ border: 1px solid #555;
126
+ line-height: 1.4;
127
+ display: none;
128
+ }
129
+
130
+ .tooltip-header {
131
+ font-weight: bold;
132
+ font-size: 13px;
133
+ margin-bottom: 8px;
134
+ padding-bottom: 4px;
135
+ border-bottom: 1px solid #555;
136
+ color: #87ceeb;
137
+ }
138
+
139
+ .tooltip-section {
140
+ margin: 6px 0;
141
+ }
142
+
143
+ .tooltip-label {
144
+ font-weight: bold;
145
+ color: #ffd700;
146
+ display: inline-block;
147
+ min-width: 80px;
148
+ }
149
+
150
+ .tooltip-value {
151
+ color: #e0e0e0;
152
+ }
153
+
154
+ .tooltip-list {
155
+ margin: 4px 0 4px 16px;
156
+ padding: 0;
157
+ }
158
+
159
+ .tooltip-list li {
160
+ list-style: none;
161
+ margin: 2px 0;
162
+ color: #ccc;
163
+ }
164
+
165
+ .tooltip-list li:before {
166
+ content: "• ";
167
+ color: #87ceeb;
168
+ margin-right: 4px;
169
+ }
170
+ </style>
171
+ </head>
172
+
173
+ <body>
174
+ <div class="controls">
175
+ <div class="control-group">
176
+ <label>
177
+ <input type="checkbox" id="show-external" checked>
178
+ Show External Dependencies
179
+ </label>
66
180
  </div>
67
- <div id="mermaid-container" style="width: 100%; overflow-x: auto; border: 1px solid #ddd; cursor: grab; position: relative;">
68
- <div id="mermaid-content" style="width: max-content; height: max-content;">
69
- <pre class="mermaid" id="graph">
70
- %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
71
- graph LR
72
- %%DATA%%
73
- classDef external stroke-dasharray: 5 5;
74
- </pre>
75
- </div>
181
+
182
+ <div class="control-group">
183
+ <label>
184
+ <input type="checkbox" id="show-blocks" checked>
185
+ Group Block Dependencies
186
+ </label>
187
+ </div>
188
+
189
+ <div class="control-group">
190
+ <label>Layout:</label>
191
+ <select id="layout-select">
192
+ <option value="dagre" selected>Dagre (Default)</option>
193
+ <option value="elk">ELK</option>
194
+ <option value="klay">Klay</option>
195
+ </select>
196
+ </div>
197
+
198
+ <div class="control-group">
199
+ <label>Theme:</label>
200
+ <select id="theme-select">
201
+ <option value="default" selected>Default</option>
202
+ <option value="dark">Dark</option>
203
+ <option value="forest">Forest</option>
204
+ <option value="base">Base</option>
205
+ <option value="neutral">Neutral</option>
206
+ </select>
207
+ </div>
208
+
209
+ <div class="control-group">
210
+ <button id="fit-view">Fit to View</button>
211
+ <button id="refresh-graph">Refresh</button>
212
+ </div>
213
+
214
+ <div class="stats">
215
+ <span id="node-count">0 nodes</span>
216
+ <span id="edge-count">0 edges</span>
217
+ </div>
218
+ </div>
219
+
220
+ <div id="mermaid-container">
221
+ <div class="loading" id="loading">
222
+ <div>Loading dependency graph...</div>
223
+ </div>
224
+ <div id="mermaid-content">
225
+ <div id="graph"></div>
226
+ </div>
227
+ </div>
228
+
229
+ <div class="legend-container">
230
+ <h3>Legend</h3>
231
+ <div id="legend-content">
232
+ <div id="legend"></div>
76
233
  </div>
77
- </body>
234
+ </div>
235
+
236
+ <div id="node-tooltip" class="node-tooltip"></div>
237
+
238
+ <script type="application/json" id="graph-data">
239
+ %%GRAPH_DATA%%
240
+ </script>
241
+
242
+ <script type="module">
243
+ import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
244
+
245
+ let graphData = null;
246
+ let currentConfig = {
247
+ showExternal: true,
248
+ showBlocks: true,
249
+ layout: 'dagre',
250
+ theme: 'default',
251
+ };
252
+
253
+ // Initialize mermaid (will be re-initialized with proper config later)
254
+ mermaid.initialize({
255
+ startOnLoad: false,
256
+ flowchart: {
257
+ useMaxWidth: true,
258
+ htmlLabels: true,
259
+ defaultRenderer: 'dagre'
260
+ },
261
+ theme: 'default'
262
+ });
263
+
264
+ // Load graph data
265
+ function loadGraphData() {
266
+ const dataScript = document.getElementById('graph-data');
267
+ try {
268
+ graphData = JSON.parse(dataScript.textContent);
269
+ return true;
270
+ } catch (e) {
271
+ console.error('Failed to parse graph data:', e);
272
+ showError('Error loading graph data: ' + e.message);
273
+ return false;
274
+ }
275
+ }
276
+
277
+ // Show error message
278
+ function showError(message) {
279
+ const loading = document.getElementById('loading');
280
+ loading.innerHTML = `<div class="error">${message}</div>`;
281
+ loading.classList.remove('hidden');
282
+ }
283
+
284
+ // Generate mermaid syntax from filtered data
285
+ function generateMermaidSyntax() {
286
+ if (!graphData) return '';
287
+
288
+ const {nodes, edges, blocks} = graphData;
289
+
290
+ // Filter nodes based on current settings
291
+ let filteredNodes = nodes;
292
+ if (!currentConfig.showExternal) {
293
+ filteredNodes = nodes.filter(node => !node.external);
294
+ }
295
+
296
+ // Create a set of visible node IDs for edge filtering
297
+ const visibleNodeIds = new Set(filteredNodes.map(node => node.id));
298
+
299
+ // Filter edges to only include those between visible nodes
300
+ const filteredEdges = edges.filter(edge =>
301
+ visibleNodeIds.has(edge.from) && visibleNodeIds.has(edge.to)
302
+ );
303
+
304
+ // Generate mermaid syntax
305
+ let mermaidSyntax = `%%{init: {"flowchart": {"defaultRenderer": "${currentConfig.layout}"}} }%%\n`;
306
+ mermaidSyntax += 'graph LR\n';
307
+
308
+ if (currentConfig.showBlocks && blocks.length > 0) {
309
+ // GROUPED MODE: Show blocks as subgraphs
310
+
311
+ // Add edges that originate or end outside of blocks or go between blocks
312
+ const mainEdges = filteredEdges.filter(edge => (!edge.from_block || !edge.to_block || edge.to_block !== edge.from_block));
313
+ for (const edge of mainEdges) {
314
+ const fromNode = nodes.find(n => n.id === edge.from);
315
+ const toNode = nodes.find(n => n.id === edge.to);
316
+ if (fromNode && toNode) {
317
+ mermaidSyntax += ` ${renderNode(fromNode)} --> ${renderNode(toNode)}\n`;
318
+ }
319
+ }
320
+
321
+ // Add block subgraphs
322
+ for (const block of blocks) {
323
+ const blockNodes = filteredNodes.filter(node => node.block === block);
324
+ if (blockNodes.length > 1) {
325
+ mermaidSyntax += ` subgraph ${block.replace(/[^a-zA-Z0-9]/g, '_')}\n`;
326
+
327
+ // Add edges within this block
328
+ const blockEdges = filteredEdges.filter(edge => {
329
+ const fromNode = nodes.find(n => n.id === edge.from);
330
+ const toNode = nodes.find(n => n.id === edge.to);
331
+ return fromNode?.block === block && toNode?.block === block;
332
+ });
333
+
334
+ for (const edge of blockEdges) {
335
+ const fromNode = nodes.find(n => n.id === edge.from);
336
+ const toNode = nodes.find(n => n.id === edge.to);
337
+
338
+ if (fromNode && toNode) {
339
+ mermaidSyntax += ` ${renderNode(fromNode, false)} --> ${renderNode(toNode, false)}\n`;
340
+ }
341
+ }
342
+
343
+ mermaidSyntax += ' end\n';
344
+ } else if (blockNodes.length === 1) {
345
+ mermaidSyntax += ` subgraph ${block.replace(/[^a-zA-Z0-9]/g, '_')}\n`;
346
+ mermaidSyntax += ` ${renderNode(blockNodes[0], false)}\n`
347
+ mermaidSyntax += ' end\n';
348
+ }
349
+ }
350
+ } else {
351
+ // FLAT MODE: Show all edges as regular connections without block grouping
352
+ for (const edge of filteredEdges) {
353
+ const fromNode = nodes.find(n => n.id === edge.from);
354
+ const toNode = nodes.find(n => n.id === edge.to);
355
+ if (fromNode && toNode) {
356
+ mermaidSyntax += ` ${renderNode(fromNode)} --> ${renderNode(toNode)}\n`;
357
+ }
358
+ }
359
+ }
360
+
361
+ // Add CSS classes
362
+ mermaidSyntax += ' classDef external stroke-dasharray: 5 5;\n';
363
+ mermaidSyntax += ' classDef b0 fill:#7fc97f;\n';
364
+
365
+ return mermaidSyntax;
366
+ }
367
+
368
+ // Render a single node in mermaid format
369
+ function renderNode(node, includeBlock = true) {
370
+ let label = '';
371
+ let shape;
372
+ let styleClasses = '';
373
+
374
+ if (includeBlock && node.block) {
375
+ label += `_${node.block}_<br/>`;
376
+ }
377
+
378
+ // Escape the label for mermaid
379
+ const escapedLabel = node.label.replace(/["`]/g, '');
380
+ label += escapedLabel;
381
+
382
+ switch (node.type) {
383
+ case 'Supply':
384
+ shape = `${node.id}("${label}")`;
385
+ break;
386
+ case 'Provide':
387
+ shape = `${node.id}["${label}"]`;
388
+ break;
389
+ case 'Entrypoint':
390
+ shape = `${node.id}[/"${label}"\\]`;
391
+ break;
392
+ case 'Invoke':
393
+ shape = `${node.id}[/"${label}"/]`;
394
+ break;
395
+ case 'APIRoute':
396
+ shape = `${node.id}[["${label}"]]`;
397
+ break;
398
+ default:
399
+ shape = `${node.id}["${label}"]`;
400
+ }
401
+
402
+ if (node.style_classes && node.style_classes.length > 0) {
403
+ styleClasses = `:::${node.style_classes.join(',')}`;
404
+ }
405
+
406
+ return shape + styleClasses;
407
+ }
408
+
409
+ // Update statistics
410
+ function updateStats() {
411
+ if (!graphData) return;
412
+
413
+ const {nodes, edges} = graphData;
414
+ let visibleNodes = nodes;
415
+
416
+ if (!currentConfig.showExternal) {
417
+ visibleNodes = nodes.filter(node => !node.external);
418
+ }
419
+
420
+ const visibleNodeIds = new Set(visibleNodes.map(node => node.id));
421
+ const visibleEdges = edges.filter(edge =>
422
+ visibleNodeIds.has(edge.from) && visibleNodeIds.has(edge.to)
423
+ );
424
+
425
+ document.getElementById('node-count').textContent = `${visibleNodes.length} nodes`;
426
+ document.getElementById('edge-count').textContent = `${visibleEdges.length} edges`;
427
+ }
428
+
429
+ // Tooltip functionality
430
+ function createTooltipContent(nodeData) {
431
+ const {type, label, details, block, external} = nodeData;
432
+
433
+ let content = `<div class="tooltip-header">${type}: ${label}</div>`;
434
+
435
+ // Basic information
436
+ content += `<div class="tooltip-section">`;
437
+ content += `<span class="tooltip-label">Module:</span> <span class="tooltip-value">${details.source_module}</span><br>`;
438
+ content += `<span class="tooltip-label">Package:</span> <span class="tooltip-value">${details.source_package}</span><br>`;
439
+ if (external) {
440
+ content += `<span class="tooltip-label">External:</span> <span class="tooltip-value">Yes</span><br>`;
441
+ }
442
+ if (block) {
443
+ content += `<span class="tooltip-label">Block:</span> <span class="tooltip-value">${block}</span><br>`;
444
+ }
445
+ content += `</div>`;
446
+
447
+ // Type-specific information
448
+ if (details.return_type) {
449
+ content += `<div class="tooltip-section">`;
450
+ content += `<span class="tooltip-label">Returns:</span> <span class="tooltip-value">${details.return_type}</span><br>`;
451
+ if (details.value_type) {
452
+ content += `<span class="tooltip-label">Value Type:</span> <span class="tooltip-value">${details.value_type}</span><br>`;
453
+ }
454
+ if (details.factory_function) {
455
+ content += `<span class="tooltip-label">Factory:</span> <span class="tooltip-value">${details.factory_function}</span><br>`;
456
+ }
457
+ if (details.scope) {
458
+ content += `<span class="tooltip-label">Scope:</span> <span class="tooltip-value">${details.scope}</span><br>`;
459
+ }
460
+ if (details.multiprovider) {
461
+ content += `<span class="tooltip-label">Multi:</span> <span class="tooltip-value">Yes</span><br>`;
462
+ }
463
+ content += `</div>`;
464
+ }
465
+
466
+ if (details.function) {
467
+ content += `<div class="tooltip-section">`;
468
+ content += `<span class="tooltip-label">Function:</span> <span class="tooltip-value">${details.function}</span><br>`;
469
+ content += `</div>`;
470
+ }
471
+
472
+ if (details.entrypoint_type) {
473
+ content += `<div class="tooltip-section">`;
474
+ content += `<span class="tooltip-label">Entry Type:</span> <span class="tooltip-value">${details.entrypoint_type}</span><br>`;
475
+ content += `</div>`;
476
+ }
477
+
478
+ if (details.methods && details.methods.length > 0) {
479
+ content += `<div class="tooltip-section">`;
480
+ content += `<span class="tooltip-label">Methods:</span> <span class="tooltip-value">${details.methods.join(', ')}</span><br>`;
481
+ if (details.path) {
482
+ content += `<span class="tooltip-label">Path:</span> <span class="tooltip-value">${details.path}</span><br>`;
483
+ }
484
+ content += `</div>`;
485
+ }
486
+
487
+ return content;
488
+ }
489
+
490
+ function showTooltip(event, nodeId) {
491
+ const tooltip = document.getElementById('node-tooltip');
492
+ if (!tooltip) return;
493
+
494
+ const nodeData = graphData.nodes.find(n => n.id === nodeId);
495
+ if (!nodeData) return;
496
+
497
+ tooltip.innerHTML = createTooltipContent(nodeData);
498
+ tooltip.style.display = 'block';
499
+
500
+ // Position tooltip near mouse
501
+ const rect = document.getElementById('mermaid-container').getBoundingClientRect();
502
+ let x = event.clientX - rect.left + 15;
503
+ let y = event.clientY - rect.top + 15;
504
+
505
+ // Ensure tooltip is always visible on screen
506
+ const minX = 10;
507
+ const minY = 10;
508
+ const maxX = rect.width - 320;
509
+ const maxY = rect.height - 200;
510
+
511
+ x = Math.max(minX, Math.min(x, maxX));
512
+ y = Math.max(minY, Math.min(y, maxY));
513
+
514
+ tooltip.style.left = `${x}px`;
515
+ tooltip.style.top = `${y}px`;
516
+ };
517
+
518
+ function hideTooltip() {
519
+ const tooltip = document.getElementById('node-tooltip');
520
+ tooltip.style.display = 'none';
521
+ }
522
+
523
+ // Helper function to extract node ID from flowchart format
524
+ function extractNodeId(elementId) {
525
+ if (!elementId) return null;
526
+
527
+ // Handle direct format: n1234567890
528
+ if (/^n\d+$/.test(elementId)) {
529
+ return elementId;
530
+ }
531
+
532
+ // Handle flowchart format: flowchart-n1234567890-0
533
+ const flowchartMatch = elementId.match(/^flowchart-(n\d+)-\d+$/);
534
+ if (flowchartMatch) {
535
+ return flowchartMatch[1];
536
+ }
537
+
538
+ return null;
539
+ }
540
+
541
+ function attachTooltipListeners() {
542
+ setTimeout(() => {
543
+ const svgElement = document.querySelector('#graph svg');
544
+ if (!svgElement) return;
545
+
546
+ // Find all node group elements
547
+ const nodeGroups = svgElement.querySelectorAll('g.node');
548
+
549
+ nodeGroups.forEach(group => {
550
+ // Extract node ID from flowchart format: flowchart-n1234567890-0 → n1234567890
551
+ const flowchartMatch = group.id.match(/^flowchart-(n\d+)-\d+$/);
552
+ if (flowchartMatch) {
553
+ const nodeId = flowchartMatch[1];
554
+
555
+ if (!group.hasAttribute('data-tooltip-attached')) {
556
+ group.setAttribute('data-tooltip-attached', 'true');
557
+ group.style.cursor = 'pointer';
558
+
559
+ group.addEventListener('mouseenter', (event) => {
560
+ showTooltip(event, nodeId);
561
+ });
562
+
563
+ group.addEventListener('mouseleave', () => {
564
+ hideTooltip();
565
+ });
566
+
567
+ group.addEventListener('mousemove', (event) => {
568
+ if (document.getElementById('node-tooltip').style.display === 'block') {
569
+ showTooltip(event, nodeId);
570
+ }
571
+ });
572
+ }
573
+ }
574
+ });
575
+ }, 300);
576
+ };
577
+
578
+ // Render the graph
579
+ async function renderGraph() {
580
+ if (!graphData) return;
581
+
582
+ const loading = document.getElementById('loading');
583
+ const content = document.getElementById('mermaid-content');
584
+ const graphElement = document.getElementById('graph');
585
+
586
+ loading.classList.remove('hidden');
587
+
588
+ try {
589
+ graphElement.textContent = generateMermaidSyntax();
590
+ graphElement.removeAttribute('data-processed');
591
+
592
+ await mermaid.run({
593
+ querySelector: '#graph'
594
+ });
595
+
596
+ updateStats();
597
+ attachTooltipListeners();
598
+
599
+ loading.classList.add('hidden');
600
+ content.classList.remove('hidden');
601
+
602
+ } catch (error) {
603
+ console.error('Error rendering graph:', error);
604
+ showError('Error rendering graph: ' + error.message);
605
+ }
606
+ }
607
+
608
+ // Render legend
609
+ async function renderLegend() {
610
+ if (!graphData || !graphData.legend) return;
611
+
612
+ const legendElement = document.getElementById('legend');
613
+ legendElement.textContent = `graph LR\n ${graphData.legend}\n classDef b0 fill:#7fc97f;\n classDef external stroke-dasharray: 5 5;`;
614
+ legendElement.removeAttribute('data-processed');
615
+
616
+ try {
617
+ await mermaid.run({
618
+ querySelector: '#legend'
619
+ });
620
+ } catch (error) {
621
+ console.error('Error rendering legend:', error);
622
+ }
623
+ }
624
+
625
+ function handleContainerMouseOver(event) {
626
+ const target = event.target;
627
+
628
+ // Only process events within the SVG area
629
+ const svgElement = document.querySelector('#graph svg');
630
+ if (!svgElement || !svgElement.contains(target)) {
631
+ return;
632
+ }
633
+
634
+ // Check if the target or its parent might be a mermaid node
635
+ let nodeElement = target;
636
+ let nodeId = null;
637
+ let attempts = 0;
638
+
639
+ // Traverse up the DOM tree to find a node with an ID starting with 'n' followed by digits
640
+ while (nodeElement && attempts < 5) {
641
+ // More specific check: ID starts with 'n' followed by digits (actual node IDs)
642
+ if (nodeElement.id && /^n\d+$/.test(nodeElement.id)) {
643
+ nodeId = nodeElement.id;
644
+ break;
645
+ }
646
+
647
+ // Check if this element has a child with a valid node ID
648
+ if (nodeElement.querySelector) {
649
+ const childWithId = nodeElement.querySelector('[id]');
650
+ if (childWithId && childWithId.id && /^n\d+$/.test(childWithId.id)) {
651
+ nodeId = childWithId.id;
652
+ break;
653
+ }
654
+ }
655
+
656
+ nodeElement = nodeElement.parentElement;
657
+ attempts++;
658
+ }
659
+
660
+ if (nodeId) {
661
+ console.log(`Alternative method found node: ${nodeId}`);
662
+ showTooltip(event, nodeId);
663
+ }
664
+ }
665
+
666
+ function handleContainerMouseOut(event) {
667
+ // Only hide if we're actually leaving the container area
668
+ const container = document.getElementById('mermaid-container');
669
+ if (!event.relatedTarget || !container.contains(event.relatedTarget)) {
670
+ hideTooltip();
671
+ }
672
+ }
673
+
674
+ // Setup drag functionality
675
+ function setupDragFunctionality() {
676
+ const container = document.getElementById("mermaid-container");
677
+ let isDragging = false;
678
+ let startX, startY, scrollLeft, scrollTop;
679
+
680
+ container.addEventListener("pointerdown", (e) => {
681
+ isDragging = true;
682
+ startX = e.clientX;
683
+ startY = e.clientY;
684
+ scrollLeft = container.scrollLeft;
685
+ scrollTop = container.scrollTop;
686
+ container.style.cursor = "grabbing";
687
+ // Hide tooltip when starting to drag
688
+ hideTooltip();
689
+ });
690
+
691
+ container.addEventListener("pointermove", (e) => {
692
+ if (!isDragging) return;
693
+ const x = e.clientX - startX;
694
+ const y = e.clientY - startY;
695
+ container.scrollLeft = scrollLeft - x;
696
+ container.scrollTop = scrollTop - y;
697
+ });
698
+
699
+ container.addEventListener("pointerup", () => {
700
+ isDragging = false;
701
+ container.style.cursor = "grab";
702
+ });
703
+
704
+ container.addEventListener("pointerleave", () => {
705
+ isDragging = false;
706
+ container.style.cursor = "grab";
707
+ hideTooltip();
708
+ });
709
+
710
+ container.addEventListener("scroll", () => {
711
+ hideTooltip();
712
+ });
713
+
714
+ document.addEventListener("click", (e) => {
715
+ if (!container.contains(e.target)) {
716
+ hideTooltip();
717
+ }
718
+ });
719
+ }
720
+
721
+ // Sync config with DOM elements
722
+ function syncConfigWithDOM() {
723
+ const showExternalEl = document.getElementById('show-external');
724
+ const showBlocksEl = document.getElementById('show-blocks');
725
+ const layoutSelectEl = document.getElementById('layout-select');
726
+ const themeSelectEl = document.getElementById('theme-select');
727
+
728
+ // Set DOM values to match config
729
+ showExternalEl.checked = currentConfig.showExternal;
730
+ showBlocksEl.checked = currentConfig.showBlocks;
731
+ layoutSelectEl.value = currentConfig.layout;
732
+ themeSelectEl.value = currentConfig.theme;
733
+ }
734
+
735
+ function setupEventListeners() {
736
+ document.getElementById('show-external').addEventListener('change', (e) => {
737
+ currentConfig.showExternal = e.target.checked;
738
+ renderGraph();
739
+ });
740
+
741
+ document.getElementById('show-blocks').addEventListener('change', (e) => {
742
+ currentConfig.showBlocks = e.target.checked;
743
+ renderGraph();
744
+ });
745
+
746
+ document.getElementById('layout-select').addEventListener('change', async (e) => {
747
+ currentConfig.layout = e.target.value;
748
+
749
+ // Reinitialize mermaid with new layout
750
+ mermaid.initialize({
751
+ startOnLoad: false,
752
+ flowchart: {
753
+ useMaxWidth: true,
754
+ htmlLabels: true,
755
+ defaultRenderer: e.target.value
756
+ },
757
+ theme: currentConfig.theme
758
+ });
759
+
760
+ // Clear the graph and force re-render
761
+ const graphElement = document.getElementById('graph');
762
+ graphElement.innerHTML = '';
763
+ graphElement.removeAttribute('data-processed');
764
+
765
+ await renderGraph();
766
+ });
767
+
768
+ document.getElementById('theme-select').addEventListener('change', async (e) => {
769
+ currentConfig.theme = e.target.value;
770
+
771
+ // Reinitialize mermaid with new theme
772
+ mermaid.initialize({
773
+ startOnLoad: false,
774
+ flowchart: {
775
+ useMaxWidth: true,
776
+ htmlLabels: true,
777
+ defaultRenderer: currentConfig.layout
778
+ },
779
+ theme: e.target.value
780
+ });
781
+
782
+ // Clear both graph and legend and force re-render
783
+ const graphElement = document.getElementById('graph');
784
+ const legendElement = document.getElementById('legend');
785
+
786
+ graphElement.innerHTML = '';
787
+ graphElement.removeAttribute('data-processed');
788
+ legendElement.innerHTML = '';
789
+ legendElement.removeAttribute('data-processed');
790
+
791
+ await renderGraph();
792
+ await renderLegend();
793
+ });
794
+
795
+ document.getElementById('fit-view').addEventListener('click', () => {
796
+ const container = document.getElementById('mermaid-container');
797
+ container.scrollTo({top: 0, left: 0, behavior: 'smooth'});
798
+ });
799
+
800
+ document.getElementById('refresh-graph').addEventListener('click', () => {
801
+ renderGraph();
802
+ });
803
+ }
804
+
805
+ // Initialize everything
806
+ async function init() {
807
+ try {
808
+ if (!loadGraphData()) {
809
+ return;
810
+ }
811
+
812
+ syncConfigWithDOM();
813
+ setupEventListeners();
814
+ setupDragFunctionality();
815
+
816
+ // Initialize mermaid with current config
817
+ mermaid.initialize({
818
+ startOnLoad: false,
819
+ flowchart: {
820
+ useMaxWidth: true,
821
+ htmlLabels: true,
822
+ defaultRenderer: currentConfig.layout
823
+ },
824
+ theme: currentConfig.theme
825
+ });
826
+
827
+ // Initial render
828
+ await renderGraph();
829
+ await renderLegend();
830
+
831
+ } catch (error) {
832
+ console.error('Initialization error:', error);
833
+ showError('Initialization error: ' + error.message);
834
+ }
835
+ }
836
+
837
+ // Debug function to inspect SVG structure
838
+ window.debugTooltips = function () {
839
+ const svgElement = document.querySelector('#graph svg');
840
+ if (!svgElement) {
841
+ console.log('No SVG found');
842
+ return;
843
+ }
844
+
845
+ console.log('=== SVG STRUCTURE DEBUG ===');
846
+
847
+ // Check all elements with IDs
848
+ const allElementsWithIds = svgElement.querySelectorAll('[id]');
849
+ console.log(`Found ${allElementsWithIds.length} elements with IDs:`);
850
+ allElementsWithIds.forEach(el => {
851
+ console.log(`- ${el.tagName}: "${el.id}" (classes: ${el.className.baseVal || el.className})`);
852
+ });
853
+
854
+ // Check g.node elements specifically
855
+ const nodeGroups = svgElement.querySelectorAll('g.node');
856
+ console.log(`\nFound ${nodeGroups.length} g.node elements:`);
857
+ nodeGroups.forEach((group, i) => {
858
+ console.log(`Node ${i}:`);
859
+ console.log(` Group ID: "${group.id}"`);
860
+ console.log(` Group classes: ${group.className.baseVal}`);
861
+
862
+ const children = Array.from(group.children);
863
+ children.forEach(child => {
864
+ console.log(` Child: ${child.tagName} ID:"${child.id}" classes:"${child.className.baseVal || child.className}"`);
865
+ });
866
+ });
867
+
868
+ // Test the regex pattern
869
+ console.log('\n=== PATTERN TEST ===');
870
+ allElementsWithIds.forEach(el => {
871
+ if (el.id) {
872
+ const matches = /^n\d+$/.test(el.id);
873
+ console.log(`ID "${el.id}" matches pattern: ${matches}`);
874
+ }
875
+ });
876
+ };
877
+
878
+ // Start when DOM is ready
879
+ document.addEventListener('DOMContentLoaded', init);
880
+ </script>
881
+ </body>
882
+
78
883
  </html>