@yuneta/gobj-ui 1.0.1 → 2.2.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 +40 -363
- package/dist/gobj-ui.cjs.js +11154 -5506
- package/dist/gobj-ui.es.js +11131 -5508
- package/index.js +41 -14
- package/package.json +11 -9
- package/src/c_g6_nodes_tree.js +6 -1
- package/src/c_yui_form.js +1 -1
- package/src/c_yui_gobj_tree_js.js +1 -1
- package/src/c_yui_json_graph.js +1 -1
- package/src/c_yui_main.js +3 -3
- package/src/c_yui_map.js +6 -1
- package/src/c_yui_nav.js +881 -0
- package/src/c_yui_pager.js +545 -0
- package/src/c_yui_routing.css +1 -1
- package/src/c_yui_routing.js +1 -1
- package/src/c_yui_shell.css +571 -0
- package/src/c_yui_shell.js +2474 -0
- package/src/c_yui_tabs.js +1 -1
- package/src/c_yui_treedb_graph.js +35 -9
- package/src/c_yui_treedb_topic_with_form.js +1 -1
- package/src/c_yui_treedb_topics.js +36 -8
- package/src/c_yui_uplot.js +1 -1
- package/src/c_yui_window.js +242 -21
- package/src/c_yui_window_manager.js +602 -0
- package/src/c_yui_wizard.js +612 -0
- package/src/pager_helpers.js +138 -0
- package/src/pager_helpers.test.js +140 -0
- package/src/route_resolver.js +53 -0
- package/src/route_resolver.test.js +82 -0
- package/src/shell_focus_trap.js +123 -0
- package/src/shell_focus_trap.test.js +299 -0
- package/src/shell_modals.js +445 -0
- package/src/shell_show_on.js +91 -0
- package/src/shell_show_on.test.js +86 -0
- package/src/shell_toolbar_helpers.js +221 -0
- package/src/shell_toolbar_helpers.test.js +207 -0
- package/src/tabulator.css +53 -0
- package/src/wizard_helpers.js +117 -0
- package/src/wizard_helpers.test.js +122 -0
- package/src/yui_dev.js +1257 -362
- package/src/yui_icons.css +5 -0
- package/src/yui_inputs.css +32 -0
- package/src/yui_inputs.js +71 -0
- package/vite-plugin-yuneta-html.js +2 -2
- package/skeleton/config.json +0 -20
- package/skeleton/index.html +0 -37
package/src/yui_dev.js
CHANGED
|
@@ -1,37 +1,825 @@
|
|
|
1
1
|
/***********************************************************************
|
|
2
2
|
* ui_dev.js
|
|
3
3
|
*
|
|
4
|
-
* Development Tools
|
|
4
|
+
* Development Tools — yuno monitor / audit console
|
|
5
5
|
*
|
|
6
|
-
* Copyright (c) 2024, ArtGins.
|
|
6
|
+
* Copyright (c) 2024-2026, ArtGins.
|
|
7
7
|
* All Rights Reserved.
|
|
8
8
|
***********************************************************************/
|
|
9
9
|
import {
|
|
10
10
|
gobj_yuno,
|
|
11
|
-
log_error,
|
|
12
11
|
is_string,
|
|
13
12
|
createElement2,
|
|
14
13
|
kw_get_local_storage_value,
|
|
15
14
|
kw_set_local_storage_value,
|
|
16
15
|
gobj_write_attr,
|
|
17
16
|
gobj_create_service,
|
|
17
|
+
gobj_find_service,
|
|
18
|
+
set_log_callback,
|
|
19
|
+
gobj_set_trace_machine_format,
|
|
18
20
|
trace_json,
|
|
19
21
|
} from "@yuneta/gobj-js";
|
|
20
22
|
|
|
21
23
|
import i18next from 'i18next';
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
/***********************************************************************
|
|
26
|
+
* Traffic model (bounded ring buffer)
|
|
27
|
+
*
|
|
28
|
+
* Every inter-event message is kept as a lightweight record so view
|
|
29
|
+
* and filter changes re-render instantly from memory instead of
|
|
30
|
+
* losing history. Reopening the window repaints the buffer.
|
|
31
|
+
***********************************************************************/
|
|
32
|
+
const TRAFFIC_MAX = 600; // capped history
|
|
33
|
+
const PERIODIC_THRESHOLD = 5; // a signature seen >= N times reads as recurring
|
|
34
|
+
const PERIODIC_RE = /PERIODIC|TIMEOUT|HEARTBEAT|PING/i;
|
|
35
|
+
|
|
36
|
+
let TRAFFIC_LOG = []; // [{title,event,command,sig,dir,size,ts,kw,jn,hay,$node}]
|
|
37
|
+
let TRAFFIC_COUNTS = new Map(); // signature -> occurrences (for periodic detection)
|
|
38
|
+
let SEARCH_TEXT = ""; // session-only free-text filter (not persisted)
|
|
39
|
+
|
|
40
|
+
/* Field names whose numeric value is a Unix timestamp (seconds). */
|
|
41
|
+
const TRAFFIC_TS_FIELDS = {
|
|
42
|
+
"__t__": 1, "__tm__": 1, "tm": 1, "t": 1,
|
|
43
|
+
"from_t": 1, "to_t": 1, "from_tm": 1, "to_tm": 1, "time": 1,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/* Trace toggles: [localStorage key, display label, handler]. */
|
|
47
|
+
const TRACE_DEFS = [
|
|
48
|
+
["trace_automata", "Automata", trace_automata],
|
|
49
|
+
["trace_creation", "Creation", trace_creation],
|
|
50
|
+
["trace_start_stop", "Start/Stop", trace_start_stop],
|
|
51
|
+
["trace_subscriptions", "Subscriptions", trace_subscriptions],
|
|
52
|
+
["trace_i18n", "I18n", trace_i18n],
|
|
53
|
+
["trace_traffic", "Traffic", trace_traffic],
|
|
54
|
+
["no_poll", "No Poll", set_no_poll],
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/******************************
|
|
59
|
+
* Small helpers
|
|
60
|
+
******************************/
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
/************************************************************
|
|
64
|
+
* hh:mm:ss.SSS wall-clock of the moment a message arrives.
|
|
65
|
+
************************************************************/
|
|
66
|
+
function traffic_now()
|
|
67
|
+
{
|
|
68
|
+
let now = new Date();
|
|
69
|
+
let pad = (num, len) => ('000' + num).slice(len * -1);
|
|
70
|
+
let hours = pad(now.getHours(), 2);
|
|
71
|
+
let minutes = pad(now.getMinutes(), 2);
|
|
72
|
+
let seconds = pad(now.getSeconds(), 2);
|
|
73
|
+
let ms = pad(now.getMilliseconds(), 3);
|
|
74
|
+
return `${hours}:${minutes}:${seconds}.${ms}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/************************************************************
|
|
78
|
+
* Human byte size (B / KB / MB).
|
|
79
|
+
************************************************************/
|
|
80
|
+
function traffic_size(n)
|
|
81
|
+
{
|
|
82
|
+
n = Number(n) || 0;
|
|
83
|
+
if(n < 1024) {
|
|
84
|
+
return n + " B";
|
|
85
|
+
}
|
|
86
|
+
if(n < 1024 * 1024) {
|
|
87
|
+
return (n / 1024).toFixed(1) + " KB";
|
|
88
|
+
}
|
|
89
|
+
return (n / (1024 * 1024)).toFixed(1) + " MB";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/************************************************************
|
|
93
|
+
* Seconds-since-epoch → ISO string, or null if not a plausible
|
|
94
|
+
* timestamp (guards against 0 / NaN / out-of-range values).
|
|
95
|
+
************************************************************/
|
|
96
|
+
function traffic_iso(value)
|
|
97
|
+
{
|
|
98
|
+
let n = Number(value);
|
|
99
|
+
if(!isFinite(n) || n <= 0) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
return new Date(n * 1000).toISOString();
|
|
104
|
+
} catch(e) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/************************************************************
|
|
110
|
+
* Clip a string for inline display (full text kept elsewhere).
|
|
111
|
+
************************************************************/
|
|
112
|
+
function traffic_clip(s, n)
|
|
113
|
+
{
|
|
114
|
+
s = String(s);
|
|
115
|
+
return s.length > n ? s.slice(0, n) + "…" : s;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/************************************************************
|
|
119
|
+
* A scalar rendered as a short inline token (for summaries).
|
|
120
|
+
************************************************************/
|
|
121
|
+
function traffic_scalar_text(v)
|
|
122
|
+
{
|
|
123
|
+
if(v === null) {
|
|
124
|
+
return "null";
|
|
125
|
+
}
|
|
126
|
+
if(typeof v === "string") {
|
|
127
|
+
return traffic_clip(v, 40);
|
|
128
|
+
}
|
|
129
|
+
return String(v);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function dir_class(dir)
|
|
133
|
+
{
|
|
134
|
+
return (dir === 2) ? "dir-in" : (dir === 3) ? "dir-err" : "dir-out";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function dir_arrow(dir)
|
|
138
|
+
{
|
|
139
|
+
return (dir === 2) ? "⇠" : (dir === 3) ? "⚠" : "⇢";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
/******************************
|
|
144
|
+
* Preferences
|
|
145
|
+
******************************/
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
function dev_num(key, def)
|
|
149
|
+
{
|
|
150
|
+
return Number(kw_get_local_storage_value(key, (def === undefined ? 0 : def), false));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function dev_view()
|
|
154
|
+
{
|
|
155
|
+
let v = kw_get_local_storage_value("dev_view_mode", "detailed", false);
|
|
156
|
+
return (v === "compact" || v === "name" || v === "full") ? v : "detailed";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function dev_hide_periodic()
|
|
160
|
+
{
|
|
161
|
+
return dev_num("dev_hide_periodic", 0) ? true : false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function dev_muted()
|
|
165
|
+
{
|
|
166
|
+
let a = kw_get_local_storage_value("dev_muted_events", [], false);
|
|
167
|
+
if(!Array.isArray(a)) {
|
|
168
|
+
a = [];
|
|
169
|
+
}
|
|
170
|
+
return new Set(a);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function dev_set_muted(set)
|
|
174
|
+
{
|
|
175
|
+
kw_set_local_storage_value("dev_muted_events", Array.from(set));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function set_view(v)
|
|
179
|
+
{
|
|
180
|
+
kw_set_local_storage_value("dev_view_mode", v);
|
|
181
|
+
rerender_all();
|
|
182
|
+
refresh_dev_chrome();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function toggle_pref(key, def)
|
|
186
|
+
{
|
|
187
|
+
let v = dev_num(key, def) ? 0 : 1;
|
|
188
|
+
kw_set_local_storage_value(key, v);
|
|
189
|
+
rerender_all();
|
|
190
|
+
refresh_dev_chrome();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function mute_signature(sig)
|
|
194
|
+
{
|
|
195
|
+
let set = dev_muted();
|
|
196
|
+
set.add(sig);
|
|
197
|
+
dev_set_muted(set);
|
|
198
|
+
rerender_all();
|
|
199
|
+
refresh_dev_chrome();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function unmute_signature(sig)
|
|
203
|
+
{
|
|
204
|
+
let set = dev_muted();
|
|
205
|
+
set.delete(sig);
|
|
206
|
+
dev_set_muted(set);
|
|
207
|
+
rerender_all();
|
|
208
|
+
refresh_dev_chrome();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
/******************************
|
|
213
|
+
* Filtering
|
|
214
|
+
******************************/
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
/* Signature identifies a "kind" of message. Command answers share
|
|
218
|
+
* the generic EV_MT_COMMAND event, so fold the command in to tell
|
|
219
|
+
* a get-stats poll apart from a user action. */
|
|
220
|
+
function traffic_signature(event, command)
|
|
221
|
+
{
|
|
222
|
+
return command ? (event + " · " + command) : event;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function traffic_is_periodic(sig)
|
|
226
|
+
{
|
|
227
|
+
if(PERIODIC_RE.test(sig)) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
return (TRAFFIC_COUNTS.get(sig) || 0) >= PERIODIC_THRESHOLD;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function build_filter_ctx()
|
|
234
|
+
{
|
|
235
|
+
return {
|
|
236
|
+
out: dev_num("dev_filter_out", 1),
|
|
237
|
+
inc: dev_num("dev_filter_in", 1),
|
|
238
|
+
err: dev_num("dev_filter_err", 1),
|
|
239
|
+
muted: dev_muted(),
|
|
240
|
+
hide_periodic: dev_hide_periodic(),
|
|
241
|
+
search: SEARCH_TEXT,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function entry_hidden(e, ctx)
|
|
246
|
+
{
|
|
247
|
+
if(e.kind === "log") {
|
|
248
|
+
/* Mirrored console logs respect the search box only — not the
|
|
249
|
+
* in/out/err/periodic traffic filters. */
|
|
250
|
+
return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
|
|
251
|
+
}
|
|
252
|
+
if(e.dir === 1 && !ctx.out) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
if(e.dir === 2 && !ctx.inc) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
if(e.dir === 3 && !ctx.err) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if(ctx.muted.has(e.sig)) {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
if(ctx.hide_periodic && traffic_is_periodic(e.sig)) {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
if(ctx.search && e.hay.indexOf(ctx.search) < 0) {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
/******************************
|
|
275
|
+
* Style
|
|
276
|
+
******************************/
|
|
277
|
+
|
|
25
278
|
|
|
26
279
|
/************************************************************
|
|
280
|
+
* Inject the monitor stylesheet once. Theme-aware via
|
|
281
|
+
* <html data-theme>; direction-coloured (out / in / error).
|
|
282
|
+
************************************************************/
|
|
283
|
+
function ensure_dev_style()
|
|
284
|
+
{
|
|
285
|
+
if(document.getElementById('yui-dev-style')) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
let css = `
|
|
289
|
+
/* -------- layout -------- */
|
|
290
|
+
.YDEV_BODY {
|
|
291
|
+
display: flex;
|
|
292
|
+
flex-direction: column;
|
|
293
|
+
height: 100%;
|
|
294
|
+
min-height: 0;
|
|
295
|
+
box-sizing: border-box;
|
|
296
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
297
|
+
}
|
|
298
|
+
.YDEV_LOG { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 6px 10px; }
|
|
299
|
+
.YDEV_MUTED {
|
|
300
|
+
display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
|
|
301
|
+
padding: 4px 10px; border-bottom: 1px solid rgba(0,0,0,0.08); font-size: 12px;
|
|
302
|
+
}
|
|
303
|
+
.YDEV_MUTED:empty { display: none; }
|
|
304
|
+
.YDEV_STATS {
|
|
305
|
+
flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: 14px;
|
|
306
|
+
padding: 6px 10px; border-top: 1px solid rgba(0,0,0,0.1);
|
|
307
|
+
background: rgba(0,0,0,0.03);
|
|
308
|
+
font-family: "DejaVu Sans Mono", monospace; font-size: 11px;
|
|
309
|
+
opacity: 0.9; font-variant-numeric: tabular-nums;
|
|
310
|
+
}
|
|
311
|
+
/* -------- control bar -------- */
|
|
312
|
+
.YDEV_BAR {
|
|
313
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px;
|
|
314
|
+
padding: 8px 10px; border-bottom: 1px solid rgba(0,0,0,0.1);
|
|
315
|
+
background: rgba(0,0,0,0.03);
|
|
316
|
+
}
|
|
317
|
+
.YDEV_GROUP { display: inline-flex; align-items: center; gap: 5px; }
|
|
318
|
+
.YDEV_LABEL {
|
|
319
|
+
font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
|
|
320
|
+
opacity: 0.5; align-self: center;
|
|
321
|
+
}
|
|
322
|
+
.YDEV_SEP { width: 1px; align-self: stretch; background: rgba(0,0,0,0.12); }
|
|
323
|
+
.YDEV_CHIP {
|
|
324
|
+
font: inherit; font-size: 12px; line-height: 1.4; padding: 3px 9px;
|
|
325
|
+
border: 1px solid rgba(0,0,0,0.18); border-radius: 999px;
|
|
326
|
+
background: transparent; color: inherit; cursor: pointer;
|
|
327
|
+
display: inline-flex; align-items: center; gap: 5px;
|
|
328
|
+
}
|
|
329
|
+
.YDEV_CHIP:hover { border-color: currentColor; }
|
|
330
|
+
.YDEV_CHIP.is-active { background: rgba(37,99,235,0.14); border-color: #2563eb; color: #2563eb; font-weight: 600; }
|
|
331
|
+
.YDEV_CHIP.s-out.is-active { background: rgba(37,99,235,0.16); border-color: #2563eb; color: #2563eb; }
|
|
332
|
+
.YDEV_CHIP.s-in.is-active { background: rgba(5,150,105,0.16); border-color: #059669; color: #059669; }
|
|
333
|
+
.YDEV_CHIP.s-err.is-active { background: rgba(220,38,38,0.16); border-color: #dc2626; color: #dc2626; }
|
|
334
|
+
.YDEV_CHIP[data-dir]:not(.is-active) { opacity: 0.4; text-decoration: line-through; }
|
|
335
|
+
.YDEV_CHIP[data-toggle="periodic"].is-active { background: rgba(217,119,6,0.16); border-color: #d97706; color: #b45309; font-weight: 600; }
|
|
336
|
+
.YDEV_SEG { display: inline-flex; border: 1px solid rgba(0,0,0,0.18); border-radius: 7px; overflow: hidden; }
|
|
337
|
+
.YDEV_SEG_BTN {
|
|
338
|
+
font: inherit; font-size: 12px; padding: 4px 10px; border: 0;
|
|
339
|
+
border-right: 1px solid rgba(0,0,0,0.12);
|
|
340
|
+
background: transparent; color: inherit; cursor: pointer;
|
|
341
|
+
}
|
|
342
|
+
.YDEV_SEG_BTN:last-child { border-right: 0; }
|
|
343
|
+
.YDEV_SEG_BTN.is-active { background: #2563eb; color: #fff; font-weight: 600; }
|
|
344
|
+
.YDEV_SEARCH {
|
|
345
|
+
font: inherit; font-size: 12px; padding: 4px 9px; min-width: 170px;
|
|
346
|
+
border: 1px solid rgba(0,0,0,0.18); border-radius: 7px;
|
|
347
|
+
background: transparent; color: inherit;
|
|
348
|
+
}
|
|
349
|
+
.YDEV_MUTED_CHIP {
|
|
350
|
+
font: inherit; font-family: "DejaVu Sans Mono", monospace; font-size: 12px;
|
|
351
|
+
display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px;
|
|
352
|
+
border: 1px solid rgba(217,119,6,0.5); border-radius: 999px;
|
|
353
|
+
background: rgba(217,119,6,0.12); color: #b45309; cursor: pointer;
|
|
354
|
+
}
|
|
355
|
+
.YDEV_STAT.s-out { color: #2563eb; } .YDEV_STAT.s-in { color: #059669; } .YDEV_STAT.s-err { color: #dc2626; }
|
|
356
|
+
.YDEV_TITLE { display: flex; align-items: baseline; gap: 8px; }
|
|
357
|
+
.YDEV_TITLE_MAIN { font-weight: 700; }
|
|
358
|
+
.YDEV_TITLE_SUB { opacity: 0.7; font-size: 12px; }
|
|
359
|
+
/* -------- entries (shared) -------- */
|
|
360
|
+
.TRAFFIC_ENTRY, .TRAFFIC_LINE, .TRAFFIC_NAME {
|
|
361
|
+
border-left: 3px solid #94a3b8; border-radius: 3px;
|
|
362
|
+
font-family: "DejaVu Sans Mono", monospace, consolas, monaco; font-size: 13px;
|
|
363
|
+
background: rgba(0,0,0,0.02);
|
|
364
|
+
}
|
|
365
|
+
.TRAFFIC_ENTRY { margin: 6px 0; padding: 4px 8px; line-height: 1.55; }
|
|
366
|
+
.TRAFFIC_LINE { margin: 2px 0; padding: 2px 8px; display: flex; align-items: baseline; gap: 8px; }
|
|
367
|
+
.TRAFFIC_NAME { margin: 1px 0; padding: 1px 8px; display: flex; align-items: baseline; gap: 8px; background: transparent; }
|
|
368
|
+
.TRAFFIC_ENTRY.dir-out, .TRAFFIC_LINE.dir-out, .TRAFFIC_NAME.dir-out { border-left-color: #2563eb; }
|
|
369
|
+
.TRAFFIC_ENTRY.dir-in, .TRAFFIC_LINE.dir-in, .TRAFFIC_NAME.dir-in { border-left-color: #059669; }
|
|
370
|
+
.TRAFFIC_ENTRY.dir-err, .TRAFFIC_LINE.dir-err, .TRAFFIC_NAME.dir-err { border-left-color: #dc2626; }
|
|
371
|
+
.TRAFFIC_HEADER { display: flex; align-items: baseline; gap: 8px; }
|
|
372
|
+
.TRAFFIC_ARROW { font-weight: 700; }
|
|
373
|
+
.TRAFFIC_EVENT { font-weight: 700; }
|
|
374
|
+
.TRAFFIC_CMD { opacity: 0.75; font-weight: 600; }
|
|
375
|
+
.dir-out .TRAFFIC_ARROW, .dir-out .TRAFFIC_EVENT { color: #2563eb; }
|
|
376
|
+
.dir-in .TRAFFIC_ARROW, .dir-in .TRAFFIC_EVENT { color: #059669; }
|
|
377
|
+
.dir-err .TRAFFIC_ARROW, .dir-err .TRAFFIC_EVENT { color: #dc2626; }
|
|
378
|
+
.TRAFFIC_SUMMARY { opacity: 0.6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0; }
|
|
379
|
+
.TRAFFIC_META { margin-left: auto; opacity: 0.6; font-size: 11px; white-space: nowrap; }
|
|
380
|
+
.TRAFFIC_MUTE { border: 0; background: transparent; color: inherit; cursor: pointer; opacity: 0; font-size: 12px; padding: 0 2px; flex: 0 0 auto; }
|
|
381
|
+
.TRAFFIC_ENTRY:hover .TRAFFIC_MUTE, .TRAFFIC_LINE:hover .TRAFFIC_MUTE, .TRAFFIC_NAME:hover .TRAFFIC_MUTE { opacity: 0.5; }
|
|
382
|
+
.TRAFFIC_MUTE:hover { opacity: 1 !important; color: #d97706; }
|
|
383
|
+
.TRAFFIC_KW { margin: 2px 0 0 16px; }
|
|
384
|
+
.TRAFFIC_FULL { margin: 4px 0 0 16px; padding: 6px 8px; font-family: monospace; font-size: 11px; line-height: 1.4; white-space: pre-wrap; word-break: break-word; background: rgba(0,0,0,0.04); border-radius: 4px; overflow-x: auto; }
|
|
385
|
+
.TRAFFIC_ROW { display: flex; gap: 6px; align-items: baseline; }
|
|
386
|
+
.TRAFFIC_BULLET { opacity: 0.45; flex: 0 0 auto; }
|
|
387
|
+
.TRAFFIC_KEY { opacity: 0.85; flex: 0 0 auto; }
|
|
388
|
+
.TRAFFIC_VAL { word-break: break-word; }
|
|
389
|
+
.TRAFFIC_VAL.t-num { color: #0891b2; }
|
|
390
|
+
.TRAFFIC_VAL.t-bool { color: #9333ea; }
|
|
391
|
+
.TRAFFIC_VAL.t-null { color: #9333ea; font-style: italic; }
|
|
392
|
+
.TRAFFIC_VAL.t-empty { opacity: 0.5; }
|
|
393
|
+
.TRAFFIC_TS { opacity: 0.6; margin-left: 8px; }
|
|
394
|
+
details.TRAFFIC_NEST > summary { cursor: pointer; list-style: none; display: flex; gap: 6px; align-items: baseline; }
|
|
395
|
+
details.TRAFFIC_NEST > summary::-webkit-details-marker { display: none; }
|
|
396
|
+
.TRAFFIC_NEST_KEY { opacity: 0.85; }
|
|
397
|
+
.TRAFFIC_NEST_HINT { opacity: 0.5; margin-left: 4px; }
|
|
398
|
+
.YDEV_EMPTY { opacity: 0.5; font-size: 12px; padding: 18px 10px; text-align: center; }
|
|
399
|
+
/* -------- mirrored console logs (error/warning/info/debug/msg; the automata trace shows as debug) -------- */
|
|
400
|
+
.YDEV_LOGROW { display: flex; align-items: baseline; gap: 8px; margin: 1px 0; padding: 2px 8px; border-left: 3px solid #94a3b8; border-radius: 3px; font-family: "DejaVu Sans Mono", monospace, consolas, monaco; font-size: 12px; background: rgba(0,0,0,0.015); }
|
|
401
|
+
.YDEV_LOG_LVL { flex: 0 0 auto; text-transform: uppercase; font-size: 9px; font-weight: 700; letter-spacing: 0.04em; opacity: 0.8; min-width: 48px; }
|
|
402
|
+
.YDEV_LOG_TXT { flex: 1 1 auto; min-width: 0; white-space: pre-wrap; word-break: break-word; opacity: 0.9; }
|
|
403
|
+
.YDEV_LOG_error { border-left-color: #dc2626; } .YDEV_LOG_error .YDEV_LOG_LVL { color: #dc2626; }
|
|
404
|
+
.YDEV_LOG_warning { border-left-color: #d97706; } .YDEV_LOG_warning .YDEV_LOG_LVL { color: #d97706; }
|
|
405
|
+
.YDEV_LOG_info { border-left-color: #2563eb; } .YDEV_LOG_info .YDEV_LOG_LVL { color: #2563eb; }
|
|
406
|
+
.YDEV_LOG_msg { border-left-color: #0891b2; } .YDEV_LOG_msg .YDEV_LOG_LVL { color: #0891b2; }
|
|
407
|
+
.YDEV_LOG_debug { border-left-color: #94a3b8; } .YDEV_LOG_debug .YDEV_LOG_LVL { color: #94a3b8; } .YDEV_LOG_debug .YDEV_LOG_TXT { opacity: 0.72; }
|
|
408
|
+
.YDEV_LOG_json { border-left-color: #9333ea; align-items: flex-start; } .YDEV_LOG_json .YDEV_LOG_LVL { color: #9333ea; } .YDEV_LOG_json .YDEV_LOG_TXT { font-size: 11px; line-height: 1.35; opacity: 0.8; }
|
|
409
|
+
/* -------- dark theme -------- */
|
|
410
|
+
:root[data-theme="dark"] .TRAFFIC_FULL { background: rgba(255,255,255,0.05); }
|
|
411
|
+
:root[data-theme="dark"] .YDEV_BAR, :root[data-theme="dark"] .YDEV_STATS { background: rgba(255,255,255,0.04); }
|
|
412
|
+
:root[data-theme="dark"] .YDEV_SEP { background: rgba(255,255,255,0.14); }
|
|
413
|
+
:root[data-theme="dark"] .YDEV_CHIP, :root[data-theme="dark"] .YDEV_SEG, :root[data-theme="dark"] .YDEV_SEARCH { border-color: rgba(255,255,255,0.2); }
|
|
414
|
+
:root[data-theme="dark"] .TRAFFIC_ENTRY, :root[data-theme="dark"] .TRAFFIC_LINE { background: rgba(255,255,255,0.03); }
|
|
415
|
+
:root[data-theme="dark"] .TRAFFIC_ENTRY.dir-out, :root[data-theme="dark"] .TRAFFIC_LINE.dir-out, :root[data-theme="dark"] .TRAFFIC_NAME.dir-out { border-left-color: #60a5fa; }
|
|
416
|
+
:root[data-theme="dark"] .TRAFFIC_ENTRY.dir-in, :root[data-theme="dark"] .TRAFFIC_LINE.dir-in, :root[data-theme="dark"] .TRAFFIC_NAME.dir-in { border-left-color: #34d399; }
|
|
417
|
+
:root[data-theme="dark"] .TRAFFIC_ENTRY.dir-err, :root[data-theme="dark"] .TRAFFIC_LINE.dir-err, :root[data-theme="dark"] .TRAFFIC_NAME.dir-err { border-left-color: #f87171; }
|
|
418
|
+
:root[data-theme="dark"] .dir-out .TRAFFIC_ARROW, :root[data-theme="dark"] .dir-out .TRAFFIC_EVENT { color: #60a5fa; }
|
|
419
|
+
:root[data-theme="dark"] .dir-in .TRAFFIC_ARROW, :root[data-theme="dark"] .dir-in .TRAFFIC_EVENT { color: #34d399; }
|
|
420
|
+
:root[data-theme="dark"] .dir-err .TRAFFIC_ARROW, :root[data-theme="dark"] .dir-err .TRAFFIC_EVENT { color: #f87171; }
|
|
421
|
+
:root[data-theme="dark"] .YDEV_STAT.s-out { color: #60a5fa; } :root[data-theme="dark"] .YDEV_STAT.s-in { color: #34d399; } :root[data-theme="dark"] .YDEV_STAT.s-err { color: #f87171; }
|
|
422
|
+
:root[data-theme="dark"] .TRAFFIC_VAL.t-num { color: #22d3ee; }
|
|
423
|
+
:root[data-theme="dark"] .TRAFFIC_VAL.t-bool, :root[data-theme="dark"] .TRAFFIC_VAL.t-null { color: #c084fc; }
|
|
424
|
+
:root[data-theme="dark"] .YDEV_CHIP.is-active { background: rgba(96,165,250,0.2); border-color: #60a5fa; color: #93c5fd; }
|
|
425
|
+
:root[data-theme="dark"] .YDEV_SEG_BTN.is-active { background: #2563eb; color: #fff; }
|
|
426
|
+
:root[data-theme="dark"] .YDEV_CHIP[data-toggle="periodic"].is-active { background: rgba(217,119,6,0.24); border-color: #f59e0b; color: #fbbf24; }
|
|
427
|
+
:root[data-theme="dark"] .YDEV_MUTED_CHIP { border-color: rgba(245,158,11,0.5); background: rgba(245,158,11,0.16); color: #fbbf24; }
|
|
428
|
+
`;
|
|
429
|
+
let $style = document.createElement('style');
|
|
430
|
+
$style.id = 'yui-dev-style';
|
|
431
|
+
$style.textContent = css;
|
|
432
|
+
document.head.appendChild($style);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
/******************************
|
|
437
|
+
* kw bullet rendering
|
|
438
|
+
******************************/
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
/************************************************************
|
|
442
|
+
* One scalar field as a bullet row: `• key: value`.
|
|
443
|
+
* Type-coloured; long strings clipped (full text on hover);
|
|
444
|
+
* timestamp fields get an ISO annotation.
|
|
445
|
+
************************************************************/
|
|
446
|
+
function traffic_scalar_row(key, value)
|
|
447
|
+
{
|
|
448
|
+
let cls;
|
|
449
|
+
let text;
|
|
450
|
+
if(value === null) {
|
|
451
|
+
cls = "t-null";
|
|
452
|
+
text = "null";
|
|
453
|
+
} else if(typeof value === "boolean") {
|
|
454
|
+
cls = "t-bool";
|
|
455
|
+
text = value ? "true" : "false";
|
|
456
|
+
} else if(typeof value === "number") {
|
|
457
|
+
cls = "t-num";
|
|
458
|
+
text = String(value);
|
|
459
|
+
} else {
|
|
460
|
+
cls = "t-str";
|
|
461
|
+
text = String(value);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
let full = text;
|
|
465
|
+
if(text.length > 200) {
|
|
466
|
+
text = text.slice(0, 200) + "…";
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
let val_children = [
|
|
470
|
+
['span', {class: 'TRAFFIC_VAL ' + cls, title: full}, text],
|
|
471
|
+
];
|
|
472
|
+
if((key in TRAFFIC_TS_FIELDS) && typeof value === "number") {
|
|
473
|
+
let iso = traffic_iso(value);
|
|
474
|
+
if(iso) {
|
|
475
|
+
val_children.push(['span', {class: 'TRAFFIC_TS'}, iso]);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return ['div', {class: 'TRAFFIC_ROW'}, [
|
|
480
|
+
['span', {class: 'TRAFFIC_BULLET'}, '•'],
|
|
481
|
+
['span', {class: 'TRAFFIC_KEY'}, key + ':'],
|
|
482
|
+
['span', {}, val_children],
|
|
483
|
+
]];
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/************************************************************
|
|
487
|
+
* One field of any type. Scalars → a bullet row; objects and
|
|
488
|
+
* arrays → a collapsed <details> so metadata / nested payloads
|
|
489
|
+
* stay folded. Empty containers render inline.
|
|
490
|
+
************************************************************/
|
|
491
|
+
function traffic_value_node(key, value)
|
|
492
|
+
{
|
|
493
|
+
if(value === null || typeof value !== "object") {
|
|
494
|
+
return traffic_scalar_row(key, value);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
let is_arr = Array.isArray(value);
|
|
498
|
+
let count = is_arr ? value.length : Object.keys(value).length;
|
|
499
|
+
if(count === 0) {
|
|
500
|
+
return ['div', {class: 'TRAFFIC_ROW'}, [
|
|
501
|
+
['span', {class: 'TRAFFIC_BULLET'}, '•'],
|
|
502
|
+
['span', {class: 'TRAFFIC_KEY'}, key + ':'],
|
|
503
|
+
['span', {class: 'TRAFFIC_VAL t-empty'}, is_arr ? '[ ]' : '{ }'],
|
|
504
|
+
]];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
let hint = is_arr ? `[${count}]` : `{${count}}`;
|
|
508
|
+
return ['details', {class: 'TRAFFIC_NEST'}, [
|
|
509
|
+
['summary', {}, [
|
|
510
|
+
['span', {class: 'TRAFFIC_BULLET'}, '▸'],
|
|
511
|
+
['span', {class: 'TRAFFIC_NEST_KEY'}, key],
|
|
512
|
+
['span', {class: 'TRAFFIC_NEST_HINT'}, hint],
|
|
513
|
+
]],
|
|
514
|
+
['div', {class: 'TRAFFIC_KW'}, traffic_bullets(value)],
|
|
515
|
+
]];
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/************************************************************
|
|
519
|
+
* A whole object/array → an array of bullet nodes.
|
|
520
|
+
************************************************************/
|
|
521
|
+
function traffic_bullets(obj)
|
|
522
|
+
{
|
|
523
|
+
let out = [];
|
|
524
|
+
if(Array.isArray(obj)) {
|
|
525
|
+
for(let i = 0; i < obj.length; i++) {
|
|
526
|
+
out.push(traffic_value_node(String(i), obj[i]));
|
|
527
|
+
}
|
|
528
|
+
} else {
|
|
529
|
+
for(let k of Object.keys(obj)) {
|
|
530
|
+
out.push(traffic_value_node(k, obj[k]));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return out;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
/******************************
|
|
538
|
+
* Entry rendering (per view)
|
|
539
|
+
******************************/
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
/* A small mute affordance that silences this signature (persistent). */
|
|
543
|
+
function mute_button(sig)
|
|
544
|
+
{
|
|
545
|
+
return ['button', {class: 'TRAFFIC_MUTE', type: 'button', title: 'Mute ' + sig}, '⊘', {
|
|
546
|
+
click: (ev) => {
|
|
547
|
+
ev.stopPropagation();
|
|
548
|
+
ev.preventDefault();
|
|
549
|
+
mute_signature(sig);
|
|
550
|
+
}
|
|
551
|
+
}];
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function event_spans(e)
|
|
555
|
+
{
|
|
556
|
+
let spans = [['span', {class: 'TRAFFIC_ARROW'}, dir_arrow(e.dir)],
|
|
557
|
+
['span', {class: 'TRAFFIC_EVENT'}, e.event]];
|
|
558
|
+
if(e.command) {
|
|
559
|
+
spans.push(['span', {class: 'TRAFFIC_CMD'}, e.command]);
|
|
560
|
+
}
|
|
561
|
+
return spans;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/* One-line summary of the kw for the compact view. */
|
|
565
|
+
function compact_summary(kw)
|
|
566
|
+
{
|
|
567
|
+
if(!kw) {
|
|
568
|
+
return "";
|
|
569
|
+
}
|
|
570
|
+
let parts = [];
|
|
571
|
+
if("result" in kw) {
|
|
572
|
+
parts.push("result=" + traffic_scalar_text(kw.result));
|
|
573
|
+
}
|
|
574
|
+
if(typeof kw.comment === "string" && kw.comment) {
|
|
575
|
+
parts.push(traffic_clip(kw.comment, 80));
|
|
576
|
+
}
|
|
577
|
+
if(!parts.length) {
|
|
578
|
+
let n = 0;
|
|
579
|
+
for(let k of Object.keys(kw)) {
|
|
580
|
+
if(k === "command") {
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
let v = kw[k];
|
|
584
|
+
if(v === null || typeof v !== "object") {
|
|
585
|
+
parts.push(k + "=" + traffic_scalar_text(v));
|
|
586
|
+
if(++n >= 3) {
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return traffic_clip(parts.join(" · "), 140);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function render_detailed(e)
|
|
596
|
+
{
|
|
597
|
+
let head = event_spans(e);
|
|
598
|
+
head.push(mute_button(e.sig));
|
|
599
|
+
head.push(['span', {class: 'TRAFFIC_META'}, `${traffic_size(e.size)} · ${e.ts}`]);
|
|
600
|
+
|
|
601
|
+
let children = [['div', {class: 'TRAFFIC_HEADER'}, head]];
|
|
602
|
+
let kw = e.kw;
|
|
603
|
+
if(kw && Object.keys(kw).length > 0) {
|
|
604
|
+
children.push(['div', {class: 'TRAFFIC_KW'}, traffic_bullets(kw)]);
|
|
605
|
+
} else if(!kw) {
|
|
606
|
+
children.push(['div', {class: 'TRAFFIC_KW'}, traffic_bullets(e.jn)]);
|
|
607
|
+
}
|
|
608
|
+
return createElement2(['div', {class: 'TRAFFIC_ENTRY ' + dir_class(e.dir), title: e.title}, children]);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/* Whether an Expanded-view section is shown (persisted toggles). schema
|
|
612
|
+
* defaults OFF (rarely wanted); data + metadata default as noted. */
|
|
613
|
+
function full_show(key)
|
|
614
|
+
{
|
|
615
|
+
let def = (key === "dev_full_data") ? 1 : 0;
|
|
616
|
+
return !!dev_num(key, def);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/* Filter a payload's top-level keys for the Expanded view: the `schema`
|
|
620
|
+
* and `data` keys and the `__…__` metadata markers are each shown only
|
|
621
|
+
* when their toggle is on; everything else is always kept. */
|
|
622
|
+
function full_sections(payload)
|
|
623
|
+
{
|
|
624
|
+
if(!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
625
|
+
return payload;
|
|
626
|
+
}
|
|
627
|
+
let show_schema = full_show("dev_full_schema");
|
|
628
|
+
let show_data = full_show("dev_full_data");
|
|
629
|
+
let show_meta = full_show("dev_full_meta");
|
|
630
|
+
let out = {};
|
|
631
|
+
for(let k of Object.keys(payload)) {
|
|
632
|
+
if(k === "schema") {
|
|
633
|
+
if(show_schema) { out[k] = payload[k]; }
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if(k === "data") {
|
|
637
|
+
if(show_data) { out[k] = payload[k]; }
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if(/^__.*__$/.test(k)) {
|
|
641
|
+
if(show_meta) { out[k] = payload[k]; }
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
out[k] = payload[k];
|
|
645
|
+
}
|
|
646
|
+
return out;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/* Full view: the message payload pretty-printed and fully expanded
|
|
650
|
+
* (nothing folded) — for reading / copying a whole payload. The
|
|
651
|
+
* schema / data / metadata sections are toggled by the Expand chips. */
|
|
652
|
+
function render_full(e)
|
|
653
|
+
{
|
|
654
|
+
let head = event_spans(e);
|
|
655
|
+
head.push(mute_button(e.sig));
|
|
656
|
+
head.push(['span', {class: 'TRAFFIC_META'}, `${traffic_size(e.size)} · ${e.ts}`]);
|
|
657
|
+
|
|
658
|
+
let payload = full_sections(e.kw ? e.kw : e.jn);
|
|
659
|
+
let text;
|
|
660
|
+
try {
|
|
661
|
+
text = JSON.stringify(payload, null, 2);
|
|
662
|
+
} catch(err) {
|
|
663
|
+
text = String(payload);
|
|
664
|
+
}
|
|
665
|
+
let children = [
|
|
666
|
+
['div', {class: 'TRAFFIC_HEADER'}, head],
|
|
667
|
+
['pre', {class: 'TRAFFIC_FULL'}, text],
|
|
668
|
+
];
|
|
669
|
+
return createElement2(['div', {class: 'TRAFFIC_ENTRY ' + dir_class(e.dir), title: e.title}, children]);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function render_compact(e)
|
|
673
|
+
{
|
|
674
|
+
let kids = event_spans(e);
|
|
675
|
+
kids.push(['span', {class: 'TRAFFIC_SUMMARY'}, compact_summary(e.kw)]);
|
|
676
|
+
kids.push(mute_button(e.sig));
|
|
677
|
+
kids.push(['span', {class: 'TRAFFIC_META'}, `${traffic_size(e.size)} · ${e.ts}`]);
|
|
678
|
+
return createElement2(['div', {class: 'TRAFFIC_LINE ' + dir_class(e.dir), title: e.title}, kids]);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function render_name(e)
|
|
682
|
+
{
|
|
683
|
+
let kids = event_spans(e);
|
|
684
|
+
kids.push(mute_button(e.sig));
|
|
685
|
+
kids.push(['span', {class: 'TRAFFIC_META'}, e.ts]);
|
|
686
|
+
return createElement2(['div', {class: 'TRAFFIC_NAME ' + dir_class(e.dir), title: e.title}, kids]);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* A mirrored framework log line (error/warning/info/debug/msg). */
|
|
690
|
+
function render_log(e)
|
|
691
|
+
{
|
|
692
|
+
return createElement2(
|
|
693
|
+
['div', {class: 'YDEV_LOGROW YDEV_LOG_' + e.level, title: e.level}, [
|
|
694
|
+
['span', {class: 'YDEV_LOG_LVL'}, e.level],
|
|
695
|
+
['span', {class: 'YDEV_LOG_TXT'}, e.text],
|
|
696
|
+
['span', {class: 'TRAFFIC_META'}, e.ts],
|
|
697
|
+
]]
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function render_entry(e)
|
|
702
|
+
{
|
|
703
|
+
if(e.kind === "log") {
|
|
704
|
+
return render_log(e);
|
|
705
|
+
}
|
|
706
|
+
let view = dev_view();
|
|
707
|
+
if(view === "full") {
|
|
708
|
+
return render_full(e);
|
|
709
|
+
}
|
|
710
|
+
if(view === "compact") {
|
|
711
|
+
return render_compact(e);
|
|
712
|
+
}
|
|
713
|
+
if(view === "name") {
|
|
714
|
+
return render_name(e);
|
|
715
|
+
}
|
|
716
|
+
return render_detailed(e);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
/******************************
|
|
721
|
+
* Log painting
|
|
722
|
+
******************************/
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
function clear_traffic()
|
|
726
|
+
{
|
|
727
|
+
TRAFFIC_LOG.length = 0;
|
|
728
|
+
TRAFFIC_COUNTS.clear();
|
|
729
|
+
let logger = document.getElementById('developer-traffic-logger');
|
|
730
|
+
if(logger) {
|
|
731
|
+
logger.replaceChildren();
|
|
732
|
+
}
|
|
733
|
+
update_stats();
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/* Full repaint from the buffer (view / filter changes, reopen). */
|
|
737
|
+
function rerender_all()
|
|
738
|
+
{
|
|
739
|
+
let logger = document.getElementById('developer-traffic-logger');
|
|
740
|
+
if(!logger) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
ensure_dev_style();
|
|
744
|
+
let ctx = build_filter_ctx();
|
|
745
|
+
let frag = document.createDocumentFragment();
|
|
746
|
+
let shown = 0;
|
|
747
|
+
for(let e of TRAFFIC_LOG) {
|
|
748
|
+
e.$node = null;
|
|
749
|
+
if(!entry_hidden(e, ctx)) {
|
|
750
|
+
let node = render_entry(e);
|
|
751
|
+
e.$node = node;
|
|
752
|
+
frag.appendChild(node);
|
|
753
|
+
shown++;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
logger.replaceChildren(frag);
|
|
757
|
+
if(shown === 0) {
|
|
758
|
+
let $empty = document.createElement('div');
|
|
759
|
+
$empty.className = 'YDEV_EMPTY';
|
|
760
|
+
$empty.textContent = TRAFFIC_LOG.length
|
|
761
|
+
? "No messages match the current filters."
|
|
762
|
+
: "Waiting for activity — console logs and errors show automatically; enable Traffic or Automata for more.";
|
|
763
|
+
logger.appendChild($empty);
|
|
764
|
+
}
|
|
765
|
+
logger.scrollTop = logger.scrollHeight;
|
|
766
|
+
update_stats();
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/* Live counters in the status strip. */
|
|
770
|
+
function update_stats()
|
|
771
|
+
{
|
|
772
|
+
let $s = document.getElementById('ydev-stats');
|
|
773
|
+
if(!$s) {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
let ctx = build_filter_ctx();
|
|
777
|
+
let total = TRAFFIC_LOG.length;
|
|
778
|
+
let shown = 0, out = 0, inc = 0, err = 0, hidden = 0, bytes = 0;
|
|
779
|
+
for(let e of TRAFFIC_LOG) {
|
|
780
|
+
bytes += e.size || 0;
|
|
781
|
+
if(e.dir === 1) {
|
|
782
|
+
out++;
|
|
783
|
+
} else if(e.dir === 2) {
|
|
784
|
+
inc++;
|
|
785
|
+
} else if(e.dir === 3) {
|
|
786
|
+
err++;
|
|
787
|
+
}
|
|
788
|
+
if(entry_hidden(e, ctx)) {
|
|
789
|
+
hidden++;
|
|
790
|
+
} else {
|
|
791
|
+
shown++;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
$s.replaceChildren();
|
|
795
|
+
let cells = [
|
|
796
|
+
[`${shown}/${total} shown`, ''],
|
|
797
|
+
[`⇢ ${out}`, 's-out'],
|
|
798
|
+
[`⇠ ${inc}`, 's-in'],
|
|
799
|
+
[`⚠ ${err}`, 's-err'],
|
|
800
|
+
[`⊘ ${hidden} hidden`, ''],
|
|
801
|
+
[`${traffic_size(bytes)}`, ''],
|
|
802
|
+
];
|
|
803
|
+
cells.forEach(([text, cls]) => {
|
|
804
|
+
let d = document.createElement('span');
|
|
805
|
+
d.className = 'YDEV_STAT' + (cls ? ' ' + cls : '');
|
|
806
|
+
d.textContent = text;
|
|
807
|
+
$s.appendChild(d);
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/************************************************************
|
|
812
|
+
* Append one inter-event message. Kept in a bounded buffer so
|
|
813
|
+
* view/filter switches repaint from memory. Shared by the legacy
|
|
814
|
+
* C_YUI_WINDOW (setup_dev) and the modal (build_dev_panel).
|
|
27
815
|
*
|
|
816
|
+
* direction: 1 outgoing (⇢), 2 incoming (⇠), 3 error (⚠).
|
|
817
|
+
* With no logger mounted, fall back to a console dump.
|
|
28
818
|
************************************************************/
|
|
29
819
|
function info_traffic(title, msg, direction, size)
|
|
30
820
|
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// modal). Otherwise just dump to the console.
|
|
34
|
-
if(!document.getElementById('developer-traffic-logger')) {
|
|
821
|
+
let logger = document.getElementById('developer-traffic-logger');
|
|
822
|
+
if(!logger) {
|
|
35
823
|
trace_json(msg);
|
|
36
824
|
return;
|
|
37
825
|
}
|
|
@@ -40,104 +828,152 @@ function info_traffic(title, msg, direction, size)
|
|
|
40
828
|
size = 0;
|
|
41
829
|
}
|
|
42
830
|
|
|
43
|
-
let
|
|
831
|
+
let jn;
|
|
44
832
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} else {
|
|
48
|
-
jn_msg = JSON.parse(JSON.stringify(msg));
|
|
49
|
-
}
|
|
50
|
-
} catch (e) {
|
|
833
|
+
jn = is_string(msg) ? JSON.parse(msg) : JSON.parse(JSON.stringify(msg));
|
|
834
|
+
} catch(e) {
|
|
51
835
|
return;
|
|
52
836
|
}
|
|
53
837
|
|
|
54
|
-
|
|
55
|
-
text: undefined,
|
|
56
|
-
json: jn_msg
|
|
57
|
-
};
|
|
838
|
+
ensure_dev_style();
|
|
58
839
|
|
|
59
|
-
|
|
60
|
-
|
|
840
|
+
let event = (jn && jn.event) ? String(jn.event) : "(no event)";
|
|
841
|
+
let kw = (jn && jn.kw && typeof jn.kw === "object") ? jn.kw : null;
|
|
842
|
+
let command = (kw && typeof kw.command === "string") ? kw.command : "";
|
|
843
|
+
let sig = traffic_signature(event, command);
|
|
844
|
+
|
|
845
|
+
let hay = "";
|
|
846
|
+
try {
|
|
847
|
+
hay = (event + " " + command + " " + (kw ? JSON.stringify(kw) : "")).toLowerCase();
|
|
848
|
+
} catch(e) {
|
|
849
|
+
hay = (event + " " + command).toLowerCase();
|
|
850
|
+
}
|
|
61
851
|
|
|
62
|
-
|
|
63
|
-
|
|
852
|
+
let entry = {
|
|
853
|
+
title: title || "", event: event, command: command, sig: sig,
|
|
854
|
+
dir: direction, size: size, ts: traffic_now(),
|
|
855
|
+
kw: kw, jn: jn, hay: hay, $node: null,
|
|
856
|
+
};
|
|
64
857
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
let seconds = pad(now.getSeconds(), 2);
|
|
68
|
-
let milliseconds = pad(now.getMilliseconds(), 4);
|
|
858
|
+
TRAFFIC_LOG.push(entry);
|
|
859
|
+
TRAFFIC_COUNTS.set(sig, (TRAFFIC_COUNTS.get(sig) || 0) + 1);
|
|
69
860
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
861
|
+
/* When a signature just crosses the "recurring" threshold and
|
|
862
|
+
* the periodic filter is on, its earlier entries must disappear
|
|
863
|
+
* too — a full repaint is the correct, simple answer. */
|
|
864
|
+
let crossed = dev_hide_periodic() && (TRAFFIC_COUNTS.get(sig) === PERIODIC_THRESHOLD);
|
|
73
865
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
let
|
|
77
|
-
if(
|
|
78
|
-
|
|
79
|
-
} else if(direction === 3) {
|
|
80
|
-
style += "color:red;";
|
|
866
|
+
if(TRAFFIC_LOG.length > TRAFFIC_MAX) {
|
|
867
|
+
let old = TRAFFIC_LOG.shift();
|
|
868
|
+
let c = (TRAFFIC_COUNTS.get(old.sig) || 0) - 1;
|
|
869
|
+
if(c <= 0) {
|
|
870
|
+
TRAFFIC_COUNTS.delete(old.sig);
|
|
81
871
|
} else {
|
|
82
|
-
|
|
872
|
+
TRAFFIC_COUNTS.set(old.sig, c);
|
|
83
873
|
}
|
|
874
|
+
if(old.$node && old.$node.parentNode) {
|
|
875
|
+
old.$node.parentNode.removeChild(old.$node);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
84
878
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
let sz = 15;
|
|
98
|
-
$target.style.setProperty('--jse-font-size-mono', sz + 'px');
|
|
99
|
-
$target.style.setProperty('--jse-font-family-mono', font_family);
|
|
100
|
-
|
|
101
|
-
document.getElementById("developer-traffic-logger").appendChild($item);
|
|
102
|
-
|
|
103
|
-
let editor = new JSONEditor({
|
|
104
|
-
target: $target,
|
|
105
|
-
props: {
|
|
106
|
-
content: content,
|
|
107
|
-
readOnly: true,
|
|
108
|
-
timestampTag: function ({field, value, path}) {
|
|
109
|
-
if (field === '__t__' || field === '__tm__' || field === 'tm' ||
|
|
110
|
-
field === 'from_t' || field === 'to_t' || field === 't' ||
|
|
111
|
-
field === 'from_tm' || field === 'to_tm' || field === 'time'
|
|
112
|
-
) {
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
return false;
|
|
116
|
-
},
|
|
117
|
-
timestampFormat: function ({field, value, path}) {
|
|
118
|
-
if (field === '__t__' || field === '__tm__' || field === 'tm' ||
|
|
119
|
-
field === 'from_t' || field === 'to_t' || field === 't' ||
|
|
120
|
-
field === 'from_tm' || field === 'to_tm' || field === 'time'
|
|
121
|
-
) {
|
|
122
|
-
return new Date(value * 1000).toISOString();
|
|
123
|
-
}
|
|
124
|
-
return null;
|
|
125
|
-
},
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
editor.expand(path => path.length < 2);
|
|
129
|
-
|
|
130
|
-
element.scrollIntoView({block: "end"});
|
|
879
|
+
if(crossed) {
|
|
880
|
+
rerender_all();
|
|
881
|
+
} else if(!entry_hidden(entry, build_filter_ctx())) {
|
|
882
|
+
let node = render_entry(entry);
|
|
883
|
+
entry.$node = node;
|
|
884
|
+
/* Drop the "no traffic yet" placeholder before the first row. */
|
|
885
|
+
let ph = logger.querySelector('.YDEV_EMPTY');
|
|
886
|
+
if(ph) {
|
|
887
|
+
ph.remove();
|
|
888
|
+
}
|
|
889
|
+
logger.appendChild(node);
|
|
890
|
+
node.scrollIntoView({block: "end"});
|
|
131
891
|
}
|
|
892
|
+
update_stats();
|
|
132
893
|
}
|
|
133
894
|
|
|
895
|
+
|
|
896
|
+
/* Re-entrancy guard: rendering a captured log line must not itself capture
|
|
897
|
+
* the logs it emits (that would recurse). */
|
|
898
|
+
let __in_info_log__ = false;
|
|
899
|
+
|
|
134
900
|
/************************************************************
|
|
135
|
-
*
|
|
901
|
+
* Mirror one framework log line into the monitor, alongside the
|
|
902
|
+
* inter-event traffic. level ∈ error|warning|info|debug|msg — the automata
|
|
903
|
+
* (FSM) trace arrives here too, as `debug`. No-op while the window is closed
|
|
904
|
+
* (the line already went to the browser console).
|
|
136
905
|
************************************************************/
|
|
906
|
+
function info_log(level, msg, hora)
|
|
907
|
+
{
|
|
908
|
+
if(__in_info_log__) {
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
let logger = document.getElementById('developer-traffic-logger');
|
|
912
|
+
if(!logger) {
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
__in_info_log__ = true;
|
|
916
|
+
try {
|
|
917
|
+
ensure_dev_style();
|
|
918
|
+
let lvl = level || "debug";
|
|
919
|
+
let text;
|
|
920
|
+
if(lvl === "json") {
|
|
921
|
+
try {
|
|
922
|
+
text = JSON.stringify(msg, null, 2);
|
|
923
|
+
} catch(e) {
|
|
924
|
+
text = String(msg);
|
|
925
|
+
}
|
|
926
|
+
if(text.length > 4000) {
|
|
927
|
+
text = text.slice(0, 4000) + "\n…(truncated)";
|
|
928
|
+
}
|
|
929
|
+
} else {
|
|
930
|
+
text = is_string(msg) ? msg : String(msg);
|
|
931
|
+
}
|
|
932
|
+
let entry = {
|
|
933
|
+
kind: "log", level: lvl, text: text,
|
|
934
|
+
dir: 0, size: 0, ts: traffic_now(),
|
|
935
|
+
sig: "log:" + lvl, hay: (lvl + " " + text).toLowerCase(), $node: null,
|
|
936
|
+
};
|
|
937
|
+
TRAFFIC_LOG.push(entry);
|
|
938
|
+
if(TRAFFIC_LOG.length > TRAFFIC_MAX) {
|
|
939
|
+
let old = TRAFFIC_LOG.shift();
|
|
940
|
+
if(old.kind !== "log") {
|
|
941
|
+
let c = (TRAFFIC_COUNTS.get(old.sig) || 0) - 1;
|
|
942
|
+
if(c <= 0) {
|
|
943
|
+
TRAFFIC_COUNTS.delete(old.sig);
|
|
944
|
+
} else {
|
|
945
|
+
TRAFFIC_COUNTS.set(old.sig, c);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if(old.$node && old.$node.parentNode) {
|
|
949
|
+
old.$node.parentNode.removeChild(old.$node);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if(!entry_hidden(entry, build_filter_ctx())) {
|
|
953
|
+
let node = render_entry(entry);
|
|
954
|
+
entry.$node = node;
|
|
955
|
+
let ph = logger.querySelector('.YDEV_EMPTY');
|
|
956
|
+
if(ph) {
|
|
957
|
+
ph.remove();
|
|
958
|
+
}
|
|
959
|
+
logger.appendChild(node);
|
|
960
|
+
node.scrollIntoView({block: "end"});
|
|
961
|
+
}
|
|
962
|
+
update_stats();
|
|
963
|
+
} finally {
|
|
964
|
+
__in_info_log__ = false;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
/******************************
|
|
970
|
+
* Trace toggles
|
|
971
|
+
******************************/
|
|
972
|
+
|
|
973
|
+
|
|
137
974
|
function trace_traffic()
|
|
138
975
|
{
|
|
139
|
-
let v = kw_get_local_storage_value("trace_traffic");
|
|
140
|
-
v = Number(v);
|
|
976
|
+
let v = Number(kw_get_local_storage_value("trace_traffic"));
|
|
141
977
|
if(v) {
|
|
142
978
|
gobj_write_attr(gobj_yuno(), "trace_inter_event", false);
|
|
143
979
|
v = 0;
|
|
@@ -147,147 +983,348 @@ function trace_traffic()
|
|
|
147
983
|
v = 1;
|
|
148
984
|
}
|
|
149
985
|
kw_set_local_storage_value("trace_traffic", v);
|
|
150
|
-
|
|
986
|
+
refresh_dev_chrome();
|
|
151
987
|
}
|
|
152
988
|
|
|
153
|
-
/************************************************************
|
|
154
|
-
*
|
|
155
|
-
************************************************************/
|
|
156
989
|
function trace_automata()
|
|
157
990
|
{
|
|
158
|
-
let v = kw_get_local_storage_value("trace_automata");
|
|
159
|
-
v
|
|
160
|
-
if(v===0) {
|
|
991
|
+
let v = Number(kw_get_local_storage_value("trace_automata"));
|
|
992
|
+
if(v === 0) {
|
|
161
993
|
v = 1;
|
|
162
|
-
} else if(v===1) {
|
|
994
|
+
} else if(v === 1) {
|
|
163
995
|
v = 2;
|
|
164
996
|
} else {
|
|
165
997
|
v = 0;
|
|
166
998
|
}
|
|
167
999
|
gobj_write_attr(gobj_yuno(), "tracing", v);
|
|
168
1000
|
kw_set_local_storage_value("trace_automata", v);
|
|
169
|
-
|
|
1001
|
+
refresh_dev_chrome();
|
|
170
1002
|
}
|
|
171
1003
|
|
|
172
|
-
/************************************************************
|
|
173
|
-
*
|
|
174
|
-
************************************************************/
|
|
175
1004
|
function trace_creation()
|
|
176
1005
|
{
|
|
177
|
-
let v = kw_get_local_storage_value("trace_creation");
|
|
178
|
-
v =
|
|
179
|
-
if(v===0) {
|
|
180
|
-
v = 1;
|
|
181
|
-
} else {
|
|
182
|
-
v = 0;
|
|
183
|
-
}
|
|
1006
|
+
let v = Number(kw_get_local_storage_value("trace_creation"));
|
|
1007
|
+
v = v === 0 ? 1 : 0;
|
|
184
1008
|
gobj_write_attr(gobj_yuno(), "trace_creation", v);
|
|
185
1009
|
kw_set_local_storage_value("trace_creation", v);
|
|
186
|
-
|
|
1010
|
+
refresh_dev_chrome();
|
|
187
1011
|
}
|
|
188
1012
|
|
|
189
|
-
/************************************************************
|
|
190
|
-
*
|
|
191
|
-
************************************************************/
|
|
192
1013
|
function trace_start_stop()
|
|
193
1014
|
{
|
|
194
|
-
let v = kw_get_local_storage_value("trace_start_stop");
|
|
195
|
-
v =
|
|
196
|
-
if(v===0) {
|
|
197
|
-
v = 1;
|
|
198
|
-
} else {
|
|
199
|
-
v = 0;
|
|
200
|
-
}
|
|
1015
|
+
let v = Number(kw_get_local_storage_value("trace_start_stop"));
|
|
1016
|
+
v = v === 0 ? 1 : 0;
|
|
201
1017
|
gobj_write_attr(gobj_yuno(), "trace_start_stop", v);
|
|
202
1018
|
kw_set_local_storage_value("trace_start_stop", v);
|
|
203
|
-
|
|
1019
|
+
refresh_dev_chrome();
|
|
204
1020
|
}
|
|
205
1021
|
|
|
206
|
-
/************************************************************
|
|
207
|
-
*
|
|
208
|
-
************************************************************/
|
|
209
1022
|
function trace_subscriptions()
|
|
210
1023
|
{
|
|
211
|
-
let v = kw_get_local_storage_value("trace_subscriptions");
|
|
212
|
-
v =
|
|
213
|
-
if(v===0) {
|
|
214
|
-
v = 1;
|
|
215
|
-
} else {
|
|
216
|
-
v = 0;
|
|
217
|
-
}
|
|
1024
|
+
let v = Number(kw_get_local_storage_value("trace_subscriptions"));
|
|
1025
|
+
v = v === 0 ? 1 : 0;
|
|
218
1026
|
gobj_write_attr(gobj_yuno(), "trace_subscriptions", v);
|
|
219
1027
|
kw_set_local_storage_value("trace_subscriptions", v);
|
|
220
|
-
|
|
1028
|
+
refresh_dev_chrome();
|
|
221
1029
|
}
|
|
222
1030
|
|
|
223
|
-
/************************************************************
|
|
224
|
-
*
|
|
225
|
-
************************************************************/
|
|
226
1031
|
function trace_i18n()
|
|
227
1032
|
{
|
|
228
|
-
let v = kw_get_local_storage_value("trace_i18n");
|
|
229
|
-
v =
|
|
230
|
-
|
|
231
|
-
v = 1;
|
|
232
|
-
} else {
|
|
233
|
-
v = 0;
|
|
234
|
-
}
|
|
235
|
-
i18next.options.debug = v?true:false;
|
|
1033
|
+
let v = Number(kw_get_local_storage_value("trace_i18n"));
|
|
1034
|
+
v = v === 0 ? 1 : 0;
|
|
1035
|
+
i18next.options.debug = v ? true : false;
|
|
236
1036
|
kw_set_local_storage_value("trace_i18n", v);
|
|
237
|
-
|
|
1037
|
+
refresh_dev_chrome();
|
|
238
1038
|
}
|
|
239
1039
|
|
|
240
|
-
/************************************************************
|
|
241
|
-
*
|
|
242
|
-
************************************************************/
|
|
243
1040
|
function set_no_poll()
|
|
244
1041
|
{
|
|
245
|
-
let v = kw_get_local_storage_value("no_poll");
|
|
246
|
-
v =
|
|
247
|
-
if(v) {
|
|
248
|
-
v = 0;
|
|
249
|
-
} else {
|
|
250
|
-
v = 1;
|
|
251
|
-
}
|
|
1042
|
+
let v = Number(kw_get_local_storage_value("no_poll"));
|
|
1043
|
+
v = v ? 0 : 1;
|
|
252
1044
|
gobj_write_attr(gobj_yuno(), "no_poll", v);
|
|
253
1045
|
kw_set_local_storage_value("no_poll", v);
|
|
254
|
-
|
|
1046
|
+
refresh_dev_chrome();
|
|
255
1047
|
}
|
|
256
1048
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
1049
|
+
|
|
1050
|
+
/******************************
|
|
1051
|
+
* Chrome (controls)
|
|
1052
|
+
******************************/
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
/* Sync every control's visual state from persisted prefs, plus the
|
|
1056
|
+
* muted-events row and the stats strip. Idempotent; null-guarded so
|
|
1057
|
+
* it is safe to call whether or not the window is mounted. */
|
|
1058
|
+
function refresh_dev_chrome()
|
|
261
1059
|
{
|
|
262
|
-
|
|
1060
|
+
document.querySelectorAll('.YDEV_CHIP[data-trace]').forEach(($b) => {
|
|
1061
|
+
let key = $b.getAttribute('data-trace');
|
|
1062
|
+
let label = $b.getAttribute('data-label') || '';
|
|
1063
|
+
let v = dev_num(key, 0);
|
|
1064
|
+
$b.textContent = (key === "trace_automata" && v > 0) ? (label + " " + v) : label;
|
|
1065
|
+
$b.classList.toggle('is-active', v > 0);
|
|
1066
|
+
});
|
|
263
1067
|
|
|
264
|
-
let
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
1068
|
+
let view = dev_view();
|
|
1069
|
+
document.querySelectorAll('.YDEV_SEG_BTN[data-view]').forEach(($b) => {
|
|
1070
|
+
$b.classList.toggle('is-active', $b.getAttribute('data-view') === view);
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
/* The Expand section toggles only apply to the Expanded view — show
|
|
1074
|
+
* the group only there, and reflect each toggle's persisted state. */
|
|
1075
|
+
let $eg = document.getElementById('ydev-expand-grp');
|
|
1076
|
+
if($eg) {
|
|
1077
|
+
$eg.style.display = (view === 'full') ? '' : 'none';
|
|
1078
|
+
}
|
|
1079
|
+
document.querySelectorAll('.YDEV_CHIP[data-expand]').forEach(($b) => {
|
|
1080
|
+
$b.classList.toggle('is-active', full_show($b.getAttribute('data-expand')));
|
|
1081
|
+
});
|
|
269
1082
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// Code repeated
|
|
274
|
-
// Build with DOM instead of innerHTML to prevent any XSS via localStorage values
|
|
275
|
-
$info.replaceChildren();
|
|
276
|
-
[
|
|
277
|
-
`Automata: ${trace}`,
|
|
278
|
-
`Creation: ${creation}`,
|
|
279
|
-
`Start/Stop: ${start_stop}`,
|
|
280
|
-
`Subscriptions: ${subscriptions}`,
|
|
281
|
-
`I18n: ${i18n}`,
|
|
282
|
-
`Traffic: ${traffic}`,
|
|
283
|
-
`No poll: ${no_poll}`,
|
|
284
|
-
].forEach(text => {
|
|
285
|
-
const div = document.createElement('div');
|
|
286
|
-
div.textContent = text;
|
|
287
|
-
$info.appendChild(div);
|
|
1083
|
+
document.querySelectorAll('.YDEV_CHIP[data-dir]').forEach(($b) => {
|
|
1084
|
+
$b.classList.toggle('is-active', !!dev_num($b.getAttribute('data-dir'), 1));
|
|
288
1085
|
});
|
|
1086
|
+
|
|
1087
|
+
document.querySelectorAll('.YDEV_CHIP[data-toggle="periodic"]').forEach(($b) => {
|
|
1088
|
+
$b.classList.toggle('is-active', dev_hide_periodic());
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
document.querySelectorAll('.YDEV_CHIP[data-toggle="automata-simple"]').forEach(($b) => {
|
|
1092
|
+
$b.classList.toggle('is-active', !!dev_num('dev_automata_simple', 0));
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
let $m = document.getElementById('ydev-muted');
|
|
1096
|
+
if($m) {
|
|
1097
|
+
$m.replaceChildren();
|
|
1098
|
+
let set = dev_muted();
|
|
1099
|
+
if(set.size) {
|
|
1100
|
+
let $lbl = document.createElement('span');
|
|
1101
|
+
$lbl.className = 'YDEV_LABEL';
|
|
1102
|
+
$lbl.textContent = 'Muted';
|
|
1103
|
+
$m.appendChild($lbl);
|
|
1104
|
+
set.forEach((sig) => {
|
|
1105
|
+
$m.appendChild(createElement2(
|
|
1106
|
+
['button', {class: 'YDEV_MUTED_CHIP', type: 'button', title: 'Unmute'},
|
|
1107
|
+
'⊘ ' + sig + ' ✕', {
|
|
1108
|
+
click: (ev) => {
|
|
1109
|
+
ev.stopPropagation();
|
|
1110
|
+
unmute_signature(sig);
|
|
1111
|
+
}
|
|
1112
|
+
}]
|
|
1113
|
+
));
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
update_stats();
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/* Serialize the currently-visible (filtered) traffic to plain text:
|
|
1122
|
+
* one header line per entry (time · direction · title · event/command)
|
|
1123
|
+
* followed by its pretty-printed payload. Honours the active filters and
|
|
1124
|
+
* search so the copy matches exactly what is on screen. */
|
|
1125
|
+
function traffic_to_text()
|
|
1126
|
+
{
|
|
1127
|
+
let ctx = build_filter_ctx();
|
|
1128
|
+
let out = [];
|
|
1129
|
+
for(let e of TRAFFIC_LOG) {
|
|
1130
|
+
if(entry_hidden(e, ctx)) {
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1133
|
+
let head = `${e.ts} ${dir_arrow(e.dir)} ` +
|
|
1134
|
+
`${e.title ? "[" + e.title + "] " : ""}${e.event}` +
|
|
1135
|
+
`${e.command ? " " + e.command : ""}`;
|
|
1136
|
+
out.push(head);
|
|
1137
|
+
let payload = e.kw ? e.kw : e.jn;
|
|
1138
|
+
try {
|
|
1139
|
+
out.push(JSON.stringify(payload, null, 2));
|
|
1140
|
+
} catch(err) {
|
|
1141
|
+
out.push(String(payload));
|
|
1142
|
+
}
|
|
1143
|
+
out.push("");
|
|
1144
|
+
}
|
|
1145
|
+
return out.join("\n");
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/* Copy text to the clipboard, with a fallback for insecure contexts. */
|
|
1149
|
+
function dev_copy_text(text)
|
|
1150
|
+
{
|
|
1151
|
+
if(navigator.clipboard && navigator.clipboard.writeText) {
|
|
1152
|
+
return navigator.clipboard.writeText(text).catch(() => {
|
|
1153
|
+
dev_fallback_copy(text);
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
dev_fallback_copy(text);
|
|
1157
|
+
return Promise.resolve();
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function dev_fallback_copy(text)
|
|
1161
|
+
{
|
|
1162
|
+
let ta = document.createElement("textarea");
|
|
1163
|
+
ta.value = text;
|
|
1164
|
+
ta.style.position = "fixed";
|
|
1165
|
+
ta.style.left = "-9999px";
|
|
1166
|
+
document.body.appendChild(ta);
|
|
1167
|
+
ta.select();
|
|
1168
|
+
try {
|
|
1169
|
+
document.execCommand("copy");
|
|
1170
|
+
} catch(e) {
|
|
1171
|
+
/* nothing else to try */
|
|
1172
|
+
}
|
|
1173
|
+
document.body.removeChild(ta);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/* The control bar: trace toggles, view selector, direction /
|
|
1177
|
+
* periodic filters, free-text search, copy, clear. Returns an element. */
|
|
1178
|
+
function build_control_bar()
|
|
1179
|
+
{
|
|
1180
|
+
let trace_chips = TRACE_DEFS.map(([key, label, fn]) => ['button', {
|
|
1181
|
+
class: 'YDEV_CHIP', 'data-trace': key, 'data-label': label, type: 'button',
|
|
1182
|
+
}, label, {
|
|
1183
|
+
click: (ev) => {
|
|
1184
|
+
ev.stopPropagation();
|
|
1185
|
+
fn();
|
|
1186
|
+
}
|
|
1187
|
+
}]);
|
|
1188
|
+
|
|
1189
|
+
/* Compact automata format (like the C kernel's trace_machine_format==1):
|
|
1190
|
+
* one short line per transition, no return line. Applies to the FSM trace
|
|
1191
|
+
* emitted while Automata is on. */
|
|
1192
|
+
let simple_mach = ['button', {
|
|
1193
|
+
class: 'YDEV_CHIP', 'data-toggle': 'automata-simple', type: 'button',
|
|
1194
|
+
title: 'Compact automata format (one line per transition, like C)',
|
|
1195
|
+
}, 'Simple mach', {
|
|
1196
|
+
click: (ev) => {
|
|
1197
|
+
ev.stopPropagation();
|
|
1198
|
+
let v = dev_num('dev_automata_simple', 0) ? 0 : 1;
|
|
1199
|
+
kw_set_local_storage_value('dev_automata_simple', v);
|
|
1200
|
+
gobj_set_trace_machine_format(v);
|
|
1201
|
+
refresh_dev_chrome();
|
|
1202
|
+
}
|
|
1203
|
+
}];
|
|
1204
|
+
|
|
1205
|
+
let mk_view = (v, label) => ['button', {class: 'YDEV_SEG_BTN', 'data-view': v, type: 'button'}, label, {
|
|
1206
|
+
click: (ev) => {
|
|
1207
|
+
ev.stopPropagation();
|
|
1208
|
+
set_view(v);
|
|
1209
|
+
}
|
|
1210
|
+
}];
|
|
1211
|
+
|
|
1212
|
+
let view_seg = ['div', {class: 'YDEV_SEG', id: 'ydev-seg'}, [
|
|
1213
|
+
mk_view('detailed', 'Detailed'),
|
|
1214
|
+
mk_view('full', 'Expanded'),
|
|
1215
|
+
mk_view('compact', 'Compact'),
|
|
1216
|
+
mk_view('name', 'Name only'),
|
|
1217
|
+
]];
|
|
1218
|
+
|
|
1219
|
+
/* Expanded-view section toggles (only meaningful in the 'full' view;
|
|
1220
|
+
* the group is shown/hidden by refresh_dev_chrome). */
|
|
1221
|
+
let mk_expand = (key, label) => ['button', {
|
|
1222
|
+
class: 'YDEV_CHIP', 'data-expand': key, type: 'button',
|
|
1223
|
+
title: 'Show ' + label + ' in the Expanded view',
|
|
1224
|
+
}, label, {
|
|
1225
|
+
click: (ev) => {
|
|
1226
|
+
ev.stopPropagation();
|
|
1227
|
+
toggle_pref(key, (key === 'dev_full_data') ? 1 : 0);
|
|
1228
|
+
}
|
|
1229
|
+
}];
|
|
1230
|
+
let expand_grp = ['div', {class: 'YDEV_GROUP', id: 'ydev-expand-grp'}, [
|
|
1231
|
+
['span', {class: 'YDEV_LABEL'}, 'Expand'],
|
|
1232
|
+
mk_expand('dev_full_schema', 'Schema'),
|
|
1233
|
+
mk_expand('dev_full_data', 'Data'),
|
|
1234
|
+
mk_expand('dev_full_meta', 'Metadata'),
|
|
1235
|
+
]];
|
|
1236
|
+
|
|
1237
|
+
let mk_dir = (dir, glyph, key, title) => ['button', {
|
|
1238
|
+
class: 'YDEV_CHIP s-' + dir, 'data-dir': key, type: 'button', title: title,
|
|
1239
|
+
}, glyph, {
|
|
1240
|
+
click: (ev) => {
|
|
1241
|
+
ev.stopPropagation();
|
|
1242
|
+
toggle_pref(key, 1);
|
|
1243
|
+
}
|
|
1244
|
+
}];
|
|
1245
|
+
|
|
1246
|
+
let dir_chips = [
|
|
1247
|
+
mk_dir('out', '⇢', 'dev_filter_out', 'Outgoing'),
|
|
1248
|
+
mk_dir('in', '⇠', 'dev_filter_in', 'Incoming'),
|
|
1249
|
+
mk_dir('err', '⚠', 'dev_filter_err', 'Errors'),
|
|
1250
|
+
];
|
|
1251
|
+
|
|
1252
|
+
let periodic_chip = ['button', {
|
|
1253
|
+
class: 'YDEV_CHIP', 'data-toggle': 'periodic', type: 'button',
|
|
1254
|
+
title: 'Hide recurring / periodic events (polls, heartbeats)',
|
|
1255
|
+
}, '⊘ Periodic', {
|
|
1256
|
+
click: (ev) => {
|
|
1257
|
+
ev.stopPropagation();
|
|
1258
|
+
toggle_pref('dev_hide_periodic', 0);
|
|
1259
|
+
}
|
|
1260
|
+
}];
|
|
1261
|
+
|
|
1262
|
+
let search = ['input', {
|
|
1263
|
+
class: 'YDEV_SEARCH', type: 'search', placeholder: 'filter events / payload…', 'data-role': 'search',
|
|
1264
|
+
}, '', {
|
|
1265
|
+
input: (ev) => {
|
|
1266
|
+
SEARCH_TEXT = String(ev.target.value || '').toLowerCase().trim();
|
|
1267
|
+
rerender_all();
|
|
1268
|
+
}
|
|
1269
|
+
}];
|
|
1270
|
+
|
|
1271
|
+
let copy = ['button', {class: 'YDEV_CHIP', type: 'button', title: 'Copy visible traffic to clipboard'}, 'Copy', {
|
|
1272
|
+
click: (ev) => {
|
|
1273
|
+
ev.stopPropagation();
|
|
1274
|
+
let btn = ev.currentTarget;
|
|
1275
|
+
dev_copy_text(traffic_to_text()).then(() => {
|
|
1276
|
+
let prev = btn.textContent;
|
|
1277
|
+
btn.textContent = 'Copied';
|
|
1278
|
+
setTimeout(() => { btn.textContent = prev; }, 1000);
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
}];
|
|
1282
|
+
|
|
1283
|
+
let clear = ['button', {class: 'YDEV_CHIP', type: 'button', title: 'Clear captured traffic'}, 'Clear', {
|
|
1284
|
+
click: (ev) => {
|
|
1285
|
+
ev.stopPropagation();
|
|
1286
|
+
clear_traffic();
|
|
1287
|
+
}
|
|
1288
|
+
}];
|
|
1289
|
+
|
|
1290
|
+
let grp = (label, items) => ['div', {class: 'YDEV_GROUP'}, [['span', {class: 'YDEV_LABEL'}, label], ...items]];
|
|
1291
|
+
let sep = () => ['span', {class: 'YDEV_SEP'}, ''];
|
|
1292
|
+
|
|
1293
|
+
return createElement2(['div', {class: 'YDEV_BAR'}, [
|
|
1294
|
+
grp('Traces', [...trace_chips, simple_mach]), sep(),
|
|
1295
|
+
grp('View', [view_seg]), expand_grp, sep(),
|
|
1296
|
+
grp('Show', [...dir_chips, periodic_chip]), sep(),
|
|
1297
|
+
grp('Find', [search]), sep(),
|
|
1298
|
+
grp('Log', [copy, clear]),
|
|
1299
|
+
]]);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
/* The window title strip (draggable header of C_YUI_WINDOW). */
|
|
1303
|
+
function build_title_header()
|
|
1304
|
+
{
|
|
1305
|
+
return createElement2(['div', {class: 'YDEV_TITLE'}, [
|
|
1306
|
+
['span', {class: 'YDEV_TITLE_MAIN'}, 'Developer'],
|
|
1307
|
+
['span', {class: 'YDEV_TITLE_SUB'}, 'yuno monitor · traffic & traces'],
|
|
1308
|
+
]]);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/* The monitor body: control bar + muted row + log + stats strip. */
|
|
1312
|
+
function build_dev_body()
|
|
1313
|
+
{
|
|
1314
|
+
return createElement2(['div', {class: 'YDEV_BODY'}, [
|
|
1315
|
+
build_control_bar(),
|
|
1316
|
+
['div', {class: 'YDEV_MUTED', id: 'ydev-muted'}, []],
|
|
1317
|
+
['div', {class: 'YDEV_LOG', id: 'developer-traffic-logger'}, []],
|
|
1318
|
+
['div', {class: 'YDEV_STATS', id: 'ydev-stats'}, []],
|
|
1319
|
+
]]);
|
|
289
1320
|
}
|
|
290
1321
|
|
|
1322
|
+
|
|
1323
|
+
/******************************
|
|
1324
|
+
* Public API
|
|
1325
|
+
******************************/
|
|
1326
|
+
|
|
1327
|
+
|
|
291
1328
|
/************************************************************
|
|
292
1329
|
* Was the developer window open last session? setup_dev()
|
|
293
1330
|
* persists open_developer_window (1 on open, 0 on close), so
|
|
@@ -304,9 +1341,6 @@ function dev_window_was_open()
|
|
|
304
1341
|
* Apply ALL persisted developer-trace flags to the running
|
|
305
1342
|
* yuno. Independent of the dev window — call it once at app
|
|
306
1343
|
* startup so a refresh keeps logging whatever was enabled.
|
|
307
|
-
* Single source of truth for "localStorage flag → effect";
|
|
308
|
-
* setup_dev() and build_dev_panel() reuse it instead of each
|
|
309
|
-
* re-applying a partial subset.
|
|
310
1344
|
************************************************************/
|
|
311
1345
|
function apply_dev_traces()
|
|
312
1346
|
{
|
|
@@ -330,121 +1364,30 @@ function apply_dev_traces()
|
|
|
330
1364
|
gobj_write_attr(gobj_yuno(), "trace_subscriptions", subscriptions);
|
|
331
1365
|
gobj_write_attr(gobj_yuno(), "no_poll", no_poll);
|
|
332
1366
|
i18next.options.debug = i18n ? true : false;
|
|
1367
|
+
|
|
1368
|
+
/* Compact vs verbose automata trace format (persisted). */
|
|
1369
|
+
gobj_set_trace_machine_format(
|
|
1370
|
+
Number(kw_get_local_storage_value("dev_automata_simple", 0, false)) ? 1 : 0);
|
|
1371
|
+
|
|
1372
|
+
/* Mirror the browser console (log_error/warning/info/debug/msg — the
|
|
1373
|
+
* automata FSM trace arrives as debug) into the monitor. info_log no-ops
|
|
1374
|
+
* while the window is closed, so this is safe to leave armed. */
|
|
1375
|
+
set_log_callback(info_log);
|
|
333
1376
|
}
|
|
334
1377
|
|
|
335
1378
|
/************************************************************
|
|
336
|
-
* Open the developer
|
|
1379
|
+
* Open the developer monitor inside a non-modal C_YUI_WINDOW
|
|
337
1380
|
* (title bar + maximize + close + resize).
|
|
338
1381
|
*
|
|
339
1382
|
* Shell-agnostic: the legacy C_YUI_MAIN shell has a
|
|
340
1383
|
* '#top-layer' stacking element; the new C_YUI_SHELL does not.
|
|
341
|
-
* We pass that element when present, otherwise null —
|
|
342
|
-
*
|
|
343
|
-
* new shell gets the same windowed dev panel instead of the
|
|
344
|
-
* floating build_dev_panel() box. Legacy behaviour is
|
|
345
|
-
* unchanged (when '#top-layer' exists it is still used).
|
|
1384
|
+
* We pass that element when present, otherwise null — C_YUI_WINDOW
|
|
1385
|
+
* falls back to document.body by contract.
|
|
346
1386
|
************************************************************/
|
|
347
1387
|
function setup_dev(self, show)
|
|
348
1388
|
{
|
|
349
|
-
let traffic = Number(kw_get_local_storage_value("trace_traffic", 0, false));
|
|
350
|
-
let trace = Number(kw_get_local_storage_value("trace_automata", 0, false));
|
|
351
|
-
let creation = Number(kw_get_local_storage_value("trace_creation", 0, false));
|
|
352
|
-
let start_stop = Number(kw_get_local_storage_value("trace_start_stop", 0, false));
|
|
353
|
-
let subscriptions = Number(kw_get_local_storage_value("trace_subscriptions", 0, false));
|
|
354
|
-
let i18n = Number(kw_get_local_storage_value("trace_i18n", 0, false));
|
|
355
|
-
let no_poll = Number(kw_get_local_storage_value("no_poll", 0, false));
|
|
356
|
-
|
|
357
1389
|
if(show) {
|
|
358
|
-
|
|
359
|
-
['div', {class: 'buttons'}, [
|
|
360
|
-
['button', {
|
|
361
|
-
class: 'button',
|
|
362
|
-
}, 'Automata', {
|
|
363
|
-
click: (evt) => {
|
|
364
|
-
evt.stopPropagation();
|
|
365
|
-
trace_automata();
|
|
366
|
-
}
|
|
367
|
-
}],
|
|
368
|
-
['button', {
|
|
369
|
-
class: 'button',
|
|
370
|
-
}, 'Creation', {
|
|
371
|
-
click: (evt) => {
|
|
372
|
-
evt.stopPropagation();
|
|
373
|
-
trace_creation();
|
|
374
|
-
}
|
|
375
|
-
}],
|
|
376
|
-
['button', {
|
|
377
|
-
class: 'button',
|
|
378
|
-
}, 'Star/Stop', {
|
|
379
|
-
click: (evt) => {
|
|
380
|
-
evt.stopPropagation();
|
|
381
|
-
trace_start_stop();
|
|
382
|
-
}
|
|
383
|
-
}],
|
|
384
|
-
['button', {
|
|
385
|
-
class: 'button',
|
|
386
|
-
}, 'Subscriptions', {
|
|
387
|
-
click: (evt) => {
|
|
388
|
-
evt.stopPropagation();
|
|
389
|
-
trace_subscriptions();
|
|
390
|
-
}
|
|
391
|
-
}],
|
|
392
|
-
['button', {
|
|
393
|
-
class: 'button',
|
|
394
|
-
}, 'I18n', {
|
|
395
|
-
click: (evt) => {
|
|
396
|
-
evt.stopPropagation();
|
|
397
|
-
trace_i18n();
|
|
398
|
-
}
|
|
399
|
-
}],
|
|
400
|
-
['button', {
|
|
401
|
-
class: 'button',
|
|
402
|
-
}, 'Traffic', {
|
|
403
|
-
click: (evt) => {
|
|
404
|
-
evt.stopPropagation();
|
|
405
|
-
trace_traffic();
|
|
406
|
-
}
|
|
407
|
-
}],
|
|
408
|
-
['button', {
|
|
409
|
-
class: 'button',
|
|
410
|
-
}, 'No Poll', {
|
|
411
|
-
click: (evt) => {
|
|
412
|
-
evt.stopPropagation();
|
|
413
|
-
set_no_poll();
|
|
414
|
-
}
|
|
415
|
-
}],
|
|
416
|
-
['button', {
|
|
417
|
-
class: 'button',
|
|
418
|
-
}, 'Clear Traffic', {
|
|
419
|
-
click: (evt) => {
|
|
420
|
-
evt.stopPropagation();
|
|
421
|
-
document.getElementById("developer-traffic-logger").innerHTML = "";
|
|
422
|
-
}
|
|
423
|
-
}],
|
|
424
|
-
]]
|
|
425
|
-
);
|
|
426
|
-
|
|
427
|
-
// TODO repon la position
|
|
428
|
-
// onViewResize: function() {
|
|
429
|
-
// var record = filter_dict(this.config, self.config.traffic_window_position);
|
|
430
|
-
// gobj_update_writable_attrs({traffic_window_position: record});
|
|
431
|
-
// gobj_save_persistent_attrs();
|
|
432
|
-
// },
|
|
433
|
-
// onViewMoveEnd: function() {
|
|
434
|
-
// var record = filter_dict(this.config, self.config.traffic_window_position);
|
|
435
|
-
// gobj_update_writable_attrs({traffic_window_position: record});
|
|
436
|
-
// gobj_save_persistent_attrs();
|
|
437
|
-
// }
|
|
438
|
-
|
|
439
|
-
// Code repeated
|
|
440
|
-
let estados = `
|
|
441
|
-
<div>Automata: ${trace}</div>
|
|
442
|
-
<div>Creation: ${creation}</div>
|
|
443
|
-
<div>Start/Stop: ${start_stop}</div>
|
|
444
|
-
<div>Subscriptions: ${subscriptions}</div>
|
|
445
|
-
<div>I18n: ${i18n}</div>
|
|
446
|
-
<div>Traffic: ${traffic}</div>
|
|
447
|
-
<div>No poll: ${no_poll}</div>`;
|
|
1390
|
+
ensure_dev_style();
|
|
448
1391
|
|
|
449
1392
|
gobj_create_service(
|
|
450
1393
|
"Developer-Window",
|
|
@@ -454,12 +1397,18 @@ function setup_dev(self, show)
|
|
|
454
1397
|
subscriber: null,
|
|
455
1398
|
showMax: true,
|
|
456
1399
|
modal: false,
|
|
457
|
-
header:
|
|
1400
|
+
header: build_title_header(),
|
|
1401
|
+
body: build_dev_body(),
|
|
1402
|
+
showFooter: false,
|
|
458
1403
|
auto_save_size_and_position: true,
|
|
459
1404
|
center: false,
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
1405
|
+
title: "Developer",
|
|
1406
|
+
icon: "yi-terminal",
|
|
1407
|
+
/* Opt into the dock/taskbar if the app provides one. `|| null`
|
|
1408
|
+
* because gobj_find_service returns undefined when absent, and
|
|
1409
|
+
* an undefined attr value logs "attr undefined: manager" (apps
|
|
1410
|
+
* without a window manager, e.g. wattyzer). null = no dock. */
|
|
1411
|
+
manager: gobj_find_service("__window_manager__", false) || null,
|
|
463
1412
|
on_close: function() {
|
|
464
1413
|
kw_set_local_storage_value("open_developer_window", 0);
|
|
465
1414
|
}
|
|
@@ -469,53 +1418,31 @@ function setup_dev(self, show)
|
|
|
469
1418
|
|
|
470
1419
|
kw_set_local_storage_value("open_developer_window", 1);
|
|
471
1420
|
|
|
1421
|
+
/* Mounted synchronously above; paint state + buffered history
|
|
1422
|
+
* on the next tick to be safe against mount ordering. */
|
|
1423
|
+
setTimeout(() => {
|
|
1424
|
+
refresh_dev_chrome();
|
|
1425
|
+
rerender_all();
|
|
1426
|
+
}, 0);
|
|
472
1427
|
}
|
|
473
1428
|
|
|
474
1429
|
apply_dev_traces();
|
|
475
1430
|
}
|
|
476
1431
|
|
|
477
1432
|
/************************************************************
|
|
478
|
-
* Build the developer
|
|
1433
|
+
* Build the developer monitor as a self-contained DOM subtree,
|
|
479
1434
|
* to be mounted by the new declarative shell via
|
|
480
1435
|
* yui_shell_show_modal (no C_YUI_WINDOW, no 'top-layer').
|
|
481
1436
|
*
|
|
482
1437
|
* Returns { $el, dispose }:
|
|
483
|
-
* - $el: the panel element (
|
|
484
|
-
* body + footer counters).
|
|
1438
|
+
* - $el: the panel element (control bar + log + stats).
|
|
485
1439
|
* - dispose: stops the inter-event traffic trace; call it from
|
|
486
1440
|
* the modal's on_close.
|
|
487
|
-
*
|
|
488
|
-
* Backwards compatible: setup_dev() (old shell, C_YUI_WINDOW) is
|
|
489
|
-
* untouched; the trace_* helpers and info_traffic are shared.
|
|
490
1441
|
************************************************************/
|
|
491
1442
|
function build_dev_panel()
|
|
492
1443
|
{
|
|
493
|
-
|
|
494
|
-
let trace = Number(kw_get_local_storage_value("trace_automata", 0, false));
|
|
495
|
-
let creation = Number(kw_get_local_storage_value("trace_creation", 0, false));
|
|
496
|
-
let start_stop = Number(kw_get_local_storage_value("trace_start_stop", 0, false));
|
|
497
|
-
let subscriptions = Number(kw_get_local_storage_value("trace_subscriptions", 0, false));
|
|
498
|
-
let i18n = Number(kw_get_local_storage_value("trace_i18n", 0, false));
|
|
499
|
-
let no_poll = Number(kw_get_local_storage_value("no_poll", 0, false));
|
|
1444
|
+
ensure_dev_style();
|
|
500
1445
|
|
|
501
|
-
let mk_btn = (label, fn) => ['button', {
|
|
502
|
-
class: 'button is-small',
|
|
503
|
-
}, label, {
|
|
504
|
-
click: (evt) => {
|
|
505
|
-
evt.stopPropagation();
|
|
506
|
-
fn();
|
|
507
|
-
}
|
|
508
|
-
}];
|
|
509
|
-
|
|
510
|
-
let counters = [
|
|
511
|
-
`Automata: ${trace}`, `Creation: ${creation}`,
|
|
512
|
-
`Start/Stop: ${start_stop}`, `Subscriptions: ${subscriptions}`,
|
|
513
|
-
`I18n: ${i18n}`, `Traffic: ${traffic}`, `No poll: ${no_poll}`,
|
|
514
|
-
].map(txt => ['div', {style: 'padding:0 8px;'}, txt]);
|
|
515
|
-
|
|
516
|
-
// The shell modal drops content into a transparent, unsized
|
|
517
|
-
// Bulma .modal-content; the panel must be its own opaque,
|
|
518
|
-
// sized window box. Theme-aware (read <html data-theme>).
|
|
519
1446
|
let dark = (typeof document !== "undefined") &&
|
|
520
1447
|
document.documentElement.getAttribute("data-theme") === "dark";
|
|
521
1448
|
let surface = dark ? "#1f2733" : "#ffffff";
|
|
@@ -530,50 +1457,18 @@ function build_dev_panel()
|
|
|
530
1457
|
'width:100%;height:min(72vh,720px);max-height:82vh;' +
|
|
531
1458
|
'background:' + surface + ';color:' + fg + ';' +
|
|
532
1459
|
'border:1px solid ' + bd + ';border-radius:10px;' +
|
|
533
|
-
'box-shadow:0 10px 30px rgba(0,0,0,0.35);'
|
|
534
|
-
|
|
535
|
-
'font-family:-apple-system,BlinkMacSystemFont,' +
|
|
536
|
-
"'Segoe UI',Roboto,Helvetica,Arial,sans-serif;",
|
|
537
|
-
}, [
|
|
538
|
-
['div', {
|
|
539
|
-
class: 'buttons',
|
|
540
|
-
style: 'flex:0 0 auto;display:flex;flex-wrap:wrap;' +
|
|
541
|
-
'gap:6px;margin:0 0 8px 0;',
|
|
542
|
-
}, [
|
|
543
|
-
mk_btn('Automata', trace_automata),
|
|
544
|
-
mk_btn('Creation', trace_creation),
|
|
545
|
-
mk_btn('Star/Stop', trace_start_stop),
|
|
546
|
-
mk_btn('Subscriptions', trace_subscriptions),
|
|
547
|
-
mk_btn('I18n', trace_i18n),
|
|
548
|
-
mk_btn('Traffic', trace_traffic),
|
|
549
|
-
mk_btn('No Poll', set_no_poll),
|
|
550
|
-
mk_btn('Clear Traffic', () => {
|
|
551
|
-
let l = document.getElementById("developer-traffic-logger");
|
|
552
|
-
if(l) {
|
|
553
|
-
l.innerHTML = "";
|
|
554
|
-
}
|
|
555
|
-
}),
|
|
556
|
-
]],
|
|
557
|
-
['div', {
|
|
558
|
-
style: 'flex:1 1 auto;min-height:0;overflow:auto;',
|
|
559
|
-
}, [
|
|
560
|
-
['div', {id: 'developer-traffic-logger',
|
|
561
|
-
style: 'margin:0 4px;'}, []],
|
|
562
|
-
]],
|
|
563
|
-
['div', {
|
|
564
|
-
id: 'developer-window-info',
|
|
565
|
-
class: 'is-flex is-justify-content-space-between',
|
|
566
|
-
style: 'flex:0 0 auto;border-top:1px solid ' + bd +
|
|
567
|
-
';padding-top:6px;margin-top:6px;font-size:12px;' +
|
|
568
|
-
'opacity:0.85;flex-wrap:nowrap;gap:1.25rem;white-space:nowrap;',
|
|
569
|
-
}, counters],
|
|
570
|
-
]]
|
|
1460
|
+
'box-shadow:0 10px 30px rgba(0,0,0,0.35);overflow:hidden;',
|
|
1461
|
+
}, [build_dev_body()]]
|
|
571
1462
|
);
|
|
572
1463
|
|
|
573
1464
|
apply_dev_traces();
|
|
574
1465
|
|
|
1466
|
+
setTimeout(() => {
|
|
1467
|
+
refresh_dev_chrome();
|
|
1468
|
+
rerender_all();
|
|
1469
|
+
}, 0);
|
|
1470
|
+
|
|
575
1471
|
let dispose = function() {
|
|
576
|
-
// Stop feeding traffic into a detached DOM.
|
|
577
1472
|
gobj_write_attr(gobj_yuno(), "trace_inter_event", false);
|
|
578
1473
|
};
|
|
579
1474
|
|