eruda-storage 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1313 @@
1
+ let storagePatched = false;
2
+ const STORAGE_CHANGE_EVENT = "eruda-storage-change";
3
+ const dispatchStorageChange = () => {
4
+ window.dispatchEvent(new Event(STORAGE_CHANGE_EVENT));
5
+ };
6
+ const patchStorageMutations = () => {
7
+ if (storagePatched)
8
+ return;
9
+ storagePatched = true;
10
+ const setItem = Storage.prototype.setItem;
11
+ Storage.prototype.setItem = function patchedSetItem(key, value) {
12
+ const result = setItem.call(this, key, value);
13
+ dispatchStorageChange();
14
+ return result;
15
+ };
16
+ const removeItem = Storage.prototype.removeItem;
17
+ Storage.prototype.removeItem = function patchedRemoveItem(key) {
18
+ const result = removeItem.call(this, key);
19
+ dispatchStorageChange();
20
+ return result;
21
+ };
22
+ const clear = Storage.prototype.clear;
23
+ Storage.prototype.clear = function patchedClear() {
24
+ const result = clear.call(this);
25
+ dispatchStorageChange();
26
+ return result;
27
+ };
28
+ };
29
+ const STYLE = `
30
+ .eruda-storage-tool {
31
+ position: relative;
32
+ height: 100%;
33
+ max-height: 100%;
34
+ color: #d6d6d6;
35
+ background: #242424;
36
+ font-size: 13px;
37
+ overflow: hidden;
38
+ }
39
+ .eruda-storage-toolbar {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 8px;
43
+ height: 38px;
44
+ padding: 6px 8px;
45
+ box-sizing: border-box;
46
+ border-bottom: 1px solid #454545;
47
+ background: #2f2f2f;
48
+ }
49
+ .eruda-storage-toolbar button,
50
+ .eruda-storage-editor-actions button {
51
+ border: 1px solid #5f5f5f;
52
+ border-radius: 4px;
53
+ color: #ddd;
54
+ background: #3a3a3a;
55
+ height: 26px;
56
+ padding: 0 9px;
57
+ cursor: pointer;
58
+ }
59
+ .eruda-storage-toolbar button:disabled {
60
+ color: #777;
61
+ cursor: not-allowed;
62
+ }
63
+ .eruda-storage-filter {
64
+ min-width: 0;
65
+ flex: 1;
66
+ height: 26px;
67
+ padding: 0 10px;
68
+ border: 0;
69
+ border-radius: 14px;
70
+ outline: none;
71
+ color: #eee;
72
+ background: #444;
73
+ }
74
+ .eruda-storage-layout {
75
+ display: flex;
76
+ height: calc(100% - 38px);
77
+ min-height: 0;
78
+ overflow: hidden;
79
+ }
80
+ .eruda-storage-sidebar {
81
+ width: 34%;
82
+ min-width: 120px;
83
+ max-width: 230px;
84
+ overflow: auto;
85
+ border-right: 1px solid #555;
86
+ background: #292929;
87
+ }
88
+ .eruda-storage-nav,
89
+ .eruda-storage-store {
90
+ display: block;
91
+ width: 100%;
92
+ border: 0;
93
+ box-sizing: border-box;
94
+ color: #ddd;
95
+ background: transparent;
96
+ text-align: left;
97
+ padding: 9px 10px;
98
+ overflow: hidden;
99
+ white-space: nowrap;
100
+ text-overflow: ellipsis;
101
+ cursor: pointer;
102
+ }
103
+ .eruda-storage-db {
104
+ padding-left: 18px;
105
+ color: #cfcfcf;
106
+ }
107
+ .eruda-storage-store {
108
+ padding-left: 34px;
109
+ color: #bbb;
110
+ }
111
+ .eruda-storage-nav.is-active,
112
+ .eruda-storage-store.is-active {
113
+ background: #174a87;
114
+ color: #fff;
115
+ }
116
+ .eruda-storage-db-title {
117
+ padding: 8px 10px 4px;
118
+ color: #8fbfff;
119
+ word-break: break-all;
120
+ }
121
+ .eruda-storage-panel {
122
+ flex: 1;
123
+ min-width: 0;
124
+ min-height: 0;
125
+ height: 100%;
126
+ display: flex;
127
+ flex-direction: column;
128
+ overflow: hidden;
129
+ }
130
+ .eruda-storage-title {
131
+ flex: none;
132
+ padding: 12px 14px;
133
+ border-bottom: 1px solid #494949;
134
+ color: #fff;
135
+ font-size: 15px;
136
+ overflow: hidden;
137
+ white-space: nowrap;
138
+ text-overflow: ellipsis;
139
+ }
140
+ .eruda-storage-table-wrap {
141
+ flex: 0 1 auto;
142
+ max-height: 52%;
143
+ min-height: 96px;
144
+ overflow: auto;
145
+ overscroll-behavior: contain;
146
+ }
147
+ .eruda-storage-table {
148
+ width: 100%;
149
+ border-collapse: collapse;
150
+ table-layout: fixed;
151
+ }
152
+ .eruda-storage-table th,
153
+ .eruda-storage-table td {
154
+ height: 28px;
155
+ padding: 0 8px;
156
+ border-right: 1px solid #4b4b4b;
157
+ border-bottom: 1px solid #333;
158
+ overflow: hidden;
159
+ white-space: nowrap;
160
+ text-overflow: ellipsis;
161
+ }
162
+ .eruda-storage-table th {
163
+ position: sticky;
164
+ top: 0;
165
+ color: #ddd;
166
+ background: #383838;
167
+ text-align: left;
168
+ z-index: 1;
169
+ }
170
+ .eruda-storage-table tr {
171
+ cursor: pointer;
172
+ }
173
+ .eruda-storage-table tr:nth-child(even) td {
174
+ background: #2d2d2d;
175
+ }
176
+ .eruda-storage-table tr.is-selected td {
177
+ background: #27538d;
178
+ color: #fff;
179
+ }
180
+ .eruda-storage-empty {
181
+ padding: 18px 14px;
182
+ color: #aaa;
183
+ }
184
+ .eruda-storage-db-overview {
185
+ flex: 1;
186
+ min-height: 0;
187
+ padding: 16px 14px;
188
+ border-top: 1px solid #444;
189
+ color: #ddd;
190
+ background: #242424;
191
+ overflow: auto;
192
+ }
193
+ .eruda-storage-db-meta {
194
+ margin-bottom: 18px;
195
+ border-bottom: 1px solid #4a4a4a;
196
+ }
197
+ .eruda-storage-db-meta-row {
198
+ display: grid;
199
+ grid-template-columns: minmax(120px, 45%) 1fr;
200
+ gap: 12px;
201
+ padding: 6px 0;
202
+ }
203
+ .eruda-storage-db-meta-key {
204
+ color: #9cdcfe;
205
+ }
206
+ .eruda-storage-db-meta-value {
207
+ color: #eee;
208
+ word-break: break-all;
209
+ }
210
+ .eruda-storage-db-actions {
211
+ display: flex;
212
+ flex-wrap: wrap;
213
+ gap: 8px;
214
+ }
215
+ .eruda-storage-db-actions button {
216
+ height: 28px;
217
+ padding: 0 12px;
218
+ border: 1px solid #4f8dcc;
219
+ border-radius: 15px;
220
+ color: #8ab4f8;
221
+ background: transparent;
222
+ cursor: pointer;
223
+ }
224
+ .eruda-storage-detail {
225
+ flex: 0 0 auto;
226
+ min-height: 0;
227
+ max-height: none;
228
+ border-top: 1px solid #555;
229
+ overflow-x: scroll;
230
+ overflow-y: scroll;
231
+ overscroll-behavior: contain;
232
+ -webkit-overflow-scrolling: touch;
233
+ touch-action: pan-x pan-y;
234
+ background: #202020;
235
+ }
236
+ .eruda-storage-detail pre {
237
+ margin: 0;
238
+ padding: 12px 14px;
239
+ white-space: pre-wrap;
240
+ word-break: break-word;
241
+ color: #dfe8b8;
242
+ }
243
+ .eruda-storage-tree {
244
+ display: inline-block;
245
+ width: max-content;
246
+ min-width: 100%;
247
+ padding: 10px 8px;
248
+ box-sizing: border-box;
249
+ color: #dfe8b8;
250
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
251
+ font-size: 12px;
252
+ line-height: 1.55;
253
+ }
254
+ .eruda-storage-tree-row {
255
+ display: flex;
256
+ align-items: flex-start;
257
+ width: max-content;
258
+ min-height: 20px;
259
+ white-space: nowrap;
260
+ }
261
+ .eruda-storage-tree-toggle {
262
+ position: relative;
263
+ flex: none;
264
+ width: 16px;
265
+ height: 20px;
266
+ padding: 0;
267
+ border: 0;
268
+ color: #bbb;
269
+ background: transparent;
270
+ cursor: pointer;
271
+ }
272
+ .eruda-storage-tree-toggle::before {
273
+ position: absolute;
274
+ top: 6px;
275
+ left: 4px;
276
+ width: 0;
277
+ height: 0;
278
+ border-top: 5px solid transparent;
279
+ border-bottom: 5px solid transparent;
280
+ border-left: 7px solid #bbb;
281
+ content: "";
282
+ }
283
+ .eruda-storage-tree-toggle.is-expanded::before {
284
+ top: 7px;
285
+ left: 2px;
286
+ transform: rotate(90deg);
287
+ }
288
+ .eruda-storage-tree-spacer {
289
+ display: inline-block;
290
+ width: 16px;
291
+ height: 20px;
292
+ flex: none;
293
+ }
294
+ .eruda-storage-tree-content {
295
+ color: #d4d4d4;
296
+ }
297
+ .eruda-storage-tree-key {
298
+ color: #9cdcfe;
299
+ }
300
+ .eruda-storage-tree-index {
301
+ color: #dcdcaa;
302
+ }
303
+ .eruda-storage-tree-colon {
304
+ color: #cfcfcf;
305
+ margin-right: 4px;
306
+ }
307
+ .eruda-storage-tree-string {
308
+ color: #ce9178;
309
+ }
310
+ .eruda-storage-tree-number {
311
+ color: #b5cea8;
312
+ }
313
+ .eruda-storage-tree-boolean {
314
+ color: #569cd6;
315
+ }
316
+ .eruda-storage-tree-null {
317
+ color: #569cd6;
318
+ }
319
+ .eruda-storage-tree-summary {
320
+ color: #aaa;
321
+ }
322
+ .eruda-storage-editor-mask {
323
+ position: absolute;
324
+ inset: 0;
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: center;
328
+ background: rgba(0, 0, 0, 0.62);
329
+ z-index: 20;
330
+ }
331
+ .eruda-storage-editor {
332
+ width: min(92%, 520px);
333
+ max-height: 88%;
334
+ padding: 12px;
335
+ box-sizing: border-box;
336
+ border: 1px solid #646464;
337
+ border-radius: 6px;
338
+ background: #2e2e2e;
339
+ box-shadow: 0 8px 26px rgba(0, 0, 0, 0.45);
340
+ }
341
+ .eruda-storage-editor h3 {
342
+ margin: 0 0 10px;
343
+ font-size: 15px;
344
+ color: #fff;
345
+ }
346
+ .eruda-storage-editor label {
347
+ display: block;
348
+ margin: 8px 0 4px;
349
+ color: #bbb;
350
+ }
351
+ .eruda-storage-editor input,
352
+ .eruda-storage-editor textarea {
353
+ width: 100%;
354
+ box-sizing: border-box;
355
+ border: 1px solid #555;
356
+ border-radius: 4px;
357
+ outline: none;
358
+ color: #eee;
359
+ background: #1f1f1f;
360
+ }
361
+ .eruda-storage-editor input {
362
+ height: 30px;
363
+ padding: 0 8px;
364
+ }
365
+ .eruda-storage-editor textarea {
366
+ height: 160px;
367
+ padding: 8px;
368
+ resize: vertical;
369
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
370
+ }
371
+ .eruda-storage-editor-actions {
372
+ display: flex;
373
+ justify-content: flex-end;
374
+ gap: 8px;
375
+ margin-top: 10px;
376
+ }
377
+ `;
378
+ const escapeHtml = (value) => value
379
+ .replace(/&/g, "&")
380
+ .replace(/</g, "&lt;")
381
+ .replace(/>/g, "&gt;")
382
+ .replace(/"/g, "&quot;")
383
+ .replace(/'/g, "&#39;");
384
+ const safeJsonStringify = (value) => {
385
+ try {
386
+ return JSON.stringify(value, null, 2);
387
+ }
388
+ catch {
389
+ return String(value);
390
+ }
391
+ };
392
+ const formatValue = (value) => {
393
+ if (typeof value === "string") {
394
+ try {
395
+ return JSON.stringify(JSON.parse(value), null, 2);
396
+ }
397
+ catch {
398
+ return value;
399
+ }
400
+ }
401
+ return safeJsonStringify(value);
402
+ };
403
+ const parseEditableValue = (value) => {
404
+ try {
405
+ return JSON.parse(value);
406
+ }
407
+ catch {
408
+ return value;
409
+ }
410
+ };
411
+ const requestToPromise = (request) => new Promise((resolve, reject) => {
412
+ request.onsuccess = () => resolve(request.result);
413
+ request.onerror = () => reject(request.error);
414
+ });
415
+ const txDone = (tx) => new Promise((resolve, reject) => {
416
+ tx.oncomplete = () => resolve();
417
+ tx.onerror = () => reject(tx.error);
418
+ tx.onabort = () => reject(tx.error);
419
+ });
420
+ class ErudaStorageTool {
421
+ constructor(eruda) {
422
+ this.eruda = eruda;
423
+ this.name = "Storage";
424
+ this.activeKind = "local";
425
+ this.dbs = [];
426
+ this.editor = null;
427
+ this.filter = "";
428
+ this.selectedDb = "";
429
+ this.selectedIndex = -1;
430
+ this.selectedStore = "";
431
+ this.stores = [];
432
+ this.rows = [];
433
+ this.detailTouchX = 0;
434
+ this.detailTouchY = 0;
435
+ this.detailExpandedPaths = new Set();
436
+ this.detailSelectedKey = "";
437
+ this.isVisible = false;
438
+ this.lastSnapshot = "";
439
+ this.handleExternalStorageChange = () => {
440
+ if (!this.isVisible)
441
+ return;
442
+ this.refreshIfChanged();
443
+ };
444
+ this.handleVisibilityChange = () => {
445
+ if (document.visibilityState === "visible") {
446
+ this.handleExternalStorageChange();
447
+ }
448
+ };
449
+ this.keepDetailScroll = (event) => {
450
+ const target = event.target;
451
+ const detailEl = target instanceof Element ? target.closest(".eruda-storage-detail") : null;
452
+ if (!detailEl)
453
+ return;
454
+ if (event instanceof WheelEvent) {
455
+ detailEl.scrollTop += event.deltaY;
456
+ detailEl.scrollLeft += event.shiftKey ? event.deltaY : event.deltaX;
457
+ event.preventDefault();
458
+ event.stopPropagation();
459
+ return;
460
+ }
461
+ if (event instanceof TouchEvent && event.touches[0]) {
462
+ const currentX = event.touches[0].clientX;
463
+ const currentY = event.touches[0].clientY;
464
+ detailEl.scrollLeft += this.detailTouchX - currentX;
465
+ detailEl.scrollTop += this.detailTouchY - currentY;
466
+ this.detailTouchX = currentX;
467
+ this.detailTouchY = currentY;
468
+ event.preventDefault();
469
+ event.stopPropagation();
470
+ }
471
+ };
472
+ this.handleDetailTouchStart = (event) => {
473
+ var _a, _b, _c, _d;
474
+ const target = event.target;
475
+ if (!(target instanceof Element) || !target.closest(".eruda-storage-detail"))
476
+ return;
477
+ this.detailTouchX = (_b = (_a = event.touches[0]) === null || _a === void 0 ? void 0 : _a.clientX) !== null && _b !== void 0 ? _b : 0;
478
+ this.detailTouchY = (_d = (_c = event.touches[0]) === null || _c === void 0 ? void 0 : _c.clientY) !== null && _d !== void 0 ? _d : 0;
479
+ };
480
+ this.handleClick = (event) => {
481
+ var _a, _b, _c, _d, _e;
482
+ const target = event.target;
483
+ if (!(target instanceof Element))
484
+ return;
485
+ const detailToggle = target.closest("[data-detail-path]");
486
+ if (detailToggle) {
487
+ event.preventDefault();
488
+ event.stopPropagation();
489
+ this.toggleDetailPath((_a = detailToggle.dataset.detailPath) !== null && _a !== void 0 ? _a : "");
490
+ return;
491
+ }
492
+ const actionEl = target.closest("[data-action]");
493
+ if (actionEl) {
494
+ event.preventDefault();
495
+ this.handleAction((_b = actionEl.dataset.action) !== null && _b !== void 0 ? _b : "");
496
+ return;
497
+ }
498
+ const navEl = target.closest("[data-kind]");
499
+ if (navEl) {
500
+ this.activeKind = navEl.dataset.kind;
501
+ this.selectedIndex = -1;
502
+ this.refresh();
503
+ return;
504
+ }
505
+ const storeEl = target.closest("[data-db][data-store]");
506
+ if (storeEl) {
507
+ this.activeKind = "indexeddb";
508
+ this.selectedDb = (_c = storeEl.dataset.db) !== null && _c !== void 0 ? _c : "";
509
+ this.selectedStore = (_d = storeEl.dataset.store) !== null && _d !== void 0 ? _d : "";
510
+ this.selectedIndex = -1;
511
+ void this.refreshIndexedDbRows();
512
+ return;
513
+ }
514
+ const dbEl = target.closest("[data-db-only]");
515
+ if (dbEl) {
516
+ this.activeKind = "indexeddb";
517
+ this.selectedDb = (_e = dbEl.dataset.dbOnly) !== null && _e !== void 0 ? _e : "";
518
+ this.selectedStore = "";
519
+ this.selectedIndex = -1;
520
+ void this.refreshStores();
521
+ return;
522
+ }
523
+ const rowEl = target.closest("[data-row-index]");
524
+ if (rowEl) {
525
+ this.selectRow(Number(rowEl.dataset.rowIndex));
526
+ this.renderSelection();
527
+ }
528
+ };
529
+ this.handleDoubleClick = (event) => {
530
+ const target = event.target;
531
+ if (!(target instanceof Element))
532
+ return;
533
+ if (target.closest("[data-row-index]")) {
534
+ this.openEditor("edit");
535
+ }
536
+ };
537
+ this.handleInput = (event) => {
538
+ const target = event.target;
539
+ if (!(target instanceof HTMLInputElement))
540
+ return;
541
+ if (target.dataset.role !== "filter")
542
+ return;
543
+ this.filter = target.value;
544
+ this.selectedIndex = -1;
545
+ this.render();
546
+ };
547
+ }
548
+ init($el) {
549
+ this.root = this.resolveElement($el);
550
+ this.root.style.height = "100%";
551
+ this.root.style.overflow = "hidden";
552
+ this.styleEl = this.eruda.util.evalCss(STYLE);
553
+ patchStorageMutations();
554
+ this.bindEvents();
555
+ this.refresh();
556
+ }
557
+ show() {
558
+ var _a;
559
+ this.isVisible = true;
560
+ (_a = this.root) === null || _a === void 0 ? void 0 : _a.style.setProperty("display", "block");
561
+ this.startAutoRefresh();
562
+ this.refreshIfChanged();
563
+ this.syncScrollLayout();
564
+ return this;
565
+ }
566
+ hide() {
567
+ var _a;
568
+ this.isVisible = false;
569
+ this.stopAutoRefresh();
570
+ (_a = this.root) === null || _a === void 0 ? void 0 : _a.style.setProperty("display", "none");
571
+ return this;
572
+ }
573
+ destroy() {
574
+ var _a, _b, _c, _d, _e, _f, _g;
575
+ this.stopAutoRefresh();
576
+ (_a = this.styleEl) === null || _a === void 0 ? void 0 : _a.remove();
577
+ (_b = this.root) === null || _b === void 0 ? void 0 : _b.removeEventListener("click", this.handleClick);
578
+ (_c = this.root) === null || _c === void 0 ? void 0 : _c.removeEventListener("dblclick", this.handleDoubleClick);
579
+ (_d = this.root) === null || _d === void 0 ? void 0 : _d.removeEventListener("input", this.handleInput);
580
+ (_e = this.root) === null || _e === void 0 ? void 0 : _e.removeEventListener("wheel", this.keepDetailScroll);
581
+ (_f = this.root) === null || _f === void 0 ? void 0 : _f.removeEventListener("touchstart", this.handleDetailTouchStart);
582
+ (_g = this.root) === null || _g === void 0 ? void 0 : _g.removeEventListener("touchmove", this.keepDetailScroll);
583
+ window.removeEventListener("storage", this.handleExternalStorageChange);
584
+ window.removeEventListener(STORAGE_CHANGE_EVENT, this.handleExternalStorageChange);
585
+ window.removeEventListener("focus", this.handleExternalStorageChange);
586
+ document.removeEventListener("visibilitychange", this.handleVisibilityChange);
587
+ }
588
+ resolveElement($el) {
589
+ var _a;
590
+ const wrapped = $el;
591
+ return (_a = wrapped[0]) !== null && _a !== void 0 ? _a : $el;
592
+ }
593
+ bindEvents() {
594
+ if (!this.root)
595
+ return;
596
+ this.root.addEventListener("click", this.handleClick);
597
+ this.root.addEventListener("dblclick", this.handleDoubleClick);
598
+ this.root.addEventListener("input", this.handleInput);
599
+ this.root.addEventListener("wheel", this.keepDetailScroll);
600
+ this.root.addEventListener("touchstart", this.handleDetailTouchStart);
601
+ this.root.addEventListener("touchmove", this.keepDetailScroll);
602
+ window.addEventListener("storage", this.handleExternalStorageChange);
603
+ window.addEventListener(STORAGE_CHANGE_EVENT, this.handleExternalStorageChange);
604
+ window.addEventListener("focus", this.handleExternalStorageChange);
605
+ document.addEventListener("visibilitychange", this.handleVisibilityChange);
606
+ }
607
+ startAutoRefresh() {
608
+ this.stopAutoRefresh();
609
+ this.refreshTimer = window.setInterval(() => {
610
+ this.refreshIfChanged();
611
+ }, 1500);
612
+ }
613
+ stopAutoRefresh() {
614
+ if (this.refreshTimer === undefined)
615
+ return;
616
+ window.clearInterval(this.refreshTimer);
617
+ this.refreshTimer = undefined;
618
+ }
619
+ handleAction(action) {
620
+ if (action === "refresh") {
621
+ this.refresh();
622
+ return;
623
+ }
624
+ if (action === "add") {
625
+ this.openEditor("add");
626
+ return;
627
+ }
628
+ if (action === "edit") {
629
+ this.openEditor("edit");
630
+ return;
631
+ }
632
+ if (action === "delete") {
633
+ void this.deleteSelected();
634
+ return;
635
+ }
636
+ if (action === "clear") {
637
+ void this.clearActive();
638
+ return;
639
+ }
640
+ if (action === "delete-db") {
641
+ void this.deleteSelectedDatabase();
642
+ return;
643
+ }
644
+ if (action === "refresh-db") {
645
+ void this.refreshStores();
646
+ return;
647
+ }
648
+ if (action === "editor-close") {
649
+ this.editor = null;
650
+ this.render();
651
+ return;
652
+ }
653
+ if (action === "editor-save") {
654
+ void this.saveEditor();
655
+ }
656
+ }
657
+ refresh() {
658
+ var _a;
659
+ if (this.activeKind === "indexeddb") {
660
+ void this.refreshIndexedDb();
661
+ return;
662
+ }
663
+ const selectedKey = (_a = this.selectedRow()) === null || _a === void 0 ? void 0 : _a.key;
664
+ this.rows = this.readRows();
665
+ this.lastSnapshot = this.createSnapshot();
666
+ this.restoreSelection(selectedKey);
667
+ this.render();
668
+ }
669
+ refreshIfChanged() {
670
+ var _a;
671
+ if (this.editor)
672
+ return;
673
+ if (this.activeKind === "indexeddb") {
674
+ void this.refreshIndexedDbIfChanged();
675
+ return;
676
+ }
677
+ const nextRows = this.readRows();
678
+ const nextSnapshot = this.createSnapshot(nextRows);
679
+ if (nextSnapshot === this.lastSnapshot)
680
+ return;
681
+ const selectedKey = (_a = this.selectedRow()) === null || _a === void 0 ? void 0 : _a.key;
682
+ this.rows = nextRows;
683
+ this.lastSnapshot = nextSnapshot;
684
+ this.restoreSelection(selectedKey);
685
+ this.render();
686
+ }
687
+ readRows() {
688
+ if (this.activeKind === "local") {
689
+ return this.readWebStorage(localStorage);
690
+ }
691
+ if (this.activeKind === "session") {
692
+ return this.readWebStorage(sessionStorage);
693
+ }
694
+ if (this.activeKind === "cookie") {
695
+ return this.readCookies();
696
+ }
697
+ return this.rows;
698
+ }
699
+ readWebStorage(storage) {
700
+ var _a;
701
+ const rows = [];
702
+ for (let index = 0; index < storage.length; index += 1) {
703
+ const key = storage.key(index);
704
+ if (key === null)
705
+ continue;
706
+ rows.push({ key, value: (_a = storage.getItem(key)) !== null && _a !== void 0 ? _a : "" });
707
+ }
708
+ return rows.sort((left, right) => left.key.localeCompare(right.key));
709
+ }
710
+ readCookies() {
711
+ if (!document.cookie)
712
+ return [];
713
+ return document.cookie
714
+ .split(";")
715
+ .map((item) => item.trim())
716
+ .filter(Boolean)
717
+ .map((item) => {
718
+ const separatorIndex = item.indexOf("=");
719
+ const key = separatorIndex >= 0 ? item.slice(0, separatorIndex) : item;
720
+ const value = separatorIndex >= 0 ? item.slice(separatorIndex + 1) : "";
721
+ return {
722
+ key: decodeURIComponent(key),
723
+ value: decodeURIComponent(value),
724
+ };
725
+ })
726
+ .sort((left, right) => left.key.localeCompare(right.key));
727
+ }
728
+ async refreshIndexedDb() {
729
+ this.renderLoading();
730
+ const factory = indexedDB;
731
+ if (!factory.databases) {
732
+ this.rows = [];
733
+ this.dbs = [];
734
+ this.stores = [];
735
+ this.render();
736
+ return;
737
+ }
738
+ const dbs = await factory.databases();
739
+ this.dbs = dbs
740
+ .filter((db) => Boolean(db.name))
741
+ .map((db) => ({ name: db.name, version: db.version }))
742
+ .sort((left, right) => left.name.localeCompare(right.name));
743
+ if (this.selectedDb && !this.dbs.some((db) => db.name === this.selectedDb)) {
744
+ this.selectedDb = "";
745
+ this.selectedStore = "";
746
+ }
747
+ if (!this.selectedDb && this.dbs[0]) {
748
+ this.selectedDb = this.dbs[0].name;
749
+ }
750
+ await this.refreshStores();
751
+ }
752
+ async refreshStores() {
753
+ this.stores = [];
754
+ this.rows = [];
755
+ if (!this.selectedDb) {
756
+ this.render();
757
+ return;
758
+ }
759
+ const db = await this.openDb(this.selectedDb);
760
+ this.stores = Array.from(db.objectStoreNames).sort((left, right) => left.localeCompare(right));
761
+ db.close();
762
+ if (this.selectedStore && !this.stores.includes(this.selectedStore)) {
763
+ this.selectedStore = "";
764
+ }
765
+ await this.refreshIndexedDbRows();
766
+ }
767
+ async refreshIndexedDbRows() {
768
+ if (!this.selectedDb || !this.selectedStore) {
769
+ this.rows = [];
770
+ this.render();
771
+ return;
772
+ }
773
+ this.renderLoading();
774
+ const db = await this.openDb(this.selectedDb);
775
+ const tx = db.transaction(this.selectedStore, "readonly");
776
+ const store = tx.objectStore(this.selectedStore);
777
+ const rows = [];
778
+ await new Promise((resolve, reject) => {
779
+ const request = store.openCursor();
780
+ request.onerror = () => reject(request.error);
781
+ request.onsuccess = () => {
782
+ const cursor = request.result;
783
+ if (!cursor) {
784
+ resolve();
785
+ return;
786
+ }
787
+ rows.push({
788
+ key: safeJsonStringify(cursor.primaryKey),
789
+ value: formatValue(cursor.value),
790
+ rawKey: cursor.primaryKey,
791
+ rawValue: cursor.value,
792
+ });
793
+ cursor.continue();
794
+ };
795
+ });
796
+ db.close();
797
+ this.rows = rows;
798
+ this.lastSnapshot = this.createSnapshot();
799
+ this.render();
800
+ }
801
+ async refreshIndexedDbIfChanged() {
802
+ var _a;
803
+ if (!this.selectedDb || !this.selectedStore) {
804
+ await this.refreshIndexedDb();
805
+ return;
806
+ }
807
+ const db = await this.openDb(this.selectedDb);
808
+ const tx = db.transaction(this.selectedStore, "readonly");
809
+ const store = tx.objectStore(this.selectedStore);
810
+ const rows = [];
811
+ await new Promise((resolve, reject) => {
812
+ const request = store.openCursor();
813
+ request.onerror = () => reject(request.error);
814
+ request.onsuccess = () => {
815
+ const cursor = request.result;
816
+ if (!cursor) {
817
+ resolve();
818
+ return;
819
+ }
820
+ rows.push({
821
+ key: safeJsonStringify(cursor.primaryKey),
822
+ value: formatValue(cursor.value),
823
+ rawKey: cursor.primaryKey,
824
+ rawValue: cursor.value,
825
+ });
826
+ cursor.continue();
827
+ };
828
+ });
829
+ db.close();
830
+ const nextSnapshot = this.createSnapshot(rows);
831
+ if (nextSnapshot === this.lastSnapshot)
832
+ return;
833
+ const selectedKey = (_a = this.selectedRow()) === null || _a === void 0 ? void 0 : _a.key;
834
+ this.rows = rows;
835
+ this.lastSnapshot = nextSnapshot;
836
+ this.restoreSelection(selectedKey);
837
+ this.render();
838
+ }
839
+ openDb(name) {
840
+ return requestToPromise(indexedDB.open(name));
841
+ }
842
+ filteredRows() {
843
+ const keyword = this.filter.trim().toLowerCase();
844
+ if (!keyword)
845
+ return this.rows;
846
+ return this.rows.filter((row) => row.key.toLowerCase().includes(keyword) || row.value.toLowerCase().includes(keyword));
847
+ }
848
+ selectedRow() {
849
+ return this.filteredRows()[this.selectedIndex];
850
+ }
851
+ selectRow(index) {
852
+ var _a;
853
+ this.selectedIndex = index;
854
+ const row = this.selectedRow();
855
+ const detailKey = (_a = row === null || row === void 0 ? void 0 : row.key) !== null && _a !== void 0 ? _a : "";
856
+ if (detailKey === this.detailSelectedKey)
857
+ return;
858
+ this.detailSelectedKey = detailKey;
859
+ this.detailExpandedPaths = new Set(["$"]);
860
+ }
861
+ toggleDetailPath(path) {
862
+ if (!path)
863
+ return;
864
+ if (this.detailExpandedPaths.has(path)) {
865
+ this.detailExpandedPaths.delete(path);
866
+ }
867
+ else {
868
+ this.detailExpandedPaths.add(path);
869
+ }
870
+ this.renderSelection();
871
+ }
872
+ createSnapshot(rows = this.rows) {
873
+ return rows.map((row) => `${row.key}\u0000${row.value}`).join("\u0001");
874
+ }
875
+ restoreSelection(selectedKey) {
876
+ if (!selectedKey) {
877
+ this.selectedIndex = -1;
878
+ return;
879
+ }
880
+ const index = this.filteredRows().findIndex((row) => row.key === selectedKey);
881
+ this.selectedIndex = index;
882
+ }
883
+ renderSelection() {
884
+ if (!this.root)
885
+ return;
886
+ const row = this.selectedRow();
887
+ this.root.querySelectorAll(".eruda-storage-table tr.is-selected").forEach((el) => {
888
+ el.classList.remove("is-selected");
889
+ });
890
+ const rowEl = this.root.querySelector(`[data-row-index="${this.selectedIndex}"]`);
891
+ rowEl === null || rowEl === void 0 ? void 0 : rowEl.classList.add("is-selected");
892
+ const detailEl = this.root.querySelector(".eruda-storage-detail");
893
+ if (detailEl) {
894
+ detailEl.innerHTML = this.renderDetail(row);
895
+ }
896
+ this.updateToolbarState(Boolean(row));
897
+ }
898
+ updateToolbarState(hasSelected) {
899
+ if (!this.root)
900
+ return;
901
+ this.root.querySelectorAll('[data-action="edit"], [data-action="delete"]').forEach((button) => {
902
+ button.disabled = !hasSelected;
903
+ });
904
+ }
905
+ openEditor(mode) {
906
+ var _a, _b;
907
+ const row = this.selectedRow();
908
+ if (mode === "edit" && !row)
909
+ return;
910
+ this.editor = {
911
+ mode,
912
+ key: (_a = row === null || row === void 0 ? void 0 : row.key) !== null && _a !== void 0 ? _a : "",
913
+ value: (_b = row === null || row === void 0 ? void 0 : row.value) !== null && _b !== void 0 ? _b : "",
914
+ originalKey: row === null || row === void 0 ? void 0 : row.key,
915
+ readonlyKey: this.activeKind === "indexeddb" && mode === "edit",
916
+ };
917
+ this.render();
918
+ }
919
+ async saveEditor() {
920
+ var _a, _b, _c, _d;
921
+ if (!this.editor)
922
+ return;
923
+ const keyInput = (_a = this.root) === null || _a === void 0 ? void 0 : _a.querySelector("[data-editor-key]");
924
+ const valueInput = (_b = this.root) === null || _b === void 0 ? void 0 : _b.querySelector("[data-editor-value]");
925
+ const key = (_c = keyInput === null || keyInput === void 0 ? void 0 : keyInput.value.trim()) !== null && _c !== void 0 ? _c : "";
926
+ const value = (_d = valueInput === null || valueInput === void 0 ? void 0 : valueInput.value) !== null && _d !== void 0 ? _d : "";
927
+ if (!key && this.activeKind !== "indexeddb")
928
+ return;
929
+ if (this.activeKind === "local") {
930
+ this.saveWebStorage(localStorage, key, value);
931
+ }
932
+ else if (this.activeKind === "session") {
933
+ this.saveWebStorage(sessionStorage, key, value);
934
+ }
935
+ else if (this.activeKind === "cookie") {
936
+ this.setCookie(key, value);
937
+ }
938
+ else {
939
+ await this.saveIndexedDbValue(key, value);
940
+ }
941
+ this.editor = null;
942
+ this.selectedIndex = -1;
943
+ this.refresh();
944
+ }
945
+ saveWebStorage(storage, key, value) {
946
+ var _a;
947
+ const originalKey = (_a = this.editor) === null || _a === void 0 ? void 0 : _a.originalKey;
948
+ if (originalKey && originalKey !== key) {
949
+ storage.removeItem(originalKey);
950
+ }
951
+ storage.setItem(key, value);
952
+ }
953
+ setCookie(key, value) {
954
+ document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; path=/; max-age=31536000; SameSite=Lax`;
955
+ }
956
+ async saveIndexedDbValue(key, value) {
957
+ var _a;
958
+ if (!this.selectedDb || !this.selectedStore)
959
+ return;
960
+ const db = await this.openDb(this.selectedDb);
961
+ const tx = db.transaction(this.selectedStore, "readwrite");
962
+ const store = tx.objectStore(this.selectedStore);
963
+ const parsedValue = parseEditableValue(value);
964
+ if (((_a = this.editor) === null || _a === void 0 ? void 0 : _a.mode) === "edit") {
965
+ const row = this.selectedRow();
966
+ if (store.keyPath === null) {
967
+ store.put(parsedValue, row === null || row === void 0 ? void 0 : row.rawKey);
968
+ }
969
+ else {
970
+ store.put(parsedValue);
971
+ }
972
+ }
973
+ else if (store.keyPath === null) {
974
+ store.add(parsedValue, parseEditableValue(key));
975
+ }
976
+ else {
977
+ store.add(parsedValue);
978
+ }
979
+ await txDone(tx);
980
+ db.close();
981
+ }
982
+ async deleteSelected() {
983
+ const row = this.selectedRow();
984
+ if (!row)
985
+ return;
986
+ if (this.activeKind === "local") {
987
+ localStorage.removeItem(row.key);
988
+ }
989
+ else if (this.activeKind === "session") {
990
+ sessionStorage.removeItem(row.key);
991
+ }
992
+ else if (this.activeKind === "cookie") {
993
+ document.cookie = `${encodeURIComponent(row.key)}=; path=/; max-age=0`;
994
+ }
995
+ else {
996
+ await this.deleteIndexedDbRow(row);
997
+ }
998
+ this.selectedIndex = -1;
999
+ this.refresh();
1000
+ }
1001
+ async deleteIndexedDbRow(row) {
1002
+ if (!this.selectedDb || !this.selectedStore)
1003
+ return;
1004
+ const db = await this.openDb(this.selectedDb);
1005
+ const tx = db.transaction(this.selectedStore, "readwrite");
1006
+ tx.objectStore(this.selectedStore).delete(row.rawKey);
1007
+ await txDone(tx);
1008
+ db.close();
1009
+ }
1010
+ async clearActive() {
1011
+ if (this.activeKind === "local") {
1012
+ localStorage.clear();
1013
+ }
1014
+ else if (this.activeKind === "session") {
1015
+ sessionStorage.clear();
1016
+ }
1017
+ else if (this.activeKind === "cookie") {
1018
+ for (const row of this.rows) {
1019
+ document.cookie = `${encodeURIComponent(row.key)}=; path=/; max-age=0`;
1020
+ }
1021
+ }
1022
+ else {
1023
+ await this.clearIndexedDbStore();
1024
+ }
1025
+ this.selectedIndex = -1;
1026
+ this.refresh();
1027
+ }
1028
+ async clearIndexedDbStore() {
1029
+ if (!this.selectedDb || !this.selectedStore)
1030
+ return;
1031
+ const db = await this.openDb(this.selectedDb);
1032
+ const tx = db.transaction(this.selectedStore, "readwrite");
1033
+ tx.objectStore(this.selectedStore).clear();
1034
+ await txDone(tx);
1035
+ db.close();
1036
+ }
1037
+ async deleteSelectedDatabase() {
1038
+ if (!this.selectedDb)
1039
+ return;
1040
+ const dbName = this.selectedDb;
1041
+ await new Promise((resolve, reject) => {
1042
+ const request = indexedDB.deleteDatabase(dbName);
1043
+ request.onerror = () => reject(request.error);
1044
+ request.onblocked = () => reject(new Error(`Delete IndexedDB "${dbName}" blocked.`));
1045
+ request.onsuccess = () => resolve();
1046
+ });
1047
+ this.selectedDb = "";
1048
+ this.selectedStore = "";
1049
+ this.selectedIndex = -1;
1050
+ await this.refreshIndexedDb();
1051
+ }
1052
+ renderLoading() {
1053
+ if (!this.root)
1054
+ return;
1055
+ this.root.innerHTML = `<div class="eruda-storage-tool"><div class="eruda-storage-empty">Loading...</div></div>`;
1056
+ }
1057
+ render() {
1058
+ if (!this.root)
1059
+ return;
1060
+ const rows = this.filteredRows();
1061
+ const selected = rows[this.selectedIndex];
1062
+ const isDbOverview = this.isIndexedDbDatabaseOverview();
1063
+ this.root.innerHTML = `
1064
+ <div class="eruda-storage-tool">
1065
+ ${this.renderToolbar()}
1066
+ <div class="eruda-storage-layout">
1067
+ ${this.renderSidebar()}
1068
+ <section class="eruda-storage-panel">
1069
+ <div class="eruda-storage-title">${escapeHtml(this.currentTitle())}${isDbOverview ? "" : ` (${rows.length})`}</div>
1070
+ ${isDbOverview ? this.renderDatabaseOverview() : this.renderRowsPanel(rows, selected)}
1071
+ </section>
1072
+ </div>
1073
+ ${this.renderEditor()}
1074
+ </div>
1075
+ `;
1076
+ this.syncScrollLayout();
1077
+ }
1078
+ renderRowsPanel(rows, selected) {
1079
+ return `
1080
+ <div class="eruda-storage-table-wrap">
1081
+ ${this.renderTable(rows)}
1082
+ </div>
1083
+ <div class="eruda-storage-detail">
1084
+ ${this.renderDetail(selected)}
1085
+ </div>
1086
+ `;
1087
+ }
1088
+ syncScrollLayout() {
1089
+ window.requestAnimationFrame(() => {
1090
+ if (!this.root)
1091
+ return;
1092
+ const panel = this.root.querySelector(".eruda-storage-panel");
1093
+ const title = this.root.querySelector(".eruda-storage-title");
1094
+ const tableWrap = this.root.querySelector(".eruda-storage-table-wrap");
1095
+ const detail = this.root.querySelector(".eruda-storage-detail");
1096
+ if (!panel || !title || !tableWrap || !detail)
1097
+ return;
1098
+ const panelHeight = panel.clientHeight;
1099
+ const titleHeight = title.offsetHeight;
1100
+ const availableHeight = Math.max(panelHeight - titleHeight, 180);
1101
+ const tableHeight = Math.max(96, Math.floor(availableHeight * 0.48));
1102
+ const detailHeight = Math.max(96, availableHeight - tableHeight);
1103
+ tableWrap.style.height = `${tableHeight}px`;
1104
+ tableWrap.style.maxHeight = `${tableHeight}px`;
1105
+ detail.style.height = `${detailHeight}px`;
1106
+ detail.style.maxHeight = `${detailHeight}px`;
1107
+ });
1108
+ }
1109
+ renderToolbar() {
1110
+ const hasSelected = Boolean(this.selectedRow());
1111
+ const canAdd = this.activeKind !== "indexeddb" || Boolean(this.selectedDb && this.selectedStore);
1112
+ const canClear = this.activeKind !== "indexeddb" || Boolean(this.selectedDb && this.selectedStore);
1113
+ return `
1114
+ <div class="eruda-storage-toolbar">
1115
+ <button data-action="refresh" title="Refresh">Refresh</button>
1116
+ <input class="eruda-storage-filter" data-role="filter" placeholder="Filter" value="${escapeHtml(this.filter)}" />
1117
+ <button data-action="add" title="Add" ${canAdd ? "" : "disabled"}>Add</button>
1118
+ <button data-action="edit" title="Edit" ${hasSelected ? "" : "disabled"}>Edit</button>
1119
+ <button data-action="delete" title="Delete" ${hasSelected ? "" : "disabled"}>Delete</button>
1120
+ <button data-action="clear" title="Clear" ${canClear ? "" : "disabled"}>Clear</button>
1121
+ </div>
1122
+ `;
1123
+ }
1124
+ renderSidebar() {
1125
+ return `
1126
+ <aside class="eruda-storage-sidebar">
1127
+ ${this.renderNav("local", "Local Storage")}
1128
+ ${this.renderNav("session", "Session Storage")}
1129
+ ${this.renderNav("cookie", "Cookie")}
1130
+ ${this.renderNav("indexeddb", "IndexedDB")}
1131
+ ${this.renderIndexedDbTree()}
1132
+ </aside>
1133
+ `;
1134
+ }
1135
+ renderNav(kind, label) {
1136
+ const active = this.activeKind === kind && (kind !== "indexeddb" || (!this.selectedDb && !this.selectedStore));
1137
+ return `<button class="eruda-storage-nav ${active ? "is-active" : ""}" data-kind="${kind}">${label}</button>`;
1138
+ }
1139
+ renderIndexedDbTree() {
1140
+ if (this.activeKind !== "indexeddb")
1141
+ return "";
1142
+ if (!("databases" in indexedDB)) {
1143
+ return `<div class="eruda-storage-empty">indexedDB.databases() is not supported.</div>`;
1144
+ }
1145
+ if (this.dbs.length === 0) {
1146
+ return `<div class="eruda-storage-empty">Empty</div>`;
1147
+ }
1148
+ return this.dbs
1149
+ .map((db) => {
1150
+ const stores = db.name === this.selectedDb
1151
+ ? this.stores
1152
+ .map((store) => `<button class="eruda-storage-store ${store === this.selectedStore ? "is-active" : ""}" data-db="${escapeHtml(db.name)}" data-store="${escapeHtml(store)}" title="${escapeHtml(store)}">${escapeHtml(store)}</button>`)
1153
+ .join("")
1154
+ : "";
1155
+ const version = db.version ? ` v${db.version}` : "";
1156
+ return `<button class="eruda-storage-nav eruda-storage-db ${db.name === this.selectedDb && !this.selectedStore ? "is-active" : ""}" data-db-only="${escapeHtml(db.name)}" title="${escapeHtml(db.name)}${version}">${escapeHtml(db.name)}${version}</button>${stores}`;
1157
+ })
1158
+ .join("");
1159
+ }
1160
+ renderTable(rows) {
1161
+ if (rows.length === 0) {
1162
+ return `<div class="eruda-storage-empty">Empty</div>`;
1163
+ }
1164
+ return `
1165
+ <table class="eruda-storage-table">
1166
+ <thead>
1167
+ <tr><th>Key</th><th>Value</th></tr>
1168
+ </thead>
1169
+ <tbody>
1170
+ ${rows
1171
+ .map((row, index) => `
1172
+ <tr class="${index === this.selectedIndex ? "is-selected" : ""}" data-row-index="${index}">
1173
+ <td title="${escapeHtml(row.key)}">${escapeHtml(row.key)}</td>
1174
+ <td title="${escapeHtml(row.value)}">${escapeHtml(row.value)}</td>
1175
+ </tr>
1176
+ `)
1177
+ .join("")}
1178
+ </tbody>
1179
+ </table>
1180
+ `;
1181
+ }
1182
+ renderDatabaseOverview() {
1183
+ var _a;
1184
+ const dbInfo = this.dbs.find((db) => db.name === this.selectedDb);
1185
+ if (!this.selectedDb || !dbInfo) {
1186
+ return `<div class="eruda-storage-db-overview"><div class="eruda-storage-empty">Select an IndexedDB database.</div></div>`;
1187
+ }
1188
+ return `
1189
+ <div class="eruda-storage-db-overview">
1190
+ <div class="eruda-storage-db-meta">
1191
+ ${this.renderDatabaseMetaRow("Storage bucket name", "Default bucket")}
1192
+ ${this.renderDatabaseMetaRow("Version", String((_a = dbInfo.version) !== null && _a !== void 0 ? _a : ""))}
1193
+ ${this.renderDatabaseMetaRow("Object stores", String(this.stores.length))}
1194
+ </div>
1195
+ <div class="eruda-storage-db-actions">
1196
+ <button data-action="delete-db">Delete database</button>
1197
+ <button data-action="refresh-db">Refresh database</button>
1198
+ </div>
1199
+ </div>
1200
+ `;
1201
+ }
1202
+ renderDatabaseMetaRow(label, value) {
1203
+ return `
1204
+ <div class="eruda-storage-db-meta-row">
1205
+ <span class="eruda-storage-db-meta-key">${escapeHtml(label)}</span>
1206
+ <span class="eruda-storage-db-meta-value">${escapeHtml(value)}</span>
1207
+ </div>
1208
+ `;
1209
+ }
1210
+ renderDetail(row) {
1211
+ if (!row) {
1212
+ return `<pre>Select a row to preview value.</pre>`;
1213
+ }
1214
+ const value = this.toDetailValue(row);
1215
+ return `<div class="eruda-storage-tree">${this.renderTreeNode("$", "", value, 0, true)}</div>`;
1216
+ }
1217
+ toDetailValue(row) {
1218
+ if (row.rawValue !== undefined)
1219
+ return row.rawValue;
1220
+ return parseEditableValue(row.value);
1221
+ }
1222
+ renderTreeNode(path, key, value, depth, isRoot = false) {
1223
+ if (this.isRecord(value)) {
1224
+ const entries = Array.isArray(value)
1225
+ ? value.map((item, index) => [String(index), item])
1226
+ : Object.entries(value);
1227
+ const hasChildren = entries.length > 0;
1228
+ const expanded = this.detailExpandedPaths.has(path);
1229
+ const summary = Array.isArray(value) ? `Array(${value.length})` : `{${entries.length ? "..." : ""}}`;
1230
+ const label = isRoot
1231
+ ? `<span class="eruda-storage-tree-summary">${escapeHtml(summary)}</span>`
1232
+ : `${this.renderKey(key)}<span class="eruda-storage-tree-summary">${escapeHtml(summary)}</span>`;
1233
+ const children = expanded
1234
+ ? entries.map(([childKey, childValue]) => this.renderTreeNode(`${path}.${childKey}`, childKey, childValue, depth + 1)).join("")
1235
+ : "";
1236
+ return `
1237
+ <div class="eruda-storage-tree-row" style="padding-left: ${depth * 12}px">
1238
+ ${hasChildren
1239
+ ? `<button class="eruda-storage-tree-toggle ${expanded ? "is-expanded" : ""}" data-detail-path="${escapeHtml(path)}"></button>`
1240
+ : `<span class="eruda-storage-tree-spacer"></span>`}
1241
+ <span class="eruda-storage-tree-content">${label}</span>
1242
+ </div>
1243
+ ${children}
1244
+ `;
1245
+ }
1246
+ const label = isRoot ? this.renderPrimitive(value) : `${this.renderKey(key)}${this.renderPrimitive(value)}`;
1247
+ return `
1248
+ <div class="eruda-storage-tree-row" style="padding-left: ${depth * 12}px">
1249
+ <span class="eruda-storage-tree-spacer"></span>
1250
+ <span>${label}</span>
1251
+ </div>
1252
+ `;
1253
+ }
1254
+ renderKey(key) {
1255
+ const keyClassName = /^\d+$/.test(key) ? "eruda-storage-tree-index" : "eruda-storage-tree-key";
1256
+ return `<span class="${keyClassName}">${escapeHtml(key)}</span><span class="eruda-storage-tree-colon">:</span>`;
1257
+ }
1258
+ isRecord(value) {
1259
+ return typeof value === "object" && value !== null;
1260
+ }
1261
+ renderPrimitive(value) {
1262
+ if (value === null)
1263
+ return `<span class="eruda-storage-tree-null">null</span>`;
1264
+ if (typeof value === "string")
1265
+ return `<span class="eruda-storage-tree-string">"${escapeHtml(value)}"</span>`;
1266
+ if (typeof value === "number")
1267
+ return `<span class="eruda-storage-tree-number">${value}</span>`;
1268
+ if (typeof value === "boolean")
1269
+ return `<span class="eruda-storage-tree-boolean">${value}</span>`;
1270
+ if (value === undefined)
1271
+ return `<span class="eruda-storage-tree-null">undefined</span>`;
1272
+ return `<span class="eruda-storage-tree-summary">${escapeHtml(String(value))}</span>`;
1273
+ }
1274
+ renderEditor() {
1275
+ if (!this.editor)
1276
+ return "";
1277
+ const title = this.editor.mode === "add" ? "Add Storage Item" : "Edit Storage Item";
1278
+ return `
1279
+ <div class="eruda-storage-editor-mask">
1280
+ <div class="eruda-storage-editor">
1281
+ <h3>${title}</h3>
1282
+ <label>Key</label>
1283
+ <input data-editor-key value="${escapeHtml(this.editor.key)}" ${this.editor.readonlyKey ? "readonly" : ""} />
1284
+ <label>Value</label>
1285
+ <textarea data-editor-value>${escapeHtml(this.editor.value)}</textarea>
1286
+ <div class="eruda-storage-editor-actions">
1287
+ <button data-action="editor-close">Cancel</button>
1288
+ <button data-action="editor-save">Save</button>
1289
+ </div>
1290
+ </div>
1291
+ </div>
1292
+ `;
1293
+ }
1294
+ currentTitle() {
1295
+ if (this.activeKind === "local")
1296
+ return `${location.origin} / Local Storage`;
1297
+ if (this.activeKind === "session")
1298
+ return `${location.origin} / Session Storage`;
1299
+ if (this.activeKind === "cookie")
1300
+ return `${location.origin} / Cookie`;
1301
+ if (this.selectedDb && this.selectedStore)
1302
+ return `IndexedDB / ${this.selectedDb} / ${this.selectedStore}`;
1303
+ if (this.selectedDb)
1304
+ return this.selectedDb;
1305
+ return "IndexedDB";
1306
+ }
1307
+ isIndexedDbDatabaseOverview() {
1308
+ return this.activeKind === "indexeddb" && Boolean(this.selectedDb) && !this.selectedStore;
1309
+ }
1310
+ }
1311
+ export const createErudaStoragePlugin = (eruda) => new ErudaStorageTool(eruda);
1312
+ export default createErudaStoragePlugin;
1313
+ //# sourceMappingURL=index.js.map