🎨 Redesign from AnyCoder

#1
by Stable-X - opened
Files changed (1) hide show
  1. index.html +339 -425
index.html CHANGED
@@ -3,284 +3,237 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Parquet Viewer - Dataset Explorer</title>
7
 
8
  <!-- Google Fonts -->
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
12
 
13
  <!-- Phosphor Icons -->
14
  <script src="https://unpkg.com/@phosphor-icons/web"></script>
15
 
16
  <style>
 
17
  :root {
18
- /* Hugging Face Style Palette */
19
- --hf-yellow: #ffd21e;
20
- --hf-black: #0b0f19;
21
- --hf-gray-100: #f9fafb;
22
- --hf-gray-200: #f3f4f6;
23
- --hf-gray-300: #e5e7eb;
24
- --hf-gray-400: #9ca3af;
25
- --hf-gray-500: #6b7280;
26
- --hf-white: #ffffff;
27
- --hf-border: #e5e7eb;
28
 
29
- --font-family: 'Inter', system-ui, -apple-system, sans-serif;
30
- --radius-md: 6px;
31
- --radius-sm: 4px;
32
- --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
33
- --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
 
 
 
 
 
 
 
 
 
 
34
 
35
  --header-height: 64px;
36
  --sidebar-width: 280px;
37
  }
38
 
39
- * {
40
- box-sizing: border-box;
41
- margin: 0;
42
- padding: 0;
43
- }
44
 
45
  body {
46
- font-family: var(--font-family);
47
- background-color: var(--hf-white);
48
- color: var(--hf-black);
49
  height: 100vh;
50
  display: flex;
51
  flex-direction: column;
52
  overflow: hidden;
 
53
  }
54
 
55
- a { text-decoration: none; color: inherit; }
56
- ul { list-style: none; }
57
- button { cursor: pointer; font-family: inherit; }
 
 
58
 
59
  /* --- Header --- */
60
  .header {
61
  height: var(--header-height);
62
- border-bottom: 1px solid var(--hf-border);
 
63
  display: flex;
64
  align-items: center;
65
  justify-content: space-between;
66
- padding: 0 24px;
67
- background: var(--hf-white);
68
  flex-shrink: 0;
 
69
  }
70
 
71
- .header-left {
72
- display: flex;
73
- align-items: center;
74
- gap: 16px;
75
- }
76
 
77
- .brand {
78
- font-weight: 700;
79
- font-size: 1.1rem;
80
- display: flex;
81
- align-items: center;
82
- gap: 8px;
 
 
 
83
  }
 
84
 
85
- .brand i { color: var(--hf-yellow); font-size: 1.5rem; }
86
-
87
- .header-actions {
88
  display: flex;
89
  align-items: center;
90
- gap: 12px;
 
 
 
91
  }
 
 
 
92
 
93
  .btn {
94
  padding: 8px 16px;
95
  border-radius: var(--radius-md);
96
- font-size: 0.9rem;
97
  font-weight: 500;
98
- border: 1px solid var(--hf-border);
99
- background: var(--hf-white);
100
- color: var(--hf-black);
101
- transition: all 0.2s;
102
  display: inline-flex;
103
  align-items: center;
104
  gap: 8px;
 
105
  }
106
-
107
- .btn:hover {
108
- background-color: var(--hf-gray-100);
109
- border-color: var(--hf-gray-300);
110
- }
111
-
112
- .btn-primary {
113
- background-color: var(--hf-black);
114
- color: var(--hf-white);
115
- border-color: var(--hf-black);
116
- }
117
-
118
- .btn-primary:hover {
119
- background-color: #333;
120
- border-color: #333;
121
- }
122
-
123
- .anycoder-link {
124
- font-size: 0.85rem;
125
- color: var(--hf-gray-500);
126
- font-weight: 500;
127
- }
128
- .anycoder-link:hover { color: var(--hf-yellow); text-decoration: underline; }
129
 
130
  /* --- Layout --- */
131
- .layout-container {
132
  display: flex;
133
  flex: 1;
134
  overflow: hidden;
 
135
  }
136
 
137
  /* --- Sidebar --- */
138
  .sidebar {
139
  width: var(--sidebar-width);
140
- border-right: 1px solid var(--hf-border);
141
- background: var(--hf-white);
142
  display: flex;
143
  flex-direction: column;
144
- overflow-y: auto;
145
- flex-shrink: 0;
146
- transition: transform 0.3s ease;
147
- z-index: 20;
148
- }
149
-
150
- .sidebar-section {
151
  padding: 20px;
152
- border-bottom: 1px solid var(--hf-gray-100);
 
 
153
  }
154
 
 
155
  .sidebar-title {
156
- font-size: 0.75rem;
157
  text-transform: uppercase;
158
  letter-spacing: 0.05em;
159
- color: var(--hf-gray-500);
160
- font-weight: 700;
161
  margin-bottom: 12px;
162
- }
163
-
164
- .dataset-card {
165
- background: var(--hf-gray-100);
166
- border: 1px solid var(--hf-border);
167
- border-radius: var(--radius-md);
168
- padding: 12px;
169
- margin-bottom: 16px;
170
- }
171
-
172
- .dataset-title {
173
  font-weight: 600;
174
- font-size: 0.95rem;
175
- margin-bottom: 4px;
176
- }
177
-
178
- .dataset-meta {
179
- font-size: 0.8rem;
180
- color: var(--hf-gray-500);
181
- display: flex;
182
- gap: 8px;
183
- flex-wrap: wrap;
184
- }
185
-
186
- .tag {
187
- background: #fff;
188
- border: 1px solid var(--hf-border);
189
- padding: 2px 6px;
190
- border-radius: 4px;
191
- font-size: 0.7rem;
192
- }
193
-
194
- .file-list {
195
- display: flex;
196
- flex-direction: column;
197
- gap: 4px;
198
  }
199
 
 
 
200
  .file-item {
201
  display: flex;
202
  align-items: center;
203
- gap: 8px;
204
  padding: 8px 12px;
205
  border-radius: var(--radius-md);
206
- cursor: pointer;
207
  font-size: 0.9rem;
208
- color: var(--hf-gray-500);
209
- }
210
-
211
- .file-item.active {
212
- background-color: #fff9c4; /* Light yellow */
213
- color: var(--hf-black);
214
- font-weight: 500;
215
  }
 
 
 
216
 
217
- .file-item:hover:not(.active) {
218
- background-color: var(--hf-gray-100);
 
 
 
219
  }
220
-
221
- .file-icon { color: var(--hf-gray-400); }
 
 
222
 
223
  /* --- Main Content --- */
224
  .main-content {
225
  flex: 1;
226
  display: flex;
227
  flex-direction: column;
 
228
  overflow: hidden;
229
- background: var(--hf-white);
230
  position: relative;
231
  }
232
 
 
233
  .toolbar {
234
- padding: 16px 24px;
235
- border-bottom: 1px solid var(--hf-border);
236
  display: flex;
237
  align-items: center;
238
  justify-content: space-between;
239
- flex-wrap: wrap;
240
- gap: 16px;
241
  }
242
 
243
- .toolbar-left {
244
- display: flex;
245
- align-items: center;
246
- gap: 16px;
247
- }
248
-
249
- .config-selector {
250
- display: flex;
251
- align-items: center;
252
- gap: 8px;
253
  font-size: 0.9rem;
254
- font-weight: 500;
255
  }
256
 
257
- .search-box {
258
  position: relative;
 
259
  }
260
-
261
- .search-box i {
262
  position: absolute;
263
- left: 10px;
264
  top: 50%;
265
  transform: translateY(-50%);
266
- color: var(--hf-gray-400);
267
  }
268
-
269
  .search-input {
 
 
 
 
270
  padding: 8px 12px 8px 36px;
271
- border: 1px solid var(--hf-border);
272
  border-radius: var(--radius-md);
273
  font-size: 0.9rem;
274
- width: 250px;
275
- outline: none;
276
  transition: border-color 0.2s;
277
  }
 
278
 
279
- .search-input:focus {
280
- border-color: var(--hf-black);
281
- }
282
-
283
- /* --- Table Area --- */
284
  .table-wrapper {
285
  flex: 1;
286
  overflow: auto;
@@ -294,130 +247,85 @@
294
  min-width: 800px;
295
  }
296
 
297
- thead {
 
 
 
 
 
298
  position: sticky;
299
  top: 0;
300
- background: var(--hf-gray-100);
301
  z-index: 10;
302
- }
303
-
304
- th {
305
- text-align: left;
306
- padding: 12px 16px;
307
- font-weight: 600;
308
- color: var(--hf-gray-500);
309
- border-bottom: 1px solid var(--hf-border);
310
- white-space: nowrap;
311
  user-select: none;
312
  }
313
-
314
- th:hover {
315
- color: var(--hf-black);
316
- background: var(--hf-gray-200);
317
- }
318
 
319
  td {
320
- padding: 12px 16px;
321
- border-bottom: 1px solid var(--hf-border);
322
- color: var(--hf-black);
323
  vertical-align: middle;
324
  max-width: 300px;
325
  overflow: hidden;
326
  text-overflow: ellipsis;
327
  white-space: nowrap;
328
  }
 
329
 
330
- tr:hover td {
331
- background-color: #fafafa;
332
- }
333
-
334
- .cell-content {
335
- display: flex;
336
- align-items: center;
337
- gap: 8px;
338
- }
339
-
340
- .cell-preview {
341
- cursor: pointer;
342
- color: var(--hf-gray-500);
343
- font-family: monospace;
344
- }
345
-
346
- .cell-preview:hover {
347
- color: var(--hf-black);
348
- text-decoration: underline;
349
- }
350
-
351
- .img-thumbnail {
352
  width: 40px;
353
  height: 40px;
 
354
  object-fit: cover;
355
- border-radius: var(--radius-sm);
356
- border: 1px solid var(--hf-border);
357
- background: #eee;
 
 
 
 
358
  }
 
359
 
360
- /* --- Pagination --- */
361
  .pagination {
362
- padding: 12px 24px;
363
- border-top: 1px solid var(--hf-border);
364
  display: flex;
365
  align-items: center;
366
  justify-content: space-between;
367
- background: var(--hf-white);
368
- }
369
-
370
- .page-info {
371
  font-size: 0.85rem;
372
- color: var(--hf-gray-500);
373
  }
374
 
375
- .page-controls {
 
376
  display: flex;
377
- gap: 8px;
378
- }
379
-
380
- .page-btn {
381
- padding: 6px 10px;
382
- border: 1px solid var(--hf-border);
383
- background: var(--hf-white);
384
- border-radius: var(--radius-sm);
385
- font-size: 0.85rem;
386
- cursor: pointer;
387
- }
388
- .page-btn:disabled {
389
- opacity: 0.5;
390
- cursor: not-allowed;
391
- }
392
- .page-btn:not(:disabled):hover {
393
- border-color: var(--hf-black);
394
- }
395
-
396
- /* --- Modal/Toast --- */
397
- .toast {
398
- position: fixed;
399
- bottom: 24px;
400
- right: 24px;
401
- background: var(--hf-black);
402
- color: white;
403
- padding: 12px 24px;
404
- border-radius: var(--radius-md);
405
- box-shadow: var(--shadow-md);
406
- transform: translateY(100px);
407
- opacity: 0;
408
- transition: all 0.3s ease;
409
- z-index: 100;
410
  }
411
- .toast.show { transform: translateY(0); opacity: 1; }
412
 
 
413
  .modal-overlay {
414
  position: fixed;
415
- top: 0; left: 0; right: 0; bottom: 0;
416
- background: rgba(0,0,0,0.5);
 
 
417
  display: flex;
418
  align-items: center;
419
  justify-content: center;
420
- z-index: 50;
421
  opacity: 0;
422
  pointer-events: none;
423
  transition: opacity 0.2s;
@@ -425,53 +333,66 @@
425
  .modal-overlay.open { opacity: 1; pointer-events: auto; }
426
 
427
  .modal {
428
- background: white;
429
  width: 90%;
430
- max-width: 600px;
431
- border-radius: var(--radius-md);
432
- box-shadow: var(--shadow-md);
 
 
433
  display: flex;
434
  flex-direction: column;
435
- max-height: 80vh;
 
436
  }
 
437
 
438
  .modal-header {
439
  padding: 16px 24px;
440
- border-bottom: 1px solid var(--hf-border);
441
  display: flex;
442
  justify-content: space-between;
443
  align-items: center;
444
  font-weight: 600;
 
445
  }
446
-
447
  .modal-body {
448
  padding: 24px;
449
  overflow: auto;
450
- font-family: monospace;
 
 
 
451
  white-space: pre-wrap;
452
- background: var(--hf-gray-100);
453
- color: #333;
454
  }
455
-
456
  .close-modal {
457
- background: none;
458
- border: none;
459
- font-size: 1.2rem;
460
- cursor: pointer;
461
  }
 
462
 
463
- /* Empty State */
464
- .empty-state {
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  display: flex;
466
- flex-direction: column;
467
  align-items: center;
468
- justify-content: center;
469
- height: 100%;
470
- color: var(--hf-gray-500);
471
- text-align: center;
472
- padding: 40px;
473
  }
474
- .empty-state i { font-size: 3rem; margin-bottom: 16px; color: var(--hf-gray-300); }
 
 
475
 
476
  /* Responsive */
477
  @media (max-width: 768px) {
@@ -479,15 +400,15 @@
479
  position: absolute;
480
  height: 100%;
481
  transform: translateX(-100%);
482
- box-shadow: var(--shadow-md);
483
  }
484
  .sidebar.open { transform: translateX(0); }
485
- .header-left { gap: 8px; }
486
- .header { padding: 0 16px; }
487
- .brand span { display: none; }
488
- .brand i { display: block; }
489
- .toolbar { padding: 12px 16px; }
490
- .search-input { width: 150px; }
491
  }
492
  </style>
493
  </head>
@@ -496,97 +417,84 @@
496
  <!-- Header -->
497
  <header class="header">
498
  <div class="header-left">
499
- <button class="btn" id="menu-toggle" style="padding: 8px; border: none;">
500
- <i class="ph ph-list" style="font-size: 1.2rem;"></i>
501
  </button>
502
- <a href="#" class="brand">
503
- <i class="ph-fill ph-parachute"></i> <!-- Using parachute as icon for generic dataset -->
504
- <span>ParquetViewer</span>
505
  </a>
506
  </div>
507
  <div class="header-actions">
508
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
509
- Built with anycoder
510
- </a>
511
  <button class="btn" id="upload-btn">
512
- <i class="ph ph-upload-simple"></i> Upload File
 
513
  </button>
514
  <input type="file" id="file-input" hidden accept=".json,.csv,.parquet">
 
 
 
 
515
  </div>
516
  </header>
517
 
518
- <div class="layout-container">
519
  <!-- Sidebar -->
520
  <aside class="sidebar" id="sidebar">
521
  <div class="sidebar-section">
522
- <div class="dataset-card">
523
- <div class="dataset-title" id="dataset-name">Sample Dataset</div>
524
- <div class="dataset-meta">
525
- <span class="tag">Default</span>
526
- <span class="tag">10k Rows</span>
 
 
 
 
 
 
 
 
527
  </div>
528
  </div>
529
  </div>
530
 
531
  <div class="sidebar-section">
532
- <div class="sidebar-title">This Dataset</div>
533
  <ul class="file-list">
534
  <li class="file-item active">
535
- <i class="ph ph-file-text file-icon"></i>
536
- <span>train.parquet</span>
537
- </li>
538
- <li class="file-item">
539
- <i class="ph ph-file-code file-icon"></i>
540
- <span>test.parquet</span>
541
  </li>
542
  <li class="file-item">
543
- <i class="ph ph-file-json file-icon"></i>
544
- <span>dataset_dict.json</span>
545
  </li>
546
  <li class="file-item">
547
- <i class="ph ph-read-cv-logo file-icon"></i>
548
- <span>README.md</span>
549
  </li>
550
  </ul>
551
  </div>
552
-
553
- <div class="sidebar-section">
554
- <div class="sidebar-title">Dataset Info</div>
555
- <div style="font-size: 0.85rem; color: var(--hf-gray-500); line-height: 1.6;">
556
- <p>Features: <strong>Image, Text, Label</strong></p>
557
- <p>Splits: <strong>train, validation</strong></p>
558
- <p>Size: <strong>45.2 MB</strong></p>
559
- </div>
560
- </div>
561
  </aside>
562
 
563
  <!-- Main Content -->
564
  <main class="main-content">
565
  <!-- Toolbar -->
566
  <div class="toolbar">
567
- <div class="toolbar-left">
568
- <div class="config-selector">
569
- <i class="ph ph-sliders-horizontal"></i>
570
- <select id="config-select" style="border:none; background:transparent; font-weight:600; cursor:pointer;">
571
- <option>default</option>
572
- </select>
573
- </div>
574
- <div class="config-selector">
575
- <i class="ph ph-git-branch"></i>
576
- <select id="split-select" style="border:none; background:transparent; font-weight:600; cursor:pointer;">
577
- <option>train</option>
578
- <option>test</option>
579
- </select>
580
- </div>
581
- </div>
582
-
583
- <div class="toolbar-right">
584
- <div class="search-box">
585
  <i class="ph ph-magnifying-glass"></i>
586
- <input type="text" class="search-input" placeholder="Filter by value..." id="search-input">
587
  </div>
 
 
588
  <button class="btn" id="columns-btn">
589
- <i class="ph ph-table"></i> Columns
590
  </button>
591
  </div>
592
  </div>
@@ -605,26 +513,26 @@
605
  </table>
606
  </div>
607
 
608
- <!-- Empty State (Hidden by default) -->
609
  <div class="empty-state" id="empty-state" style="display: none;">
610
- <i class="ph ph-database"></i>
611
  <h3>No Data Loaded</h3>
612
- <p>Upload a JSON or CSV file to visualize data.</p>
613
  </div>
614
 
615
  <!-- Pagination -->
616
  <div class="pagination">
617
- <div class="page-info" id="page-info">Showing 1-100 of 10,000 rows</div>
618
- <div class="page-controls">
619
- <button class="page-btn" id="prev-btn"><i class="ph ph-caret-left"></i> Prev</button>
620
- <button class="page-btn" id="next-btn">Next <i class="ph ph-caret-right"></i></button>
621
  </div>
622
  </div>
623
  </main>
624
  </div>
625
 
626
- <!-- Cell Viewer Modal -->
627
- <div class="modal-overlay" id="cell-modal">
628
  <div class="modal">
629
  <div class="modal-header">
630
  <span id="modal-title">Cell Content</span>
@@ -634,8 +542,11 @@
634
  </div>
635
  </div>
636
 
637
- <!-- Toast Notification -->
638
- <div class="toast" id="toast">Message here</div>
 
 
 
639
 
640
  <script>
641
  // --- State Management ---
@@ -644,45 +555,37 @@
644
  filteredData: [],
645
  columns: [],
646
  currentPage: 1,
647
- rowsPerPage: 100,
648
  sortCol: null,
649
- sortAsc: true
 
650
  };
651
 
652
- // --- Mock Data Generator (Simulating Parquet structure) ---
653
- function generateMockData(count = 1000) {
654
  const data = [];
655
- const labels = ['cat', 'dog', 'bird', 'car', 'person'];
656
  for (let i = 0; i < count; i++) {
657
- const id = i;
658
- const width = 100 + Math.floor(Math.random() * 50);
659
- const height = 100 + Math.floor(Math.random() * 50);
660
  data.push({
661
- id: id,
662
- image: {
663
- bytes: null, // Placeholder
664
- path: `image_${id}.jpg`
665
- },
666
- text: `This is a sample text entry for row ${id}. It contains some descriptive information.`,
667
- label: labels[Math.floor(Math.random() * labels.length)],
668
- embedding: Array(5).fill(0).map(() => (Math.random()).toFixed(4)),
669
- metadata: {
670
- width: width,
671
- height: height,
672
- timestamp: new Date().toISOString()
673
- }
674
  });
675
  }
676
  return data;
677
  }
678
 
679
  // --- Core Application Logic ---
680
-
681
  const App = {
682
  init() {
683
  this.cacheDOM();
684
  this.bindEvents();
685
- this.loadMockData(); // Load initial data
686
  },
687
 
688
  cacheDOM() {
@@ -695,13 +598,18 @@
695
  searchInput: document.getElementById('search-input'),
696
  fileInput: document.getElementById('file-input'),
697
  uploadBtn: document.getElementById('upload-btn'),
698
- modal: document.getElementById('cell-modal'),
699
  modalContent: document.getElementById('modal-content'),
700
  closeModal: document.getElementById('close-modal'),
701
  menuToggle: document.getElementById('menu-toggle'),
702
  sidebar: document.getElementById('sidebar'),
703
  toast: document.getElementById('toast'),
704
- datasetName: document.getElementById('dataset-name')
 
 
 
 
 
705
  };
706
  },
707
 
@@ -719,51 +627,51 @@
719
 
720
  // Modal
721
  this.dom.closeModal.addEventListener('click', () => this.closeModal());
722
- this.dom.modal.addEventListener('click', (e) => {
723
- if (e.target === this.dom.modal) this.closeModal();
724
  });
725
 
726
- // Sidebar Toggle (Mobile)
727
  this.dom.menuToggle.addEventListener('click', () => {
728
  this.dom.sidebar.classList.toggle('open');
729
  });
730
  },
731
 
732
  loadMockData() {
733
- const mockData = generateMockData(2500);
734
  this.processData(mockData);
735
- this.showToast("Sample dataset loaded");
736
  },
737
 
738
  processData(rawData) {
739
  if (!rawData || rawData.length === 0) {
740
- document.getElementById('table-container').style.display = 'none';
741
  document.querySelector('.pagination').style.display = 'none';
742
- document.getElementById('empty-state').style.display = 'flex';
743
  return;
744
  }
745
 
746
- document.getElementById('table-container').style.display = 'block';
747
  document.querySelector('.pagination').style.display = 'flex';
748
- document.getElementById('empty-state').style.display = 'none';
749
 
750
  state.data = rawData;
751
  state.filteredData = rawData;
752
-
753
- // Extract columns from first row keys
754
  state.columns = Object.keys(rawData[0]);
 
755
 
756
  state.currentPage = 1;
757
  this.renderHeaders();
758
  this.renderTable();
759
  this.updatePagination();
 
760
  },
761
 
762
  renderHeaders() {
763
  this.dom.tableHeader.innerHTML = '';
764
  state.columns.forEach(col => {
765
  const th = document.createElement('th');
766
- th.textContent = col;
767
  th.addEventListener('click', () => this.handleSort(col));
768
  this.dom.tableHeader.appendChild(th);
769
  });
@@ -784,25 +692,19 @@
784
  const val = row[col];
785
 
786
  if (val === null || val === undefined) {
787
- td.innerHTML = '<span style="color:#ccc">null</span>';
 
788
  } else if (typeof val === 'object') {
789
- // Check if it's an image object (mock structure)
790
- if (col === 'image' && val.path) {
791
- const seed = val.path; // Use path as seed for consistency
792
- td.innerHTML = `<img src="https://picsum.photos/seed/${seed}/100/100" class="img-thumbnail" alt="img">`;
793
  } else {
794
- // Render object summary
795
- const str = JSON.stringify(val);
796
- const preview = str.length > 50 ? str.substring(0, 50) + '...' : str;
797
- td.innerHTML = `<span class="cell-preview" title="Click to view JSON">{...}</span> <span style="color:#666">${preview}</span>`;
798
- td.querySelector('.cell-preview').addEventListener('click', () => this.openModal(val));
799
  }
800
  } else if (Array.isArray(val)) {
801
- td.innerHTML = `<span class="cell-preview">Array[${val.length}]</span>`;
802
- td.querySelector('.cell-preview').addEventListener('click', () => this.openModal(val));
803
  } else {
804
  td.textContent = val;
805
- td.title = val; // Tooltip for truncated text
806
  }
807
 
808
  tr.appendChild(td);
@@ -817,7 +719,7 @@
817
  const start = total === 0 ? 0 : (state.currentPage - 1) * state.rowsPerPage + 1;
818
  const end = Math.min(start + state.rowsPerPage - 1, total);
819
 
820
- this.dom.pageInfo.textContent = `Showing ${start}-${end} of ${total.toLocaleString()} rows`;
821
 
822
  this.dom.prevBtn.disabled = state.currentPage === 1;
823
  this.dom.nextBtn.disabled = end >= total;
@@ -831,8 +733,7 @@
831
  state.currentPage = newPage;
832
  this.renderTable();
833
  this.updatePagination();
834
- // Scroll table to top
835
- document.querySelector('.table-wrapper').scrollTop = 0;
836
  }
837
  },
838
 
@@ -878,6 +779,7 @@
878
  if (!file) return;
879
 
880
  this.dom.datasetName.textContent = file.name;
 
881
  this.showToast(`Loading ${file.name}...`);
882
 
883
  const reader = new FileReader();
@@ -885,61 +787,74 @@
885
  try {
886
  if (file.name.endsWith('.json')) {
887
  const json = JSON.parse(event.target.result);
888
- // Handle if json is array or object with 'data' key
889
  const data = Array.isArray(json) ? json : (json.data || json);
890
  this.processData(data);
891
- this.showToast("JSON loaded successfully");
892
  } else if (file.name.endsWith('.csv')) {
893
  const csv = this.parseCSV(event.target.result);
894
  this.processData(csv);
895
- this.showToast("CSV loaded successfully");
896
  } else {
897
- // Mocking Parquet load since we can't do real WASM easily in single file without CORS
898
- this.showToast("Parquet parsing simulated (Loading JSON fallback)", 3000);
899
- // Just load mock data to show UI works
900
- setTimeout(() => this.loadMockData(), 500);
901
  }
902
  } catch (err) {
903
  console.error(err);
904
- this.showToast("Error parsing file");
905
  }
906
  };
907
  reader.readAsText(file);
908
  },
909
 
910
  parseCSV(text) {
911
- // Simple CSV parser
912
  const lines = text.trim().split('\n');
913
- const headers = lines[0].split(',').map(h => h.trim());
914
  const data = [];
915
 
916
  for (let i = 1; i < lines.length; i++) {
917
  const row = lines[i].split(',');
918
  const obj = {};
919
  headers.forEach((h, index) => {
920
- obj[h] = row[index] ? row[index].trim() : null;
 
 
 
921
  });
922
  data.push(obj);
923
  }
924
  return data;
925
  },
926
 
 
 
 
 
927
  openModal(content) {
928
- const formatted = JSON.stringify(content, null, 2);
 
 
 
 
 
 
 
929
  this.dom.modalContent.textContent = formatted;
930
- this.dom.modal.classList.add('open');
931
  },
932
 
933
  closeModal() {
934
- this.dom.modal.classList.remove('open');
935
  },
936
 
937
- showToast(msg, duration = 3000) {
938
- this.dom.toast.textContent = msg;
939
- this.dom.toast.classList.add('show');
 
 
 
 
940
  setTimeout(() => {
941
  this.dom.toast.classList.remove('show');
942
- }, duration);
943
  }
944
  };
945
 
@@ -947,7 +862,6 @@
947
  document.addEventListener('DOMContentLoaded', () => {
948
  App.init();
949
  });
950
-
951
  </script>
952
  </body>
953
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Anycoder - Dataset Explorer</title>
7
 
8
  <!-- Google Fonts -->
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
12
 
13
  <!-- Phosphor Icons -->
14
  <script src="https://unpkg.com/@phosphor-icons/web"></script>
15
 
16
  <style>
17
+ /* --- CSS Variables & Reset --- */
18
  :root {
19
+ /* Modern Palette */
20
+ --bg-body: #0f1115;
21
+ --bg-sidebar: #161b22;
22
+ --bg-card: #1c2128;
23
+ --bg-hover: #2a303c;
 
 
 
 
 
24
 
25
+ --text-main: #f0f6fc;
26
+ --text-muted: #8b949e;
27
+
28
+ --accent-primary: #58a6ff; /* GitHub Blue */
29
+ --accent-secondary: #3fb950; /* Success Green */
30
+ --accent-warn: #d29922;
31
+ --accent-danger: #f85149;
32
+
33
+ --border-subtle: #30363d;
34
+ --border-focus: #58a6ff;
35
+
36
+ --radius-md: 8px;
37
+ --radius-lg: 12px;
38
+
39
+ --shadow-card: 0 4px 12px rgba(0, 0, 0, 0.4);
40
 
41
  --header-height: 64px;
42
  --sidebar-width: 280px;
43
  }
44
 
45
+ * { box-sizing: border-box; margin: 0; padding: 0; outline: none; }
 
 
 
 
46
 
47
  body {
48
+ font-family: 'Inter', sans-serif;
49
+ background-color: var(--bg-body);
50
+ color: var(--text-main);
51
  height: 100vh;
52
  display: flex;
53
  flex-direction: column;
54
  overflow: hidden;
55
+ -webkit-font-smoothing: antialiased;
56
  }
57
 
58
+ /* --- Scrollbar --- */
59
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
60
+ ::-webkit-scrollbar-track { background: var(--bg-body); }
61
+ ::-webkit-scrollbar-thumb { background: var(--border-subtle); border-radius: 4px; }
62
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
63
 
64
  /* --- Header --- */
65
  .header {
66
  height: var(--header-height);
67
+ background: var(--bg-sidebar);
68
+ border-bottom: 1px solid var(--border-subtle);
69
  display: flex;
70
  align-items: center;
71
  justify-content: space-between;
72
+ padding: 0 20px;
 
73
  flex-shrink: 0;
74
+ z-index: 50;
75
  }
76
 
77
+ .header-left { display: flex; align-items: center; gap: 16px; }
 
 
 
 
78
 
79
+ .btn-icon {
80
+ background: transparent;
81
+ border: none;
82
+ color: var(--text-muted);
83
+ font-size: 1.25rem;
84
+ cursor: pointer;
85
+ padding: 6px;
86
+ border-radius: var(--radius-md);
87
+ transition: all 0.2s;
88
  }
89
+ .btn-icon:hover { background: var(--bg-hover); color: var(--text-main); }
90
 
91
+ .logo {
 
 
92
  display: flex;
93
  align-items: center;
94
+ gap: 10px;
95
+ font-weight: 700;
96
+ font-size: 1.1rem;
97
+ color: var(--text-main);
98
  }
99
+ .logo i { color: var(--accent-primary); font-size: 1.5rem; }
100
+
101
+ .header-actions { display: flex; align-items: center; gap: 12px; }
102
 
103
  .btn {
104
  padding: 8px 16px;
105
  border-radius: var(--radius-md);
106
+ font-size: 0.85rem;
107
  font-weight: 500;
108
+ cursor: pointer;
109
+ border: 1px solid var(--border-subtle);
110
+ background: var(--bg-card);
111
+ color: var(--text-main);
112
  display: inline-flex;
113
  align-items: center;
114
  gap: 8px;
115
+ transition: all 0.2s ease;
116
  }
117
+ .btn:hover { background: var(--bg-hover); border-color: var(--text-muted); }
118
+ .btn-primary { background: var(--text-main); color: var(--bg-body); border-color: var(--text-main); }
119
+ .btn-primary:hover { opacity: 0.9; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
  /* --- Layout --- */
122
+ .app-container {
123
  display: flex;
124
  flex: 1;
125
  overflow: hidden;
126
+ position: relative;
127
  }
128
 
129
  /* --- Sidebar --- */
130
  .sidebar {
131
  width: var(--sidebar-width);
132
+ background: var(--bg-sidebar);
133
+ border-right: 1px solid var(--border-subtle);
134
  display: flex;
135
  flex-direction: column;
 
 
 
 
 
 
 
136
  padding: 20px;
137
+ overflow-y: auto;
138
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
139
+ z-index: 40;
140
  }
141
 
142
+ .sidebar-section { margin-bottom: 24px; }
143
  .sidebar-title {
144
+ font-size: 0.7rem;
145
  text-transform: uppercase;
146
  letter-spacing: 0.05em;
147
+ color: var(--text-muted);
 
148
  margin-bottom: 12px;
 
 
 
 
 
 
 
 
 
 
 
149
  font-weight: 600;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  }
151
 
152
+ .file-list { display: flex; flex-direction: column; gap: 4px; }
153
+
154
  .file-item {
155
  display: flex;
156
  align-items: center;
157
+ gap: 10px;
158
  padding: 8px 12px;
159
  border-radius: var(--radius-md);
160
+ color: var(--text-muted);
161
  font-size: 0.9rem;
162
+ cursor: pointer;
163
+ transition: all 0.2s;
 
 
 
 
 
164
  }
165
+ .file-item:hover { background: var(--bg-hover); color: var(--text-main); }
166
+ .file-item.active { background: rgba(88, 166, 255, 0.1); color: var(--accent-primary); font-weight: 500; }
167
+ .file-item i { font-size: 1.1rem; }
168
 
169
+ .info-card {
170
+ background: var(--bg-card);
171
+ padding: 16px;
172
+ border-radius: var(--radius-md);
173
+ border: 1px solid var(--border-subtle);
174
  }
175
+ .info-row { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 0.85rem; }
176
+ .info-row:last-child { margin-bottom: 0; }
177
+ .info-label { color: var(--text-muted); }
178
+ .info-val { font-weight: 500; color: var(--text-main); }
179
 
180
  /* --- Main Content --- */
181
  .main-content {
182
  flex: 1;
183
  display: flex;
184
  flex-direction: column;
185
+ background: var(--bg-body);
186
  overflow: hidden;
 
187
  position: relative;
188
  }
189
 
190
+ /* Toolbar */
191
  .toolbar {
192
+ height: 60px;
193
+ border-bottom: 1px solid var(--border-subtle);
194
  display: flex;
195
  align-items: center;
196
  justify-content: space-between;
197
+ padding: 0 20px;
198
+ background: var(--bg-body);
199
  }
200
 
201
+ .toolbar-group { display: flex; align-items: center; gap: 16px; }
202
+
203
+ .select-control {
204
+ background: var(--bg-card);
205
+ border: 1px solid var(--border-subtle);
206
+ color: var(--text-main);
207
+ padding: 6px 12px;
208
+ border-radius: var(--radius-md);
 
 
209
  font-size: 0.9rem;
210
+ cursor: pointer;
211
  }
212
 
213
+ .search-wrapper {
214
  position: relative;
215
+ width: 300px;
216
  }
217
+ .search-wrapper i {
 
218
  position: absolute;
219
+ left: 12px;
220
  top: 50%;
221
  transform: translateY(-50%);
222
+ color: var(--text-muted);
223
  }
 
224
  .search-input {
225
+ width: 100%;
226
+ background: var(--bg-card);
227
+ border: 1px solid var(--border-subtle);
228
+ color: var(--text-main);
229
  padding: 8px 12px 8px 36px;
 
230
  border-radius: var(--radius-md);
231
  font-size: 0.9rem;
 
 
232
  transition: border-color 0.2s;
233
  }
234
+ .search-input:focus { border-color: var(--accent-primary); }
235
 
236
+ /* Table Area */
 
 
 
 
237
  .table-wrapper {
238
  flex: 1;
239
  overflow: auto;
 
247
  min-width: 800px;
248
  }
249
 
250
+ th {
251
+ background: var(--bg-sidebar);
252
+ color: var(--text-muted);
253
+ font-weight: 600;
254
+ text-align: left;
255
+ padding: 12px 20px;
256
  position: sticky;
257
  top: 0;
 
258
  z-index: 10;
259
+ border-bottom: 1px solid var(--border-subtle);
260
+ cursor: pointer;
 
 
 
 
 
 
 
261
  user-select: none;
262
  }
263
+ th:hover { color: var(--text-main); background: #1a1e23; }
264
+ th i { margin-left: 6px; font-size: 0.8rem; opacity: 0.5; }
 
 
 
265
 
266
  td {
267
+ padding: 12px 20px;
268
+ border-bottom: 1px solid var(--border-subtle);
269
+ color: var(--text-main);
270
  vertical-align: middle;
271
  max-width: 300px;
272
  overflow: hidden;
273
  text-overflow: ellipsis;
274
  white-space: nowrap;
275
  }
276
+ tr:hover td { background: rgba(255, 255, 255, 0.02); }
277
 
278
+ /* Cell Types */
279
+ .cell-img {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  width: 40px;
281
  height: 40px;
282
+ border-radius: 6px;
283
  object-fit: cover;
284
+ border: 1px solid var(--border-subtle);
285
+ background: #000;
286
+ }
287
+ .cell-link {
288
+ color: var(--accent-primary);
289
+ text-decoration: none;
290
+ cursor: pointer;
291
  }
292
+ .cell-link:hover { text-decoration: underline; }
293
 
294
+ /* Pagination */
295
  .pagination {
296
+ height: 50px;
297
+ border-top: 1px solid var(--border-subtle);
298
  display: flex;
299
  align-items: center;
300
  justify-content: space-between;
301
+ padding: 0 20px;
302
+ background: var(--bg-sidebar);
 
 
303
  font-size: 0.85rem;
304
+ color: var(--text-muted);
305
  }
306
 
307
+ /* Empty State */
308
+ .empty-state {
309
  display: flex;
310
+ flex-direction: column;
311
+ align-items: center;
312
+ justify-content: center;
313
+ height: 100%;
314
+ color: var(--text-muted);
315
+ text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  }
317
+ .empty-state i { font-size: 3rem; margin-bottom: 16px; color: var(--border-subtle); opacity: 0.5; }
318
 
319
+ /* Modal */
320
  .modal-overlay {
321
  position: fixed;
322
+ top: 0; left: 0; width: 100%; height: 100%;
323
+ background: rgba(0,0,0,0.7);
324
+ backdrop-filter: blur(4px);
325
+ z-index: 100;
326
  display: flex;
327
  align-items: center;
328
  justify-content: center;
 
329
  opacity: 0;
330
  pointer-events: none;
331
  transition: opacity 0.2s;
 
333
  .modal-overlay.open { opacity: 1; pointer-events: auto; }
334
 
335
  .modal {
336
+ background: var(--bg-card);
337
  width: 90%;
338
+ max-width: 700px;
339
+ max-height: 85vh;
340
+ border-radius: var(--radius-lg);
341
+ border: 1px solid var(--border-subtle);
342
+ box-shadow: var(--shadow-card);
343
  display: flex;
344
  flex-direction: column;
345
+ transform: scale(0.95);
346
+ transition: transform 0.2s;
347
  }
348
+ .modal-overlay.open .modal { transform: scale(1); }
349
 
350
  .modal-header {
351
  padding: 16px 24px;
352
+ border-bottom: 1px solid var(--border-subtle);
353
  display: flex;
354
  justify-content: space-between;
355
  align-items: center;
356
  font-weight: 600;
357
+ color: var(--text-main);
358
  }
 
359
  .modal-body {
360
  padding: 24px;
361
  overflow: auto;
362
+ font-family: 'Menlo', 'Monaco', monospace;
363
+ font-size: 0.9rem;
364
+ color: var(--text-main);
365
+ background: #0d1117;
366
  white-space: pre-wrap;
 
 
367
  }
 
368
  .close-modal {
369
+ background: none; border: none; color: var(--text-muted);
370
+ font-size: 1.5rem; cursor: pointer;
 
 
371
  }
372
+ .close-modal:hover { color: var(--text-main); }
373
 
374
+ /* Toast */
375
+ .toast {
376
+ position: fixed;
377
+ bottom: 24px;
378
+ right: 24px;
379
+ background: var(--bg-sidebar);
380
+ color: var(--text-main);
381
+ padding: 12px 20px;
382
+ border-radius: var(--radius-md);
383
+ border: 1px solid var(--border-subtle);
384
+ box-shadow: var(--shadow-card);
385
+ transform: translateY(100px);
386
+ opacity: 0;
387
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
388
+ z-index: 200;
389
  display: flex;
 
390
  align-items: center;
391
+ gap: 10px;
 
 
 
 
392
  }
393
+ .toast.show { transform: translateY(0); opacity: 1; }
394
+ .toast-success { border-left: 3px solid var(--accent-secondary); }
395
+ .toast-error { border-left: 3px solid var(--accent-danger); }
396
 
397
  /* Responsive */
398
  @media (max-width: 768px) {
 
400
  position: absolute;
401
  height: 100%;
402
  transform: translateX(-100%);
403
+ box-shadow: 10px 0 20px rgba(0,0,0,0.5);
404
  }
405
  .sidebar.open { transform: translateX(0); }
406
+
407
+ .toolbar { padding: 0 12px; }
408
+ .search-wrapper { width: 140px; }
409
+ .header-actions span { display: none; } /* Hide text in header */
410
+ .logo span { display: none; }
411
+ .logo i { display: block; }
412
  }
413
  </style>
414
  </head>
 
417
  <!-- Header -->
418
  <header class="header">
419
  <div class="header-left">
420
+ <button class="btn-icon" id="menu-toggle" aria-label="Toggle Menu">
421
+ <i class="ph ph-list"></i>
422
  </button>
423
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="logo">
424
+ <i class="ph-fill ph-cube-transparent"></i>
425
+ <span>Anycoder</span>
426
  </a>
427
  </div>
428
  <div class="header-actions">
 
 
 
429
  <button class="btn" id="upload-btn">
430
+ <i class="ph ph-upload-simple"></i>
431
+ <span>Load Data</span>
432
  </button>
433
  <input type="file" id="file-input" hidden accept=".json,.csv,.parquet">
434
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="btn btn-primary" style="padding: 8px 14px;">
435
+ <i class="ph-fill ph-code"></i>
436
+ <span>Spaces</span>
437
+ </a>
438
  </div>
439
  </header>
440
 
441
+ <div class="app-container">
442
  <!-- Sidebar -->
443
  <aside class="sidebar" id="sidebar">
444
  <div class="sidebar-section">
445
+ <div class="sidebar-title">Dataset Overview</div>
446
+ <div class="info-card">
447
+ <div class="info-row">
448
+ <span class="info-label">Name</span>
449
+ <span class="info-val" id="dataset-name">Sample Dataset</span>
450
+ </div>
451
+ <div class="info-row">
452
+ <span class="info-label">Size</span>
453
+ <span class="info-val" id="dataset-size">--</span>
454
+ </div>
455
+ <div class="info-row">
456
+ <span class="info-label">Columns</span>
457
+ <span class="info-val" id="dataset-cols">--</span>
458
  </div>
459
  </div>
460
  </div>
461
 
462
  <div class="sidebar-section">
463
+ <div class="sidebar-title">Files</div>
464
  <ul class="file-list">
465
  <li class="file-item active">
466
+ <i class="ph ph-file-json"></i>
467
+ <span>train_data.json</span>
 
 
 
 
468
  </li>
469
  <li class="file-item">
470
+ <i class="ph ph-file-code"></i>
471
+ <span>config.json</span>
472
  </li>
473
  <li class="file-item">
474
+ <i class="ph ph-database"></i>
475
+ <span>metadata.parquet</span>
476
  </li>
477
  </ul>
478
  </div>
 
 
 
 
 
 
 
 
 
479
  </aside>
480
 
481
  <!-- Main Content -->
482
  <main class="main-content">
483
  <!-- Toolbar -->
484
  <div class="toolbar">
485
+ <div class="toolbar-group">
486
+ <select class="select-control" id="split-select">
487
+ <option value="train">Train Split</option>
488
+ <option value="test">Test Split</option>
489
+ </select>
490
+ <div class="search-wrapper">
 
 
 
 
 
 
 
 
 
 
 
 
491
  <i class="ph ph-magnifying-glass"></i>
492
+ <input type="text" class="search-input" placeholder="Search..." id="search-input">
493
  </div>
494
+ </div>
495
+ <div class="toolbar-group">
496
  <button class="btn" id="columns-btn">
497
+ <i class="ph ph-table"></i> <span>Columns</span>
498
  </button>
499
  </div>
500
  </div>
 
513
  </table>
514
  </div>
515
 
516
+ <!-- Empty State -->
517
  <div class="empty-state" id="empty-state" style="display: none;">
518
+ <i class="ph ph-files"></i>
519
  <h3>No Data Loaded</h3>
520
+ <p>Click "Load Data" to upload a JSON or CSV file.</p>
521
  </div>
522
 
523
  <!-- Pagination -->
524
  <div class="pagination">
525
+ <span id="page-info">Showing 0 - 0 of 0</span>
526
+ <div style="display: flex; gap: 8px;">
527
+ <button class="btn" id="prev-btn" style="padding: 4px 12px; font-size: 0.8rem;"><i class="ph ph-caret-left"></i> Prev</button>
528
+ <button class="btn" id="next-btn" style="padding: 4px 12px; font-size: 0.8rem;">Next <i class="ph ph-caret-right"></i></button>
529
  </div>
530
  </div>
531
  </main>
532
  </div>
533
 
534
+ <!-- Modal -->
535
+ <div class="modal-overlay" id="modal-overlay">
536
  <div class="modal">
537
  <div class="modal-header">
538
  <span id="modal-title">Cell Content</span>
 
542
  </div>
543
  </div>
544
 
545
+ <!-- Toast -->
546
+ <div class="toast" id="toast">
547
+ <i class="ph-fill ph-check-circle" id="toast-icon"></i>
548
+ <span id="toast-msg">Operation successful</span>
549
+ </div>
550
 
551
  <script>
552
  // --- State Management ---
 
555
  filteredData: [],
556
  columns: [],
557
  currentPage: 1,
558
+ rowsPerPage: 50,
559
  sortCol: null,
560
+ sortAsc: true,
561
+ totalRows: 0
562
  };
563
 
564
+ // --- Mock Data Generator ---
565
+ function generateMockData(count = 500) {
566
  const data = [];
567
+ const items = ['Laptop', 'Coffee', 'Phone', 'Keyboard', 'Mouse'];
568
  for (let i = 0; i < count; i++) {
 
 
 
569
  data.push({
570
+ id: i,
571
+ item: items[Math.floor(Math.random() * items.length)],
572
+ price: (Math.random() * 1000).toFixed(2),
573
+ in_stock: Math.random() > 0.2,
574
+ rating: (Math.random() * 5).toFixed(1),
575
+ category: ['Electronics', 'Office', 'Home'][Math.floor(Math.random() * 3)],
576
+ image_url: `https://picsum.photos/seed/${i}/100/100`,
577
+ description: `High-quality ${items[Math.floor(Math.random() * items.length)]} for professional use.`
 
 
 
 
 
578
  });
579
  }
580
  return data;
581
  }
582
 
583
  // --- Core Application Logic ---
 
584
  const App = {
585
  init() {
586
  this.cacheDOM();
587
  this.bindEvents();
588
+ this.loadMockData();
589
  },
590
 
591
  cacheDOM() {
 
598
  searchInput: document.getElementById('search-input'),
599
  fileInput: document.getElementById('file-input'),
600
  uploadBtn: document.getElementById('upload-btn'),
601
+ modalOverlay: document.getElementById('modal-overlay'),
602
  modalContent: document.getElementById('modal-content'),
603
  closeModal: document.getElementById('close-modal'),
604
  menuToggle: document.getElementById('menu-toggle'),
605
  sidebar: document.getElementById('sidebar'),
606
  toast: document.getElementById('toast'),
607
+ toastMsg: document.getElementById('toast-msg'),
608
+ datasetName: document.getElementById('dataset-name'),
609
+ datasetSize: document.getElementById('dataset-size'),
610
+ datasetCols: document.getElementById('dataset-cols'),
611
+ emptyState: document.getElementById('empty-state'),
612
+ tableContainer: document.getElementById('table-container')
613
  };
614
  },
615
 
 
627
 
628
  // Modal
629
  this.dom.closeModal.addEventListener('click', () => this.closeModal());
630
+ this.dom.modalOverlay.addEventListener('click', (e) => {
631
+ if (e.target === this.dom.modalOverlay) this.closeModal();
632
  });
633
 
634
+ // Sidebar Toggle
635
  this.dom.menuToggle.addEventListener('click', () => {
636
  this.dom.sidebar.classList.toggle('open');
637
  });
638
  },
639
 
640
  loadMockData() {
641
+ const mockData = generateMockData(2000);
642
  this.processData(mockData);
643
+ this.showToast("Sample dataset loaded", "success");
644
  },
645
 
646
  processData(rawData) {
647
  if (!rawData || rawData.length === 0) {
648
+ this.dom.tableContainer.style.display = 'none';
649
  document.querySelector('.pagination').style.display = 'none';
650
+ this.dom.emptyState.style.display = 'flex';
651
  return;
652
  }
653
 
654
+ this.dom.tableContainer.style.display = 'block';
655
  document.querySelector('.pagination').style.display = 'flex';
656
+ this.dom.emptyState.style.display = 'none';
657
 
658
  state.data = rawData;
659
  state.filteredData = rawData;
 
 
660
  state.columns = Object.keys(rawData[0]);
661
+ state.totalRows = rawData.length;
662
 
663
  state.currentPage = 1;
664
  this.renderHeaders();
665
  this.renderTable();
666
  this.updatePagination();
667
+ this.updateSidebarInfo();
668
  },
669
 
670
  renderHeaders() {
671
  this.dom.tableHeader.innerHTML = '';
672
  state.columns.forEach(col => {
673
  const th = document.createElement('th');
674
+ th.innerHTML = `${col} <i class="ph ph-caret-up-down"></i>`;
675
  th.addEventListener('click', () => this.handleSort(col));
676
  this.dom.tableHeader.appendChild(th);
677
  });
 
692
  const val = row[col];
693
 
694
  if (val === null || val === undefined) {
695
+ td.textContent = 'null';
696
+ td.style.color = 'var(--text-muted)';
697
  } else if (typeof val === 'object') {
698
+ // Image check
699
+ if (col === 'image_url' || col === 'image') {
700
+ td.innerHTML = `<img src="${val}" class="cell-img" loading="lazy" alt="preview">`;
 
701
  } else {
702
+ td.innerHTML = `<span class="cell-link" onclick="App.openModal(${JSON.stringify(val)})">View JSON</span>`;
 
 
 
 
703
  }
704
  } else if (Array.isArray(val)) {
705
+ td.innerHTML = `<span class="cell-link" onclick="App.openModal(${JSON.stringify(val)})">Array[${val.length}]</span>`;
 
706
  } else {
707
  td.textContent = val;
 
708
  }
709
 
710
  tr.appendChild(td);
 
719
  const start = total === 0 ? 0 : (state.currentPage - 1) * state.rowsPerPage + 1;
720
  const end = Math.min(start + state.rowsPerPage - 1, total);
721
 
722
+ this.dom.pageInfo.textContent = `Showing ${start}-${end} of ${total.toLocaleString()}`;
723
 
724
  this.dom.prevBtn.disabled = state.currentPage === 1;
725
  this.dom.nextBtn.disabled = end >= total;
 
733
  state.currentPage = newPage;
734
  this.renderTable();
735
  this.updatePagination();
736
+ this.dom.tableContainer.scrollTop = 0;
 
737
  }
738
  },
739
 
 
779
  if (!file) return;
780
 
781
  this.dom.datasetName.textContent = file.name;
782
+ this.dom.datasetSize.textContent = (file.size / 1024).toFixed(1) + ' KB';
783
  this.showToast(`Loading ${file.name}...`);
784
 
785
  const reader = new FileReader();
 
787
  try {
788
  if (file.name.endsWith('.json')) {
789
  const json = JSON.parse(event.target.result);
 
790
  const data = Array.isArray(json) ? json : (json.data || json);
791
  this.processData(data);
792
+ this.showToast("JSON loaded successfully", "success");
793
  } else if (file.name.endsWith('.csv')) {
794
  const csv = this.parseCSV(event.target.result);
795
  this.processData(csv);
796
+ this.showToast("CSV loaded successfully", "success");
797
  } else {
798
+ this.showToast("Unsupported file type", "error");
 
 
 
799
  }
800
  } catch (err) {
801
  console.error(err);
802
+ this.showToast("Error parsing file", "error");
803
  }
804
  };
805
  reader.readAsText(file);
806
  },
807
 
808
  parseCSV(text) {
 
809
  const lines = text.trim().split('\n');
810
+ const headers = lines[0].split(',').map(h => h.trim().replace(/"/g, ''));
811
  const data = [];
812
 
813
  for (let i = 1; i < lines.length; i++) {
814
  const row = lines[i].split(',');
815
  const obj = {};
816
  headers.forEach((h, index) => {
817
+ let val = row[index] ? row[index].trim().replace(/"/g, '') : null;
818
+ // Simple type conversion
819
+ if (!isNaN(val) && val !== '') val = Number(val);
820
+ obj[h] = val;
821
  });
822
  data.push(obj);
823
  }
824
  return data;
825
  },
826
 
827
+ updateSidebarInfo() {
828
+ this.dom.datasetCols.textContent = state.columns.length;
829
+ },
830
+
831
  openModal(content) {
832
+ // Handle circular JSON or non-string objects safely
833
+ let formatted;
834
+ try {
835
+ formatted = JSON.stringify(content, null, 2);
836
+ } catch (e) {
837
+ formatted = content.toString();
838
+ }
839
+
840
  this.dom.modalContent.textContent = formatted;
841
+ this.dom.modalOverlay.classList.add('open');
842
  },
843
 
844
  closeModal() {
845
+ this.dom.modalOverlay.classList.remove('open');
846
  },
847
 
848
+ showToast(msg, type = 'success') {
849
+ this.dom.toastMsg.textContent = msg;
850
+ this.dom.toast.className = `toast toast-${type} show`;
851
+
852
+ const icon = document.getElementById('toast-icon');
853
+ icon.className = type === 'success' ? 'ph-fill ph-check-circle' : 'ph-fill ph-warning-circle';
854
+
855
  setTimeout(() => {
856
  this.dom.toast.classList.remove('show');
857
+ }, 3000);
858
  }
859
  };
860
 
 
862
  document.addEventListener('DOMContentLoaded', () => {
863
  App.init();
864
  });
 
865
  </script>
866
  </body>
867
  </html>