@sava-info-systems/api-maker-with-extensions 2.2.0 → 2.3.0

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.
@@ -0,0 +1,652 @@
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>Table Viewer</title>
7
+
8
+ <!-- AG Grid Community -->
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ag-grid-community@31.3.4/styles/ag-grid.css">
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ag-grid-community@31.3.4/styles/ag-theme-alpine-dark.css">
11
+ <script src="https://cdn.jsdelivr.net/npm/ag-grid-community@31.3.4/dist/ag-grid-community.min.js"></script>
12
+
13
+ <!-- SheetJS for Excel export -->
14
+ <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
15
+
16
+ <!-- jsPDF + autotable for PDF export -->
17
+ <script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
18
+ <script src="https://cdn.jsdelivr.net/npm/jspdf-autotable@3.8.2/dist/jspdf.plugin.autotable.min.js"></script>
19
+
20
+ <style>
21
+ *, *::before, *::after {
22
+ box-sizing: border-box;
23
+ margin: 0;
24
+ padding: 0;
25
+ }
26
+
27
+ :root {
28
+ --bg-primary: #282828;
29
+ --bg-secondary: #303030;
30
+ --bg-tertiary: #404040;
31
+ --hairline: #3c3c3c;
32
+ --hairline-strong: #4e4e4e;
33
+ --text-primary: #f0f0f0;
34
+ --text-secondary: #969696;
35
+ --text-muted: #959595;
36
+ --brand: #F26B3A;
37
+ --blue: #097BED;
38
+ --success: #26B47F;
39
+ --warning: #FFB400;
40
+ --danger: #ED4B48;
41
+ --editor-bg: #1e1e1e;
42
+ }
43
+
44
+ html, body {
45
+ height: 100%;
46
+ overflow: hidden;
47
+ background: var(--editor-bg);
48
+ color: var(--text-primary);
49
+ font-family: 'JetBrains Mono', 'Menlo', 'Monaco', 'Consolas', monospace;
50
+ font-size: 12px;
51
+ }
52
+
53
+ #app {
54
+ display: flex;
55
+ flex-direction: column;
56
+ height: 100vh;
57
+ position: relative;
58
+ }
59
+
60
+ /* ── Toolbar ─────────────────────────────────────────────────────────────── */
61
+ .toolbar {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 4px;
65
+ padding: 4px 36px 4px 8px;
66
+ background: var(--bg-secondary);
67
+ border-bottom: 1px solid var(--hairline);
68
+ flex-shrink: 0;
69
+ flex-wrap: nowrap;
70
+ min-height: 30px;
71
+ }
72
+
73
+ .btn {
74
+ background: var(--bg-tertiary);
75
+ border: 1px solid var(--hairline-strong);
76
+ color: var(--text-primary);
77
+ padding: 2px 7px;
78
+ border-radius: 3px;
79
+ cursor: pointer;
80
+ font-size: 11px;
81
+ font-family: inherit;
82
+ display: inline-flex;
83
+ align-items: center;
84
+ gap: 3px;
85
+ white-space: nowrap;
86
+ transition: background 0.12s, border-color 0.12s;
87
+ line-height: 18px;
88
+ height: 22px;
89
+ }
90
+
91
+ .btn:hover {
92
+ background: #505050;
93
+ border-color: #666;
94
+ }
95
+
96
+ .btn:active {
97
+ background: #606060;
98
+ }
99
+
100
+ .btn-success {
101
+ color: var(--success);
102
+ border-color: rgba(38, 180, 127, 0.4);
103
+ }
104
+
105
+ .btn-success:hover {
106
+ background: rgba(38, 180, 127, 0.12);
107
+ }
108
+
109
+ .btn-danger {
110
+ color: var(--danger);
111
+ border-color: rgba(237, 75, 72, 0.4);
112
+ }
113
+
114
+ .btn-danger:hover {
115
+ background: rgba(237, 75, 72, 0.12);
116
+ }
117
+
118
+ .btn-warning {
119
+ color: var(--warning);
120
+ border-color: rgba(255, 180, 0, 0.4);
121
+ }
122
+
123
+ .btn-blue {
124
+ color: var(--blue);
125
+ border-color: rgba(9, 123, 237, 0.4);
126
+ }
127
+
128
+ .btn-blue:hover {
129
+ background: rgba(9, 123, 237, 0.12);
130
+ }
131
+
132
+ .toolbar-sep {
133
+ width: 1px;
134
+ height: 16px;
135
+ background: var(--hairline-strong);
136
+ flex-shrink: 0;
137
+ }
138
+
139
+ .toolbar-spacer {
140
+ flex: 1;
141
+ }
142
+
143
+ .filter-input {
144
+ background: var(--bg-tertiary);
145
+ border: 1px solid var(--hairline-strong);
146
+ color: var(--text-primary);
147
+ padding: 2px 7px;
148
+ border-radius: 3px;
149
+ font-size: 11px;
150
+ font-family: inherit;
151
+ width: 160px;
152
+ outline: none;
153
+ height: 22px;
154
+ transition: border-color 0.12s;
155
+ }
156
+
157
+ .filter-input:focus {
158
+ border-color: var(--blue);
159
+ }
160
+
161
+ .filter-input::placeholder {
162
+ color: var(--text-muted);
163
+ }
164
+
165
+ /* ── Grid container ──────────────────────────────────────────────────────── */
166
+ .grid-wrap {
167
+ flex: 1;
168
+ position: relative;
169
+ overflow: hidden;
170
+ min-height: 0;
171
+ }
172
+
173
+ #myGrid {
174
+ width: 100%;
175
+ height: 100%;
176
+ }
177
+
178
+ /* ── AG Grid overrides (dark theme) ──────────────────────────────────────── */
179
+ .ag-theme-alpine-dark {
180
+ --ag-background-color: #1e1e1e;
181
+ --ag-foreground-color: #e8e8e8;
182
+ --ag-border-color: #3a3a3a;
183
+ --ag-secondary-border-color: #3a3a3a;
184
+ --ag-header-background-color: #252525;
185
+ --ag-header-foreground-color: #b0b0b0;
186
+ --ag-odd-row-background-color: #222222;
187
+ --ag-even-row-background-color: #1e1e1e;
188
+ --ag-row-hover-color: #2a2a2a;
189
+ --ag-selected-row-background-color: rgba(9, 123, 237, 0.18);
190
+ --ag-range-selection-background-color: rgba(9, 123, 237, 0.12);
191
+ --ag-range-selection-border-color: #097BED;
192
+ --ag-header-cell-hover-background-color: #2e2e2e;
193
+ --ag-cell-horizontal-padding: 5px;
194
+ --ag-font-size: 12px;
195
+ --ag-font-family: 'JetBrains Mono', 'Menlo', 'Monaco', monospace;
196
+ --ag-row-height: 26px;
197
+ --ag-header-height: 28px;
198
+ --ag-filter-tool-panel-header-height: 26px;
199
+ --ag-input-focus-border-color: #097BED;
200
+ --ag-checkbox-checked-color: #097BED;
201
+ --ag-checkbox-unchecked-color: #686868;
202
+ --ag-active-color: #097BED;
203
+ --ag-balham-active-color: #097BED;
204
+ }
205
+
206
+ /* Tighter header padding */
207
+ .ag-theme-alpine-dark .ag-header-cell {
208
+ padding-left: 5px;
209
+ padding-right: 5px;
210
+ font-size: 11px;
211
+ color: #b0b0b0;
212
+ font-weight: 600;
213
+ letter-spacing: 0.02em;
214
+ }
215
+
216
+ .ag-theme-alpine-dark .ag-cell {
217
+ line-height: 26px;
218
+ padding-left: 5px;
219
+ padding-right: 5px;
220
+ }
221
+
222
+ /* Modified cell highlight */
223
+ .ag-theme-alpine-dark .cell-modified {
224
+ background-color: rgba(255, 180, 0, 0.08) !important;
225
+ border-left: 2px solid var(--warning) !important;
226
+ }
227
+
228
+ /* Server-side field error highlight */
229
+ .ag-theme-alpine-dark .cell-error {
230
+ background-color: rgba(237, 75, 72, 0.1) !important;
231
+ border-left: 2px solid var(--danger) !important;
232
+ }
233
+
234
+ /* Cell editing */
235
+ .ag-theme-alpine-dark .ag-cell.ag-cell-inline-editing {
236
+ background-color: #1a3555 !important;
237
+ border: 1px solid var(--blue) !important;
238
+ box-shadow: 0 0 0 1px rgba(9, 123, 237, 0.3);
239
+ }
240
+
241
+ /* Sortable header arrows */
242
+ .ag-theme-alpine-dark .ag-sort-indicator-icon {
243
+ color: var(--blue);
244
+ }
245
+
246
+ /* Filter active indicator */
247
+ .ag-theme-alpine-dark .ag-header-icon.ag-filter-icon {
248
+ color: var(--warning);
249
+ }
250
+
251
+ /* Scrollbars */
252
+ .ag-theme-alpine-dark ::-webkit-scrollbar {
253
+ width: 6px;
254
+ height: 6px;
255
+ }
256
+
257
+ .ag-theme-alpine-dark ::-webkit-scrollbar-track {
258
+ background: #1a1a1a;
259
+ }
260
+
261
+ .ag-theme-alpine-dark ::-webkit-scrollbar-thumb {
262
+ background: #444;
263
+ border-radius: 3px;
264
+ }
265
+
266
+ .ag-theme-alpine-dark ::-webkit-scrollbar-thumb:hover {
267
+ background: #666;
268
+ }
269
+
270
+ /* ── Status bar ──────────────────────────────────────────────────────────── */
271
+ .status-bar {
272
+ padding: 2px 8px;
273
+ background: var(--bg-secondary);
274
+ border-top: 1px solid var(--hairline);
275
+ font-size: 10px;
276
+ color: var(--text-secondary);
277
+ flex-shrink: 0;
278
+ display: flex;
279
+ align-items: center;
280
+ gap: 10px;
281
+ min-height: 20px;
282
+ }
283
+
284
+ .status-dot {
285
+ width: 6px;
286
+ height: 6px;
287
+ border-radius: 50%;
288
+ background: var(--text-muted);
289
+ flex-shrink: 0;
290
+ transition: background 0.2s;
291
+ }
292
+
293
+ .status-dot.loading {
294
+ background: var(--warning);
295
+ animation: blink 0.9s ease-in-out infinite;
296
+ }
297
+
298
+ .status-dot.success {
299
+ background: var(--success);
300
+ }
301
+
302
+ .status-dot.error {
303
+ background: var(--danger);
304
+ }
305
+
306
+ @keyframes blink {
307
+ 0%, 100% {
308
+ opacity: 1;
309
+ }
310
+ 50% {
311
+ opacity: 0.3;
312
+ }
313
+ }
314
+
315
+ .status-sep {
316
+ color: var(--text-muted);
317
+ }
318
+
319
+ /* ── Overlays ─────────────────────────────────────────────────────────────── */
320
+ .loading-overlay {
321
+ position: absolute;
322
+ inset: 0;
323
+ background: rgba(30, 30, 30, 0.8);
324
+ display: flex;
325
+ align-items: center;
326
+ justify-content: center;
327
+ z-index: 100;
328
+ }
329
+
330
+ .spinner {
331
+ width: 26px;
332
+ height: 26px;
333
+ border: 2.5px solid rgba(255, 255, 255, 0.1);
334
+ border-top-color: var(--blue);
335
+ border-radius: 50%;
336
+ animation: spin 0.65s linear infinite;
337
+ }
338
+
339
+ .confirm-overlay {
340
+ position: fixed;
341
+ inset: 0;
342
+ background: rgba(0, 0, 0, 0.55);
343
+ display: none;
344
+ align-items: center;
345
+ justify-content: center;
346
+ z-index: 200;
347
+ padding: 16px;
348
+ }
349
+
350
+ .confirm-dialog {
351
+ width: min(440px, 100%);
352
+ background: var(--bg-secondary);
353
+ border: 1px solid var(--hairline-strong);
354
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
355
+ border-radius: 12px;
356
+ padding: 18px;
357
+ color: var(--text-primary);
358
+ }
359
+
360
+ .confirm-dialog h3 {
361
+ margin-bottom: 10px;
362
+ font-size: 14px;
363
+ color: var(--text-primary);
364
+ }
365
+
366
+ .confirm-dialog p {
367
+ margin-bottom: 20px;
368
+ font-size: 12px;
369
+ color: var(--text-muted);
370
+ line-height: 1.5;
371
+ white-space: pre-wrap;
372
+ }
373
+
374
+ .confirm-actions {
375
+ display: flex;
376
+ justify-content: flex-end;
377
+ gap: 8px;
378
+ flex-wrap: wrap;
379
+ }
380
+
381
+ .confirm-actions .btn {
382
+ min-width: 88px;
383
+ justify-content: center;
384
+ }
385
+
386
+ @keyframes spin {
387
+ to {
388
+ transform: rotate(360deg);
389
+ }
390
+ }
391
+
392
+ .empty-state {
393
+ position: absolute;
394
+ inset: 0;
395
+ display: flex;
396
+ flex-direction: column;
397
+ align-items: center;
398
+ justify-content: center;
399
+ gap: 6px;
400
+ color: var(--text-muted);
401
+ font-size: 12px;
402
+ pointer-events: none;
403
+ }
404
+
405
+ .empty-state svg {
406
+ opacity: 0.3;
407
+ margin-bottom: 4px;
408
+ }
409
+
410
+ /* ── Pending badge ────────────────────────────────────────────────────────── */
411
+ .pending-pill {
412
+ display: inline-flex;
413
+ align-items: center;
414
+ justify-content: center;
415
+ background: var(--warning);
416
+ color: #111;
417
+ border-radius: 9px;
418
+ font-size: 10px;
419
+ font-weight: 700;
420
+ padding: 0 5px;
421
+ min-width: 16px;
422
+ height: 16px;
423
+ line-height: 1;
424
+ }
425
+
426
+ /* ── Export dropdown ─────────────────────────────────────────────────────── */
427
+ .export-wrap {
428
+ position: relative;
429
+ display: inline-flex;
430
+ }
431
+
432
+ .export-menu {
433
+ display: none;
434
+ flex-direction: column;
435
+ position: absolute;
436
+ top: calc(100% + 4px);
437
+ right: 0;
438
+ background: var(--bg-secondary);
439
+ border: 1px solid var(--hairline-strong);
440
+ border-radius: 4px;
441
+ box-shadow: 0 6px 18px rgba(0,0,0,0.35);
442
+ z-index: 500;
443
+ min-width: 110px;
444
+ overflow: hidden;
445
+ }
446
+
447
+ .export-menu.open {
448
+ display: flex;
449
+ }
450
+
451
+ .export-menu-item {
452
+ display: flex;
453
+ align-items: center;
454
+ gap: 6px;
455
+ padding: 5px 10px;
456
+ font-size: 11px;
457
+ font-family: inherit;
458
+ color: var(--text-primary);
459
+ background: none;
460
+ border: none;
461
+ cursor: pointer;
462
+ text-align: left;
463
+ white-space: nowrap;
464
+ transition: background 0.1s;
465
+ }
466
+
467
+ .export-menu-item:hover {
468
+ background: var(--bg-tertiary);
469
+ }
470
+
471
+ .export-menu-item.item-csv { color: var(--text-primary); }
472
+ .export-menu-item.item-xlsx { color: var(--success); }
473
+ .export-menu-item.item-pdf { color: var(--danger); }
474
+
475
+ /* ── Tooltip for long text ───────────────────────────────────────────────── */
476
+ .ag-tooltip {
477
+ background: #2a2a2a !important;
478
+ border: 1px solid var(--hairline-strong) !important;
479
+ color: var(--text-primary) !important;
480
+ font-size: 11px !important;
481
+ border-radius: 3px !important;
482
+ padding: 4px 7px !important;
483
+ max-width: 480px !important;
484
+ word-break: break-all !important;
485
+ }
486
+ </style>
487
+ </head>
488
+ <body>
489
+ <div id="app">
490
+
491
+ <!-- Toolbar -->
492
+ <div class="toolbar" id="toolbar">
493
+ <div class="toolbar-sep" id="editSep" style="display:none"></div>
494
+
495
+ <button class="btn btn-success" id="btnSaveEdits" style="display:none" title="Save pending edits via API">
496
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
497
+ <polyline points="20 6 9 17 4 12"/>
498
+ </svg>
499
+ Save
500
+ <span class="pending-pill" id="pendingPill">0</span>
501
+ </button>
502
+
503
+ <button class="btn btn-danger" id="btnDiscardEdits" style="display:none" title="Discard all pending edits">
504
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
505
+ <line x1="18" y1="6" x2="6" y2="18"/>
506
+ <line x1="6" y1="6" x2="18" y2="18"/>
507
+ </svg>
508
+ Discard
509
+ </button>
510
+
511
+ <button class="btn btn-danger" id="btnDeleteRows" style="display:none; margin-left:4px" title="Delete selected rows via API">
512
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
513
+ <path d="M6 7h12" />
514
+ <path d="M9 7V5h6v2" />
515
+ <path d="M10 10v8" />
516
+ <path d="M14 10v8" />
517
+ <rect x="5" y="7" width="14" height="12" rx="2" />
518
+ </svg>
519
+ </button>
520
+
521
+ <button class="btn" id="btnAddColumn" style="display:none; margin-left:4px" title="Add a new column">
522
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
523
+ <line x1="12" y1="5" x2="12" y2="19"/>
524
+ <line x1="5" y1="12" x2="19" y2="12"/>
525
+ </svg>
526
+ Col
527
+ </button>
528
+
529
+ <div class="toolbar-sep"></div>
530
+
531
+ <input class="filter-input" id="quickFilter" placeholder="Quick filter rows…" type="text" spellcheck="false">
532
+
533
+ <div class="toolbar-spacer"></div>
534
+
535
+ <div class="toolbar-sep"></div>
536
+
537
+ <button class="btn" id="btnAutoSize" title="Auto-size all columns to fit content">
538
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
539
+ <line x1="5" y1="12" x2="19" y2="12"/>
540
+ <polyline points="12 5 19 12 12 19"/>
541
+ </svg>
542
+ Fit Cols
543
+ </button>
544
+
545
+ <button class="btn" id="btnFitGrid" title="Size columns to fit grid width">
546
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
547
+ <line x1="8" y1="3" x2="8" y2="21"/>
548
+ <line x1="16" y1="3" x2="16" y2="21"/>
549
+ <line x1="3" y1="8" x2="21" y2="8"/>
550
+ <line x1="3" y1="16" x2="21" y2="16"/>
551
+ </svg>
552
+ Fit Grid
553
+ </button>
554
+
555
+ <div class="export-wrap" id="exportWrap">
556
+ <button class="btn btn-blue" id="btnExport" title="Export data">
557
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
558
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
559
+ <polyline points="14 2 14 8 20 8"/>
560
+ <line x1="12" y1="12" x2="12" y2="18"/>
561
+ <polyline points="9 15 12 18 15 15"/>
562
+ </svg>
563
+ Export
564
+ <svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
565
+ <polyline points="6 9 12 15 18 9"/>
566
+ </svg>
567
+ </button>
568
+ <div class="export-menu" id="exportMenu">
569
+ <button class="export-menu-item item-csv" id="btnExportCsv">
570
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
571
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
572
+ <polyline points="14 2 14 8 20 8"/>
573
+ </svg>
574
+ CSV
575
+ </button>
576
+ <button class="export-menu-item item-xlsx" id="btnExportExcel">
577
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
578
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
579
+ <polyline points="14 2 14 8 20 8"/>
580
+ </svg>
581
+ Excel (.xlsx)
582
+ </button>
583
+ <button class="export-menu-item item-pdf" id="btnExportPdf">
584
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
585
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
586
+ <polyline points="14 2 14 8 20 8"/>
587
+ </svg>
588
+ PDF
589
+ </button>
590
+ </div>
591
+ </div>
592
+ </div>
593
+
594
+ <!-- Grid -->
595
+ <div class="grid-wrap">
596
+ <div id="myGrid" class="ag-theme-alpine-dark"></div>
597
+
598
+ <div class="empty-state" id="emptyState">
599
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2">
600
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
601
+ <line x1="3" y1="9" x2="21" y2="9"/>
602
+ <line x1="3" y1="15" x2="21" y2="15"/>
603
+ <line x1="9" y1="3" x2="9" y2="21"/>
604
+ <line x1="15" y1="3" x2="15" y2="21"/>
605
+ </svg>
606
+ <span>Waiting for data…</span>
607
+ <small>Trigger an API call or click Refresh</small>
608
+ </div>
609
+
610
+ <div class="loading-overlay" id="loadingOverlay" style="display:none">
611
+ <div class="spinner"></div>
612
+ </div>
613
+
614
+ <div class="confirm-overlay" id="confirmOverlay" role="dialog" aria-modal="true" aria-labelledby="confirmTitle" aria-describedby="confirmMessage">
615
+ <div class="confirm-dialog">
616
+ <h3 id="confirmTitle">Delete selected rows?</h3>
617
+ <p id="confirmMessage">This action cannot be undone.</p>
618
+ <div class="confirm-actions">
619
+ <button class="btn" id="confirmCancelBtn" type="button">Cancel</button>
620
+ <button class="btn btn-danger" id="confirmOkBtn" type="button">Delete</button>
621
+ </div>
622
+ </div>
623
+ </div>
624
+
625
+ <div class="confirm-overlay" id="addColOverlay" role="dialog" aria-modal="true">
626
+ <div class="confirm-dialog">
627
+ <h3>Add Column</h3>
628
+ <p style="margin-bottom:10px">Enter a name for the new column:</p>
629
+ <input type="text" id="addColInput" class="filter-input" style="width:100%;margin-bottom:16px" placeholder="column_name" spellcheck="false">
630
+ <div class="confirm-actions">
631
+ <button class="btn" id="addColCancelBtn" type="button">Cancel</button>
632
+ <button class="btn btn-success" id="addColOkBtn" type="button">Add</button>
633
+ </div>
634
+ </div>
635
+ </div>
636
+ </div>
637
+
638
+ <!-- Status bar -->
639
+ <div class="status-bar">
640
+ <span class="status-dot" id="statusDot"></span>
641
+ <span id="statusText">Ready</span>
642
+ <span class="status-sep" id="statusSep" style="display:none">·</span>
643
+ <span id="rowCount" style="display:none"></span>
644
+ <span class="status-sep" id="statusSep2" style="display:none">·</span>
645
+ <span id="selCount" style="display:none"></span>
646
+ </div>
647
+
648
+ </div>
649
+
650
+ <script src="index.js"></script>
651
+ </body>
652
+ </html>