awacke1 commited on
Commit
875e204
·
verified ·
1 Parent(s): 3362e92

Create index.html.v2

Browse files
Files changed (1) hide show
  1. index.html.v2 +994 -0
index.html.v2 ADDED
@@ -0,0 +1,994 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Squarified Treemap Explorer</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Arial', sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 20px;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1400px;
23
+ margin: 0 auto;
24
+ background: white;
25
+ border-radius: 15px;
26
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
27
+ overflow: hidden;
28
+ }
29
+
30
+ .header {
31
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
32
+ color: white;
33
+ padding: 30px;
34
+ text-align: center;
35
+ }
36
+
37
+ .header h1 {
38
+ font-size: 2.5em;
39
+ margin-bottom: 10px;
40
+ text-shadow: 0 2px 4px rgba(0,0,0,0.3);
41
+ }
42
+
43
+ .header p {
44
+ opacity: 0.9;
45
+ font-size: 1.1em;
46
+ }
47
+
48
+ .controls {
49
+ padding: 30px;
50
+ background: #f8f9fa;
51
+ border-bottom: 1px solid #e9ecef;
52
+ }
53
+
54
+ .file-input-wrapper {
55
+ display: flex;
56
+ flex-direction: column;
57
+ align-items: center;
58
+ gap: 15px;
59
+ }
60
+
61
+ .file-input {
62
+ display: none;
63
+ }
64
+
65
+ .file-input-button {
66
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
67
+ color: white;
68
+ padding: 15px 30px;
69
+ border: none;
70
+ border-radius: 50px;
71
+ font-size: 1.1em;
72
+ cursor: pointer;
73
+ transition: transform 0.2s, box-shadow 0.2s;
74
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
75
+ position: relative;
76
+ overflow: hidden;
77
+ }
78
+
79
+ .file-input-button:hover {
80
+ transform: translateY(-2px);
81
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
82
+ }
83
+
84
+ .demo-button {
85
+ background: linear-gradient(135deg, #00b894 0%, #00a085 100%);
86
+ margin-left: 15px;
87
+ }
88
+
89
+ .button-group {
90
+ display: flex;
91
+ flex-wrap: wrap;
92
+ gap: 15px;
93
+ justify-content: center;
94
+ }
95
+
96
+ .stats {
97
+ display: flex;
98
+ justify-content: center;
99
+ gap: 30px;
100
+ margin-top: 20px;
101
+ flex-wrap: wrap;
102
+ }
103
+
104
+ .stat-item {
105
+ background: white;
106
+ padding: 15px 25px;
107
+ border-radius: 10px;
108
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
109
+ text-align: center;
110
+ }
111
+
112
+ .stat-number {
113
+ font-size: 1.5em;
114
+ font-weight: bold;
115
+ color: #667eea;
116
+ }
117
+
118
+ .stat-label {
119
+ color: #666;
120
+ font-size: 0.9em;
121
+ }
122
+
123
+ .visualization-area {
124
+ padding: 30px;
125
+ min-height: 600px;
126
+ }
127
+
128
+ .treemap-container {
129
+ margin-bottom: 40px;
130
+ border-radius: 10px;
131
+ overflow: hidden;
132
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
133
+ }
134
+
135
+ .treemap-header {
136
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
137
+ color: white;
138
+ padding: 15px 20px;
139
+ font-weight: bold;
140
+ font-size: 1.1em;
141
+ }
142
+
143
+ .treemap {
144
+ position: relative;
145
+ background: #f8f9fa;
146
+ min-height: 400px;
147
+ border: 1px solid #e9ecef;
148
+ }
149
+
150
+ .treemap-node {
151
+ position: absolute;
152
+ border: 1px solid #fff;
153
+ cursor: pointer;
154
+ transition: all 0.3s ease;
155
+ display: flex;
156
+ align-items: center;
157
+ justify-content: center;
158
+ font-size: 12px;
159
+ font-weight: 500;
160
+ color: #333;
161
+ overflow: hidden;
162
+ }
163
+
164
+ .treemap-node:hover {
165
+ border-color: #667eea;
166
+ border-width: 2px;
167
+ transform: scale(1.02);
168
+ z-index: 100;
169
+ box-shadow: 0 5px 15px rgba(0,0,0,0.3);
170
+ }
171
+
172
+ .treemap-node.file {
173
+ color: white;
174
+ }
175
+
176
+ .treemap-node.folder {
177
+ background: linear-gradient(135deg, #fd79a8 0%, #e84393 100%);
178
+ color: white;
179
+ }
180
+
181
+ /* File type specific colors */
182
+ .treemap-node.file-image {
183
+ background: linear-gradient(135deg, #00cec9 0%, #00b894 100%);
184
+ }
185
+
186
+ .treemap-node.file-video {
187
+ background: linear-gradient(135deg, #e17055 0%, #d63031 100%);
188
+ }
189
+
190
+ .treemap-node.file-audio {
191
+ background: linear-gradient(135deg, #a29bfe 0%, #6c5ce7 100%);
192
+ }
193
+
194
+ .treemap-node.file-document {
195
+ background: linear-gradient(135deg, #fdcb6e 0%, #e17055 100%);
196
+ }
197
+
198
+ .treemap-node.file-office {
199
+ background: linear-gradient(135deg, #00b894 0%, #00a085 100%);
200
+ }
201
+
202
+ .treemap-node.file-code {
203
+ background: linear-gradient(135deg, #81ecec 0%, #00cec9 100%);
204
+ }
205
+
206
+ .legend {
207
+ display: flex;
208
+ justify-content: center;
209
+ flex-wrap: wrap;
210
+ gap: 15px;
211
+ margin-top: 20px;
212
+ padding: 20px;
213
+ background: white;
214
+ border-radius: 10px;
215
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
216
+ }
217
+
218
+ .legend-item {
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 8px;
222
+ font-size: 0.9em;
223
+ }
224
+
225
+ .legend-color {
226
+ width: 16px;
227
+ height: 16px;
228
+ border-radius: 3px;
229
+ border: 1px solid rgba(0,0,0,0.1);
230
+ }
231
+
232
+ .legend-color.folder { background: linear-gradient(135deg, #fd79a8 0%, #e84393 100%); }
233
+ .legend-color.image { background: linear-gradient(135deg, #00cec9 0%, #00b894 100%); }
234
+ .legend-color.video { background: linear-gradient(135deg, #e17055 0%, #d63031 100%); }
235
+ .legend-color.audio { background: linear-gradient(135deg, #a29bfe 0%, #6c5ce7 100%); }
236
+ .legend-color.document { background: linear-gradient(135deg, #fdcb6e 0%, #e17055 100%); }
237
+ .legend-color.office { background: linear-gradient(135deg, #00b894 0%, #00a085 100%); }
238
+ .legend-color.code { background: linear-gradient(135deg, #81ecec 0%, #00cec9 100%); }
239
+ .legend-color.other { background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%); }
240
+
241
+ .tooltip {
242
+ position: absolute;
243
+ background: rgba(0, 0, 0, 0.9);
244
+ color: white;
245
+ padding: 10px 15px;
246
+ border-radius: 5px;
247
+ font-size: 12px;
248
+ pointer-events: none;
249
+ z-index: 1000;
250
+ opacity: 0;
251
+ transition: opacity 0.3s;
252
+ max-width: 250px;
253
+ line-height: 1.4;
254
+ }
255
+
256
+ .tooltip.visible {
257
+ opacity: 1;
258
+ }
259
+
260
+ .loading {
261
+ text-align: center;
262
+ padding: 60px;
263
+ color: #666;
264
+ }
265
+
266
+ .loading-spinner {
267
+ width: 50px;
268
+ height: 50px;
269
+ border: 3px solid #f3f3f3;
270
+ border-top: 3px solid #667eea;
271
+ border-radius: 50%;
272
+ animation: spin 1s linear infinite;
273
+ margin: 0 auto 20px;
274
+ }
275
+
276
+ @keyframes spin {
277
+ 0% { transform: rotate(0deg); }
278
+ 100% { transform: rotate(360deg); }
279
+ }
280
+
281
+ .breadcrumb {
282
+ padding: 10px 20px;
283
+ background: #e9ecef;
284
+ font-size: 14px;
285
+ color: #666;
286
+ }
287
+
288
+ @media (max-width: 768px) {
289
+ .header h1 {
290
+ font-size: 1.8em;
291
+ }
292
+
293
+ .stats {
294
+ gap: 15px;
295
+ }
296
+
297
+ .stat-item {
298
+ padding: 10px 15px;
299
+ font-size: 0.9em;
300
+ }
301
+
302
+ .visualization-area {
303
+ padding: 15px;
304
+ }
305
+ }
306
+ </style>
307
+ </head>
308
+ <body>
309
+ <div class="container">
310
+ <div class="header">
311
+ <h1>🗂️ Squarified Treemap Explorer</h1>
312
+ <p>Visualize hierarchical file structures using advanced treemap algorithms</p>
313
+ </div>
314
+
315
+ <div class="controls">
316
+ <div class="file-input-wrapper">
317
+ <input type="file" id="folderInput" class="file-input" webkitdirectory multiple>
318
+ <div class="button-group">
319
+ <button class="file-input-button" onclick="selectFolder()">
320
+ 📁 Select Folder to Explore
321
+ </button>
322
+ <button class="file-input-button demo-button" onclick="generateDemoData()">
323
+ 🎮 Try Demo Data
324
+ </button>
325
+ </div>
326
+ <div class="stats" id="stats" style="display: none;">
327
+ <div class="stat-item">
328
+ <div class="stat-number" id="totalFiles">0</div>
329
+ <div class="stat-label">Files</div>
330
+ </div>
331
+ <div class="stat-item">
332
+ <div class="stat-number" id="totalFolders">0</div>
333
+ <div class="stat-label">Folders</div>
334
+ </div>
335
+ <div class="stat-item">
336
+ <div class="stat-number" id="totalSize">0 MB</div>
337
+ <div class="stat-label">Total Size</div>
338
+ </div>
339
+ <div class="stat-item">
340
+ <div class="stat-number" id="maxDepth">0</div>
341
+ <div class="stat-label">Max Depth</div>
342
+ </div>
343
+ </div>
344
+ <div class="legend" id="legend" style="display: none;">
345
+ <div class="legend-item">
346
+ <div class="legend-color folder"></div>
347
+ <span>📁 Folders</span>
348
+ </div>
349
+ <div class="legend-item">
350
+ <div class="legend-color image"></div>
351
+ <span>🖼️ Images</span>
352
+ </div>
353
+ <div class="legend-item">
354
+ <div class="legend-color video"></div>
355
+ <span>🎥 Video</span>
356
+ </div>
357
+ <div class="legend-item">
358
+ <div class="legend-color audio"></div>
359
+ <span>🎵 Audio</span>
360
+ </div>
361
+ <div class="legend-item">
362
+ <div class="legend-color document"></div>
363
+ <span>📄 Documents</span>
364
+ </div>
365
+ <div class="legend-item">
366
+ <div class="legend-color office"></div>
367
+ <span>📊 Office</span>
368
+ </div>
369
+ <div class="legend-item">
370
+ <div class="legend-color code"></div>
371
+ <span>💻 Code</span>
372
+ </div>
373
+ <div class="legend-item">
374
+ <div class="legend-color other"></div>
375
+ <span>📦 Other</span>
376
+ </div>
377
+ </div>
378
+ </div>
379
+ </div>
380
+
381
+ <div class="visualization-area" id="visualizationArea">
382
+ <div style="text-align: center; padding: 60px; color: #999;">
383
+ <h3>🎯 Ready to Explore</h3>
384
+ <p>Select a folder above to visualize its structure, or try the demo data to see how it works!</p>
385
+ </div>
386
+ </div>
387
+ </div>
388
+
389
+ <div class="tooltip" id="tooltip"></div>
390
+
391
+ <script>
392
+ class SquarifiedTreemapExplorer {
393
+ constructor() {
394
+ this.tooltip = document.getElementById('tooltip');
395
+ this.visualizationArea = document.getElementById('visualizationArea');
396
+ this.folderInput = document.getElementById('folderInput');
397
+ this.fileData = null;
398
+ this.setupEventListeners();
399
+ }
400
+
401
+ setupEventListeners() {
402
+ document.addEventListener('mousemove', (e) => {
403
+ this.tooltip.style.left = e.pageX + 10 + 'px';
404
+ this.tooltip.style.top = e.pageY + 10 + 'px';
405
+ });
406
+
407
+ // Listen for file input changes
408
+ this.folderInput.addEventListener('change', (e) => {
409
+ if (e.target.files.length > 0) {
410
+ this.processFiles(e.target.files);
411
+ }
412
+ });
413
+ }
414
+
415
+ async selectFolder() {
416
+ try {
417
+ // Try modern File System Access API first
418
+ if ('showDirectoryPicker' in window && window.location.protocol === 'https:') {
419
+ const directoryHandle = await window.showDirectoryPicker();
420
+ await this.processDirectory(directoryHandle);
421
+ } else {
422
+ // Fall back to file input
423
+ this.folderInput.click();
424
+ }
425
+ } catch (error) {
426
+ if (error.name !== 'AbortError') {
427
+ console.log('File System Access API not available, using fallback');
428
+ this.folderInput.click();
429
+ }
430
+ }
431
+ }
432
+
433
+ async processFiles(files) {
434
+ this.showLoading();
435
+
436
+ try {
437
+ const fileTree = this.buildFileTreeFromFiles(files);
438
+ this.fileData = fileTree;
439
+ this.updateStats(fileTree);
440
+ this.generateTreemaps(fileTree);
441
+ } catch (error) {
442
+ console.error('Error processing files:', error);
443
+ this.showError('Error processing file structure.');
444
+ }
445
+ }
446
+
447
+ buildFileTreeFromFiles(files) {
448
+ const root = {
449
+ name: 'Selected Folder',
450
+ path: '',
451
+ type: 'directory',
452
+ size: 0,
453
+ children: []
454
+ };
455
+
456
+ const pathMap = new Map();
457
+ pathMap.set('', root);
458
+
459
+ // Sort files by path to ensure directories are created before their contents
460
+ const sortedFiles = Array.from(files).sort((a, b) => a.webkitRelativePath.localeCompare(b.webkitRelativePath));
461
+
462
+ for (const file of sortedFiles) {
463
+ const pathParts = file.webkitRelativePath.split('/');
464
+ let currentPath = '';
465
+
466
+ // Create directory structure
467
+ for (let i = 0; i < pathParts.length - 1; i++) {
468
+ const parentPath = currentPath;
469
+ currentPath = currentPath ? `${currentPath}/${pathParts[i]}` : pathParts[i];
470
+
471
+ if (!pathMap.has(currentPath)) {
472
+ const dirNode = {
473
+ name: pathParts[i],
474
+ path: currentPath,
475
+ type: 'directory',
476
+ size: 0,
477
+ children: []
478
+ };
479
+ pathMap.set(currentPath, dirNode);
480
+ pathMap.get(parentPath).children.push(dirNode);
481
+ }
482
+ }
483
+
484
+ // Add file
485
+ const fileName = pathParts[pathParts.length - 1];
486
+ const filePath = file.webkitRelativePath;
487
+ const parentPath = pathParts.slice(0, -1).join('/');
488
+
489
+ const fileNode = {
490
+ name: fileName,
491
+ path: filePath,
492
+ type: 'file',
493
+ size: file.size,
494
+ lastModified: file.lastModified
495
+ };
496
+
497
+ pathMap.get(parentPath).children.push(fileNode);
498
+ }
499
+
500
+ // Calculate directory sizes and sort children
501
+ this.calculateDirectorySizes(root);
502
+ this.sortChildrenBySize(root);
503
+
504
+ return root;
505
+ }
506
+
507
+ calculateDirectorySizes(node) {
508
+ if (node.type === 'file') {
509
+ return node.size;
510
+ }
511
+
512
+ let totalSize = 0;
513
+ for (const child of node.children || []) {
514
+ totalSize += this.calculateDirectorySizes(child);
515
+ }
516
+ node.size = totalSize;
517
+ return totalSize;
518
+ }
519
+
520
+ sortChildrenBySize(node) {
521
+ if (node.children) {
522
+ node.children.sort((a, b) => b.size - a.size);
523
+ node.children.forEach(child => this.sortChildrenBySize(child));
524
+ }
525
+ }
526
+
527
+ generateDemoData() {
528
+ const demoData = {
529
+ name: 'Demo Project',
530
+ path: '',
531
+ type: 'directory',
532
+ size: 78650000,
533
+ children: [
534
+ {
535
+ name: 'src',
536
+ path: 'src',
537
+ type: 'directory',
538
+ size: 28500000,
539
+ children: [
540
+ { name: 'main.js', path: 'src/main.js', type: 'file', size: 15200000 },
541
+ { name: 'utils.py', path: 'src/utils.py', type: 'file', size: 8500000 },
542
+ { name: 'config.json', path: 'src/config.json', type: 'file', size: 3200000 },
543
+ { name: 'index.html', path: 'src/index.html', type: 'file', size: 1600000 }
544
+ ]
545
+ },
546
+ {
547
+ name: 'assets',
548
+ path: 'assets',
549
+ type: 'directory',
550
+ size: 25800000,
551
+ children: [
552
+ { name: 'hero-video.mp4', path: 'assets/hero-video.mp4', type: 'file', size: 12400000 },
553
+ { name: 'logo.png', path: 'assets/logo.png', type: 'file', size: 5600000 },
554
+ { name: 'background.jpg', path: 'assets/background.jpg', type: 'file', size: 4200000 },
555
+ { name: 'theme-song.mp3', path: 'assets/theme-song.mp3', type: 'file', size: 3600000 }
556
+ ]
557
+ },
558
+ {
559
+ name: 'docs',
560
+ path: 'docs',
561
+ type: 'directory',
562
+ size: 8900000,
563
+ children: [
564
+ { name: 'manual.pdf', path: 'docs/manual.pdf', type: 'file', size: 5200000 },
565
+ { name: 'README.md', path: 'docs/README.md', type: 'file', size: 1800000 },
566
+ { name: 'API.txt', path: 'docs/API.txt', type: 'file', size: 1200000 },
567
+ { name: 'CHANGELOG.md', path: 'docs/CHANGELOG.md', type: 'file', size: 700000 }
568
+ ]
569
+ },
570
+ {
571
+ name: 'data',
572
+ path: 'data',
573
+ type: 'directory',
574
+ size: 11200000,
575
+ children: [
576
+ { name: 'report.xlsx', path: 'data/report.xlsx', type: 'file', size: 6800000 },
577
+ { name: 'presentation.pptx', path: 'data/presentation.pptx', type: 'file', size: 4400000 }
578
+ ]
579
+ },
580
+ {
581
+ name: 'tests',
582
+ path: 'tests',
583
+ type: 'directory',
584
+ size: 1320000,
585
+ children: [
586
+ { name: 'main.test.js', path: 'tests/main.test.js', type: 'file', size: 680000 },
587
+ { name: 'utils.test.py', path: 'tests/utils.test.py', type: 'file', size: 440000 },
588
+ { name: 'config.test.json', path: 'tests/config.test.json', type: 'file', size: 200000 }
589
+ ]
590
+ },
591
+ { name: 'package.json', path: 'package.json', type: 'file', size: 180000 },
592
+ { name: 'webpack.config.js', path: 'webpack.config.js', type: 'file', size: 120000 },
593
+ { name: 'archive.zip', path: 'archive.zip', type: 'file', size: 2630000 }
594
+ ]
595
+ };
596
+
597
+ this.fileData = demoData;
598
+ this.updateStats(demoData);
599
+ this.generateTreemaps(demoData);
600
+ }
601
+
602
+ async processDirectory(directoryHandle) {
603
+ this.showLoading();
604
+
605
+ try {
606
+ const fileTree = await this.buildFileTree(directoryHandle);
607
+ this.fileData = fileTree;
608
+ this.updateStats(fileTree);
609
+ this.generateTreemaps(fileTree);
610
+ } catch (error) {
611
+ console.error('Error processing directory:', error);
612
+ this.showError('Error processing directory structure.');
613
+ }
614
+ }
615
+
616
+ async buildFileTree(directoryHandle, path = '') {
617
+ const node = {
618
+ name: directoryHandle.name || 'Root',
619
+ path: path,
620
+ type: 'directory',
621
+ size: 0,
622
+ children: []
623
+ };
624
+
625
+ for await (const [name, handle] of directoryHandle.entries()) {
626
+ try {
627
+ const childPath = path ? `${path}/${name}` : name;
628
+
629
+ if (handle.kind === 'file') {
630
+ const file = await handle.getFile();
631
+ node.children.push({
632
+ name: name,
633
+ path: childPath,
634
+ type: 'file',
635
+ size: file.size,
636
+ lastModified: file.lastModified
637
+ });
638
+ node.size += file.size;
639
+ } else if (handle.kind === 'directory') {
640
+ const subDir = await this.buildFileTree(handle, childPath);
641
+ node.children.push(subDir);
642
+ node.size += subDir.size;
643
+ }
644
+ } catch (error) {
645
+ console.warn(`Skipping ${name}:`, error);
646
+ }
647
+ }
648
+
649
+ // Sort children by size (descending) for better treemap layout
650
+ node.children.sort((a, b) => b.size - a.size);
651
+ return node;
652
+ }
653
+
654
+ updateStats(fileTree) {
655
+ const stats = this.calculateStats(fileTree);
656
+
657
+ document.getElementById('totalFiles').textContent = stats.files.toLocaleString();
658
+ document.getElementById('totalFolders').textContent = stats.folders.toLocaleString();
659
+ document.getElementById('totalSize').textContent = this.formatFileSize(stats.size);
660
+ document.getElementById('maxDepth').textContent = stats.depth;
661
+ document.getElementById('stats').style.display = 'flex';
662
+ document.getElementById('legend').style.display = 'flex';
663
+ }
664
+
665
+ getFileTypeCategory(filename) {
666
+ if (!filename || filename.indexOf('.') === -1) return 'other';
667
+
668
+ const extension = filename.toLowerCase().split('.').pop();
669
+
670
+ const categories = {
671
+ image: ['jpg', 'jpeg', 'png', 'webp', 'gif', 'bmp', 'ico'],
672
+ video: ['mp4', 'avi', 'mov', 'mkv', 'wmv', 'flv', 'webm'],
673
+ audio: ['mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a'],
674
+ document: ['pdf', 'md', 'txt', 'rtf', 'doc', 'docx'],
675
+ office: ['xlsx', 'xls', 'pptx', 'ppt', 'csv'],
676
+ code: ['html', 'htm', 'css', 'js', 'ts', 'py', 'java', 'cpp', 'c', 'php', 'rb', 'go', 'rs', 'swift', 'kt', 'json', 'xml', 'yml', 'yaml', 'svg']
677
+ };
678
+
679
+ for (const [category, extensions] of Object.entries(categories)) {
680
+ if (extensions.includes(extension)) {
681
+ return category;
682
+ }
683
+ }
684
+
685
+ return 'other';
686
+ }
687
+
688
+ calculateStats(node, depth = 0) {
689
+ let stats = {
690
+ files: node.type === 'file' ? 1 : 0,
691
+ folders: node.type === 'directory' ? 1 : 0,
692
+ size: node.size || 0,
693
+ depth: depth
694
+ };
695
+
696
+ if (node.children) {
697
+ for (const child of node.children) {
698
+ const childStats = this.calculateStats(child, depth + 1);
699
+ stats.files += childStats.files;
700
+ stats.folders += childStats.folders;
701
+ stats.size += childStats.size;
702
+ stats.depth = Math.max(stats.depth, childStats.depth);
703
+ }
704
+ }
705
+
706
+ return stats;
707
+ }
708
+
709
+ generateTreemaps(fileTree) {
710
+ this.visualizationArea.innerHTML = '';
711
+
712
+ // Create main treemap
713
+ this.createTreemapContainer(fileTree, 'Root Directory', 0);
714
+
715
+ // Create treemaps for major subdirectories
716
+ if (fileTree.children) {
717
+ const majorFolders = fileTree.children
718
+ .filter(child => child.type === 'directory' && child.children && child.children.length > 0)
719
+ .slice(0, 5); // Show top 5 subdirectories
720
+
721
+ majorFolders.forEach((folder, index) => {
722
+ this.createTreemapContainer(folder, folder.name, index + 1);
723
+ });
724
+ }
725
+ }
726
+
727
+ createTreemapContainer(data, title, level) {
728
+ const container = document.createElement('div');
729
+ container.className = 'treemap-container';
730
+
731
+ const header = document.createElement('div');
732
+ header.className = 'treemap-header';
733
+ header.textContent = `${title} (${this.formatFileSize(data.size)})`;
734
+
735
+ const breadcrumb = document.createElement('div');
736
+ breadcrumb.className = 'breadcrumb';
737
+ breadcrumb.textContent = data.path || '/';
738
+
739
+ const treemap = document.createElement('div');
740
+ treemap.className = 'treemap';
741
+ treemap.style.height = level === 0 ? '500px' : '400px';
742
+
743
+ container.appendChild(header);
744
+ container.appendChild(breadcrumb);
745
+ container.appendChild(treemap);
746
+
747
+ this.visualizationArea.appendChild(container);
748
+
749
+ // Generate squarified treemap layout
750
+ this.renderSquarifiedTreemap(treemap, data);
751
+ }
752
+
753
+ renderSquarifiedTreemap(container, data) {
754
+ if (!data.children || data.children.length === 0) return;
755
+
756
+ const rect = container.getBoundingClientRect();
757
+ const width = rect.width || 800;
758
+ const height = rect.height || 400;
759
+
760
+ const totalSize = data.size;
761
+ const children = data.children.filter(child => child.size > 0);
762
+
763
+ if (children.length === 0) return;
764
+
765
+ // Scale areas to fit container
766
+ const scaledChildren = children.map(child => ({
767
+ ...child,
768
+ scaledSize: (child.size / totalSize) * (width * height)
769
+ }));
770
+
771
+ const layout = this.squarify(scaledChildren, [], width, { x: 0, y: 0, width, height });
772
+ this.renderLayout(container, layout);
773
+ }
774
+
775
+ squarify(children, row, w, container) {
776
+ if (children.length === 0) {
777
+ if (row.length > 0) {
778
+ return this.layoutRow(row, container);
779
+ }
780
+ return [];
781
+ }
782
+
783
+ const c = children[0];
784
+ const newRow = [...row, c];
785
+
786
+ if (row.length === 0 || this.worst(newRow, w) <= this.worst(row, w)) {
787
+ return this.squarify(children.slice(1), newRow, w, container);
788
+ } else {
789
+ const rowLayout = this.layoutRow(row, container);
790
+ const remaining = this.shrinkContainer(container, row, w);
791
+ const restLayout = this.squarify(children, [], this.getShortSide(remaining), remaining);
792
+ return [...rowLayout, ...restLayout];
793
+ }
794
+ }
795
+
796
+ worst(row, w) {
797
+ if (row.length === 0) return Infinity;
798
+
799
+ const areas = row.map(r => r.scaledSize);
800
+ const sum = areas.reduce((a, b) => a + b, 0);
801
+ const max = Math.max(...areas);
802
+ const min = Math.min(...areas);
803
+
804
+ const term1 = (w * w * max) / (sum * sum);
805
+ const term2 = (sum * sum) / (w * w * min);
806
+
807
+ return Math.max(term1, term2);
808
+ }
809
+
810
+ layoutRow(row, container) {
811
+ if (row.length === 0) return [];
812
+
813
+ const sum = row.reduce((acc, r) => acc + r.scaledSize, 0);
814
+ const isVertical = container.width >= container.height;
815
+
816
+ let layouts = [];
817
+ let offset = 0;
818
+
819
+ for (const item of row) {
820
+ let rect;
821
+ if (isVertical) {
822
+ const height = container.height;
823
+ const width = (item.scaledSize / sum) * (sum / height);
824
+ rect = {
825
+ x: container.x + offset,
826
+ y: container.y,
827
+ width: width,
828
+ height: height,
829
+ data: item
830
+ };
831
+ offset += width;
832
+ } else {
833
+ const width = container.width;
834
+ const height = (item.scaledSize / sum) * (sum / width);
835
+ rect = {
836
+ x: container.x,
837
+ y: container.y + offset,
838
+ width: width,
839
+ height: height,
840
+ data: item
841
+ };
842
+ offset += height;
843
+ }
844
+ layouts.push(rect);
845
+ }
846
+
847
+ return layouts;
848
+ }
849
+
850
+ shrinkContainer(container, row, w) {
851
+ const sum = row.reduce((acc, r) => acc + r.scaledSize, 0);
852
+ const isVertical = container.width >= container.height;
853
+
854
+ if (isVertical) {
855
+ const usedWidth = sum / container.height;
856
+ return {
857
+ x: container.x + usedWidth,
858
+ y: container.y,
859
+ width: container.width - usedWidth,
860
+ height: container.height
861
+ };
862
+ } else {
863
+ const usedHeight = sum / container.width;
864
+ return {
865
+ x: container.x,
866
+ y: container.y + usedHeight,
867
+ width: container.width,
868
+ height: container.height - usedHeight
869
+ };
870
+ }
871
+ }
872
+
873
+ getShortSide(container) {
874
+ return Math.min(container.width, container.height);
875
+ }
876
+
877
+ renderLayout(container, layout) {
878
+ container.innerHTML = '';
879
+
880
+ layout.forEach(rect => {
881
+ const element = document.createElement('div');
882
+ let cssClass = `treemap-node ${rect.data.type}`;
883
+
884
+ // Add file type specific class for files
885
+ if (rect.data.type === 'file') {
886
+ const fileType = this.getFileTypeCategory(rect.data.name);
887
+ cssClass = `treemap-node file-${fileType}`;
888
+ }
889
+
890
+ element.className = cssClass;
891
+
892
+ element.style.left = `${rect.x}px`;
893
+ element.style.top = `${rect.y}px`;
894
+ element.style.width = `${rect.width}px`;
895
+ element.style.height = `${rect.height}px`;
896
+
897
+ // Show name only if rectangle is large enough
898
+ if (rect.width > 60 && rect.height > 20) {
899
+ element.textContent = rect.data.name;
900
+ }
901
+
902
+ this.addTooltip(element, rect.data);
903
+ container.appendChild(element);
904
+ });
905
+ }
906
+
907
+ addTooltip(element, data) {
908
+ element.addEventListener('mouseenter', () => {
909
+ const tooltipContent = this.createTooltipContent(data);
910
+ this.tooltip.innerHTML = tooltipContent;
911
+ this.tooltip.classList.add('visible');
912
+ });
913
+
914
+ element.addEventListener('mouseleave', () => {
915
+ this.tooltip.classList.remove('visible');
916
+ });
917
+ }
918
+
919
+ createTooltipContent(data) {
920
+ let content = `<strong>${data.name}</strong><br>`;
921
+ content += `Type: ${data.type}`;
922
+
923
+ if (data.type === 'file') {
924
+ const fileType = this.getFileTypeCategory(data.name);
925
+ const typeLabels = {
926
+ image: '🖼️ Image',
927
+ video: '🎥 Video',
928
+ audio: '🎵 Audio',
929
+ document: '📄 Document',
930
+ office: '📊 Office',
931
+ code: '💻 Code',
932
+ other: '📦 Other'
933
+ };
934
+ content += ` (${typeLabels[fileType]})`;
935
+ }
936
+
937
+ content += `<br>Size: ${this.formatFileSize(data.size)}<br>`;
938
+ content += `Path: ${data.path}<br>`;
939
+
940
+ if (data.type === 'file' && data.lastModified) {
941
+ content += `Modified: ${new Date(data.lastModified).toLocaleDateString()}<br>`;
942
+ }
943
+
944
+ if (data.children) {
945
+ content += `Items: ${data.children.length}`;
946
+ }
947
+
948
+ return content;
949
+ }
950
+
951
+ formatFileSize(bytes) {
952
+ if (bytes === 0) return '0 B';
953
+
954
+ const k = 1024;
955
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
956
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
957
+
958
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
959
+ }
960
+
961
+ showLoading() {
962
+ this.visualizationArea.innerHTML = `
963
+ <div class="loading">
964
+ <div class="loading-spinner"></div>
965
+ <h3>📊 Processing Directory Structure</h3>
966
+ <p>Analyzing files and building treemap visualization...</p>
967
+ </div>
968
+ `;
969
+ }
970
+
971
+ showError(message) {
972
+ this.visualizationArea.innerHTML = `
973
+ <div style="text-align: center; padding: 60px; color: #e74c3c;">
974
+ <h3>❌ Error</h3>
975
+ <p>${message}</p>
976
+ </div>
977
+ `;
978
+ }
979
+ }
980
+
981
+ // Initialize the application
982
+ const app = new SquarifiedTreemapExplorer();
983
+
984
+ // Global functions for button clicks
985
+ function selectFolder() {
986
+ app.selectFolder();
987
+ }
988
+
989
+ function generateDemoData() {
990
+ app.generateDemoData();
991
+ }
992
+ </script>
993
+ </body>
994
+ </html>