what-devtools 0.5.5 → 0.7.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 +1 -1
- package/package.json +4 -4
- package/src/DevPanel.jsx +300 -59
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-devtools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Dev tools for What Framework — signal inspector, component tree, effect graph",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -20,16 +20,16 @@
|
|
|
20
20
|
"inspector"
|
|
21
21
|
],
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"what-core": "^0.
|
|
23
|
+
"what-core": "^0.7.0"
|
|
24
24
|
},
|
|
25
25
|
"author": "ZVN DEV (https://zvndev.com)",
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
29
|
-
"url": "https://github.com/CelsianJs/
|
|
29
|
+
"url": "https://github.com/CelsianJs/what-framework"
|
|
30
30
|
},
|
|
31
31
|
"bugs": {
|
|
32
|
-
"url": "https://github.com/CelsianJs/
|
|
32
|
+
"url": "https://github.com/CelsianJs/what-framework/issues"
|
|
33
33
|
},
|
|
34
34
|
"homepage": "https://whatfw.com"
|
|
35
35
|
}
|
package/src/DevPanel.jsx
CHANGED
|
@@ -4,57 +4,193 @@
|
|
|
4
4
|
* A drop-in floating UI panel that shows live signal values,
|
|
5
5
|
* active effects, and mounted components during development.
|
|
6
6
|
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Signal count, effect count, component count
|
|
9
|
+
* - Recent errors with structured output
|
|
10
|
+
* - Real-time signal watcher
|
|
11
|
+
* - Health indicator (green/yellow/red)
|
|
12
|
+
* - Toggle via Ctrl+Shift+D / Cmd+Shift+D
|
|
13
|
+
*
|
|
7
14
|
* Usage:
|
|
8
15
|
* import { DevPanel } from 'what-devtools/panel';
|
|
9
16
|
* // Add to your app:
|
|
10
17
|
* <DevPanel />
|
|
11
18
|
*
|
|
12
|
-
*
|
|
13
|
-
* when signals change.
|
|
19
|
+
* Works WITHOUT MCP devtools connected.
|
|
14
20
|
*/
|
|
15
21
|
|
|
16
22
|
import { signal, effect, onCleanup } from 'what-core';
|
|
17
|
-
import { subscribe, getSnapshot, installDevTools } from './index.js';
|
|
23
|
+
import { subscribe, getSnapshot, getErrors, installDevTools } from './index.js';
|
|
18
24
|
|
|
19
25
|
export function DevPanel() {
|
|
20
26
|
// Auto-install devtools if not already done
|
|
21
27
|
installDevTools();
|
|
22
28
|
|
|
23
29
|
const isOpen = signal(false);
|
|
24
|
-
const activeTab = signal('
|
|
30
|
+
const activeTab = signal('overview');
|
|
25
31
|
const snapshot = signal(getSnapshot());
|
|
32
|
+
const recentErrors = signal(getErrors());
|
|
26
33
|
|
|
27
34
|
// Subscribe to devtools events and refresh
|
|
28
|
-
const unsub = subscribe(() => {
|
|
35
|
+
const unsub = subscribe((event) => {
|
|
29
36
|
snapshot(getSnapshot());
|
|
37
|
+
if (event === 'error:captured') {
|
|
38
|
+
recentErrors(getErrors());
|
|
39
|
+
}
|
|
30
40
|
});
|
|
31
41
|
|
|
32
|
-
//
|
|
42
|
+
// Poll every 500ms for signal value changes (cheap -- just reads .peek())
|
|
33
43
|
const interval = setInterval(() => {
|
|
34
44
|
snapshot(getSnapshot());
|
|
45
|
+
recentErrors(getErrors());
|
|
35
46
|
}, 500);
|
|
36
47
|
|
|
48
|
+
// Keyboard shortcut: Ctrl+Shift+D / Cmd+Shift+D
|
|
49
|
+
const onKeyDown = (e) => {
|
|
50
|
+
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'D') {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
isOpen((v) => !v);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
if (typeof document !== 'undefined') {
|
|
56
|
+
document.addEventListener('keydown', onKeyDown);
|
|
57
|
+
}
|
|
58
|
+
|
|
37
59
|
onCleanup(() => {
|
|
38
60
|
unsub();
|
|
39
61
|
clearInterval(interval);
|
|
62
|
+
if (typeof document !== 'undefined') {
|
|
63
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
64
|
+
}
|
|
40
65
|
});
|
|
41
66
|
|
|
42
|
-
|
|
67
|
+
// --- Health indicator ---
|
|
68
|
+
const getHealth = () => {
|
|
69
|
+
const data = snapshot();
|
|
70
|
+
const errs = recentErrors();
|
|
71
|
+
const recentErrCount = errs.filter(
|
|
72
|
+
(e) => Date.now() - e.timestamp < 30000
|
|
73
|
+
).length;
|
|
74
|
+
|
|
75
|
+
if (recentErrCount > 0) return { color: '#ef4444', label: 'Errors detected' };
|
|
76
|
+
if (data.effects.length > 100) return { color: '#eab308', label: 'Many effects' };
|
|
77
|
+
if (data.signals.length > 200) return { color: '#eab308', label: 'Many signals' };
|
|
78
|
+
return { color: '#22c55e', label: 'Healthy' };
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const PANEL_STYLE =
|
|
82
|
+
'position:fixed;bottom:0;right:0;width:380px;max-height:55vh;z-index:99998;' +
|
|
83
|
+
'font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;' +
|
|
84
|
+
'background:#1a1a2e;color:#e0e0e0;border:1px solid #2a2a4a;' +
|
|
85
|
+
'border-radius:12px 0 0 0;box-shadow:0 -4px 24px rgba(0,0,0,0.3);' +
|
|
86
|
+
'display:flex;flex-direction:column;overflow:hidden;';
|
|
43
87
|
|
|
44
88
|
const tabStyle = (tab) => () => {
|
|
45
89
|
const isActive = activeTab() === tab;
|
|
46
|
-
return
|
|
90
|
+
return (
|
|
91
|
+
'padding:6px 10px;border:none;background:' +
|
|
92
|
+
(isActive ? '#2a2a4a' : 'transparent') +
|
|
93
|
+
';color:' +
|
|
94
|
+
(isActive ? '#fff' : '#6a6a8a') +
|
|
95
|
+
';cursor:pointer;font-family:inherit;font-size:11px;font-weight:600;border-radius:4px;'
|
|
96
|
+
);
|
|
47
97
|
};
|
|
48
98
|
|
|
99
|
+
// --- Tab: Overview ---
|
|
100
|
+
const renderOverview = () => {
|
|
101
|
+
const data = snapshot();
|
|
102
|
+
const health = getHealth();
|
|
103
|
+
const errs = recentErrors();
|
|
104
|
+
const recentErrs = errs.slice(-3).reverse();
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div style="padding:12px;">
|
|
108
|
+
{/* Health indicator */}
|
|
109
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;padding:8px;background:#0d0d1a;border-radius:6px;">
|
|
110
|
+
<div
|
|
111
|
+
style={() =>
|
|
112
|
+
'width:10px;height:10px;border-radius:50%;background:' +
|
|
113
|
+
getHealth().color +
|
|
114
|
+
';'
|
|
115
|
+
}
|
|
116
|
+
/>
|
|
117
|
+
<span style={() => 'color:' + getHealth().color + ';font-weight:600;'}>
|
|
118
|
+
{() => getHealth().label}
|
|
119
|
+
</span>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{/* Counts */}
|
|
123
|
+
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:12px;">
|
|
124
|
+
<div style="text-align:center;padding:8px;background:#0d0d1a;border-radius:6px;">
|
|
125
|
+
<div style="font-size:18px;font-weight:700;color:#818cf8;">
|
|
126
|
+
{() => snapshot().signals.length}
|
|
127
|
+
</div>
|
|
128
|
+
<div style="font-size:10px;color:#6a6a8a;margin-top:2px;">
|
|
129
|
+
Signals
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div style="text-align:center;padding:8px;background:#0d0d1a;border-radius:6px;">
|
|
133
|
+
<div style="font-size:18px;font-weight:700;color:#fbbf24;">
|
|
134
|
+
{() => snapshot().effects.length}
|
|
135
|
+
</div>
|
|
136
|
+
<div style="font-size:10px;color:#6a6a8a;margin-top:2px;">
|
|
137
|
+
Effects
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
<div style="text-align:center;padding:8px;background:#0d0d1a;border-radius:6px;">
|
|
141
|
+
<div style="font-size:18px;font-weight:700;color:#34d399;">
|
|
142
|
+
{() => snapshot().components.length}
|
|
143
|
+
</div>
|
|
144
|
+
<div style="font-size:10px;color:#6a6a8a;margin-top:2px;">
|
|
145
|
+
Components
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{/* Recent errors */}
|
|
151
|
+
{() => {
|
|
152
|
+
const errs = recentErrors();
|
|
153
|
+
if (errs.length === 0) return null;
|
|
154
|
+
const recent = errs.slice(-3).reverse();
|
|
155
|
+
return (
|
|
156
|
+
<div style="margin-top:8px;">
|
|
157
|
+
<div style="font-size:11px;font-weight:600;color:#f87171;margin-bottom:6px;">
|
|
158
|
+
Recent Errors ({errs.length})
|
|
159
|
+
</div>
|
|
160
|
+
{recent.map((e, i) => (
|
|
161
|
+
<div
|
|
162
|
+
key={i}
|
|
163
|
+
style="padding:6px 8px;background:#1c0a0a;border:1px solid #3b1219;border-radius:4px;margin-bottom:4px;font-size:11px;color:#fca5a5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;"
|
|
164
|
+
title={e.message}
|
|
165
|
+
>
|
|
166
|
+
<span style="color:#6a6a8a;">
|
|
167
|
+
[{e.type}]
|
|
168
|
+
</span>{' '}
|
|
169
|
+
{e.message}
|
|
170
|
+
</div>
|
|
171
|
+
))}
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
}}
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// --- Tab: Signals ---
|
|
49
180
|
const renderSignals = () => {
|
|
50
181
|
const data = snapshot();
|
|
51
182
|
if (!data.signals.length) {
|
|
52
|
-
return
|
|
183
|
+
return (
|
|
184
|
+
<div style="padding:12px;color:#4a4a6a;">No signals tracked</div>
|
|
185
|
+
);
|
|
53
186
|
}
|
|
54
187
|
return (
|
|
55
188
|
<div style="padding:8px;">
|
|
56
|
-
{data.signals.map(s => (
|
|
57
|
-
<div
|
|
189
|
+
{data.signals.map((s) => (
|
|
190
|
+
<div
|
|
191
|
+
key={s.id}
|
|
192
|
+
style="display:flex;justify-content:space-between;align-items:center;padding:4px 8px;border-bottom:1px solid #2a2a4a;"
|
|
193
|
+
>
|
|
58
194
|
<span style="color:#818cf8;">{s.name}</span>
|
|
59
195
|
<span style="color:#a0a0c0;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
|
60
196
|
{formatValue(s.value)}
|
|
@@ -65,89 +201,193 @@ export function DevPanel() {
|
|
|
65
201
|
);
|
|
66
202
|
};
|
|
67
203
|
|
|
204
|
+
// --- Tab: Effects ---
|
|
68
205
|
const renderEffects = () => {
|
|
69
206
|
const data = snapshot();
|
|
70
207
|
if (!data.effects.length) {
|
|
71
|
-
return
|
|
208
|
+
return (
|
|
209
|
+
<div style="padding:12px;color:#4a4a6a;">No effects tracked</div>
|
|
210
|
+
);
|
|
72
211
|
}
|
|
73
212
|
return (
|
|
74
213
|
<div style="padding:8px;">
|
|
75
|
-
{data.effects.map(e => (
|
|
76
|
-
<div
|
|
214
|
+
{data.effects.map((e) => (
|
|
215
|
+
<div
|
|
216
|
+
key={e.id}
|
|
217
|
+
style="display:flex;justify-content:space-between;align-items:center;padding:4px 8px;border-bottom:1px solid #2a2a4a;"
|
|
218
|
+
>
|
|
77
219
|
<span style="color:#fbbf24;">{e.name}</span>
|
|
220
|
+
<span style="color:#6a6a8a;font-size:10px;">
|
|
221
|
+
runs: {e.runCount || 0}
|
|
222
|
+
</span>
|
|
78
223
|
</div>
|
|
79
224
|
))}
|
|
80
225
|
</div>
|
|
81
226
|
);
|
|
82
227
|
};
|
|
83
228
|
|
|
229
|
+
// --- Tab: Components ---
|
|
84
230
|
const renderComponents = () => {
|
|
85
231
|
const data = snapshot();
|
|
86
232
|
if (!data.components.length) {
|
|
87
|
-
return
|
|
233
|
+
return (
|
|
234
|
+
<div style="padding:12px;color:#4a4a6a;">No components tracked</div>
|
|
235
|
+
);
|
|
88
236
|
}
|
|
89
237
|
return (
|
|
90
238
|
<div style="padding:8px;">
|
|
91
|
-
{data.components.map(c => (
|
|
92
|
-
<div
|
|
93
|
-
|
|
239
|
+
{data.components.map((c) => (
|
|
240
|
+
<div
|
|
241
|
+
key={c.id}
|
|
242
|
+
style="padding:4px 8px;border-bottom:1px solid #2a2a4a;"
|
|
243
|
+
>
|
|
244
|
+
<span style="color:#34d399;">
|
|
245
|
+
<{c.name} />
|
|
246
|
+
</span>
|
|
94
247
|
</div>
|
|
95
248
|
))}
|
|
96
249
|
</div>
|
|
97
250
|
);
|
|
98
251
|
};
|
|
99
252
|
|
|
253
|
+
// --- Tab: Errors ---
|
|
254
|
+
const renderErrors = () => {
|
|
255
|
+
const errs = recentErrors();
|
|
256
|
+
if (!errs.length) {
|
|
257
|
+
return (
|
|
258
|
+
<div style="padding:12px;color:#4a4a6a;">No errors captured</div>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return (
|
|
262
|
+
<div style="padding:8px;">
|
|
263
|
+
{errs
|
|
264
|
+
.slice()
|
|
265
|
+
.reverse()
|
|
266
|
+
.map((e, i) => (
|
|
267
|
+
<div
|
|
268
|
+
key={i}
|
|
269
|
+
style="padding:8px;background:#0d0d1a;border:1px solid #2a2a4a;border-radius:6px;margin-bottom:6px;"
|
|
270
|
+
>
|
|
271
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
|
|
272
|
+
<span style="color:#f87171;font-weight:600;font-size:11px;">
|
|
273
|
+
[{e.type}]
|
|
274
|
+
</span>
|
|
275
|
+
<span style="color:#4a4a6a;font-size:10px;">
|
|
276
|
+
{new Date(e.timestamp).toLocaleTimeString()}
|
|
277
|
+
</span>
|
|
278
|
+
</div>
|
|
279
|
+
<div style="color:#e0e0e0;font-size:11px;white-space:pre-wrap;word-break:break-all;">
|
|
280
|
+
{e.message}
|
|
281
|
+
</div>
|
|
282
|
+
{e.stack ? (
|
|
283
|
+
<div style="color:#4a4a6a;font-size:10px;margin-top:4px;white-space:pre-wrap;max-height:60px;overflow:hidden;">
|
|
284
|
+
{e.stack.split('\n').slice(0, 3).join('\n')}
|
|
285
|
+
</div>
|
|
286
|
+
) : null}
|
|
287
|
+
</div>
|
|
288
|
+
))}
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
|
|
100
293
|
return (
|
|
101
294
|
<>
|
|
102
|
-
{/* Toggle button */}
|
|
295
|
+
{/* Toggle button with health indicator */}
|
|
103
296
|
<button
|
|
104
|
-
onclick={() => isOpen(v => !v)}
|
|
105
|
-
style=
|
|
106
|
-
|
|
297
|
+
onclick={() => isOpen((v) => !v)}
|
|
298
|
+
style={() => {
|
|
299
|
+
const health = getHealth();
|
|
300
|
+
return (
|
|
301
|
+
'position:fixed;bottom:12px;right:12px;z-index:99999;width:36px;height:36px;' +
|
|
302
|
+
'border-radius:8px;border:1px solid #2a2a4a;' +
|
|
303
|
+
'background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;' +
|
|
304
|
+
'font-weight:800;font-size:14px;cursor:pointer;' +
|
|
305
|
+
'font-family:ui-monospace,monospace;' +
|
|
306
|
+
'box-shadow:0 4px 12px rgba(37,99,235,0.3),' +
|
|
307
|
+
'0 0 0 2px ' + health.color + ';'
|
|
308
|
+
);
|
|
309
|
+
}}
|
|
310
|
+
title="What Framework DevTools (Ctrl+Shift+D)"
|
|
107
311
|
>
|
|
108
312
|
W
|
|
109
313
|
</button>
|
|
110
314
|
|
|
111
|
-
{/* Panel
|
|
112
|
-
{() =>
|
|
113
|
-
|
|
114
|
-
{
|
|
115
|
-
|
|
116
|
-
<div style="display:flex;align-items:center;
|
|
117
|
-
<
|
|
315
|
+
{/* Panel -- conditionally rendered */}
|
|
316
|
+
{() =>
|
|
317
|
+
isOpen() ? (
|
|
318
|
+
<div style={PANEL_STYLE}>
|
|
319
|
+
{/* Header */}
|
|
320
|
+
<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid #2a2a4a;background:#16163a;">
|
|
321
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
322
|
+
<span style="font-weight:700;font-size:12px;color:#818cf8;">
|
|
323
|
+
What DevTools
|
|
324
|
+
</span>
|
|
325
|
+
<div
|
|
326
|
+
style={() =>
|
|
327
|
+
'width:8px;height:8px;border-radius:50%;background:' +
|
|
328
|
+
getHealth().color +
|
|
329
|
+
';'
|
|
330
|
+
}
|
|
331
|
+
title={() => getHealth().label}
|
|
332
|
+
/>
|
|
333
|
+
</div>
|
|
334
|
+
<button
|
|
335
|
+
onclick={() => isOpen(false)}
|
|
336
|
+
style="background:none;border:none;color:#6a6a8a;cursor:pointer;font-size:14px;"
|
|
337
|
+
>
|
|
338
|
+
x
|
|
339
|
+
</button>
|
|
118
340
|
</div>
|
|
119
|
-
<button
|
|
120
|
-
onclick={() => isOpen(false)}
|
|
121
|
-
style="background:none;border:none;color:#6a6a8a;cursor:pointer;font-size:14px;"
|
|
122
|
-
>
|
|
123
|
-
x
|
|
124
|
-
</button>
|
|
125
|
-
</div>
|
|
126
341
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
342
|
+
{/* Tabs */}
|
|
343
|
+
<div style="display:flex;gap:2px;padding:6px 8px;border-bottom:1px solid #2a2a4a;flex-wrap:wrap;">
|
|
344
|
+
<button
|
|
345
|
+
style={tabStyle('overview')}
|
|
346
|
+
onclick={() => activeTab('overview')}
|
|
347
|
+
>
|
|
348
|
+
Overview
|
|
349
|
+
</button>
|
|
350
|
+
<button
|
|
351
|
+
style={tabStyle('signals')}
|
|
352
|
+
onclick={() => activeTab('signals')}
|
|
353
|
+
>
|
|
354
|
+
Signals ({() => snapshot().signals.length})
|
|
355
|
+
</button>
|
|
356
|
+
<button
|
|
357
|
+
style={tabStyle('effects')}
|
|
358
|
+
onclick={() => activeTab('effects')}
|
|
359
|
+
>
|
|
360
|
+
Effects ({() => snapshot().effects.length})
|
|
361
|
+
</button>
|
|
362
|
+
<button
|
|
363
|
+
style={tabStyle('components')}
|
|
364
|
+
onclick={() => activeTab('components')}
|
|
365
|
+
>
|
|
366
|
+
Components ({() => snapshot().components.length})
|
|
367
|
+
</button>
|
|
368
|
+
<button
|
|
369
|
+
style={tabStyle('errors')}
|
|
370
|
+
onclick={() => activeTab('errors')}
|
|
371
|
+
>
|
|
372
|
+
Errors ({() => recentErrors().length})
|
|
373
|
+
</button>
|
|
374
|
+
</div>
|
|
139
375
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
376
|
+
{/* Content */}
|
|
377
|
+
<div style="overflow-y:auto;flex:1;">
|
|
378
|
+
{() => {
|
|
379
|
+
const tab = activeTab();
|
|
380
|
+
if (tab === 'overview') return renderOverview();
|
|
381
|
+
if (tab === 'signals') return renderSignals();
|
|
382
|
+
if (tab === 'effects') return renderEffects();
|
|
383
|
+
if (tab === 'components') return renderComponents();
|
|
384
|
+
if (tab === 'errors') return renderErrors();
|
|
385
|
+
return renderOverview();
|
|
386
|
+
}}
|
|
387
|
+
</div>
|
|
148
388
|
</div>
|
|
149
|
-
|
|
150
|
-
|
|
389
|
+
) : null
|
|
390
|
+
}
|
|
151
391
|
</>
|
|
152
392
|
);
|
|
153
393
|
}
|
|
@@ -155,7 +395,8 @@ export function DevPanel() {
|
|
|
155
395
|
function formatValue(value) {
|
|
156
396
|
if (value === null) return 'null';
|
|
157
397
|
if (value === undefined) return 'undefined';
|
|
158
|
-
if (typeof value === 'string')
|
|
398
|
+
if (typeof value === 'string')
|
|
399
|
+
return `"${value.length > 30 ? value.slice(0, 30) + '...' : value}"`;
|
|
159
400
|
if (typeof value === 'object') {
|
|
160
401
|
try {
|
|
161
402
|
const str = JSON.stringify(value);
|