tri-cli 0.0.52 → 0.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Treemap.js +56 -20
- package/dist/cli.js +166 -26
- package/package.json +1 -1
package/dist/Treemap.js
CHANGED
|
@@ -20,6 +20,7 @@ const oneHunter = {
|
|
|
20
20
|
|
|
21
21
|
// Expanded color palette for directories
|
|
22
22
|
const dirColors = [oneHunter.blue, oneHunter.cyan, oneHunter.green, oneHunter.yellow, oneHunter.orange, oneHunter.red, oneHunter.purple, oneHunter.magenta, '#4a9eff', '#7ec699', '#e8b339', '#ff7eb6'];
|
|
23
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
23
24
|
|
|
24
25
|
// Function to format bytes to human readable
|
|
25
26
|
const formatBytes = bytes => {
|
|
@@ -30,6 +31,14 @@ const formatBytes = bytes => {
|
|
|
30
31
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
31
32
|
};
|
|
32
33
|
|
|
34
|
+
// Function to format counts
|
|
35
|
+
const formatCount = count => {
|
|
36
|
+
if (count === 0) return '0';
|
|
37
|
+
if (count < 1000) return count.toString();
|
|
38
|
+
if (count < 1000000) return (count / 1000).toFixed(1) + 'K';
|
|
39
|
+
return (count / 1000000).toFixed(1) + 'M';
|
|
40
|
+
};
|
|
41
|
+
|
|
33
42
|
// Get color based on file extension
|
|
34
43
|
const getFileColor = filename => {
|
|
35
44
|
var _filename$match;
|
|
@@ -99,8 +108,19 @@ const assignGroupColors = root => {
|
|
|
99
108
|
};
|
|
100
109
|
export const TreeMapView = ({
|
|
101
110
|
selectedNode,
|
|
102
|
-
stdout
|
|
111
|
+
stdout,
|
|
112
|
+
mode = 'size',
|
|
113
|
+
loadingPaths = new Set(),
|
|
114
|
+
calculatedCounts = new Map(),
|
|
115
|
+
calculatedSizes = new Map()
|
|
103
116
|
}) => {
|
|
117
|
+
const [spinnerFrame, setSpinnerFrame] = React.useState(0);
|
|
118
|
+
|
|
119
|
+
// Spinner animation loop
|
|
120
|
+
React.useEffect(() => {
|
|
121
|
+
const interval = setInterval(() => setSpinnerFrame(f => (f + 1) % SPINNER_FRAMES.length), 80);
|
|
122
|
+
return () => clearInterval(interval);
|
|
123
|
+
}, []);
|
|
104
124
|
if (!selectedNode || !selectedNode.data) {
|
|
105
125
|
return /*#__PURE__*/React.createElement(Box, {
|
|
106
126
|
flexDirection: "column"
|
|
@@ -110,16 +130,20 @@ export const TreeMapView = ({
|
|
|
110
130
|
}, "Error: Invalid node selected"), /*#__PURE__*/React.createElement(Text, {
|
|
111
131
|
dimColor: true,
|
|
112
132
|
color: oneHunter.textAlt
|
|
113
|
-
}, "Press
|
|
133
|
+
}, "Press any key to go back to tree view"));
|
|
114
134
|
}
|
|
115
135
|
const width = Math.min((stdout === null || stdout === void 0 ? void 0 : stdout.columns) || 80, 120) - 4;
|
|
116
136
|
const height = Math.min((stdout === null || stdout === void 0 ? void 0 : stdout.rows) || 24, 40) - 8;
|
|
117
137
|
|
|
138
|
+
// Choose formatter based on mode
|
|
139
|
+
const formatValue = mode === 'count' ? formatCount : formatBytes;
|
|
140
|
+
const modeLabel = mode === 'count' ? 'Count' : 'Size';
|
|
141
|
+
|
|
118
142
|
// If it's a file, show just that file as a big rectangle
|
|
119
143
|
if (selectedNode.data.isFile) {
|
|
120
144
|
const color = getFileColor(selectedNode.data.name);
|
|
121
145
|
const name = selectedNode.data.name;
|
|
122
|
-
const
|
|
146
|
+
const valueStr = mode === 'count' ? '1 file' : selectedNode.data.size ? formatBytes(selectedNode.data.size) : '';
|
|
123
147
|
const grid = Array(height).fill(null).map(() => Array(width).fill(null).map(() => ({
|
|
124
148
|
char: ' ',
|
|
125
149
|
color
|
|
@@ -149,12 +173,12 @@ export const TreeMapView = ({
|
|
|
149
173
|
};
|
|
150
174
|
}
|
|
151
175
|
}
|
|
152
|
-
if (
|
|
153
|
-
const sizeX = Math.floor((width -
|
|
154
|
-
for (let i = 0; i <
|
|
176
|
+
if (valueStr && midY + 2 < height - 2) {
|
|
177
|
+
const sizeX = Math.floor((width - valueStr.length) / 2);
|
|
178
|
+
for (let i = 0; i < valueStr.length && sizeX + i < width - 2; i++) {
|
|
155
179
|
if (sizeX + i > 0) {
|
|
156
180
|
grid[midY + 2][sizeX + i] = {
|
|
157
|
-
char:
|
|
181
|
+
char: valueStr[i],
|
|
158
182
|
color
|
|
159
183
|
};
|
|
160
184
|
}
|
|
@@ -194,10 +218,10 @@ export const TreeMapView = ({
|
|
|
194
218
|
}, /*#__PURE__*/React.createElement(Text, {
|
|
195
219
|
bold: true,
|
|
196
220
|
color: oneHunter.cyan
|
|
197
|
-
}, "TreeMap View: ", name, " (File)"), /*#__PURE__*/React.createElement(Text, {
|
|
221
|
+
}, "TreeMap View (", modeLabel, "): ", name, " (File)"), /*#__PURE__*/React.createElement(Text, {
|
|
198
222
|
dimColor: true,
|
|
199
223
|
color: oneHunter.textAlt
|
|
200
|
-
}, "Press
|
|
224
|
+
}, "Press any key to go back to tree view | Ctrl+C: Exit"), /*#__PURE__*/React.createElement(Box, {
|
|
201
225
|
borderStyle: "single",
|
|
202
226
|
borderColor: oneHunter.textAlt,
|
|
203
227
|
flexDirection: "column",
|
|
@@ -206,7 +230,7 @@ export const TreeMapView = ({
|
|
|
206
230
|
dimColor: true,
|
|
207
231
|
color: oneHunter.textAlt,
|
|
208
232
|
marginTop: 1
|
|
209
|
-
}, "File: ", name, " |
|
|
233
|
+
}, "File: ", name, " | ", modeLabel, ": ", valueStr || 'Unknown'));
|
|
210
234
|
}
|
|
211
235
|
|
|
212
236
|
// It's a directory - show treemap of its children
|
|
@@ -216,10 +240,10 @@ export const TreeMapView = ({
|
|
|
216
240
|
}, /*#__PURE__*/React.createElement(Text, {
|
|
217
241
|
bold: true,
|
|
218
242
|
color: oneHunter.cyan
|
|
219
|
-
}, "TreeMap View: ", selectedNode.data.name, " (Empty Directory)"), /*#__PURE__*/React.createElement(Text, {
|
|
243
|
+
}, "TreeMap View (", modeLabel, "): ", selectedNode.data.name, " (Empty Directory)"), /*#__PURE__*/React.createElement(Text, {
|
|
220
244
|
dimColor: true,
|
|
221
245
|
color: oneHunter.textAlt
|
|
222
|
-
}, "Press
|
|
246
|
+
}, "Press any key to go back to tree view | Ctrl+C: Exit"), /*#__PURE__*/React.createElement(Box, {
|
|
223
247
|
borderStyle: "single",
|
|
224
248
|
borderColor: oneHunter.textAlt,
|
|
225
249
|
marginTop: 1,
|
|
@@ -248,12 +272,18 @@ export const TreeMapView = ({
|
|
|
248
272
|
const x1 = Math.ceil(node.x1);
|
|
249
273
|
const y1 = Math.ceil(node.y1);
|
|
250
274
|
const isFile = node.data.isFile;
|
|
275
|
+
const isNodeLoading = loadingPaths.has(node.data.path);
|
|
251
276
|
const boxWidth = x1 - x0;
|
|
252
277
|
const boxHeight = y1 - y0;
|
|
253
278
|
if (boxWidth < 1 || boxHeight < 1) return;
|
|
254
279
|
|
|
255
|
-
// Color based on type
|
|
256
|
-
|
|
280
|
+
// Color based on type (dimmed if loading)
|
|
281
|
+
let color = isFile ? getFileColor(node.data.name) : node.groupColor || oneHunter.blue;
|
|
282
|
+
|
|
283
|
+
// Dim the color if loading
|
|
284
|
+
if (isNodeLoading) {
|
|
285
|
+
color = oneHunter.textAlt;
|
|
286
|
+
}
|
|
257
287
|
|
|
258
288
|
// Draw border
|
|
259
289
|
for (let y = y0; y < y1 && y < height; y++) {
|
|
@@ -277,14 +307,14 @@ export const TreeMapView = ({
|
|
|
277
307
|
|
|
278
308
|
// Draw label if there's room
|
|
279
309
|
const name = node.data.name;
|
|
280
|
-
const
|
|
310
|
+
const valueStr = isNodeLoading ? ` ${SPINNER_FRAMES[spinnerFrame]}` : node.value ? ` ${formatValue(node.value)}${mode === 'count' ? ' files' : ''}` : '';
|
|
281
311
|
if (boxWidth >= 8 && boxHeight >= 2) {
|
|
282
312
|
const labelY = y0 + 1;
|
|
283
313
|
const labelX = x0 + 1;
|
|
284
314
|
const maxLength = boxWidth - 2;
|
|
285
315
|
|
|
286
|
-
// Combine name and
|
|
287
|
-
let label = name +
|
|
316
|
+
// Combine name and value
|
|
317
|
+
let label = name + valueStr;
|
|
288
318
|
if (label.length > maxLength) {
|
|
289
319
|
// Try just name
|
|
290
320
|
if (name.length <= maxLength) {
|
|
@@ -374,15 +404,21 @@ export const TreeMapView = ({
|
|
|
374
404
|
key: y
|
|
375
405
|
}, segments);
|
|
376
406
|
});
|
|
407
|
+
const totalLabel = mode === 'count' ? `${formatCount(root.value || 0)} files` : formatBytes(root.value || 0);
|
|
408
|
+
const isLoading = loadingPaths.size > 0;
|
|
377
409
|
return /*#__PURE__*/React.createElement(Box, {
|
|
378
410
|
flexDirection: "column"
|
|
379
411
|
}, /*#__PURE__*/React.createElement(Text, {
|
|
380
412
|
bold: true,
|
|
381
413
|
color: oneHunter.cyan
|
|
382
|
-
}, "TreeMap View: ", selectedNode.data.name), /*#__PURE__*/React.createElement(
|
|
414
|
+
}, "TreeMap View (", modeLabel, "): ", selectedNode.data.name), /*#__PURE__*/React.createElement(Box, {
|
|
415
|
+
justifyContent: "space-between"
|
|
416
|
+
}, /*#__PURE__*/React.createElement(Text, {
|
|
383
417
|
dimColor: true,
|
|
384
418
|
color: oneHunter.textAlt
|
|
385
|
-
}, "Press
|
|
419
|
+
}, "Press any key to go back to tree view | \u2191/\u2193: Navigate in tree | Ctrl+C: Exit"), isLoading && /*#__PURE__*/React.createElement(Text, {
|
|
420
|
+
color: oneHunter.yellow
|
|
421
|
+
}, ' ', SPINNER_FRAMES[spinnerFrame], " Loading ", loadingPaths.size, "...")), /*#__PURE__*/React.createElement(Box, {
|
|
386
422
|
borderStyle: "single",
|
|
387
423
|
borderColor: oneHunter.textAlt,
|
|
388
424
|
flexDirection: "column",
|
|
@@ -391,5 +427,5 @@ export const TreeMapView = ({
|
|
|
391
427
|
dimColor: true,
|
|
392
428
|
color: oneHunter.textAlt,
|
|
393
429
|
marginTop: 1
|
|
394
|
-
}, "Showing ", firstLevelNodes.length, " items | Total:",
|
|
430
|
+
}, "Showing ", firstLevelNodes.length, " items | Total: ", totalLabel));
|
|
395
431
|
};
|
package/dist/cli.js
CHANGED
|
@@ -30,6 +30,12 @@ const formatBytes = bytes => {
|
|
|
30
30
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
31
31
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
32
32
|
};
|
|
33
|
+
const formatCount = count => {
|
|
34
|
+
if (count === 0) return '0';
|
|
35
|
+
if (count < 1000) return count.toString();
|
|
36
|
+
if (count < 1000000) return (count / 1000).toFixed(1) + 'K';
|
|
37
|
+
return (count / 1000000).toFixed(1) + 'M';
|
|
38
|
+
};
|
|
33
39
|
const formatDate = timestamp => {
|
|
34
40
|
const date = new Date(timestamp);
|
|
35
41
|
const now = new Date();
|
|
@@ -66,6 +72,24 @@ const getDirSizeAsync = async dirPath => {
|
|
|
66
72
|
return 0;
|
|
67
73
|
}
|
|
68
74
|
};
|
|
75
|
+
|
|
76
|
+
// Fast file counting using shell command
|
|
77
|
+
const countFilesAsync = async dirPath => {
|
|
78
|
+
try {
|
|
79
|
+
const stats = fs.statSync(dirPath);
|
|
80
|
+
if (!stats.isDirectory()) return 1;
|
|
81
|
+
|
|
82
|
+
// Use find command for fast counting, excluding hidden files
|
|
83
|
+
const escapedPath = dirPath.replace(/"/g, '\\"');
|
|
84
|
+
const {
|
|
85
|
+
stdout
|
|
86
|
+
} = await execAsync(`find "${escapedPath}" -type f ! -path '*/.*' 2>/dev/null | wc -l`);
|
|
87
|
+
const count = parseInt(stdout.trim(), 10);
|
|
88
|
+
return isNaN(count) ? 0 : count;
|
|
89
|
+
} catch {
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
69
93
|
const readDirTree = (dirPath, noSize = true, currentLevel = 0, maxLevel = Infinity) => {
|
|
70
94
|
try {
|
|
71
95
|
const stats = fs.statSync(dirPath);
|
|
@@ -182,6 +206,7 @@ const TreeRow = /*#__PURE__*/React.memo(({
|
|
|
182
206
|
shortcutInput,
|
|
183
207
|
noSize,
|
|
184
208
|
calculatedSizes,
|
|
209
|
+
calculatedCounts,
|
|
185
210
|
loadingPaths,
|
|
186
211
|
spinnerFrame,
|
|
187
212
|
lastModifiedDates
|
|
@@ -199,6 +224,7 @@ const TreeRow = /*#__PURE__*/React.memo(({
|
|
|
199
224
|
} else {
|
|
200
225
|
displaySize = node.data.size;
|
|
201
226
|
}
|
|
227
|
+
const fileCount = calculatedCounts === null || calculatedCounts === void 0 ? void 0 : calculatedCounts.get(node.data.path);
|
|
202
228
|
const lastModified = lastModifiedDates.get(node.data.path);
|
|
203
229
|
const treeLines = getTreeLines(node);
|
|
204
230
|
|
|
@@ -235,7 +261,11 @@ const TreeRow = /*#__PURE__*/React.memo(({
|
|
|
235
261
|
backgroundColor: isSelected ? oneHunter.green : undefined
|
|
236
262
|
}, node.data.name, !node.data.isFile && !isRoot ? '/' : ''), hasChildren && !isCollapsed && /*#__PURE__*/React.createElement(Text, {
|
|
237
263
|
color: oneHunter.textAlt
|
|
238
|
-
}, " (", node.children.length, ")"),
|
|
264
|
+
}, " (", node.children.length, ")"), fileCount !== undefined && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, {
|
|
265
|
+
color: oneHunter.purple
|
|
266
|
+
}, " [", formatCount(fileCount), " files]"), isLoading && /*#__PURE__*/React.createElement(Text, {
|
|
267
|
+
color: oneHunter.yellow
|
|
268
|
+
}, " ", SPINNER_FRAMES[spinnerFrame])), isLoading ? /*#__PURE__*/React.createElement(Text, {
|
|
239
269
|
color: oneHunter.yellow
|
|
240
270
|
}, " ", SPINNER_FRAMES[spinnerFrame]) : displaySize > 0 ? /*#__PURE__*/React.createElement(Text, {
|
|
241
271
|
color: oneHunter.cyan
|
|
@@ -253,8 +283,10 @@ const TreeVisualization = ({
|
|
|
253
283
|
const [collapsed, setCollapsed] = useState(new Set());
|
|
254
284
|
const [shortcutInput, setShortcutInput] = useState('');
|
|
255
285
|
const [showTreeMap, setShowTreeMap] = useState(false);
|
|
286
|
+
const [treeMapMode, setTreeMapMode] = useState('size'); // 'size' or 'count'
|
|
256
287
|
const [initialized, setInitialized] = useState(false);
|
|
257
288
|
const [calculatedSizes, setCalculatedSizes] = useState(new Map());
|
|
289
|
+
const [calculatedCounts, setCalculatedCounts] = useState(new Map());
|
|
258
290
|
const [loadingPaths, setLoadingPaths] = useState(new Set());
|
|
259
291
|
const [spinnerFrame, setSpinnerFrame] = useState(0);
|
|
260
292
|
const [rootTree, setRootTree] = useState(() => hierarchy(readDirTree(dirPath, noSize, 0, maxLevel)));
|
|
@@ -385,6 +417,50 @@ const TreeVisualization = ({
|
|
|
385
417
|
await new Promise(r => setTimeout(r, 0));
|
|
386
418
|
}
|
|
387
419
|
};
|
|
420
|
+
const countFilesInTreeAsync = async node => {
|
|
421
|
+
// Collect node and all its descendants
|
|
422
|
+
const nodesToCount = [];
|
|
423
|
+
const collectNodes = n => {
|
|
424
|
+
// Skip if already counted
|
|
425
|
+
if (calculatedCounts.has(n.data.path)) {
|
|
426
|
+
// Still recurse to children in case they need counting
|
|
427
|
+
if (n.children) {
|
|
428
|
+
n.children.forEach(collectNodes);
|
|
429
|
+
}
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
nodesToCount.push(n);
|
|
433
|
+
if (n.children) {
|
|
434
|
+
n.children.forEach(collectNodes);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
collectNodes(node);
|
|
438
|
+
if (nodesToCount.length === 0) return; // Nothing to count
|
|
439
|
+
|
|
440
|
+
// Sort nodes by depth (shallowest first) for top-down display
|
|
441
|
+
nodesToCount.sort((a, b) => a.depth - b.depth);
|
|
442
|
+
|
|
443
|
+
// Mark all nodes as loading
|
|
444
|
+
const loadingSet = new Set(nodesToCount.map(n => n.data.path));
|
|
445
|
+
setLoadingPaths(loadingSet);
|
|
446
|
+
const newMap = new Map(calculatedCounts);
|
|
447
|
+
|
|
448
|
+
// Process all nodes in parallel, but update UI as each completes
|
|
449
|
+
const promises = nodesToCount.map(async targetNode => {
|
|
450
|
+
const count = await countFilesAsync(targetNode.data.path);
|
|
451
|
+
|
|
452
|
+
// Update immediately when this specific node completes
|
|
453
|
+
newMap.set(targetNode.data.path, count);
|
|
454
|
+
loadingSet.delete(targetNode.data.path);
|
|
455
|
+
|
|
456
|
+
// Trigger UI update
|
|
457
|
+
setCalculatedCounts(new Map(newMap));
|
|
458
|
+
setLoadingPaths(new Set(loadingSet));
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Wait for all counts to complete
|
|
462
|
+
await Promise.all(promises);
|
|
463
|
+
};
|
|
388
464
|
const getLastModifiedAsync = async node => {
|
|
389
465
|
// Collect node and all its descendants
|
|
390
466
|
const nodesToProcess = [];
|
|
@@ -460,8 +536,8 @@ const TreeVisualization = ({
|
|
|
460
536
|
return;
|
|
461
537
|
}
|
|
462
538
|
if (input === 'q') process.exit(0);
|
|
463
|
-
if (input === 'C') {
|
|
464
|
-
// Toggle collapse/expand all children of the selected node's PARENT
|
|
539
|
+
if (input === 'C' && !key.shift) {
|
|
540
|
+
// Toggle collapse/expand all children of the selected node's PARENT (lowercase handling moved)
|
|
465
541
|
const selectedNode = visibleNodes[selectedIndex];
|
|
466
542
|
if (!selectedNode || !selectedNode.parent) return;
|
|
467
543
|
const parentNode = selectedNode.parent;
|
|
@@ -492,6 +568,40 @@ const TreeVisualization = ({
|
|
|
492
568
|
});
|
|
493
569
|
return;
|
|
494
570
|
}
|
|
571
|
+
if (key.shift && input === 'C') {
|
|
572
|
+
// Shift+C: Show count treemap
|
|
573
|
+
const node = visibleNodes[selectedIndex];
|
|
574
|
+
if (!node) return;
|
|
575
|
+
|
|
576
|
+
// Collect all nodes that need counting
|
|
577
|
+
const nodesToCount = [];
|
|
578
|
+
const collectNodes = n => {
|
|
579
|
+
if (calculatedCounts.has(n.data.path)) {
|
|
580
|
+
if (n.children) {
|
|
581
|
+
n.children.forEach(collectNodes);
|
|
582
|
+
}
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
nodesToCount.push(n);
|
|
586
|
+
if (n.children) {
|
|
587
|
+
n.children.forEach(collectNodes);
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
collectNodes(node);
|
|
591
|
+
|
|
592
|
+
// Mark nodes as loading before starting
|
|
593
|
+
if (nodesToCount.length > 0) {
|
|
594
|
+
const pathsToLoad = nodesToCount.map(n => n.data.path);
|
|
595
|
+
setLoadingPaths(prev => new Set([...prev, ...pathsToLoad]));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Start calculating counts in the background
|
|
599
|
+
countFilesInTreeAsync(node);
|
|
600
|
+
// Show count treemap immediately
|
|
601
|
+
setTreeMapMode('count');
|
|
602
|
+
setShowTreeMap(true);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
495
605
|
if (input === 'O') {
|
|
496
606
|
// Toggle collapse/expand ALL nodes in the entire tree
|
|
497
607
|
const allNodesWithChildren = [];
|
|
@@ -524,7 +634,8 @@ const TreeVisualization = ({
|
|
|
524
634
|
if (!node) return;
|
|
525
635
|
// Always start calculating sizes in the background (calculateSizeAsync skips already-calculated nodes)
|
|
526
636
|
calculateSizeAsync(node);
|
|
527
|
-
// Show treemap immediately
|
|
637
|
+
// Show size treemap immediately
|
|
638
|
+
setTreeMapMode('size');
|
|
528
639
|
setShowTreeMap(true);
|
|
529
640
|
return;
|
|
530
641
|
}
|
|
@@ -533,6 +644,11 @@ const TreeVisualization = ({
|
|
|
533
644
|
if (node) await calculateSizeAsync(node);
|
|
534
645
|
return;
|
|
535
646
|
}
|
|
647
|
+
if (input === 'c') {
|
|
648
|
+
const node = visibleNodes[selectedIndex];
|
|
649
|
+
if (node) await countFilesInTreeAsync(node);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
536
652
|
if (input === 'l') {
|
|
537
653
|
const node = visibleNodes[selectedIndex];
|
|
538
654
|
if (node) await getLastModifiedAsync(node);
|
|
@@ -597,31 +713,52 @@ const TreeVisualization = ({
|
|
|
597
713
|
|
|
598
714
|
// Set value on the DATA object
|
|
599
715
|
const data = hierarchyNode.data;
|
|
600
|
-
if (
|
|
601
|
-
//
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
data.value =
|
|
605
|
-
} else {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
716
|
+
if (treeMapMode === 'count') {
|
|
717
|
+
// COUNT MODE: Use file counts
|
|
718
|
+
if (data.isFile) {
|
|
719
|
+
// Files always count as 1
|
|
720
|
+
data.value = 1;
|
|
721
|
+
} else if (data.children === undefined || !hierarchyNode.children || hierarchyNode.children.length === 0) {
|
|
722
|
+
// Directory with no children loaded OR empty directory
|
|
723
|
+
// Use calculatedCount if available
|
|
724
|
+
const calcCount = calculatedCounts.get(data.path);
|
|
725
|
+
if (calcCount !== undefined) {
|
|
726
|
+
data.value = calcCount;
|
|
727
|
+
} else {
|
|
609
728
|
data.value = 0;
|
|
610
729
|
}
|
|
611
|
-
}
|
|
612
|
-
} else if (data.children === undefined || !hierarchyNode.children || hierarchyNode.children.length === 0) {
|
|
613
|
-
// Directory with no children loaded OR empty directory
|
|
614
|
-
// Use calculatedSize if available
|
|
615
|
-
const calcSize = calculatedSizes.get(data.path);
|
|
616
|
-
if (calcSize !== undefined) {
|
|
617
|
-
data.value = calcSize;
|
|
618
730
|
} else {
|
|
619
|
-
//
|
|
620
|
-
data.value =
|
|
731
|
+
// Directory with loaded children - let d3's .sum() aggregate from children
|
|
732
|
+
data.value = undefined;
|
|
621
733
|
}
|
|
622
734
|
} else {
|
|
623
|
-
//
|
|
624
|
-
data.
|
|
735
|
+
// SIZE MODE: Use file sizes
|
|
736
|
+
if (data.isFile) {
|
|
737
|
+
// Files: use calculatedSize or read from disk
|
|
738
|
+
const calcSize = calculatedSizes.get(data.path);
|
|
739
|
+
if (calcSize !== undefined) {
|
|
740
|
+
data.value = calcSize;
|
|
741
|
+
} else {
|
|
742
|
+
try {
|
|
743
|
+
data.value = fs.statSync(data.path).size;
|
|
744
|
+
} catch {
|
|
745
|
+
data.value = 0;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
} else if (data.children === undefined || !hierarchyNode.children || hierarchyNode.children.length === 0) {
|
|
749
|
+
// Directory with no children loaded OR empty directory
|
|
750
|
+
// Use calculatedSize if available
|
|
751
|
+
const calcSize = calculatedSizes.get(data.path);
|
|
752
|
+
if (calcSize !== undefined) {
|
|
753
|
+
data.value = calcSize;
|
|
754
|
+
} else {
|
|
755
|
+
// For unloaded directories without calculated size, will show as 0
|
|
756
|
+
data.value = data.size || 0;
|
|
757
|
+
}
|
|
758
|
+
} else {
|
|
759
|
+
// Directory with loaded children - let d3's .sum() aggregate from children
|
|
760
|
+
data.value = undefined;
|
|
761
|
+
}
|
|
625
762
|
}
|
|
626
763
|
};
|
|
627
764
|
populateValues(node);
|
|
@@ -629,7 +766,9 @@ const TreeVisualization = ({
|
|
|
629
766
|
selectedNode: node,
|
|
630
767
|
stdout: stdout,
|
|
631
768
|
calculatedSizes: calculatedSizes,
|
|
632
|
-
|
|
769
|
+
calculatedCounts: calculatedCounts,
|
|
770
|
+
loadingPaths: loadingPaths,
|
|
771
|
+
mode: treeMapMode
|
|
633
772
|
});
|
|
634
773
|
}
|
|
635
774
|
const start = Math.max(0, selectedIndex - Math.floor(viewportHeight / 2));
|
|
@@ -643,7 +782,7 @@ const TreeVisualization = ({
|
|
|
643
782
|
}, /*#__PURE__*/React.createElement(Text, {
|
|
644
783
|
color: oneHunter.textAlt,
|
|
645
784
|
dimColor: true
|
|
646
|
-
}, "\u2191/\u2193: Navigate | Enter: Expand/Traverse | d: Size | l: Date | m: Map | q: Quit"), loadingPaths.size > 0 && /*#__PURE__*/React.createElement(Text, {
|
|
785
|
+
}, "\u2191/\u2193: Navigate | Enter: Expand/Traverse | c: Count | d: Size | l: Date | m: Map | Shift+C: CountMap | q: Quit"), loadingPaths.size > 0 && /*#__PURE__*/React.createElement(Text, {
|
|
647
786
|
color: oneHunter.yellow
|
|
648
787
|
}, ' ', SPINNER_FRAMES[spinnerFrame], " Loading ", loadingPaths.size, "...")), viewportNodes.map((node, i) => /*#__PURE__*/React.createElement(TreeRow, {
|
|
649
788
|
key: start + i,
|
|
@@ -653,6 +792,7 @@ const TreeVisualization = ({
|
|
|
653
792
|
shortcutInput: shortcutInput,
|
|
654
793
|
noSize: noSize,
|
|
655
794
|
calculatedSizes: calculatedSizes,
|
|
795
|
+
calculatedCounts: calculatedCounts,
|
|
656
796
|
loadingPaths: loadingPaths,
|
|
657
797
|
spinnerFrame: spinnerFrame,
|
|
658
798
|
lastModifiedDates: lastModifiedDates
|