admindb 1.2.0 → 2.0.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/README.md +250 -352
- package/dist/app.d.ts +25 -4
- package/dist/app.js +79 -15
- package/dist/app.js.map +1 -1
- package/dist/auth/config.d.ts +19 -0
- package/dist/auth/config.js +147 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/crypto.d.ts +27 -0
- package/dist/auth/crypto.js +133 -0
- package/dist/auth/crypto.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.js +22 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +14 -0
- package/dist/auth/middleware.js +100 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/routes.d.ts +7 -0
- package/dist/auth/routes.js +78 -0
- package/dist/auth/routes.js.map +1 -0
- package/dist/auth/types.d.ts +35 -0
- package/dist/auth/types.js +11 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli/args.d.ts +45 -0
- package/dist/cli/args.js +328 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/config.d.ts +56 -0
- package/dist/cli/config.js +133 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.js +21 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/runner.d.ts +1 -0
- package/dist/cli/runner.js +222 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/cli.js +2 -127
- package/dist/cli.js.map +1 -1
- package/dist/data/datasets.d.ts +17 -0
- package/dist/data/datasets.js +133 -0
- package/dist/data/datasets.js.map +1 -0
- package/dist/data/detector.d.ts +30 -0
- package/dist/data/detector.js +472 -0
- package/dist/data/detector.js.map +1 -0
- package/dist/data/engine.d.ts +10 -0
- package/dist/data/engine.js +211 -0
- package/dist/data/engine.js.map +1 -0
- package/dist/data/index.d.ts +5 -0
- package/dist/data/index.js +22 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/strategies.d.ts +30 -0
- package/dist/data/strategies.js +343 -0
- package/dist/data/strategies.js.map +1 -0
- package/dist/data/types.d.ts +73 -0
- package/dist/data/types.js +8 -0
- package/dist/data/types.js.map +1 -0
- package/dist/db/database.d.ts +14 -142
- package/dist/db/database.js +209 -435
- package/dist/db/database.js.map +1 -1
- package/dist/db/export.d.ts +2 -2
- package/dist/db/export.js +7 -2
- package/dist/db/export.js.map +1 -1
- package/dist/db/filters.d.ts +22 -0
- package/dist/db/filters.js +158 -0
- package/dist/db/filters.js.map +1 -0
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.js +25 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/introspection.d.ts +6 -0
- package/dist/db/introspection.js +172 -0
- package/dist/db/introspection.js.map +1 -0
- package/dist/db/manager.d.ts +32 -19
- package/dist/db/manager.js +203 -44
- package/dist/db/manager.js.map +1 -1
- package/dist/db/migrations.d.ts +5 -0
- package/dist/db/migrations.js +110 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/postgres.d.ts +85 -0
- package/dist/db/postgres.js +745 -0
- package/dist/db/postgres.js.map +1 -0
- package/dist/db/types.d.ts +206 -0
- package/dist/db/types.js +7 -0
- package/dist/db/types.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +46 -4
- package/dist/index.js.map +1 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/css/input.css +83 -13
- package/dist/public/js/app.js +142 -0
- package/dist/public/js/browse.js +591 -104
- package/dist/public/js/databases.js +105 -13
- package/dist/public/js/designer.js +40 -2
- package/dist/public/js/forms.js +610 -28
- package/dist/public/js/icons.js +51 -0
- package/dist/public/js/inspector.js +638 -0
- package/dist/public/js/query.js +93 -3
- package/dist/public/js/schema.js +170 -52
- package/dist/public/js/seed.js +1090 -0
- package/dist/routes/api/helpers.d.ts +42 -0
- package/dist/routes/api/helpers.js +133 -0
- package/dist/routes/api/helpers.js.map +1 -0
- package/dist/routes/api/import-export.d.ts +3 -0
- package/dist/routes/api/import-export.js +85 -0
- package/dist/routes/api/import-export.js.map +1 -0
- package/dist/routes/api/index.d.ts +4 -0
- package/dist/routes/api/index.js +37 -0
- package/dist/routes/api/index.js.map +1 -0
- package/dist/routes/api/query.d.ts +3 -0
- package/dist/routes/api/query.js +76 -0
- package/dist/routes/api/query.js.map +1 -0
- package/dist/routes/api/rows.d.ts +3 -0
- package/dist/routes/api/rows.js +373 -0
- package/dist/routes/api/rows.js.map +1 -0
- package/dist/routes/api/seed.d.ts +3 -0
- package/dist/routes/api/seed.js +82 -0
- package/dist/routes/api/seed.js.map +1 -0
- package/dist/routes/api/tables.d.ts +3 -0
- package/dist/routes/api/tables.js +160 -0
- package/dist/routes/api/tables.js.map +1 -0
- package/dist/routes/databases.d.ts +8 -11
- package/dist/routes/databases.js +100 -66
- package/dist/routes/databases.js.map +1 -1
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.js +20 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/pages.d.ts +3 -3
- package/dist/routes/pages.js +163 -54
- package/dist/routes/pages.js.map +1 -1
- package/dist/serverless.d.ts +40 -0
- package/dist/serverless.js +210 -0
- package/dist/serverless.js.map +1 -0
- package/dist/sql/generator.d.ts +4 -3
- package/dist/sql/generator.js +103 -6
- package/dist/sql/generator.js.map +1 -1
- package/dist/sql/index.d.ts +2 -0
- package/dist/sql/index.js +19 -0
- package/dist/sql/index.js.map +1 -0
- package/dist/types/api.d.ts +412 -0
- package/dist/types/api.js +9 -0
- package/dist/types/api.js.map +1 -0
- package/dist/utils/colors.d.ts +40 -0
- package/dist/utils/colors.js +64 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/{util.d.ts → utils/common.d.ts} +10 -6
- package/dist/{util.js → utils/common.js} +80 -13
- package/dist/utils/common.js.map +1 -0
- package/dist/utils/csv.js.map +1 -0
- package/dist/utils/datatype.d.ts +49 -0
- package/dist/utils/datatype.js +222 -0
- package/dist/utils/datatype.js.map +1 -0
- package/dist/utils/icons.d.ts +11 -0
- package/dist/utils/icons.js +72 -0
- package/dist/utils/icons.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +21 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/{logger.d.ts → utils/logger.d.ts} +1 -1
- package/dist/utils/logger.js +40 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/views/layouts/main.hbs +33 -11
- package/dist/views/pages/databases.hbs +182 -74
- package/dist/views/pages/designer.hbs +9 -6
- package/dist/views/pages/error.hbs +3 -3
- package/dist/views/pages/form.hbs +5 -3
- package/dist/views/pages/home.hbs +22 -13
- package/dist/views/pages/login.hbs +111 -0
- package/dist/views/pages/query.hbs +10 -8
- package/dist/views/pages/schema.hbs +41 -13
- package/dist/views/pages/seed.hbs +232 -0
- package/dist/views/pages/table.hbs +155 -70
- package/dist/views/partials/icon.hbs +2 -0
- package/dist/views/partials/navbar.hbs +70 -31
- package/dist/views/partials/sidebar.hbs +108 -99
- package/package.json +12 -5
- package/dist/args.d.ts +0 -28
- package/dist/args.js +0 -187
- package/dist/args.js.map +0 -1
- package/dist/config.d.ts +0 -17
- package/dist/config.js +0 -23
- package/dist/config.js.map +0 -1
- package/dist/csv.js.map +0 -1
- package/dist/logger.js +0 -25
- package/dist/logger.js.map +0 -1
- package/dist/routes/api.d.ts +0 -9
- package/dist/routes/api.js +0 -786
- package/dist/routes/api.js.map +0 -1
- package/dist/test/classifier.test.d.ts +0 -1
- package/dist/test/classifier.test.js +0 -44
- package/dist/test/classifier.test.js.map +0 -1
- package/dist/test/csv.test.d.ts +0 -1
- package/dist/test/csv.test.js +0 -64
- package/dist/test/csv.test.js.map +0 -1
- package/dist/test/database.test.d.ts +0 -1
- package/dist/test/database.test.js +0 -402
- package/dist/test/database.test.js.map +0 -1
- package/dist/test/generator.test.d.ts +0 -1
- package/dist/test/generator.test.js +0 -132
- package/dist/test/generator.test.js.map +0 -1
- package/dist/test/manager.test.d.ts +0 -1
- package/dist/test/manager.test.js +0 -146
- package/dist/test/manager.test.js.map +0 -1
- package/dist/util.js.map +0 -1
- /package/dist/{csv.d.ts → utils/csv.d.ts} +0 -0
- /package/dist/{csv.js → utils/csv.js} +0 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
/* Rich Universal Data Inspector Modal for AdminDB.
|
|
2
|
+
* Provides specialized JSON Tree/Editor, BLOB Media Preview, 3-Column Hex Dump,
|
|
3
|
+
* and Code/Text inspectors with real-time editing, formatting, and file upload/download.
|
|
4
|
+
*/
|
|
5
|
+
(function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
function escapeHtml(s) {
|
|
9
|
+
return (window.UI && UI.escapeHtml) ? UI.escapeHtml(s) : String(s == null ? '' : s);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function formatBytes(bytes) {
|
|
13
|
+
if (bytes === 0) return '0 B';
|
|
14
|
+
var k = 1024;
|
|
15
|
+
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
16
|
+
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
17
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isJson(str) {
|
|
21
|
+
if (typeof str !== 'string') return false;
|
|
22
|
+
var t = str.trim();
|
|
23
|
+
if (!((t.startsWith('{') && t.endsWith('}')) || (t.startsWith('[') && t.endsWith(']')))) return false;
|
|
24
|
+
try {
|
|
25
|
+
JSON.parse(t);
|
|
26
|
+
return true;
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isHexBlob(str) {
|
|
33
|
+
return typeof str === 'string' && (/^0x[0-9a-f]{4,}$/i.test(str.trim()) || /^\\x[0-9a-f]{4,}$/i.test(str.trim()));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// ---- JSON Tree Renderer --------------------------------------------------
|
|
38
|
+
|
|
39
|
+
function buildJsonTree(val, keyName, isRoot) {
|
|
40
|
+
var item = document.createElement('div');
|
|
41
|
+
item.className = 'json-tree-node font-mono text-[13px] leading-relaxed my-0.5';
|
|
42
|
+
|
|
43
|
+
var keyLabel = keyName !== undefined
|
|
44
|
+
? '<span class="json-key text-primary font-semibold select-text">' + escapeHtml(keyName) + '</span><span class="text-base-content/40 mr-1.5">:</span>'
|
|
45
|
+
: '';
|
|
46
|
+
|
|
47
|
+
if (val === null) {
|
|
48
|
+
item.innerHTML = keyLabel + '<span class="chip chip-null text-xs font-bold">null</span>';
|
|
49
|
+
return item;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof val === 'boolean') {
|
|
53
|
+
item.innerHTML = keyLabel + '<span class="text-secondary font-bold select-text">' + val + '</span>';
|
|
54
|
+
return item;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof val === 'number') {
|
|
58
|
+
item.innerHTML = keyLabel + '<span class="text-accent font-mono font-medium select-text">' + val + '</span>';
|
|
59
|
+
return item;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (typeof val === 'string') {
|
|
63
|
+
item.innerHTML = keyLabel + '<span class="text-success select-text break-all">"' + escapeHtml(val) + '"</span>';
|
|
64
|
+
return item;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (Array.isArray(val)) {
|
|
68
|
+
var isExpanded = isRoot || val.length <= 10;
|
|
69
|
+
var arrHeader = document.createElement('div');
|
|
70
|
+
arrHeader.className = 'flex items-center gap-1 cursor-pointer hover:bg-base-200/50 rounded px-1 -mx-1';
|
|
71
|
+
arrHeader.innerHTML =
|
|
72
|
+
'<button type="button" class="json-toggle text-base-content/40 hover:text-base-content text-[11px] w-4 text-center">' + (isExpanded ? '▼' : '▶') + '</button>' +
|
|
73
|
+
keyLabel +
|
|
74
|
+
'<span class="text-base-content/60 font-semibold">[</span>' +
|
|
75
|
+
'<span class="text-xs text-base-content/40 tabular-nums">' + val.length + ' item' + (val.length === 1 ? '' : 's') + '</span>' +
|
|
76
|
+
'<span class="text-base-content/60 font-semibold">' + (isExpanded ? '' : ' ... ]') + '</span>';
|
|
77
|
+
|
|
78
|
+
var arrBody = document.createElement('div');
|
|
79
|
+
arrBody.className = 'pl-5 border-l border-base-300/60 ml-2 space-y-0.5 ' + (isExpanded ? '' : 'hidden');
|
|
80
|
+
|
|
81
|
+
val.forEach(function (child, idx) {
|
|
82
|
+
arrBody.appendChild(buildJsonTree(child, String(idx), false));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (isExpanded) {
|
|
86
|
+
var closing = document.createElement('div');
|
|
87
|
+
closing.className = 'text-base-content/60 font-semibold pl-1';
|
|
88
|
+
closing.textContent = ']';
|
|
89
|
+
arrBody.appendChild(closing);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
arrHeader.addEventListener('click', function () {
|
|
93
|
+
var open = !arrBody.classList.contains('hidden');
|
|
94
|
+
arrBody.classList.toggle('hidden', open);
|
|
95
|
+
arrHeader.querySelector('.json-toggle').textContent = open ? '▶' : '▼';
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
item.appendChild(arrHeader);
|
|
99
|
+
item.appendChild(arrBody);
|
|
100
|
+
return item;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (typeof val === 'object') {
|
|
104
|
+
var keys = Object.keys(val);
|
|
105
|
+
var isObjExpanded = isRoot || keys.length <= 10;
|
|
106
|
+
var objHeader = document.createElement('div');
|
|
107
|
+
objHeader.className = 'flex items-center gap-1 cursor-pointer hover:bg-base-200/50 rounded px-1 -mx-1';
|
|
108
|
+
objHeader.innerHTML =
|
|
109
|
+
'<button type="button" class="json-toggle text-base-content/40 hover:text-base-content text-[11px] w-4 text-center">' + (isObjExpanded ? '▼' : '▶') + '</button>' +
|
|
110
|
+
keyLabel +
|
|
111
|
+
'<span class="text-base-content/60 font-semibold">{</span>' +
|
|
112
|
+
'<span class="text-xs text-base-content/40 tabular-nums">' + keys.length + ' key' + (keys.length === 1 ? '' : 's') + '</span>' +
|
|
113
|
+
'<span class="text-base-content/60 font-semibold">' + (isObjExpanded ? '' : ' ... }') + '</span>';
|
|
114
|
+
|
|
115
|
+
var objBody = document.createElement('div');
|
|
116
|
+
objBody.className = 'pl-5 border-l border-base-300/60 ml-2 space-y-0.5 ' + (isObjExpanded ? '' : 'hidden');
|
|
117
|
+
|
|
118
|
+
keys.forEach(function (k) {
|
|
119
|
+
objBody.appendChild(buildJsonTree(val[k], k, false));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (isObjExpanded) {
|
|
123
|
+
var objClosing = document.createElement('div');
|
|
124
|
+
objClosing.className = 'text-base-content/60 font-semibold pl-1';
|
|
125
|
+
objClosing.textContent = '}';
|
|
126
|
+
objBody.appendChild(objClosing);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
objHeader.addEventListener('click', function () {
|
|
130
|
+
var open = !objBody.classList.contains('hidden');
|
|
131
|
+
objBody.classList.toggle('hidden', open);
|
|
132
|
+
objHeader.querySelector('.json-toggle').textContent = open ? '▶' : '▼';
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
item.appendChild(objHeader);
|
|
136
|
+
item.appendChild(objBody);
|
|
137
|
+
return item;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
item.innerHTML = keyLabel + '<span class="text-base-content select-text">' + escapeHtml(String(val)) + '</span>';
|
|
141
|
+
return item;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---- Client-side Hex Dump Generator (Fallback) ---------------------------
|
|
145
|
+
|
|
146
|
+
function generateClientHexDump(bytes, maxBytes) {
|
|
147
|
+
maxBytes = maxBytes || 4096;
|
|
148
|
+
var len = Math.min(bytes.length, maxBytes);
|
|
149
|
+
var lines = [];
|
|
150
|
+
for (var i = 0; i < len; i += 16) {
|
|
151
|
+
var offset = ('00000000' + i.toString(16)).slice(-8);
|
|
152
|
+
var hexArr = [];
|
|
153
|
+
var asciiArr = [];
|
|
154
|
+
for (var j = 0; j < 16; j++) {
|
|
155
|
+
if (i + j < len) {
|
|
156
|
+
var b = bytes[i + j];
|
|
157
|
+
hexArr.push(('00' + b.toString(16)).slice(-2));
|
|
158
|
+
asciiArr.push(b >= 32 && b <= 126 ? String.fromCharCode(b) : '.');
|
|
159
|
+
} else {
|
|
160
|
+
hexArr.push(' ');
|
|
161
|
+
asciiArr.push(' ');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
var hexStr = hexArr.slice(0, 8).join(' ') + ' ' + hexArr.slice(8).join(' ');
|
|
165
|
+
lines.push({ offset: offset, hex: hexStr, ascii: asciiArr.join('') });
|
|
166
|
+
}
|
|
167
|
+
return { lines: lines, totalBytes: bytes.length, truncated: bytes.length > maxBytes };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function hexToUint8Array(hex) {
|
|
171
|
+
hex = hex.replace(/^0x/i, '').replace(/\s+/g, '');
|
|
172
|
+
var arr = new Uint8Array(hex.length / 2);
|
|
173
|
+
for (var i = 0; i < hex.length; i += 2) {
|
|
174
|
+
arr[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
175
|
+
}
|
|
176
|
+
return arr;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---- Inspector Modal -----------------------------------------------------
|
|
180
|
+
|
|
181
|
+
function openInspector(opts) {
|
|
182
|
+
var table = opts.table || '';
|
|
183
|
+
var pk = opts.pk || '';
|
|
184
|
+
var col = opts.col || '';
|
|
185
|
+
var colType = (opts.colType || '').toUpperCase();
|
|
186
|
+
var val = opts.value;
|
|
187
|
+
var isNull = opts.isNull || val === null || val === undefined;
|
|
188
|
+
var readonly = !!opts.readonly;
|
|
189
|
+
var onSave = opts.onSave; // callback(newValue)
|
|
190
|
+
|
|
191
|
+
var valStr = isNull ? '' : String(val);
|
|
192
|
+
var isBlob = opts.isBlob || colType.includes('BLOB') || isHexBlob(valStr);
|
|
193
|
+
var isJsonVal = opts.isJson || isJson(valStr);
|
|
194
|
+
|
|
195
|
+
var initialTab = isJsonVal ? 'json' : (isBlob ? 'blob' : 'text');
|
|
196
|
+
|
|
197
|
+
var overlay = document.createElement('div');
|
|
198
|
+
overlay.className = 'fixed inset-0 z-[120] flex items-center justify-center bg-black/70 backdrop-blur-xs p-3 md:p-6 opacity-0 transition-opacity duration-200';
|
|
199
|
+
overlay.setAttribute('role', 'dialog');
|
|
200
|
+
overlay.setAttribute('aria-modal', 'true');
|
|
201
|
+
|
|
202
|
+
var card = document.createElement('div');
|
|
203
|
+
card.className = 'app-card flex flex-col w-full max-w-4xl max-h-[92vh] overflow-hidden shadow-2xl border border-base-300 transform scale-95 transition-transform duration-200 bg-base-100';
|
|
204
|
+
|
|
205
|
+
// Header
|
|
206
|
+
var header = document.createElement('div');
|
|
207
|
+
header.className = 'flex items-center justify-between gap-3 border-b border-base-200 px-5 py-3.5 bg-base-200/30';
|
|
208
|
+
header.innerHTML =
|
|
209
|
+
'<div class="flex items-center gap-3 min-w-0">' +
|
|
210
|
+
' <span class="grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-primary/10 text-primary font-mono font-bold text-xs">' +
|
|
211
|
+
(isBlob ? '🗃️' : (isJsonVal ? '{ }' : '📝')) +
|
|
212
|
+
' </span>' +
|
|
213
|
+
' <div class="min-w-0">' +
|
|
214
|
+
' <div class="flex items-center gap-2">' +
|
|
215
|
+
' <h3 class="text-base font-bold text-base-content truncate">' + escapeHtml(col) + '</h3>' +
|
|
216
|
+
' <span class="chip chip-type text-[10px]">' + escapeHtml(colType || (isBlob ? 'BLOB' : (isJsonVal ? 'JSON' : 'TEXT'))) + '</span>' +
|
|
217
|
+
(isNull ? '<span class="chip chip-null">NULL</span>' : '') +
|
|
218
|
+
' </div>' +
|
|
219
|
+
' <p class="text-xs text-base-content/50 truncate">Table <span class="font-mono font-semibold">' + escapeHtml(table) + '</span>' + (pk ? ' · PK <span class="font-mono">' + escapeHtml(pk) + '</span>' : '') + '</p>' +
|
|
220
|
+
' </div>' +
|
|
221
|
+
'</div>' +
|
|
222
|
+
'<div class="flex items-center gap-2">' +
|
|
223
|
+
' <button type="button" class="btn btn-ghost btn-xs btn-circle js-inspector-close text-base-content/40 hover:text-base-content" aria-label="Close">×</button>' +
|
|
224
|
+
'</div>';
|
|
225
|
+
card.appendChild(header);
|
|
226
|
+
|
|
227
|
+
// Tab Bar
|
|
228
|
+
var tabBar = document.createElement('div');
|
|
229
|
+
tabBar.className = 'flex items-center justify-between border-b border-base-200 bg-base-200/20 px-5 py-2';
|
|
230
|
+
tabBar.innerHTML =
|
|
231
|
+
'<div class="flex gap-1" id="inspector-tabs">' +
|
|
232
|
+
(isJsonVal || (!isBlob && valStr.length > 0) ? '<button type="button" data-tab="json" class="btn btn-xs ' + (initialTab === 'json' ? 'btn-primary' : 'btn-ghost') + ' gap-1">🧩 JSON Tree & Editor</button>' : '') +
|
|
233
|
+
(isBlob ? '<button type="button" data-tab="blob" class="btn btn-xs ' + (initialTab === 'blob' ? 'btn-primary' : 'btn-ghost') + ' gap-1">🖼️ Media & Hex Dump</button>' : '') +
|
|
234
|
+
'<button type="button" data-tab="text" class="btn btn-xs ' + (initialTab === 'text' ? 'btn-primary' : 'btn-ghost') + ' gap-1">📝 Raw Text / Code</button>' +
|
|
235
|
+
'</div>' +
|
|
236
|
+
'<div id="tab-meta" class="text-xs text-base-content/50 font-mono"></div>';
|
|
237
|
+
card.appendChild(tabBar);
|
|
238
|
+
|
|
239
|
+
// Content Body Container
|
|
240
|
+
var body = document.createElement('div');
|
|
241
|
+
body.className = 'flex-1 overflow-y-auto p-5 space-y-4 min-h-[350px]';
|
|
242
|
+
card.appendChild(body);
|
|
243
|
+
|
|
244
|
+
// Footer
|
|
245
|
+
var footer = document.createElement('div');
|
|
246
|
+
footer.className = 'flex flex-wrap items-center justify-between gap-3 border-t border-base-200 bg-base-200/30 px-5 py-3.5';
|
|
247
|
+
footer.innerHTML =
|
|
248
|
+
'<div class="flex items-center gap-2" id="inspector-left-actions"></div>' +
|
|
249
|
+
'<div class="flex items-center gap-2">' +
|
|
250
|
+
' <button type="button" class="btn btn-ghost btn-sm js-inspector-close">Close</button>' +
|
|
251
|
+
(!readonly ? ' <button type="button" id="inspector-save-btn" class="btn btn-primary btn-sm gap-1.5">Save Changes</button>' : '') +
|
|
252
|
+
'</div>';
|
|
253
|
+
card.appendChild(footer);
|
|
254
|
+
|
|
255
|
+
overlay.appendChild(card);
|
|
256
|
+
document.body.appendChild(overlay);
|
|
257
|
+
|
|
258
|
+
requestAnimationFrame(function () {
|
|
259
|
+
overlay.classList.remove('opacity-0');
|
|
260
|
+
overlay.classList.add('opacity-100');
|
|
261
|
+
card.classList.remove('scale-95');
|
|
262
|
+
card.classList.add('scale-100');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
var tabMeta = card.querySelector('#tab-meta');
|
|
266
|
+
var leftActions = card.querySelector('#inspector-left-actions');
|
|
267
|
+
var saveBtn = card.querySelector('#inspector-save-btn');
|
|
268
|
+
|
|
269
|
+
var currentVal = valStr;
|
|
270
|
+
var currentTab = initialTab;
|
|
271
|
+
|
|
272
|
+
// ---- Render Functions ----
|
|
273
|
+
|
|
274
|
+
function renderJsonTab() {
|
|
275
|
+
body.innerHTML = '';
|
|
276
|
+
leftActions.innerHTML = '';
|
|
277
|
+
tabMeta.textContent = '';
|
|
278
|
+
|
|
279
|
+
var isParsed = false;
|
|
280
|
+
var parsedObj = null;
|
|
281
|
+
try {
|
|
282
|
+
parsedObj = JSON.parse(currentVal);
|
|
283
|
+
isParsed = true;
|
|
284
|
+
} catch (e) {
|
|
285
|
+
isParsed = false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!isParsed) {
|
|
289
|
+
body.innerHTML =
|
|
290
|
+
'<div class="alert alert-warning text-xs p-3">' +
|
|
291
|
+
'<span>Content is not valid JSON. You can edit raw text or format it.</span>' +
|
|
292
|
+
'</div>' +
|
|
293
|
+
'<textarea id="json-raw-input" class="field-input font-mono text-[13px] h-72 w-full resize-none p-3.5" spellcheck="false">' + escapeHtml(currentVal) + '</textarea>';
|
|
294
|
+
var ta = body.querySelector('#json-raw-input');
|
|
295
|
+
ta.addEventListener('input', function () { currentVal = ta.value; });
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
var stats = 'Size: ' + formatBytes(new Blob([currentVal]).size);
|
|
300
|
+
tabMeta.textContent = stats;
|
|
301
|
+
|
|
302
|
+
// Sub-views for JSON: Tree View vs Formatted Textarea Editor
|
|
303
|
+
var container = document.createElement('div');
|
|
304
|
+
container.className = 'space-y-3';
|
|
305
|
+
|
|
306
|
+
var toolbar = document.createElement('div');
|
|
307
|
+
toolbar.className = 'flex flex-wrap items-center justify-between gap-2 pb-2 border-b border-base-200';
|
|
308
|
+
toolbar.innerHTML =
|
|
309
|
+
'<div class="flex items-center gap-1.5">' +
|
|
310
|
+
' <button type="button" id="btn-view-tree" class="btn btn-xs btn-outline active">Tree View</button>' +
|
|
311
|
+
' <button type="button" id="btn-view-code" class="btn btn-xs btn-outline">Code Editor</button>' +
|
|
312
|
+
'</div>' +
|
|
313
|
+
'<div class="flex items-center gap-1.5">' +
|
|
314
|
+
' <button type="button" id="btn-prettify-json" class="btn btn-ghost btn-xs">Beautify</button>' +
|
|
315
|
+
' <button type="button" id="btn-minify-json" class="btn btn-ghost btn-xs">Minify</button>' +
|
|
316
|
+
' <button type="button" id="btn-copy-json" class="btn btn-ghost btn-xs gap-1">Copy JSON</button>' +
|
|
317
|
+
'</div>';
|
|
318
|
+
container.appendChild(toolbar);
|
|
319
|
+
|
|
320
|
+
var treeViewEl = document.createElement('div');
|
|
321
|
+
treeViewEl.className = 'rounded-xl border border-base-300 bg-base-200/30 p-4 max-h-[420px] overflow-y-auto';
|
|
322
|
+
treeViewEl.appendChild(buildJsonTree(parsedObj, undefined, true));
|
|
323
|
+
container.appendChild(treeViewEl);
|
|
324
|
+
|
|
325
|
+
var editorViewEl = document.createElement('div');
|
|
326
|
+
editorViewEl.className = 'hidden space-y-2';
|
|
327
|
+
editorViewEl.innerHTML =
|
|
328
|
+
'<textarea id="json-editor-ta" class="field-input font-mono text-[13px] h-96 w-full resize-none p-3.5" spellcheck="false">' +
|
|
329
|
+
escapeHtml(JSON.stringify(parsedObj, null, 2)) +
|
|
330
|
+
'</textarea>' +
|
|
331
|
+
'<div id="json-validation-msg" class="text-xs font-medium text-success">✓ Valid JSON</div>';
|
|
332
|
+
container.appendChild(editorViewEl);
|
|
333
|
+
|
|
334
|
+
body.appendChild(container);
|
|
335
|
+
|
|
336
|
+
// Wire JSON toolbar
|
|
337
|
+
var treeBtn = toolbar.querySelector('#btn-view-tree');
|
|
338
|
+
var codeBtn = toolbar.querySelector('#btn-view-code');
|
|
339
|
+
var editorTa = editorViewEl.querySelector('#json-editor-ta');
|
|
340
|
+
var validMsg = editorViewEl.querySelector('#json-validation-msg');
|
|
341
|
+
|
|
342
|
+
function setJsonSubView(view) {
|
|
343
|
+
if (view === 'tree') {
|
|
344
|
+
treeBtn.className = 'btn btn-xs btn-primary';
|
|
345
|
+
codeBtn.className = 'btn btn-xs btn-outline';
|
|
346
|
+
treeViewEl.classList.remove('hidden');
|
|
347
|
+
editorViewEl.classList.add('hidden');
|
|
348
|
+
} else {
|
|
349
|
+
codeBtn.className = 'btn btn-xs btn-primary';
|
|
350
|
+
treeBtn.className = 'btn btn-xs btn-outline';
|
|
351
|
+
treeViewEl.classList.add('hidden');
|
|
352
|
+
editorViewEl.classList.remove('hidden');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
setJsonSubView('tree');
|
|
357
|
+
|
|
358
|
+
treeBtn.addEventListener('click', function () {
|
|
359
|
+
setJsonSubView('tree');
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
codeBtn.addEventListener('click', function () {
|
|
363
|
+
setJsonSubView('code');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
toolbar.querySelector('#btn-prettify-json').addEventListener('click', function () {
|
|
367
|
+
var raw = editorTa.value || currentVal;
|
|
368
|
+
try {
|
|
369
|
+
var p = JSON.parse(raw);
|
|
370
|
+
currentVal = JSON.stringify(p, null, 2);
|
|
371
|
+
editorTa.value = currentVal;
|
|
372
|
+
treeViewEl.innerHTML = '';
|
|
373
|
+
treeViewEl.appendChild(buildJsonTree(p, undefined, true));
|
|
374
|
+
validMsg.className = 'text-xs font-medium text-success';
|
|
375
|
+
validMsg.textContent = '✓ Beautified valid JSON';
|
|
376
|
+
setJsonSubView('code');
|
|
377
|
+
if (saveBtn) saveBtn.disabled = false;
|
|
378
|
+
UI.showToast('JSON beautified.', 'success');
|
|
379
|
+
} catch (e) {
|
|
380
|
+
validMsg.className = 'text-xs font-medium text-error';
|
|
381
|
+
validMsg.textContent = '❌ Syntax Error: ' + e.message;
|
|
382
|
+
UI.showToast('Invalid JSON: ' + e.message, 'error');
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
toolbar.querySelector('#btn-minify-json').addEventListener('click', function () {
|
|
387
|
+
var raw = editorTa.value || currentVal;
|
|
388
|
+
try {
|
|
389
|
+
var p = JSON.parse(raw);
|
|
390
|
+
currentVal = JSON.stringify(p);
|
|
391
|
+
editorTa.value = currentVal;
|
|
392
|
+
treeViewEl.innerHTML = '';
|
|
393
|
+
treeViewEl.appendChild(buildJsonTree(p, undefined, true));
|
|
394
|
+
validMsg.className = 'text-xs font-medium text-success';
|
|
395
|
+
validMsg.textContent = '✓ Minified valid JSON';
|
|
396
|
+
setJsonSubView('code');
|
|
397
|
+
if (saveBtn) saveBtn.disabled = false;
|
|
398
|
+
UI.showToast('JSON minified.', 'success');
|
|
399
|
+
} catch (e) {
|
|
400
|
+
validMsg.className = 'text-xs font-medium text-error';
|
|
401
|
+
validMsg.textContent = '❌ Syntax Error: ' + e.message;
|
|
402
|
+
UI.showToast('Invalid JSON: ' + e.message, 'error');
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
toolbar.querySelector('#btn-copy-json').addEventListener('click', function () {
|
|
407
|
+
var raw = editorTa.value || currentVal;
|
|
408
|
+
navigator.clipboard.writeText(raw).then(function () {
|
|
409
|
+
UI.showToast('JSON copied to clipboard.', 'success');
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
editorTa.addEventListener('input', function () {
|
|
414
|
+
currentVal = editorTa.value;
|
|
415
|
+
try {
|
|
416
|
+
JSON.parse(currentVal);
|
|
417
|
+
validMsg.className = 'text-xs font-medium text-success';
|
|
418
|
+
validMsg.textContent = '✓ Valid JSON';
|
|
419
|
+
if (saveBtn) saveBtn.disabled = false;
|
|
420
|
+
} catch (e) {
|
|
421
|
+
validMsg.className = 'text-xs font-medium text-error';
|
|
422
|
+
validMsg.textContent = '❌ Syntax Error: ' + e.message;
|
|
423
|
+
if (saveBtn) saveBtn.disabled = true;
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function renderBlobTab() {
|
|
429
|
+
body.innerHTML = '<div class="py-12 text-center text-sm text-base-content/40 font-mono">Loading BLOB data...</div>';
|
|
430
|
+
leftActions.innerHTML = '';
|
|
431
|
+
tabMeta.textContent = '';
|
|
432
|
+
|
|
433
|
+
var blobEndpoint = (window.APP && window.APP.basePath || '') + '/api/tables/' + encodeURIComponent(table) + '/row/' + encodeURIComponent(pk) + '/blob/' + encodeURIComponent(col);
|
|
434
|
+
|
|
435
|
+
// Fetch metadata from API
|
|
436
|
+
Api.get(blobEndpoint + '/meta')
|
|
437
|
+
.then(function (res) {
|
|
438
|
+
var meta = res.data || {};
|
|
439
|
+
tabMeta.textContent = (meta.mime || 'BLOB') + ' · ' + (meta.sizeFormatted || '0 B');
|
|
440
|
+
|
|
441
|
+
body.innerHTML = '';
|
|
442
|
+
|
|
443
|
+
var container = document.createElement('div');
|
|
444
|
+
container.className = 'space-y-4';
|
|
445
|
+
|
|
446
|
+
// Media Preview Section (if image or audio)
|
|
447
|
+
if (meta.isImage) {
|
|
448
|
+
var imgWrap = document.createElement('div');
|
|
449
|
+
imgWrap.className = 'flex flex-col items-center justify-center p-4 rounded-xl border border-base-300 bg-base-200/40 min-h-[220px]';
|
|
450
|
+
imgWrap.innerHTML =
|
|
451
|
+
'<div class="relative group max-w-full max-h-[300px] overflow-hidden rounded-lg shadow-sm">' +
|
|
452
|
+
' <img src="' + blobEndpoint + '" alt="' + escapeHtml(col) + '" class="max-h-[280px] max-w-full object-contain rounded-lg transition-transform" id="blob-preview-img" />' +
|
|
453
|
+
'</div>' +
|
|
454
|
+
'<div class="mt-3 flex items-center gap-3 text-xs text-base-content/60 font-mono">' +
|
|
455
|
+
' <span>Format: <b>' + escapeHtml(meta.ext.toUpperCase()) + '</b></span>' +
|
|
456
|
+
' <span>Size: <b>' + escapeHtml(meta.sizeFormatted) + '</b></span>' +
|
|
457
|
+
' <a href="' + blobEndpoint + '" target="_blank" class="btn btn-ghost btn-xs text-primary">Open in new tab ↗</a>' +
|
|
458
|
+
'</div>';
|
|
459
|
+
container.appendChild(imgWrap);
|
|
460
|
+
} else if (meta.isAudio) {
|
|
461
|
+
var audioWrap = document.createElement('div');
|
|
462
|
+
audioWrap.className = 'flex flex-col items-center justify-center p-6 rounded-xl border border-base-300 bg-base-200/40';
|
|
463
|
+
audioWrap.innerHTML =
|
|
464
|
+
'<audio controls class="w-full max-w-md mb-2"><source src="' + blobEndpoint + '" type="' + escapeHtml(meta.mime) + '"></audio>' +
|
|
465
|
+
'<div class="text-xs text-base-content/50 font-mono">' + escapeHtml(meta.mime) + ' · ' + escapeHtml(meta.sizeFormatted) + '</div>';
|
|
466
|
+
container.appendChild(audioWrap);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// 3-Column Hex Dump Viewer
|
|
470
|
+
var hexWrap = document.createElement('div');
|
|
471
|
+
hexWrap.className = 'space-y-2';
|
|
472
|
+
|
|
473
|
+
var hexHeader = document.createElement('div');
|
|
474
|
+
hexHeader.className = 'flex items-center justify-between';
|
|
475
|
+
hexHeader.innerHTML =
|
|
476
|
+
'<h4 class="text-xs font-semibold uppercase tracking-wider text-base-content/50 font-mono">Hex Dump (Offset | Hex | ASCII)</h4>' +
|
|
477
|
+
'<button type="button" id="btn-copy-hex" class="btn btn-ghost btn-xs">Copy Hex</button>';
|
|
478
|
+
hexWrap.appendChild(hexHeader);
|
|
479
|
+
|
|
480
|
+
var hexPre = document.createElement('div');
|
|
481
|
+
hexPre.className = 'overflow-x-auto rounded-xl border border-base-300 bg-neutral p-3 font-mono text-xs leading-relaxed text-neutral-content max-h-[300px] select-text';
|
|
482
|
+
|
|
483
|
+
var hexDump = meta.hexDump;
|
|
484
|
+
if (hexDump && hexDump.lines && hexDump.lines.length) {
|
|
485
|
+
var linesHtml = hexDump.lines.map(function (line) {
|
|
486
|
+
return '<div class="grid grid-cols-[80px_1fr_150px] gap-3 hover:bg-white/[0.06] px-1 rounded">' +
|
|
487
|
+
'<span class="text-primary/70 select-none">' + line.offset + '</span>' +
|
|
488
|
+
'<span class="text-accent/90">' + escapeHtml(line.hex) + '</span>' +
|
|
489
|
+
'<span class="text-neutral-content/70 border-l border-white/10 pl-2">' + escapeHtml(line.ascii) + '</span>' +
|
|
490
|
+
'</div>';
|
|
491
|
+
}).join('');
|
|
492
|
+
hexPre.innerHTML = linesHtml;
|
|
493
|
+
} else {
|
|
494
|
+
hexPre.innerHTML = '<div class="text-center text-neutral-content/40 py-4">No binary content</div>';
|
|
495
|
+
}
|
|
496
|
+
hexWrap.appendChild(hexPre);
|
|
497
|
+
container.appendChild(hexWrap);
|
|
498
|
+
|
|
499
|
+
body.appendChild(container);
|
|
500
|
+
|
|
501
|
+
// Wire Copy Hex
|
|
502
|
+
hexHeader.querySelector('#btn-copy-hex').addEventListener('click', function () {
|
|
503
|
+
if (valStr.startsWith('0x') || valStr.startsWith('0X')) {
|
|
504
|
+
navigator.clipboard.writeText(valStr).then(function () {
|
|
505
|
+
UI.showToast('Hex data copied.', 'success');
|
|
506
|
+
});
|
|
507
|
+
} else {
|
|
508
|
+
UI.showToast('Hex copied.', 'success');
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// Download & Upload Buttons in Action Bar
|
|
513
|
+
leftActions.innerHTML =
|
|
514
|
+
'<a href="' + blobEndpoint + '?download=1" class="btn btn-outline btn-sm gap-1.5">' +
|
|
515
|
+
' <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>' +
|
|
516
|
+
' Download File' +
|
|
517
|
+
'</a>' +
|
|
518
|
+
(!readonly ?
|
|
519
|
+
'<label class="btn btn-outline btn-sm gap-1.5 cursor-pointer">' +
|
|
520
|
+
' <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" /></svg>' +
|
|
521
|
+
' Replace File' +
|
|
522
|
+
' <input type="file" id="blob-file-upload-input" class="hidden" />' +
|
|
523
|
+
'</label>' : '');
|
|
524
|
+
|
|
525
|
+
var uploadInput = leftActions.querySelector('#blob-file-upload-input');
|
|
526
|
+
if (uploadInput) {
|
|
527
|
+
uploadInput.addEventListener('change', function (e) {
|
|
528
|
+
var file = e.target.files && e.target.files[0];
|
|
529
|
+
if (!file) return;
|
|
530
|
+
var reader = new FileReader();
|
|
531
|
+
reader.onload = function () {
|
|
532
|
+
var base64 = reader.result;
|
|
533
|
+
Api.put(blobEndpoint, { data: base64, format: 'base64' })
|
|
534
|
+
.then(function () {
|
|
535
|
+
UI.showToast('BLOB file uploaded successfully.', 'success');
|
|
536
|
+
renderBlobTab();
|
|
537
|
+
if (onSave) onSave('0x...');
|
|
538
|
+
})
|
|
539
|
+
.catch(function (err) {
|
|
540
|
+
UI.showError(err.message);
|
|
541
|
+
});
|
|
542
|
+
};
|
|
543
|
+
reader.readAsDataURL(file);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
})
|
|
547
|
+
.catch(function (err) {
|
|
548
|
+
body.innerHTML = '<div class="alert alert-error text-xs p-3">Failed to load BLOB metadata: ' + escapeHtml(err.message) + '</div>';
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function renderTextTab() {
|
|
553
|
+
body.innerHTML = '';
|
|
554
|
+
leftActions.innerHTML = '';
|
|
555
|
+
tabMeta.textContent = 'Length: ' + currentVal.length + ' chars';
|
|
556
|
+
|
|
557
|
+
var container = document.createElement('div');
|
|
558
|
+
container.className = 'space-y-3';
|
|
559
|
+
|
|
560
|
+
var toolbar = document.createElement('div');
|
|
561
|
+
toolbar.className = 'flex items-center justify-between pb-2 border-b border-base-200';
|
|
562
|
+
toolbar.innerHTML =
|
|
563
|
+
'<span class="text-xs text-base-content/50 font-mono">Lines: ' + (currentVal.split('\n').length) + '</span>' +
|
|
564
|
+
'<button type="button" id="btn-copy-text" class="btn btn-ghost btn-xs gap-1">Copy Text</button>';
|
|
565
|
+
container.appendChild(toolbar);
|
|
566
|
+
|
|
567
|
+
var textarea = document.createElement('textarea');
|
|
568
|
+
textarea.className = 'field-input font-mono text-[13px] h-80 w-full resize-none p-3.5';
|
|
569
|
+
textarea.spellcheck = false;
|
|
570
|
+
textarea.value = currentVal;
|
|
571
|
+
if (readonly) textarea.readOnly = true;
|
|
572
|
+
|
|
573
|
+
textarea.addEventListener('input', function () {
|
|
574
|
+
currentVal = textarea.value;
|
|
575
|
+
tabMeta.textContent = 'Length: ' + currentVal.length + ' chars';
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
container.appendChild(textarea);
|
|
579
|
+
body.appendChild(container);
|
|
580
|
+
|
|
581
|
+
toolbar.querySelector('#btn-copy-text').addEventListener('click', function () {
|
|
582
|
+
navigator.clipboard.writeText(currentVal).then(function () {
|
|
583
|
+
UI.showToast('Text copied to clipboard.', 'success');
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function switchTab(tab) {
|
|
589
|
+
currentTab = tab;
|
|
590
|
+
tabBar.querySelectorAll('#inspector-tabs button').forEach(function (btn) {
|
|
591
|
+
var active = btn.dataset.tab === tab;
|
|
592
|
+
btn.className = 'btn btn-xs ' + (active ? 'btn-primary' : 'btn-ghost') + ' gap-1';
|
|
593
|
+
});
|
|
594
|
+
if (tab === 'json') renderJsonTab();
|
|
595
|
+
else if (tab === 'blob') renderBlobTab();
|
|
596
|
+
else renderTextTab();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
tabBar.querySelectorAll('#inspector-tabs button').forEach(function (btn) {
|
|
600
|
+
btn.addEventListener('click', function () {
|
|
601
|
+
switchTab(btn.dataset.tab);
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
switchTab(initialTab);
|
|
606
|
+
|
|
607
|
+
// Close Handler
|
|
608
|
+
function close() {
|
|
609
|
+
overlay.classList.remove('opacity-100');
|
|
610
|
+
overlay.classList.add('opacity-0');
|
|
611
|
+
card.classList.remove('scale-100');
|
|
612
|
+
card.classList.add('scale-95');
|
|
613
|
+
setTimeout(function () { overlay.remove(); }, 180);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
card.querySelectorAll('.js-inspector-close').forEach(function (btn) {
|
|
617
|
+
btn.addEventListener('click', close);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
overlay.addEventListener('click', function (e) {
|
|
621
|
+
if (e.target === overlay) close();
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// Save Changes Handler
|
|
625
|
+
if (saveBtn) {
|
|
626
|
+
saveBtn.addEventListener('click', function () {
|
|
627
|
+
if (onSave) {
|
|
628
|
+
onSave(currentVal);
|
|
629
|
+
}
|
|
630
|
+
close();
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
window.Inspector = {
|
|
636
|
+
open: openInspector,
|
|
637
|
+
};
|
|
638
|
+
})();
|