claude-mpm 4.2.9__py3-none-any.whl → 4.2.11__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/commands/dashboard.py +59 -126
- claude_mpm/cli/commands/monitor.py +71 -212
- claude_mpm/cli/commands/run.py +33 -33
- claude_mpm/dashboard/static/css/code-tree.css +8 -16
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/code-tree.js +692 -114
- claude_mpm/dashboard/static/js/components/file-viewer.js +538 -0
- claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +166 -14
- claude_mpm/dashboard/static/js/dashboard.js +108 -91
- claude_mpm/dashboard/static/js/socket-client.js +9 -7
- claude_mpm/dashboard/templates/index.html +2 -7
- claude_mpm/hooks/claude_hooks/hook_handler.py +1 -11
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +54 -59
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +112 -72
- claude_mpm/services/agents/deployment/agent_template_builder.py +0 -1
- claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
- claude_mpm/services/monitor/__init__.py +20 -0
- claude_mpm/services/monitor/daemon.py +256 -0
- claude_mpm/services/monitor/event_emitter.py +279 -0
- claude_mpm/services/monitor/handlers/__init__.py +20 -0
- claude_mpm/services/monitor/handlers/code_analysis.py +334 -0
- claude_mpm/services/monitor/handlers/dashboard.py +298 -0
- claude_mpm/services/monitor/handlers/hooks.py +491 -0
- claude_mpm/services/monitor/management/__init__.py +18 -0
- claude_mpm/services/monitor/management/health.py +124 -0
- claude_mpm/services/monitor/management/lifecycle.py +298 -0
- claude_mpm/services/monitor/server.py +442 -0
- claude_mpm/tools/code_tree_analyzer.py +33 -17
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/RECORD +41 -36
- claude_mpm/cli/commands/socketio_monitor.py +0 -233
- claude_mpm/scripts/socketio_daemon.py +0 -571
- claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
- claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
- claude_mpm/scripts/socketio_server_manager.py +0 -349
- claude_mpm/services/cli/dashboard_launcher.py +0 -423
- claude_mpm/services/cli/socketio_manager.py +0 -595
- claude_mpm/services/dashboard/stable_server.py +0 -1020
- claude_mpm/services/socketio/monitor_server.py +0 -505
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/top_level.txt +0 -0
|
@@ -59,6 +59,11 @@ class CodeTree {
|
|
|
59
59
|
this.focusedNode = null; // Track the currently focused directory
|
|
60
60
|
this.horizontalNodes = new Set(); // Track nodes that should have horizontal text
|
|
61
61
|
this.centralSpine = new Set(); // Track the main path through the tree
|
|
62
|
+
|
|
63
|
+
// CRITICAL FIX: Bind methods to preserve 'this' context when called by D3 event handlers
|
|
64
|
+
this.onNodeClick = this.onNodeClick.bind(this);
|
|
65
|
+
this.showTooltip = this.showTooltip.bind(this);
|
|
66
|
+
this.hideTooltip = this.hideTooltip.bind(this);
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
/**
|
|
@@ -292,9 +297,9 @@ class CodeTree {
|
|
|
292
297
|
|
|
293
298
|
// Add tree controls toolbar
|
|
294
299
|
this.addTreeControls();
|
|
295
|
-
|
|
296
|
-
//
|
|
297
|
-
this.addBreadcrumb();
|
|
300
|
+
|
|
301
|
+
// Breadcrumb navigation removed - redundant with root node display
|
|
302
|
+
// this.addBreadcrumb();
|
|
298
303
|
|
|
299
304
|
if (!container || !container.node()) {
|
|
300
305
|
console.error('Code tree container not found');
|
|
@@ -424,16 +429,20 @@ class CodeTree {
|
|
|
424
429
|
*/
|
|
425
430
|
initializeTreeData() {
|
|
426
431
|
const workingDir = this.getWorkingDirectory();
|
|
427
|
-
|
|
432
|
+
// Use a more descriptive root name that doesn't look like a clickable button
|
|
433
|
+
const dirName = 'Project Root'; // Always use generic name for root
|
|
428
434
|
|
|
429
435
|
// Use absolute path for consistency with API expectations
|
|
430
436
|
this.treeData = {
|
|
431
437
|
name: dirName,
|
|
432
438
|
path: workingDir || '.', // Use working directory or fallback to '.'
|
|
433
439
|
type: 'root',
|
|
440
|
+
isDirectory: true, // Critical: Mark root as directory for proper handling
|
|
434
441
|
children: [],
|
|
435
442
|
loaded: false,
|
|
436
|
-
expanded: true // Start expanded
|
|
443
|
+
expanded: true, // Start expanded
|
|
444
|
+
hasChildren: true, // Root always has potential children
|
|
445
|
+
isRoot: true // Mark as root node for special handling
|
|
437
446
|
};
|
|
438
447
|
|
|
439
448
|
if (typeof d3 !== 'undefined') {
|
|
@@ -515,7 +524,6 @@ class CodeTree {
|
|
|
515
524
|
return;
|
|
516
525
|
}
|
|
517
526
|
|
|
518
|
-
|
|
519
527
|
this.autoDiscovered = true;
|
|
520
528
|
this.analyzing = true;
|
|
521
529
|
|
|
@@ -530,54 +538,111 @@ class CodeTree {
|
|
|
530
538
|
lines: 0
|
|
531
539
|
};
|
|
532
540
|
|
|
533
|
-
// Subscribe to events if not already done
|
|
541
|
+
// Subscribe to events if not already done (for other features)
|
|
534
542
|
if (this.socket && !this.socket.hasListeners('code:node:found')) {
|
|
535
543
|
this.setupEventHandlers();
|
|
536
544
|
}
|
|
537
545
|
|
|
538
|
-
// Update tree data with working directory as the root
|
|
539
|
-
const dirName = workingDir.split('/').pop() || 'Project Root';
|
|
540
|
-
this.treeData = {
|
|
541
|
-
name: dirName,
|
|
542
|
-
path: workingDir, // Use absolute path for consistency with API expectations
|
|
543
|
-
type: 'root',
|
|
544
|
-
children: [],
|
|
545
|
-
loaded: false,
|
|
546
|
-
expanded: true // Start expanded to show discovered items
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
if (typeof d3 !== 'undefined') {
|
|
550
|
-
this.root = d3.hierarchy(this.treeData);
|
|
551
|
-
this.root.x0 = this.height / 2;
|
|
552
|
-
this.root.y0 = 0;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
546
|
// Update UI
|
|
556
547
|
this.showLoading();
|
|
548
|
+
const dirName = workingDir.split('/').pop() || 'Project Root';
|
|
557
549
|
this.updateBreadcrumb(`Discovering structure in ${dirName}...`, 'info');
|
|
558
550
|
|
|
559
|
-
//
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
// Get ignore patterns
|
|
563
|
-
const ignorePatterns = document.getElementById('ignore-patterns')?.value || '';
|
|
551
|
+
// Use REST API for initial discovery (more reliable than WebSocket)
|
|
552
|
+
console.log(`🚀 [ROOT DISCOVERY] Using REST API for root: ${workingDir}`);
|
|
564
553
|
|
|
565
|
-
|
|
554
|
+
const apiUrl = `${window.location.origin}/api/directory?path=${encodeURIComponent(workingDir)}`;
|
|
566
555
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
556
|
+
fetch(apiUrl)
|
|
557
|
+
.then(response => {
|
|
558
|
+
if (!response.ok) {
|
|
559
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
560
|
+
}
|
|
561
|
+
return response.json();
|
|
562
|
+
})
|
|
563
|
+
.then(data => {
|
|
564
|
+
console.log('✅ [ROOT DISCOVERY] REST API response:', data);
|
|
565
|
+
|
|
566
|
+
// Process the response
|
|
567
|
+
if (data.contents && Array.isArray(data.contents)) {
|
|
568
|
+
// Update root node with children
|
|
569
|
+
this.treeData.children = data.contents.map(item => ({
|
|
570
|
+
name: item.name,
|
|
571
|
+
path: item.path,
|
|
572
|
+
type: item.type || (item.is_directory ? 'directory' : 'file'),
|
|
573
|
+
size: item.size,
|
|
574
|
+
hasChildren: item.is_directory,
|
|
575
|
+
children: [],
|
|
576
|
+
loaded: false
|
|
577
|
+
}));
|
|
578
|
+
|
|
579
|
+
this.treeData.loaded = true;
|
|
580
|
+
|
|
581
|
+
// Update D3 hierarchy
|
|
582
|
+
if (typeof d3 !== 'undefined') {
|
|
583
|
+
this.root = d3.hierarchy(this.treeData);
|
|
584
|
+
this.root.x0 = this.height / 2;
|
|
585
|
+
this.root.y0 = 0;
|
|
586
|
+
|
|
587
|
+
// Expand root node to show children
|
|
588
|
+
if (this.root.children) {
|
|
589
|
+
this.root.children.forEach(child => {
|
|
590
|
+
child._children = null;
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Update the visualization
|
|
596
|
+
if (this.svg) {
|
|
597
|
+
this.update(this.root);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Update stats
|
|
601
|
+
const fileCount = data.contents.filter(item => !item.is_directory).length;
|
|
602
|
+
const dirCount = data.contents.filter(item => item.is_directory).length;
|
|
603
|
+
|
|
604
|
+
this.stats.files = fileCount;
|
|
605
|
+
this.updateStats();
|
|
606
|
+
|
|
607
|
+
this.updateBreadcrumb(
|
|
608
|
+
`📁 Found ${dirCount} directories and ${fileCount} files`,
|
|
609
|
+
'success'
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
this.updateActivityTicker(
|
|
613
|
+
`📁 Found ${dirCount} directories and ${fileCount} files`,
|
|
614
|
+
'success'
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
// Add to events display
|
|
618
|
+
this.addEventToDisplay(
|
|
619
|
+
`📁 Loaded ${data.contents.length} items from project root`,
|
|
620
|
+
'info'
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
this.analyzing = false;
|
|
625
|
+
this.hideLoading();
|
|
626
|
+
})
|
|
627
|
+
.catch(error => {
|
|
628
|
+
console.error('[ROOT DISCOVERY] Error:', error);
|
|
629
|
+
this.analyzing = false;
|
|
630
|
+
this.hideLoading();
|
|
631
|
+
this.showNotification(`Failed to load directory: ${error.message}`, 'error');
|
|
632
|
+
|
|
633
|
+
// Try WebSocket as fallback
|
|
634
|
+
if (this.socket) {
|
|
635
|
+
console.log('[ROOT DISCOVERY] Falling back to WebSocket');
|
|
636
|
+
const requestPayload = {
|
|
637
|
+
path: workingDir,
|
|
638
|
+
depth: 'top_level',
|
|
639
|
+
languages: this.getSelectedLanguages(),
|
|
640
|
+
ignore_patterns: document.getElementById('ignore-patterns')?.value || '',
|
|
641
|
+
request_id: `discover_${Date.now()}`
|
|
642
|
+
};
|
|
643
|
+
this.socket.emit('code:discover:top_level', requestPayload);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
581
646
|
|
|
582
647
|
// Update stats display
|
|
583
648
|
this.updateStats();
|
|
@@ -761,7 +826,7 @@ class CodeTree {
|
|
|
761
826
|
if (!this.root) return;
|
|
762
827
|
|
|
763
828
|
const expandNode = (node) => {
|
|
764
|
-
if (node.data.type === 'directory' && node.data.loaded === true) {
|
|
829
|
+
if ((node.data.type === 'directory' || node.data.type === 'root' || node.data.isDirectory) && node.data.loaded === true) {
|
|
765
830
|
if (node._children) {
|
|
766
831
|
node.children = node._children;
|
|
767
832
|
node._children = null;
|
|
@@ -785,7 +850,7 @@ class CodeTree {
|
|
|
785
850
|
if (!this.root) return;
|
|
786
851
|
|
|
787
852
|
const collapseNode = (node) => {
|
|
788
|
-
if (node.data.type === 'directory' && node.children) {
|
|
853
|
+
if ((node.data.type === 'directory' || node.data.type === 'root' || node.data.isDirectory) && node.children) {
|
|
789
854
|
node._children = node.children;
|
|
790
855
|
node.children = null;
|
|
791
856
|
node.data.expanded = false;
|
|
@@ -822,7 +887,7 @@ class CodeTree {
|
|
|
822
887
|
navigateToPath(path) {
|
|
823
888
|
// Implementation for navigating to a specific path
|
|
824
889
|
// This would expand the tree to show the specified path
|
|
825
|
-
this.updateBreadcrumbPath(path);
|
|
890
|
+
// this.updateBreadcrumbPath(path); // Breadcrumb removed
|
|
826
891
|
this.showNotification(`Navigating to: ${path}`, 'info');
|
|
827
892
|
}
|
|
828
893
|
|
|
@@ -1559,18 +1624,22 @@ class CodeTree {
|
|
|
1559
1624
|
line: elem.line,
|
|
1560
1625
|
methods: elem.methods ? elem.methods.length : 0
|
|
1561
1626
|
})));
|
|
1627
|
+
|
|
1628
|
+
// Show success message with element count breakdown
|
|
1629
|
+
const fileName = data.path.split('/').pop();
|
|
1630
|
+
const elementCounts = this.getElementCounts(data.elements);
|
|
1631
|
+
const summary = this.formatElementSummary(elementCounts);
|
|
1632
|
+
|
|
1633
|
+
this.showNotification(`${fileName} - ${summary}`, 'success');
|
|
1634
|
+
this.updateBreadcrumb(`${fileName} - AST parsed: ${summary}`, 'success');
|
|
1562
1635
|
} else {
|
|
1563
1636
|
const fileName = data.path.split('/').pop();
|
|
1564
1637
|
console.log('⚠️ [AST ELEMENTS] No elements found in analysis result');
|
|
1565
1638
|
|
|
1566
|
-
// Show
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
} else {
|
|
1571
|
-
this.showNotification(`${fileName} contains no classes or functions`, 'info');
|
|
1572
|
-
this.updateBreadcrumb(`${fileName} - no AST elements found`, 'info');
|
|
1573
|
-
}
|
|
1639
|
+
// Show accurate message for files with no structural AST elements
|
|
1640
|
+
const fileType = this.getFileTypeDescription(fileName);
|
|
1641
|
+
this.showNotification(`${fileName} - No structural elements to display in tree`, 'info');
|
|
1642
|
+
this.updateBreadcrumb(`${fileName} - ${fileType} analyzed, content not suitable for tree view`, 'info');
|
|
1574
1643
|
}
|
|
1575
1644
|
|
|
1576
1645
|
// Clear analysis timeout
|
|
@@ -1624,6 +1693,9 @@ class CodeTree {
|
|
|
1624
1693
|
childrenCount: children.length,
|
|
1625
1694
|
children: children.map(c => ({ name: c.name, type: c.type }))
|
|
1626
1695
|
});
|
|
1696
|
+
|
|
1697
|
+
// Auto-expand the file node to show AST tree if it was recently clicked
|
|
1698
|
+
this.autoExpandFileWithAST(data.path, fileNode);
|
|
1627
1699
|
} else {
|
|
1628
1700
|
console.log('⚠️ [FILE NODE] No elements to add as children');
|
|
1629
1701
|
}
|
|
@@ -2439,7 +2511,7 @@ class CodeTree {
|
|
|
2439
2511
|
if (children.length === 1) return children[0];
|
|
2440
2512
|
|
|
2441
2513
|
// Prioritize directories over files
|
|
2442
|
-
const directories = children.filter(child => child
|
|
2514
|
+
const directories = children.filter(child => this.isNodeDirectory(child));
|
|
2443
2515
|
if (directories.length === 1) return directories[0];
|
|
2444
2516
|
|
|
2445
2517
|
// If multiple directories, choose the first one (could be enhanced with better logic)
|
|
@@ -2580,7 +2652,7 @@ class CodeTree {
|
|
|
2580
2652
|
const nodeEnter = node.enter().append('g')
|
|
2581
2653
|
.attr('class', d => {
|
|
2582
2654
|
let classes = ['node', 'code-node'];
|
|
2583
|
-
if (d
|
|
2655
|
+
if (this.isNodeDirectory(d)) {
|
|
2584
2656
|
classes.push('directory');
|
|
2585
2657
|
if (d.data.loaded === true && d.children) {
|
|
2586
2658
|
classes.push('expanded');
|
|
@@ -2604,7 +2676,17 @@ class CodeTree {
|
|
|
2604
2676
|
return `translate(${source.y0},${source.x0})`;
|
|
2605
2677
|
}
|
|
2606
2678
|
})
|
|
2607
|
-
.on('click', (event, d) =>
|
|
2679
|
+
.on('click', (event, d) => {
|
|
2680
|
+
console.log('🔴 [G-ELEMENT] Click on node group element!', {
|
|
2681
|
+
nodeName: d?.data?.name,
|
|
2682
|
+
nodePath: d?.data?.path,
|
|
2683
|
+
eventTarget: event.target.tagName,
|
|
2684
|
+
thisContext: this,
|
|
2685
|
+
hasOnNodeClick: typeof this.onNodeClick === 'function'
|
|
2686
|
+
});
|
|
2687
|
+
// Use bound method
|
|
2688
|
+
this.onNodeClick(event, d);
|
|
2689
|
+
});
|
|
2608
2690
|
|
|
2609
2691
|
// Add circles for nodes
|
|
2610
2692
|
nodeEnter.append('circle')
|
|
@@ -2612,14 +2694,22 @@ class CodeTree {
|
|
|
2612
2694
|
.attr('r', 1e-6)
|
|
2613
2695
|
.style('fill', d => this.getNodeColor(d))
|
|
2614
2696
|
.style('stroke', d => this.getNodeStrokeColor(d))
|
|
2615
|
-
.style('stroke-width', d => d
|
|
2616
|
-
.style('cursor', 'pointer') //
|
|
2617
|
-
.on('click', (event, d) =>
|
|
2618
|
-
|
|
2619
|
-
|
|
2697
|
+
.style('stroke-width', d => this.isNodeDirectory(d) ? 2 : 1.5)
|
|
2698
|
+
.style('cursor', d => (d.data && (d.data.type === 'root' || d.data.isRoot || d.depth === 0)) ? 'default' : 'pointer') // No pointer for root
|
|
2699
|
+
.on('click', (event, d) => {
|
|
2700
|
+
console.log('🔵 [CIRCLE] Click on circle element!', {
|
|
2701
|
+
nodeName: d?.data?.name,
|
|
2702
|
+
nodePath: d?.data?.path,
|
|
2703
|
+
hasOnNodeClick: typeof this.onNodeClick === 'function'
|
|
2704
|
+
});
|
|
2705
|
+
// Use bound method
|
|
2706
|
+
this.onNodeClick(event, d);
|
|
2707
|
+
}) // CRITICAL FIX: Add click handler to circles
|
|
2708
|
+
.on('mouseover', this.showTooltip)
|
|
2709
|
+
.on('mouseout', this.hideTooltip);
|
|
2620
2710
|
|
|
2621
2711
|
// Add expand/collapse icons for directories
|
|
2622
|
-
nodeEnter.filter(d => d
|
|
2712
|
+
nodeEnter.filter(d => this.isNodeDirectory(d))
|
|
2623
2713
|
.append('text')
|
|
2624
2714
|
.attr('class', 'expand-icon')
|
|
2625
2715
|
.attr('x', 0)
|
|
@@ -2704,11 +2794,19 @@ class CodeTree {
|
|
|
2704
2794
|
}
|
|
2705
2795
|
return null;
|
|
2706
2796
|
})
|
|
2707
|
-
.on('click', (event, d) =>
|
|
2708
|
-
|
|
2797
|
+
.on('click', (event, d) => {
|
|
2798
|
+
console.log('📝 [LABEL] Click on text label!', {
|
|
2799
|
+
nodeName: d?.data?.name,
|
|
2800
|
+
nodePath: d?.data?.path,
|
|
2801
|
+
hasOnNodeClick: typeof this.onNodeClick === 'function'
|
|
2802
|
+
});
|
|
2803
|
+
// Use bound method
|
|
2804
|
+
this.onNodeClick(event, d);
|
|
2805
|
+
}) // CRITICAL FIX: Add click handler to labels
|
|
2806
|
+
.style('cursor', d => (d.data && (d.data.type === 'root' || d.data.isRoot || d.depth === 0)) ? 'default' : 'pointer');
|
|
2709
2807
|
|
|
2710
2808
|
// Add icons for node types (files only, directories use expand icons)
|
|
2711
|
-
nodeEnter.filter(d => d
|
|
2809
|
+
nodeEnter.filter(d => !this.isNodeDirectory(d))
|
|
2712
2810
|
.append('text')
|
|
2713
2811
|
.attr('class', 'node-icon')
|
|
2714
2812
|
.attr('dy', '.35em')
|
|
@@ -2717,11 +2815,11 @@ class CodeTree {
|
|
|
2717
2815
|
.text(d => this.getNodeIcon(d))
|
|
2718
2816
|
.style('font-size', '10px')
|
|
2719
2817
|
.style('fill', 'white')
|
|
2720
|
-
.on('click',
|
|
2818
|
+
.on('click', this.onNodeClick) // CRITICAL FIX: Add click handler to file icons
|
|
2721
2819
|
.style('cursor', 'pointer');
|
|
2722
2820
|
|
|
2723
2821
|
// Add item count badges for directories
|
|
2724
|
-
nodeEnter.filter(d => d
|
|
2822
|
+
nodeEnter.filter(d => this.isNodeDirectory(d) && d.data.children)
|
|
2725
2823
|
.append('text')
|
|
2726
2824
|
.attr('class', 'item-count-badge')
|
|
2727
2825
|
.attr('x', 12)
|
|
@@ -2733,7 +2831,7 @@ class CodeTree {
|
|
|
2733
2831
|
})
|
|
2734
2832
|
.style('font-size', '9px')
|
|
2735
2833
|
.style('opacity', 0.7)
|
|
2736
|
-
.on('click',
|
|
2834
|
+
.on('click', this.onNodeClick) // CRITICAL FIX: Add click handler to count badges
|
|
2737
2835
|
.style('cursor', 'pointer');
|
|
2738
2836
|
|
|
2739
2837
|
// Transition to new positions
|
|
@@ -2741,11 +2839,20 @@ class CodeTree {
|
|
|
2741
2839
|
|
|
2742
2840
|
// CRITICAL FIX: Ensure ALL nodes (new and existing) have click handlers
|
|
2743
2841
|
// This fixes the issue where subdirectory clicks stop working after tree updates
|
|
2744
|
-
nodeUpdate.on('click', (event, d) =>
|
|
2842
|
+
nodeUpdate.on('click', (event, d) => {
|
|
2843
|
+
console.log('🟡 [NODE-UPDATE] Click on updated node!', {
|
|
2844
|
+
nodeName: d?.data?.name,
|
|
2845
|
+
nodePath: d?.data?.path,
|
|
2846
|
+
hasOnNodeClick: typeof this.onNodeClick === 'function',
|
|
2847
|
+
thisContext: this
|
|
2848
|
+
});
|
|
2849
|
+
// Use bound method
|
|
2850
|
+
this.onNodeClick(event, d);
|
|
2851
|
+
});
|
|
2745
2852
|
|
|
2746
|
-
// ADDITIONAL FIX: Also ensure click handlers on all child elements
|
|
2747
|
-
nodeUpdate.selectAll('circle').on('click',
|
|
2748
|
-
nodeUpdate.selectAll('text').on('click',
|
|
2853
|
+
// ADDITIONAL FIX: Also ensure click handlers on all child elements using bound methods
|
|
2854
|
+
nodeUpdate.selectAll('circle').on('click', this.onNodeClick);
|
|
2855
|
+
nodeUpdate.selectAll('text').on('click', this.onNodeClick);
|
|
2749
2856
|
|
|
2750
2857
|
nodeUpdate.transition()
|
|
2751
2858
|
.duration(this.duration)
|
|
@@ -2761,7 +2868,7 @@ class CodeTree {
|
|
|
2761
2868
|
// Update node classes based on current state
|
|
2762
2869
|
nodeUpdate.attr('class', d => {
|
|
2763
2870
|
let classes = ['node', 'code-node'];
|
|
2764
|
-
if (d
|
|
2871
|
+
if (this.isNodeDirectory(d)) {
|
|
2765
2872
|
classes.push('directory');
|
|
2766
2873
|
if (d.data.loaded === true && d.children) {
|
|
2767
2874
|
classes.push('expanded');
|
|
@@ -2779,7 +2886,7 @@ class CodeTree {
|
|
|
2779
2886
|
});
|
|
2780
2887
|
|
|
2781
2888
|
nodeUpdate.select('circle.node-circle')
|
|
2782
|
-
.attr('r', d => d
|
|
2889
|
+
.attr('r', d => this.isNodeDirectory(d) ? 10 : 8)
|
|
2783
2890
|
.style('fill', d => this.getNodeColor(d))
|
|
2784
2891
|
|
|
2785
2892
|
// Update expand/collapse icons
|
|
@@ -2793,7 +2900,7 @@ class CodeTree {
|
|
|
2793
2900
|
// Update item count badges
|
|
2794
2901
|
nodeUpdate.select('.item-count-badge')
|
|
2795
2902
|
.text(d => {
|
|
2796
|
-
if (d
|
|
2903
|
+
if (!this.isNodeDirectory(d)) return '';
|
|
2797
2904
|
const count = d.data.children ? d.data.children.length : 0;
|
|
2798
2905
|
return count > 0 ? count : '';
|
|
2799
2906
|
})
|
|
@@ -3104,7 +3211,9 @@ class CodeTree {
|
|
|
3104
3211
|
onNodeClick(event, d) {
|
|
3105
3212
|
const clickId = Date.now() + Math.random();
|
|
3106
3213
|
// DEBUG: Log all clicks to verify handler is working
|
|
3107
|
-
console.log(
|
|
3214
|
+
console.log(`🖱️🖱️🖱️ [NODE CLICK] onNodeClick method called! (ID: ${clickId}):`, {
|
|
3215
|
+
thisContext: this,
|
|
3216
|
+
isBound: this.constructor.name === 'CodeTree',
|
|
3108
3217
|
name: d?.data?.name,
|
|
3109
3218
|
path: d?.data?.path,
|
|
3110
3219
|
type: d?.data?.type,
|
|
@@ -3137,7 +3246,13 @@ class CodeTree {
|
|
|
3137
3246
|
console.error('[CodeTree] ERROR: d is null/undefined, cannot continue');
|
|
3138
3247
|
return;
|
|
3139
3248
|
}
|
|
3140
|
-
|
|
3249
|
+
|
|
3250
|
+
// Ignore clicks on root node - it should not be interactive
|
|
3251
|
+
if (d.data && (d.data.type === 'root' || d.data.isRoot || d.depth === 0)) {
|
|
3252
|
+
console.log('🚫 [ROOT CLICK] Ignoring click on root node:', d.data.name);
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3141
3256
|
if (!d.data) {
|
|
3142
3257
|
console.error('[CodeTree] ERROR: d.data is null/undefined, cannot continue');
|
|
3143
3258
|
return;
|
|
@@ -3197,7 +3312,7 @@ class CodeTree {
|
|
|
3197
3312
|
|
|
3198
3313
|
// Add pulsing animation immediately for directories
|
|
3199
3314
|
|
|
3200
|
-
if (d
|
|
3315
|
+
if (this.isNodeDirectory(d) && !d.data.loaded) {
|
|
3201
3316
|
try {
|
|
3202
3317
|
if (typeof this.addLoadingPulse === 'function') {
|
|
3203
3318
|
this.addLoadingPulse(d);
|
|
@@ -3231,11 +3346,12 @@ class CodeTree {
|
|
|
3231
3346
|
type: d.data.type,
|
|
3232
3347
|
loaded: d.data.loaded,
|
|
3233
3348
|
loadedType: typeof d.data.loaded,
|
|
3234
|
-
isDirectory: d.data.type === 'directory',
|
|
3349
|
+
isDirectory: d.data.type === 'directory' || d.data.type === 'root',
|
|
3235
3350
|
notLoaded: !d.data.loaded,
|
|
3236
|
-
shouldLoad: d.data.type === 'directory' && !d.data.loaded
|
|
3351
|
+
shouldLoad: (d.data.type === 'directory' || d.data.type === 'root') && !d.data.loaded
|
|
3237
3352
|
});
|
|
3238
|
-
|
|
3353
|
+
// Check for both 'directory' type and 'root' type (or use isDirectory flag)
|
|
3354
|
+
if ((d.data.type === 'directory' || d.data.type === 'root' || d.data.isDirectory) && !d.data.loaded) {
|
|
3239
3355
|
console.log('✅ [SUBDIRECTORY LOADING] Load check passed, proceeding with loading logic');
|
|
3240
3356
|
console.log('🔍 [SUBDIRECTORY LOADING] Initial loading state:', {
|
|
3241
3357
|
loadingNodesSize: this.loadingNodes ? this.loadingNodes.size : 'undefined',
|
|
@@ -3306,12 +3422,12 @@ class CodeTree {
|
|
|
3306
3422
|
console.log('📡 [SUBDIRECTORY LOADING] Using REST API for directory:', {
|
|
3307
3423
|
originalPath: d.data.path,
|
|
3308
3424
|
fullPath: fullPath,
|
|
3309
|
-
apiUrl: `${window.location.origin}/api/directory
|
|
3425
|
+
apiUrl: `${window.location.origin}/api/directory?path=${encodeURIComponent(fullPath)}`,
|
|
3310
3426
|
loadingNodesSize: this.loadingNodes.size,
|
|
3311
3427
|
loadingNodesContent: Array.from(this.loadingNodes)
|
|
3312
3428
|
});
|
|
3313
3429
|
|
|
3314
|
-
const apiUrl = `${window.location.origin}/api/directory
|
|
3430
|
+
const apiUrl = `${window.location.origin}/api/directory?path=${encodeURIComponent(fullPath)}`;
|
|
3315
3431
|
|
|
3316
3432
|
fetch(apiUrl)
|
|
3317
3433
|
.then(response => {
|
|
@@ -3450,28 +3566,40 @@ class CodeTree {
|
|
|
3450
3566
|
this.showNotification(`Error loading directory: ${error.message}`, 'error');
|
|
3451
3567
|
}
|
|
3452
3568
|
}
|
|
3453
|
-
// For files
|
|
3454
|
-
else if (d.data.type === 'file'
|
|
3455
|
-
|
|
3456
|
-
const fileLanguage = this.detectLanguage(d.data.path);
|
|
3457
|
-
console.log('🔍 [FILE ANALYSIS] Language check:', {
|
|
3569
|
+
// For files, handle both content display and analysis
|
|
3570
|
+
else if (d.data.type === 'file') {
|
|
3571
|
+
console.log('📄 [FILE CLICK] File clicked:', {
|
|
3458
3572
|
fileName: d.data.name,
|
|
3459
3573
|
filePath: d.data.path,
|
|
3460
|
-
|
|
3461
|
-
selectedLanguages: selectedLanguages,
|
|
3462
|
-
isLanguageSelected: selectedLanguages.includes(fileLanguage),
|
|
3463
|
-
shouldAnalyze: selectedLanguages.includes(fileLanguage) || fileLanguage === 'unknown'
|
|
3574
|
+
analyzed: d.data.analyzed
|
|
3464
3575
|
});
|
|
3465
3576
|
|
|
3466
|
-
|
|
3467
|
-
|
|
3577
|
+
// PHASE 1: Display file content in unified data viewer
|
|
3578
|
+
this.displayFileInDataViewer(d);
|
|
3579
|
+
|
|
3580
|
+
// PHASE 2: Handle AST analysis for code files (if not already analyzed)
|
|
3581
|
+
if (!d.data.analyzed) {
|
|
3582
|
+
// Only analyze files of selected languages
|
|
3583
|
+
const fileLanguage = this.detectLanguage(d.data.path);
|
|
3584
|
+
console.log('🔍 [FILE ANALYSIS] Language check:', {
|
|
3468
3585
|
fileName: d.data.name,
|
|
3586
|
+
filePath: d.data.path,
|
|
3469
3587
|
detectedLanguage: fileLanguage,
|
|
3470
3588
|
selectedLanguages: selectedLanguages,
|
|
3471
|
-
|
|
3589
|
+
isLanguageSelected: selectedLanguages.includes(fileLanguage),
|
|
3590
|
+
shouldAnalyze: selectedLanguages.includes(fileLanguage) || fileLanguage === 'unknown'
|
|
3472
3591
|
});
|
|
3473
|
-
|
|
3474
|
-
|
|
3592
|
+
|
|
3593
|
+
if (!selectedLanguages.includes(fileLanguage) && fileLanguage !== 'unknown') {
|
|
3594
|
+
console.warn('⚠️ [FILE ANALYSIS] Skipping AST analysis for file:', {
|
|
3595
|
+
fileName: d.data.name,
|
|
3596
|
+
detectedLanguage: fileLanguage,
|
|
3597
|
+
selectedLanguages: selectedLanguages,
|
|
3598
|
+
reason: `${fileLanguage} not in selected languages`
|
|
3599
|
+
});
|
|
3600
|
+
// Still show file content, just skip AST analysis
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3475
3603
|
}
|
|
3476
3604
|
|
|
3477
3605
|
// Add pulsing animation immediately
|
|
@@ -3526,7 +3654,7 @@ class CodeTree {
|
|
|
3526
3654
|
}, 100); // 100ms delay to ensure visual effects render first
|
|
3527
3655
|
}
|
|
3528
3656
|
// Toggle children visibility for already loaded nodes
|
|
3529
|
-
else if (d
|
|
3657
|
+
else if (this.isNodeDirectory(d) && d.data.loaded === true) {
|
|
3530
3658
|
// Directory is loaded, toggle expansion
|
|
3531
3659
|
if (d.children) {
|
|
3532
3660
|
// Collapse - hide children
|
|
@@ -3652,6 +3780,14 @@ class CodeTree {
|
|
|
3652
3780
|
return path({source: s, target: d});
|
|
3653
3781
|
}
|
|
3654
3782
|
|
|
3783
|
+
/**
|
|
3784
|
+
* Helper function to check if a node is a directory or root
|
|
3785
|
+
*/
|
|
3786
|
+
isNodeDirectory(node) {
|
|
3787
|
+
const data = node.data || node;
|
|
3788
|
+
return data.type === 'directory' || data.type === 'root' || data.isDirectory === true;
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3655
3791
|
/**
|
|
3656
3792
|
* Get node color based on type and complexity
|
|
3657
3793
|
*/
|
|
@@ -3689,7 +3825,7 @@ class CodeTree {
|
|
|
3689
3825
|
if (d.data.loaded === 'loading' || d.data.analyzed === 'loading') {
|
|
3690
3826
|
return '#FCD34D'; // Yellow for loading
|
|
3691
3827
|
}
|
|
3692
|
-
if (d
|
|
3828
|
+
if (this.isNodeDirectory(d) && !d.data.loaded) {
|
|
3693
3829
|
return '#94A3B8'; // Gray for unloaded
|
|
3694
3830
|
}
|
|
3695
3831
|
if (d.data.type === 'file' && !d.data.analyzed) {
|
|
@@ -3738,7 +3874,7 @@ class CodeTree {
|
|
|
3738
3874
|
}
|
|
3739
3875
|
|
|
3740
3876
|
// Special messages for lazy-loaded nodes
|
|
3741
|
-
if (d
|
|
3877
|
+
if (this.isNodeDirectory(d) && !d.data.loaded) {
|
|
3742
3878
|
info.push('<em>Click to explore contents</em>');
|
|
3743
3879
|
} else if (d.data.type === 'file' && !d.data.analyzed) {
|
|
3744
3880
|
info.push('<em>Click to analyze file</em>');
|
|
@@ -4084,11 +4220,19 @@ class CodeTree {
|
|
|
4084
4220
|
* Show hierarchical source viewer for a source file
|
|
4085
4221
|
*/
|
|
4086
4222
|
async showSourceViewer(node) {
|
|
4087
|
-
console.log('📄 [SOURCE VIEWER]
|
|
4223
|
+
console.log('📄 [SOURCE VIEWER] Starting showSourceViewer for:', node.data.path);
|
|
4224
|
+
console.log(' Node type:', node.data.type);
|
|
4225
|
+
console.log(' Content element available:', !!this.structuredDataContent);
|
|
4226
|
+
|
|
4227
|
+
if (!this.structuredDataContent) {
|
|
4228
|
+
console.error('❌ [SOURCE VIEWER] No content element to display source in!');
|
|
4229
|
+
return;
|
|
4230
|
+
}
|
|
4088
4231
|
|
|
4089
4232
|
// Create source viewer container
|
|
4090
4233
|
const sourceViewer = document.createElement('div');
|
|
4091
4234
|
sourceViewer.className = 'source-viewer';
|
|
4235
|
+
console.log('📦 [SOURCE VIEWER] Created source viewer container');
|
|
4092
4236
|
|
|
4093
4237
|
// Create header
|
|
4094
4238
|
const header = document.createElement('div');
|
|
@@ -4108,7 +4252,11 @@ class CodeTree {
|
|
|
4108
4252
|
|
|
4109
4253
|
sourceViewer.appendChild(header);
|
|
4110
4254
|
sourceViewer.appendChild(content);
|
|
4255
|
+
console.log('🔨 [SOURCE VIEWER] Appending source viewer to content element...');
|
|
4111
4256
|
this.structuredDataContent.appendChild(sourceViewer);
|
|
4257
|
+
console.log('✅ [SOURCE VIEWER] Source viewer added to DOM');
|
|
4258
|
+
console.log(' Content element children count:', this.structuredDataContent.children.length);
|
|
4259
|
+
console.log(' Content element HTML preview:', this.structuredDataContent.innerHTML.substring(0, 200) + '...');
|
|
4112
4260
|
|
|
4113
4261
|
// Add control event listeners
|
|
4114
4262
|
document.getElementById('expand-all-source')?.addEventListener('click', () => this.expandAllSource());
|
|
@@ -4378,39 +4526,98 @@ class CodeTree {
|
|
|
4378
4526
|
* Initialize the structured data integration
|
|
4379
4527
|
*/
|
|
4380
4528
|
initializeStructuredData() {
|
|
4381
|
-
|
|
4382
|
-
this.structuredDataContent = document.getElementById('code-module-data-content');
|
|
4529
|
+
console.log('🔄 [CODE TREE] Initializing structured data integration...');
|
|
4383
4530
|
|
|
4384
|
-
//
|
|
4385
|
-
|
|
4386
|
-
this.structuredDataContent = document.getElementById('module-data-content');
|
|
4387
|
-
}
|
|
4531
|
+
// Use the existing "📊 Structured Data" section in the left panel
|
|
4532
|
+
this.structuredDataContent = document.getElementById('module-data-content');
|
|
4388
4533
|
|
|
4389
4534
|
if (!this.structuredDataContent) {
|
|
4390
|
-
console.warn('Structured data
|
|
4535
|
+
console.warn('⏳ [CODE TREE] Structured data element not found yet, retrying in 500ms...');
|
|
4536
|
+
|
|
4537
|
+
// Retry after a short delay in case DOM is still loading
|
|
4538
|
+
setTimeout(() => {
|
|
4539
|
+
this.structuredDataContent = document.getElementById('module-data-content');
|
|
4540
|
+
|
|
4541
|
+
if (!this.structuredDataContent) {
|
|
4542
|
+
console.error('❌ [CODE TREE] Structured data content element (#module-data-content) not found after retry!');
|
|
4543
|
+
console.log('[CODE TREE] Checking DOM for available elements...');
|
|
4544
|
+
// Debug: List all elements with module or data in their ID
|
|
4545
|
+
const allElements = document.querySelectorAll('[id*="module"], [id*="data"]');
|
|
4546
|
+
console.log(`[CODE TREE] Found ${allElements.length} elements with "module" or "data" in ID:`);
|
|
4547
|
+
allElements.forEach(el => {
|
|
4548
|
+
console.log(` - #${el.id} (class: ${el.className}, parent: ${el.parentElement?.id || 'no-parent'})`);
|
|
4549
|
+
});
|
|
4550
|
+
|
|
4551
|
+
// Also check for the module viewer container
|
|
4552
|
+
const moduleViewer = document.querySelector('.module-viewer');
|
|
4553
|
+
if (moduleViewer) {
|
|
4554
|
+
console.log('[CODE TREE] Module viewer found, checking children...');
|
|
4555
|
+
const moduleDataContent = moduleViewer.querySelector('#module-data-content');
|
|
4556
|
+
if (moduleDataContent) {
|
|
4557
|
+
console.log('[CODE TREE] Found module-data-content via query selector!');
|
|
4558
|
+
this.structuredDataContent = moduleDataContent;
|
|
4559
|
+
} else {
|
|
4560
|
+
console.log('[CODE TREE] Module data content not found in module viewer');
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
} else {
|
|
4564
|
+
console.log('✅ [CODE TREE] Structured data integration initialized on retry');
|
|
4565
|
+
console.log(' Target element:', this.structuredDataContent);
|
|
4566
|
+
console.log(' Parent element:', this.structuredDataContent.parentElement);
|
|
4567
|
+
}
|
|
4568
|
+
}, 500);
|
|
4391
4569
|
return;
|
|
4392
4570
|
}
|
|
4393
4571
|
|
|
4394
|
-
console.log('✅ Structured data integration initialized');
|
|
4572
|
+
console.log('✅ [CODE TREE] Structured data integration initialized immediately');
|
|
4573
|
+
console.log(' Target element:', this.structuredDataContent);
|
|
4574
|
+
console.log(' Parent element:', this.structuredDataContent.parentElement);
|
|
4395
4575
|
}
|
|
4396
4576
|
|
|
4397
4577
|
/**
|
|
4398
4578
|
* Update structured data with node information
|
|
4399
4579
|
*/
|
|
4400
4580
|
updateStructuredData(node) {
|
|
4581
|
+
console.log('📝 [STRUCTURED DATA] updateStructuredData called');
|
|
4582
|
+
|
|
4401
4583
|
if (!this.structuredDataContent) {
|
|
4402
|
-
|
|
4584
|
+
console.warn('⚠️ [STRUCTURED DATA] Content element not available, trying to find it...');
|
|
4585
|
+
// Try to find it again in case it wasn't initialized properly
|
|
4586
|
+
this.structuredDataContent = document.getElementById('module-data-content');
|
|
4587
|
+
|
|
4588
|
+
if (!this.structuredDataContent) {
|
|
4589
|
+
console.error('❌ [STRUCTURED DATA] Cannot find module-data-content element!');
|
|
4590
|
+
// Last resort - try to find it in module viewer
|
|
4591
|
+
const moduleViewer = document.querySelector('.module-viewer');
|
|
4592
|
+
if (moduleViewer) {
|
|
4593
|
+
this.structuredDataContent = moduleViewer.querySelector('#module-data-content');
|
|
4594
|
+
if (this.structuredDataContent) {
|
|
4595
|
+
console.log('✅ [STRUCTURED DATA] Found element via module-viewer query');
|
|
4596
|
+
} else {
|
|
4597
|
+
console.error('❌ [STRUCTURED DATA] Still cannot find element, aborting update');
|
|
4598
|
+
return;
|
|
4599
|
+
}
|
|
4600
|
+
} else {
|
|
4601
|
+
console.error('❌ [STRUCTURED DATA] Module viewer not found either, aborting update');
|
|
4602
|
+
return;
|
|
4603
|
+
}
|
|
4604
|
+
} else {
|
|
4605
|
+
console.log('✅ [STRUCTURED DATA] Found element on retry');
|
|
4606
|
+
}
|
|
4403
4607
|
}
|
|
4404
4608
|
|
|
4405
4609
|
console.log('🔍 [STRUCTURED DATA] Updating with node:', {
|
|
4406
4610
|
name: node?.data?.name,
|
|
4407
4611
|
type: node?.data?.type,
|
|
4612
|
+
path: node?.data?.path,
|
|
4408
4613
|
hasChildren: !!(node?.children || node?._children),
|
|
4409
|
-
dataChildren: node?.data?.children?.length || 0
|
|
4614
|
+
dataChildren: node?.data?.children?.length || 0,
|
|
4615
|
+
contentElement: this.structuredDataContent
|
|
4410
4616
|
});
|
|
4411
4617
|
|
|
4412
4618
|
// Clear previous content
|
|
4413
4619
|
this.structuredDataContent.innerHTML = '';
|
|
4620
|
+
console.log('🧹 [STRUCTURED DATA] Cleared previous content');
|
|
4414
4621
|
|
|
4415
4622
|
// Check if this is a source file that should show source viewer
|
|
4416
4623
|
if (node.data.type === 'file' && this.isSourceFile(node.data.path)) {
|
|
@@ -5135,6 +5342,377 @@ main();
|
|
|
5135
5342
|
return match ? match[1].length : 0;
|
|
5136
5343
|
}
|
|
5137
5344
|
|
|
5345
|
+
/**
|
|
5346
|
+
* Auto-expand file node to show AST tree after analysis
|
|
5347
|
+
*/
|
|
5348
|
+
autoExpandFileWithAST(filePath, fileNode) {
|
|
5349
|
+
console.log('🌳 [AST EXPANSION] Auto-expanding file with AST:', {
|
|
5350
|
+
filePath: filePath,
|
|
5351
|
+
hasChildren: !!(fileNode && fileNode.children && fileNode.children.length > 0)
|
|
5352
|
+
});
|
|
5353
|
+
|
|
5354
|
+
if (!fileNode || !fileNode.children || fileNode.children.length === 0) {
|
|
5355
|
+
console.log('⚠️ [AST EXPANSION] No children to expand');
|
|
5356
|
+
return;
|
|
5357
|
+
}
|
|
5358
|
+
|
|
5359
|
+
// Find the D3 node for this file
|
|
5360
|
+
const d3Node = this.findD3NodeByPath(filePath);
|
|
5361
|
+
if (!d3Node) {
|
|
5362
|
+
console.log('⚠️ [AST EXPANSION] D3 node not found for path:', filePath);
|
|
5363
|
+
return;
|
|
5364
|
+
}
|
|
5365
|
+
|
|
5366
|
+
// Rebuild the D3 hierarchy to include new children
|
|
5367
|
+
this.root = d3.hierarchy(this.treeData);
|
|
5368
|
+
|
|
5369
|
+
// Find the updated D3 node
|
|
5370
|
+
const updatedD3Node = this.findD3NodeByPath(filePath);
|
|
5371
|
+
if (updatedD3Node) {
|
|
5372
|
+
// Expand the file node to show AST elements
|
|
5373
|
+
if (updatedD3Node._children || (updatedD3Node.children && updatedD3Node.children.length === 0)) {
|
|
5374
|
+
updatedD3Node.children = updatedD3Node._children || updatedD3Node.children;
|
|
5375
|
+
updatedD3Node._children = null;
|
|
5376
|
+
updatedD3Node.data.expanded = true;
|
|
5377
|
+
|
|
5378
|
+
console.log('✅ [AST EXPANSION] Expanded file node to show AST elements:', {
|
|
5379
|
+
filePath: filePath,
|
|
5380
|
+
childrenCount: updatedD3Node.children ? updatedD3Node.children.length : 0
|
|
5381
|
+
});
|
|
5382
|
+
|
|
5383
|
+
// Update the visualization
|
|
5384
|
+
this.update(this.root);
|
|
5385
|
+
|
|
5386
|
+
// Show notification about AST expansion
|
|
5387
|
+
const fileName = filePath.split('/').pop();
|
|
5388
|
+
const astCount = updatedD3Node.children ? updatedD3Node.children.length : 0;
|
|
5389
|
+
this.showNotification(`📊 ${fileName} - AST tree expanded with ${astCount} elements`, 'success');
|
|
5390
|
+
this.updateBreadcrumb(`${fileName} - Code structure visible in tree`, 'success');
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
|
|
5395
|
+
/**
|
|
5396
|
+
* Display file content in the unified data viewer
|
|
5397
|
+
*/
|
|
5398
|
+
displayFileInDataViewer(d) {
|
|
5399
|
+
console.log('📊 [DATA VIEWER] Displaying file in data viewer:', {
|
|
5400
|
+
fileName: d.data.name,
|
|
5401
|
+
filePath: d.data.path,
|
|
5402
|
+
fileType: d.data.type
|
|
5403
|
+
});
|
|
5404
|
+
|
|
5405
|
+
// Create file data object for unified data viewer
|
|
5406
|
+
const fileData = {
|
|
5407
|
+
file_path: d.data.path,
|
|
5408
|
+
name: d.data.name,
|
|
5409
|
+
type: 'file',
|
|
5410
|
+
size: d.data.size || 0,
|
|
5411
|
+
extension: this.getFileExtension(d.data.path),
|
|
5412
|
+
language: this.detectLanguage(d.data.path),
|
|
5413
|
+
operations: [{
|
|
5414
|
+
operation: 'view',
|
|
5415
|
+
timestamp: new Date().toISOString(),
|
|
5416
|
+
source: 'code_tree_click'
|
|
5417
|
+
}],
|
|
5418
|
+
// Add metadata for better display
|
|
5419
|
+
metadata: {
|
|
5420
|
+
clicked_from: 'code_tree',
|
|
5421
|
+
node_type: d.data.type,
|
|
5422
|
+
has_ast: d.data.analyzed || false,
|
|
5423
|
+
tree_path: this.getNodePath(d)
|
|
5424
|
+
}
|
|
5425
|
+
};
|
|
5426
|
+
|
|
5427
|
+
// Use the unified data viewer to display file information
|
|
5428
|
+
if (window.unifiedDataViewer) {
|
|
5429
|
+
window.unifiedDataViewer.display(fileData, 'file_operation');
|
|
5430
|
+
console.log('✅ [DATA VIEWER] File data displayed in unified viewer');
|
|
5431
|
+
} else {
|
|
5432
|
+
console.warn('⚠️ [DATA VIEWER] UnifiedDataViewer not available');
|
|
5433
|
+
}
|
|
5434
|
+
|
|
5435
|
+
// Update module header to show current file
|
|
5436
|
+
const moduleHeader = document.querySelector('.module-data-header h5');
|
|
5437
|
+
if (moduleHeader) {
|
|
5438
|
+
const fileName = d.data.name;
|
|
5439
|
+
const fileIcon = this.getFileIcon(d.data.path);
|
|
5440
|
+
moduleHeader.innerHTML = `${fileIcon} File: ${fileName}`;
|
|
5441
|
+
}
|
|
5442
|
+
|
|
5443
|
+
// Add a small delay, then check if user wants to open full file viewer
|
|
5444
|
+
// This gives them time to see the data viewer content first
|
|
5445
|
+
setTimeout(() => {
|
|
5446
|
+
this.offerFileViewerOption(d);
|
|
5447
|
+
}, 1000);
|
|
5448
|
+
}
|
|
5449
|
+
|
|
5450
|
+
/**
|
|
5451
|
+
* Offer option to open file in full viewer modal
|
|
5452
|
+
*/
|
|
5453
|
+
offerFileViewerOption(d) {
|
|
5454
|
+
// Only offer for text files that can be displayed
|
|
5455
|
+
if (!this.isTextFile(d.data.path)) {
|
|
5456
|
+
return;
|
|
5457
|
+
}
|
|
5458
|
+
|
|
5459
|
+
// Create a subtle notification with option to open full viewer
|
|
5460
|
+
const fileName = d.data.name;
|
|
5461
|
+
const notification = document.createElement('div');
|
|
5462
|
+
notification.className = 'file-viewer-offer';
|
|
5463
|
+
notification.innerHTML = `
|
|
5464
|
+
<div class="offer-content">
|
|
5465
|
+
<span class="offer-text">📄 ${fileName} loaded in data viewer</span>
|
|
5466
|
+
<button class="offer-button" onclick="this.parentElement.parentElement.remove(); window.showFileViewerModal && window.showFileViewerModal('${d.data.path}')">
|
|
5467
|
+
🔍 Open Full Viewer
|
|
5468
|
+
</button>
|
|
5469
|
+
<button class="offer-close" onclick="this.parentElement.parentElement.remove()">×</button>
|
|
5470
|
+
</div>
|
|
5471
|
+
`;
|
|
5472
|
+
|
|
5473
|
+
// Style the notification
|
|
5474
|
+
notification.style.cssText = `
|
|
5475
|
+
position: fixed;
|
|
5476
|
+
top: 20px;
|
|
5477
|
+
right: 20px;
|
|
5478
|
+
background: #f8fafc;
|
|
5479
|
+
border: 1px solid #e2e8f0;
|
|
5480
|
+
border-radius: 8px;
|
|
5481
|
+
padding: 12px;
|
|
5482
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
5483
|
+
z-index: 1000;
|
|
5484
|
+
max-width: 300px;
|
|
5485
|
+
font-size: 14px;
|
|
5486
|
+
`;
|
|
5487
|
+
|
|
5488
|
+
// Style the button
|
|
5489
|
+
const style = document.createElement('style');
|
|
5490
|
+
style.textContent = `
|
|
5491
|
+
.offer-content {
|
|
5492
|
+
display: flex;
|
|
5493
|
+
align-items: center;
|
|
5494
|
+
gap: 8px;
|
|
5495
|
+
}
|
|
5496
|
+
.offer-text {
|
|
5497
|
+
flex: 1;
|
|
5498
|
+
color: #4a5568;
|
|
5499
|
+
}
|
|
5500
|
+
.offer-button {
|
|
5501
|
+
background: #4299e1;
|
|
5502
|
+
color: white;
|
|
5503
|
+
border: none;
|
|
5504
|
+
border-radius: 4px;
|
|
5505
|
+
padding: 4px 8px;
|
|
5506
|
+
font-size: 12px;
|
|
5507
|
+
cursor: pointer;
|
|
5508
|
+
transition: background 0.2s;
|
|
5509
|
+
}
|
|
5510
|
+
.offer-button:hover {
|
|
5511
|
+
background: #3182ce;
|
|
5512
|
+
}
|
|
5513
|
+
.offer-close {
|
|
5514
|
+
background: none;
|
|
5515
|
+
border: none;
|
|
5516
|
+
color: #a0aec0;
|
|
5517
|
+
cursor: pointer;
|
|
5518
|
+
font-size: 16px;
|
|
5519
|
+
padding: 0;
|
|
5520
|
+
width: 20px;
|
|
5521
|
+
height: 20px;
|
|
5522
|
+
display: flex;
|
|
5523
|
+
align-items: center;
|
|
5524
|
+
justify-content: center;
|
|
5525
|
+
}
|
|
5526
|
+
.offer-close:hover {
|
|
5527
|
+
color: #718096;
|
|
5528
|
+
}
|
|
5529
|
+
`;
|
|
5530
|
+
|
|
5531
|
+
document.head.appendChild(style);
|
|
5532
|
+
document.body.appendChild(notification);
|
|
5533
|
+
|
|
5534
|
+
// Auto-remove after 5 seconds
|
|
5535
|
+
setTimeout(() => {
|
|
5536
|
+
if (notification.parentElement) {
|
|
5537
|
+
notification.remove();
|
|
5538
|
+
}
|
|
5539
|
+
}, 5000);
|
|
5540
|
+
}
|
|
5541
|
+
|
|
5542
|
+
/**
|
|
5543
|
+
* Check if file is a text file that can be displayed
|
|
5544
|
+
*/
|
|
5545
|
+
isTextFile(filePath) {
|
|
5546
|
+
if (!filePath) return false;
|
|
5547
|
+
|
|
5548
|
+
const ext = this.getFileExtension(filePath);
|
|
5549
|
+
const textExtensions = [
|
|
5550
|
+
'py', 'js', 'ts', 'jsx', 'tsx', 'html', 'css', 'json', 'md', 'txt',
|
|
5551
|
+
'yml', 'yaml', 'xml', 'sql', 'sh', 'bash', 'dockerfile', 'makefile',
|
|
5552
|
+
'gitignore', 'readme', 'cfg', 'conf', 'ini', 'toml', 'lock'
|
|
5553
|
+
];
|
|
5554
|
+
|
|
5555
|
+
return textExtensions.includes(ext) ||
|
|
5556
|
+
textExtensions.includes(filePath.toLowerCase().split('/').pop());
|
|
5557
|
+
}
|
|
5558
|
+
|
|
5559
|
+
/**
|
|
5560
|
+
* Get the full path from root to the given node
|
|
5561
|
+
*/
|
|
5562
|
+
getNodePath(d) {
|
|
5563
|
+
const path = [];
|
|
5564
|
+
let current = d;
|
|
5565
|
+
while (current) {
|
|
5566
|
+
if (current.data && current.data.name) {
|
|
5567
|
+
path.unshift(current.data.name);
|
|
5568
|
+
}
|
|
5569
|
+
current = current.parent;
|
|
5570
|
+
}
|
|
5571
|
+
return path.join(' > ');
|
|
5572
|
+
}
|
|
5573
|
+
|
|
5574
|
+
/**
|
|
5575
|
+
* Get file extension from path
|
|
5576
|
+
*/
|
|
5577
|
+
getFileExtension(filePath) {
|
|
5578
|
+
if (!filePath) return '';
|
|
5579
|
+
const parts = filePath.split('.');
|
|
5580
|
+
return parts.length > 1 ? parts.pop().toLowerCase() : '';
|
|
5581
|
+
}
|
|
5582
|
+
|
|
5583
|
+
/**
|
|
5584
|
+
* Get descriptive file type for user messages
|
|
5585
|
+
*/
|
|
5586
|
+
getFileTypeDescription(fileName) {
|
|
5587
|
+
if (!fileName) return 'File';
|
|
5588
|
+
|
|
5589
|
+
const ext = this.getFileExtension(fileName);
|
|
5590
|
+
const baseName = fileName.toLowerCase();
|
|
5591
|
+
|
|
5592
|
+
// Special cases
|
|
5593
|
+
if (baseName.endsWith('__init__.py')) {
|
|
5594
|
+
return 'Python package initialization';
|
|
5595
|
+
}
|
|
5596
|
+
if (baseName === 'makefile') {
|
|
5597
|
+
return 'Build configuration';
|
|
5598
|
+
}
|
|
5599
|
+
if (baseName.includes('config') || baseName.includes('settings')) {
|
|
5600
|
+
return 'Configuration file';
|
|
5601
|
+
}
|
|
5602
|
+
if (baseName.includes('test') || baseName.includes('spec')) {
|
|
5603
|
+
return 'Test file';
|
|
5604
|
+
}
|
|
5605
|
+
|
|
5606
|
+
// By extension
|
|
5607
|
+
const typeMap = {
|
|
5608
|
+
'py': 'Python file',
|
|
5609
|
+
'js': 'JavaScript file',
|
|
5610
|
+
'ts': 'TypeScript file',
|
|
5611
|
+
'jsx': 'React component',
|
|
5612
|
+
'tsx': 'React TypeScript component',
|
|
5613
|
+
'html': 'HTML document',
|
|
5614
|
+
'css': 'Stylesheet',
|
|
5615
|
+
'json': 'JSON data',
|
|
5616
|
+
'md': 'Markdown document',
|
|
5617
|
+
'txt': 'Text file',
|
|
5618
|
+
'yml': 'YAML configuration',
|
|
5619
|
+
'yaml': 'YAML configuration',
|
|
5620
|
+
'xml': 'XML document',
|
|
5621
|
+
'sql': 'SQL script',
|
|
5622
|
+
'sh': 'Shell script',
|
|
5623
|
+
'bash': 'Bash script',
|
|
5624
|
+
'toml': 'TOML configuration',
|
|
5625
|
+
'ini': 'INI configuration'
|
|
5626
|
+
};
|
|
5627
|
+
|
|
5628
|
+
return typeMap[ext] || 'File';
|
|
5629
|
+
}
|
|
5630
|
+
|
|
5631
|
+
/**
|
|
5632
|
+
* Count different types of AST elements
|
|
5633
|
+
*/
|
|
5634
|
+
getElementCounts(elements) {
|
|
5635
|
+
const counts = {
|
|
5636
|
+
classes: 0,
|
|
5637
|
+
functions: 0,
|
|
5638
|
+
methods: 0,
|
|
5639
|
+
total: elements.length
|
|
5640
|
+
};
|
|
5641
|
+
|
|
5642
|
+
elements.forEach(elem => {
|
|
5643
|
+
if (elem.type === 'class') {
|
|
5644
|
+
counts.classes++;
|
|
5645
|
+
if (elem.methods) {
|
|
5646
|
+
counts.methods += elem.methods.length;
|
|
5647
|
+
}
|
|
5648
|
+
} else if (elem.type === 'function') {
|
|
5649
|
+
counts.functions++;
|
|
5650
|
+
}
|
|
5651
|
+
});
|
|
5652
|
+
|
|
5653
|
+
return counts;
|
|
5654
|
+
}
|
|
5655
|
+
|
|
5656
|
+
/**
|
|
5657
|
+
* Format element counts into a readable summary
|
|
5658
|
+
*/
|
|
5659
|
+
formatElementSummary(counts) {
|
|
5660
|
+
const parts = [];
|
|
5661
|
+
|
|
5662
|
+
if (counts.classes > 0) {
|
|
5663
|
+
parts.push(`${counts.classes} class${counts.classes !== 1 ? 'es' : ''}`);
|
|
5664
|
+
}
|
|
5665
|
+
if (counts.functions > 0) {
|
|
5666
|
+
parts.push(`${counts.functions} function${counts.functions !== 1 ? 's' : ''}`);
|
|
5667
|
+
}
|
|
5668
|
+
if (counts.methods > 0) {
|
|
5669
|
+
parts.push(`${counts.methods} method${counts.methods !== 1 ? 's' : ''}`);
|
|
5670
|
+
}
|
|
5671
|
+
|
|
5672
|
+
if (parts.length === 0) {
|
|
5673
|
+
return 'Structural elements for tree view';
|
|
5674
|
+
} else if (parts.length === 1) {
|
|
5675
|
+
return parts[0] + ' found';
|
|
5676
|
+
} else if (parts.length === 2) {
|
|
5677
|
+
return parts.join(' and ') + ' found';
|
|
5678
|
+
} else {
|
|
5679
|
+
return parts.slice(0, -1).join(', ') + ', and ' + parts[parts.length - 1] + ' found';
|
|
5680
|
+
}
|
|
5681
|
+
}
|
|
5682
|
+
|
|
5683
|
+
/**
|
|
5684
|
+
* Get file icon based on file type
|
|
5685
|
+
*/
|
|
5686
|
+
getFileIcon(filePath) {
|
|
5687
|
+
if (!filePath) return '📄';
|
|
5688
|
+
|
|
5689
|
+
const ext = this.getFileExtension(filePath);
|
|
5690
|
+
const iconMap = {
|
|
5691
|
+
'py': '🐍',
|
|
5692
|
+
'js': '📜',
|
|
5693
|
+
'ts': '📘',
|
|
5694
|
+
'jsx': '⚛️',
|
|
5695
|
+
'tsx': '⚛️',
|
|
5696
|
+
'html': '🌐',
|
|
5697
|
+
'css': '🎨',
|
|
5698
|
+
'json': '📋',
|
|
5699
|
+
'md': '📝',
|
|
5700
|
+
'txt': '📄',
|
|
5701
|
+
'yml': '⚙️',
|
|
5702
|
+
'yaml': '⚙️',
|
|
5703
|
+
'xml': '📰',
|
|
5704
|
+
'sql': '🗃️',
|
|
5705
|
+
'sh': '🐚',
|
|
5706
|
+
'bash': '🐚',
|
|
5707
|
+
'dockerfile': '🐳',
|
|
5708
|
+
'makefile': '🔨',
|
|
5709
|
+
'gitignore': '🚫',
|
|
5710
|
+
'readme': '📖'
|
|
5711
|
+
};
|
|
5712
|
+
|
|
5713
|
+
return iconMap[ext] || iconMap[filePath.toLowerCase().split('/').pop()] || '📄';
|
|
5714
|
+
}
|
|
5715
|
+
|
|
5138
5716
|
/**
|
|
5139
5717
|
* Handle click on source line with AST elements
|
|
5140
5718
|
*/
|
|
@@ -5264,4 +5842,4 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
5264
5842
|
}
|
|
5265
5843
|
});
|
|
5266
5844
|
}
|
|
5267
|
-
})
|
|
5845
|
+
});
|