@yuneta/gobj-ui 7.23.37 → 7.23.39
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 +25 -2
- package/dist/gobj-ui.cjs.js +181 -21
- package/dist/gobj-ui.es.js +181 -21
- package/package.json +1 -1
- package/src/c_yui_gobj_tree_js.js +200 -10
- package/src/gobj_tree_model.js +48 -3
- package/src/gobj_tree_model.test.js +24 -3
- package/src/yui_gclass_view.js +23 -0
package/README.md
CHANGED
|
@@ -1381,14 +1381,32 @@ because opening a popover per node to learn it is not reading a tree:
|
|
|
1381
1381
|
| On the card | Says |
|
|
1382
1382
|
|---|---|
|
|
1383
1383
|
| Colour (border + left bar) | the ROLE: sky = the yuno, emerald = a service, amber = a pure child, pink = a volatil one, violet = a plain child |
|
|
1384
|
-
| Status pill |
|
|
1384
|
+
| Status pill | a SYMBOL and, where there is room, the word: `▶` playing, `‖` running but not playing (PAUSED, which is what the popover calls it in full), `■` stopped, `⊘` disabled. The symbol and not only a colour, because a dot has one shape and stopped is the state that has to be seen |
|
|
1385
1385
|
| Badges | `service`, `pure child`, `volatil child`, `disabled`, `bottom` (it has a bottom gobj), `commands` (its gclass exposes commands) |
|
|
1386
1386
|
| Its own line | the FSM state |
|
|
1387
1387
|
| Dashed border, dimmed | disabled — that branch is out of the game |
|
|
1388
1388
|
|
|
1389
1389
|
The popover adds the rest (`full name`, `parent`, `bottom gobj`, `traces`,
|
|
1390
1390
|
`flags`, how many children a fold is hiding) and shows only the rows that have
|
|
1391
|
-
an answer.
|
|
1391
|
+
an answer. Its two state rows are `status` and **`fsm state`** — different
|
|
1392
|
+
keys on purpose: they used to be `status` and `state`, which every Spanish app
|
|
1393
|
+
translated to the same word, so the popover showed *Estado: Parado* directly
|
|
1394
|
+
above *Estado: ST_IDLE*.
|
|
1395
|
+
|
|
1396
|
+
**The find box** matches gclass, name, full name and FSM state; a match wears
|
|
1397
|
+
an amber ring over its role colour (the card still has to say what it IS) and
|
|
1398
|
+
the chip counts them, because a graph that did not move looks the same whether
|
|
1399
|
+
nothing matched or the match was already on screen. A find is a REBUILD — an
|
|
1400
|
+
html node paints no G6 state, so the highlight lives in the card's own markup —
|
|
1401
|
+
which is why the box is rate-limited, and why the view is preserved: somebody
|
|
1402
|
+
who typed a letter did not ask to be moved.
|
|
1403
|
+
|
|
1404
|
+
**A service the app creates is the app's to START.** `c_yuno`'s `mt_play` starts
|
|
1405
|
+
only the DEFAULT service, so anything else stays stopped unless its creator
|
|
1406
|
+
starts it — and a gclass with no `mt_start` (`C_YUI_WINDOW_MANAGER` builds its
|
|
1407
|
+
dock in `mt_create` and runs off events) WORKS stopped, which is how one shipped
|
|
1408
|
+
that way in two apps: nothing failed, it just read `!!C_YUI_WINDOW_MANAGER` in
|
|
1409
|
+
every trace line and *stopped* in this view.
|
|
1392
1410
|
|
|
1393
1411
|
**It draws DESCRIPTORS, not gobjs** (`gobj_tree_model.js`), which is what lets
|
|
1394
1412
|
this same view show a **backend** yuno. A descriptor is plain data whose field
|
|
@@ -1433,6 +1451,11 @@ description describes the CLASS and cannot carry it, so the host supplies it —
|
|
|
1433
1451
|
the gobj tree passes `node_data.state` — and the machine lights that column.
|
|
1434
1452
|
Omitted, nothing is lit.
|
|
1435
1453
|
|
|
1454
|
+
`close_gclass_view(handle)` is the host-driven teardown, and the ✕ runs it too:
|
|
1455
|
+
the window destroys itself, but the viewer hangs from the HOST, so nothing else
|
|
1456
|
+
would take it down — and a viewer left alive keeps its service name, which made
|
|
1457
|
+
the next click answer *"service ALREADY registered"* (fixed in 7.23.38).
|
|
1458
|
+
|
|
1436
1459
|
`gclass_view_available()` registers `C_YUI_GCLASS` on demand and reports
|
|
1437
1460
|
whether it is there, so no app has to mount a gclass for a control it never
|
|
1438
1461
|
asked for. The gobj tree still asks before drawing its `gclass` button: a
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -11442,7 +11442,7 @@ function collect_foldable_paths(value, path, out, is_root) {
|
|
|
11442
11442
|
* move looks the same whether nothing matched or the match
|
|
11443
11443
|
* was already on screen.
|
|
11444
11444
|
************************************************************/
|
|
11445
|
-
function update_find_result(gobj) {
|
|
11445
|
+
function update_find_result$1(gobj) {
|
|
11446
11446
|
let priv = gobj.priv;
|
|
11447
11447
|
if (!priv.$find_result || !priv.$find_count) return;
|
|
11448
11448
|
if ((0, _yuneta_gobj_js.empty_string)(priv.search)) {
|
|
@@ -11707,7 +11707,7 @@ function ac_change_layout$2(gobj, event, kw, src) {
|
|
|
11707
11707
|
edge: { type: cfg.edge_type }
|
|
11708
11708
|
});
|
|
11709
11709
|
refresh_json(gobj);
|
|
11710
|
-
update_find_result(gobj);
|
|
11710
|
+
update_find_result$1(gobj);
|
|
11711
11711
|
return 0;
|
|
11712
11712
|
}
|
|
11713
11713
|
/************************************************************
|
|
@@ -11736,7 +11736,7 @@ function ac_toggle_fold(gobj, event, kw, src) {
|
|
|
11736
11736
|
}
|
|
11737
11737
|
priv.pending_keep = keep_of(gobj, kw && kw.keep || path);
|
|
11738
11738
|
refresh_json(gobj, { preserve_view: true });
|
|
11739
|
-
update_find_result(gobj);
|
|
11739
|
+
update_find_result$1(gobj);
|
|
11740
11740
|
return 0;
|
|
11741
11741
|
}
|
|
11742
11742
|
/************************************************************
|
|
@@ -11747,11 +11747,11 @@ function ac_toggle_fold(gobj, event, kw, src) {
|
|
|
11747
11747
|
* keystroke is unusable. The count says whether there is
|
|
11748
11748
|
* anything to look for elsewhere.
|
|
11749
11749
|
************************************************************/
|
|
11750
|
-
function ac_find_nodes$
|
|
11750
|
+
function ac_find_nodes$3(gobj, event, kw, src) {
|
|
11751
11751
|
let priv = gobj.priv;
|
|
11752
11752
|
priv.search = (kw && kw.text || "").trim().toLowerCase();
|
|
11753
11753
|
refresh_json(gobj, { preserve_view: true });
|
|
11754
|
-
update_find_result(gobj);
|
|
11754
|
+
update_find_result$1(gobj);
|
|
11755
11755
|
return 0;
|
|
11756
11756
|
}
|
|
11757
11757
|
/************************************************************
|
|
@@ -11763,7 +11763,7 @@ function ac_expand_all$2(gobj, event, kw, src) {
|
|
|
11763
11763
|
priv.collapsed.clear();
|
|
11764
11764
|
priv.reserved.clear();
|
|
11765
11765
|
refresh_json(gobj);
|
|
11766
|
-
update_find_result(gobj);
|
|
11766
|
+
update_find_result$1(gobj);
|
|
11767
11767
|
return 0;
|
|
11768
11768
|
}
|
|
11769
11769
|
/************************************************************
|
|
@@ -11780,7 +11780,7 @@ function ac_collapse_all$2(gobj, event, kw, src) {
|
|
|
11780
11780
|
priv.collapsed = new Set(paths);
|
|
11781
11781
|
priv.reserved.clear();
|
|
11782
11782
|
refresh_json(gobj);
|
|
11783
|
-
update_find_result(gobj);
|
|
11783
|
+
update_find_result$1(gobj);
|
|
11784
11784
|
return 0;
|
|
11785
11785
|
}
|
|
11786
11786
|
/************************************************************
|
|
@@ -12054,7 +12054,7 @@ function create_gclass$13(gclass_name) {
|
|
|
12054
12054
|
],
|
|
12055
12055
|
[
|
|
12056
12056
|
"EV_FIND_NODES",
|
|
12057
|
-
ac_find_nodes$
|
|
12057
|
+
ac_find_nodes$3,
|
|
12058
12058
|
null
|
|
12059
12059
|
],
|
|
12060
12060
|
[
|
|
@@ -16132,6 +16132,21 @@ function node_status(d) {
|
|
|
16132
16132
|
if (!d || !d.running) return "stopped";
|
|
16133
16133
|
return d.playing ? "playing" : "running";
|
|
16134
16134
|
}
|
|
16135
|
+
var STATUS_SYMBOLS = {
|
|
16136
|
+
"playing": "▶︎",
|
|
16137
|
+
"running": "‖",
|
|
16138
|
+
"stopped": "■︎"
|
|
16139
|
+
};
|
|
16140
|
+
var DISABLED_SYMBOL = "⊘";
|
|
16141
|
+
/************************************************************
|
|
16142
|
+
* The symbol of a node's status, which is what the reader
|
|
16143
|
+
* actually sees first. `disabled` overrides the rest: a
|
|
16144
|
+
* disabled gobj is not going to run whatever it is doing now.
|
|
16145
|
+
************************************************************/
|
|
16146
|
+
function node_status_symbol(d) {
|
|
16147
|
+
if (d && d.disabled) return DISABLED_SYMBOL;
|
|
16148
|
+
return STATUS_SYMBOLS[node_status(d)] || STATUS_SYMBOLS.stopped;
|
|
16149
|
+
}
|
|
16135
16150
|
/************************************************************
|
|
16136
16151
|
* The i18n key of the status, which is NOT the status itself:
|
|
16137
16152
|
* a gobj that runs and does not play is PAUSED to the reader,
|
|
@@ -16260,7 +16275,7 @@ function node_labels(t) {
|
|
|
16260
16275
|
full_name: t("full name"),
|
|
16261
16276
|
role: t("role"),
|
|
16262
16277
|
status: t("status"),
|
|
16263
|
-
|
|
16278
|
+
fsm_state: t("fsm state"),
|
|
16264
16279
|
disabled: t("disabled"),
|
|
16265
16280
|
parent: t("parent"),
|
|
16266
16281
|
bottom_gobj: t("bottom gobj"),
|
|
@@ -16289,8 +16304,8 @@ function node_info_rows(d, t) {
|
|
|
16289
16304
|
rows.push([f.name, d.name || "—"]);
|
|
16290
16305
|
if (d.fullname && d.fullname !== d.shortname) rows.push([f.full_name, d.fullname]);
|
|
16291
16306
|
rows.push([f.role, L.role[role_i18n_key(node_role(d))]]);
|
|
16292
|
-
rows.push([f.status, L.status[node_status_key(d)]]);
|
|
16293
|
-
rows.push([f.
|
|
16307
|
+
rows.push([f.status, node_status_symbol(d) + " " + L.status[node_status_key(d)]]);
|
|
16308
|
+
rows.push([f.fsm_state, d.state || "—"]);
|
|
16294
16309
|
if (d.disabled) rows.push([f.disabled, f.yes]);
|
|
16295
16310
|
if (d.parent) rows.push([f.parent, d.parent]);
|
|
16296
16311
|
if (d.bottom_gobj) rows.push([f.bottom_gobj, d.bottom_gobj]);
|
|
@@ -16558,6 +16573,9 @@ function open_gclass_view(host, gclass_name, opts) {
|
|
|
16558
16573
|
};
|
|
16559
16574
|
let $box = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(jv, "$container");
|
|
16560
16575
|
let on_close = () => {
|
|
16576
|
+
handle.win = null;
|
|
16577
|
+
handle.modal = null;
|
|
16578
|
+
close_gclass_view(handle);
|
|
16561
16579
|
if (typeof opts.on_close === "function") opts.on_close();
|
|
16562
16580
|
};
|
|
16563
16581
|
if (is_mobile$3()) {
|
|
@@ -16769,6 +16787,12 @@ var PRIVATE_DATA$9 = {
|
|
|
16769
16787
|
theme: null,
|
|
16770
16788
|
theme_observer: null,
|
|
16771
16789
|
$layout_select: null,
|
|
16790
|
+
search: "",
|
|
16791
|
+
match_count: 0,
|
|
16792
|
+
find_timer: null,
|
|
16793
|
+
$find_input: null,
|
|
16794
|
+
$find_result: null,
|
|
16795
|
+
$find_count: null,
|
|
16772
16796
|
$popover: null,
|
|
16773
16797
|
$popover_title: null,
|
|
16774
16798
|
$popover_body: null,
|
|
@@ -16914,6 +16938,7 @@ var NODE_SIZES = {
|
|
|
16914
16938
|
/***************************************************************
|
|
16915
16939
|
* Shared card typography.
|
|
16916
16940
|
***************************************************************/
|
|
16941
|
+
var FIND_MATCH_COLOR = "#D9A23F";
|
|
16917
16942
|
var GT_FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
|
|
16918
16943
|
/***************************************************************
|
|
16919
16944
|
* Theme-aware card palette derived from the role colour.
|
|
@@ -17286,11 +17311,107 @@ function destroy_ui$4(gobj) {
|
|
|
17286
17311
|
/************************************************************
|
|
17287
17312
|
* Toolbar
|
|
17288
17313
|
************************************************************/
|
|
17314
|
+
/************************************************************
|
|
17315
|
+
* The find box and its count.
|
|
17316
|
+
*
|
|
17317
|
+
* A tree of a real yuno is a hundred cards, and the only way
|
|
17318
|
+
* to locate one was to read them all. The COUNT is what makes
|
|
17319
|
+
* it honest: a graph that did not move looks the same whether
|
|
17320
|
+
* nothing matched or the match was already on screen -- the
|
|
17321
|
+
* same reason the JSON graph carries one, and the same
|
|
17322
|
+
* drawing, because the two sit in the same console.
|
|
17323
|
+
************************************************************/
|
|
17324
|
+
function make_find_items(gobj) {
|
|
17325
|
+
let priv = gobj.priv;
|
|
17326
|
+
let $find_input = (0, _yuneta_gobj_js.createElement2)([
|
|
17327
|
+
"input",
|
|
17328
|
+
{
|
|
17329
|
+
class: "GOBJ_TREE_FIND_INPUT input",
|
|
17330
|
+
type: "text",
|
|
17331
|
+
placeholder: (0, i18next.t)("search"),
|
|
17332
|
+
"data-i18n-placeholder": "search",
|
|
17333
|
+
"aria-label": (0, i18next.t)("search"),
|
|
17334
|
+
"data-i18n-aria-label": "search"
|
|
17335
|
+
},
|
|
17336
|
+
[],
|
|
17337
|
+
{ input: (evt) => {
|
|
17338
|
+
evt.stopPropagation();
|
|
17339
|
+
let text = evt.target.value.trim();
|
|
17340
|
+
if (priv.find_timer) clearTimeout(priv.find_timer);
|
|
17341
|
+
priv.find_timer = setTimeout(function() {
|
|
17342
|
+
priv.find_timer = null;
|
|
17343
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_FIND_NODES", { text }, gobj);
|
|
17344
|
+
}, 250);
|
|
17345
|
+
} }
|
|
17346
|
+
]);
|
|
17347
|
+
let $find_control = (0, _yuneta_gobj_js.createElement2)([
|
|
17348
|
+
"div",
|
|
17349
|
+
{
|
|
17350
|
+
class: "GOBJ_TREE_FIND control has-icons-left",
|
|
17351
|
+
style: "margin-left:.5rem; margin-right:.5rem; max-width:12rem; min-width:7rem;"
|
|
17352
|
+
},
|
|
17353
|
+
[$find_input, [
|
|
17354
|
+
"span",
|
|
17355
|
+
{ class: "icon is-left" },
|
|
17356
|
+
[["i", { class: "yi-magnifying-glass" }]]
|
|
17357
|
+
]]
|
|
17358
|
+
]);
|
|
17359
|
+
attach_clear($find_control, $find_input);
|
|
17360
|
+
priv.$find_input = $find_input;
|
|
17361
|
+
return [$find_control, [
|
|
17362
|
+
"div",
|
|
17363
|
+
{
|
|
17364
|
+
class: "GOBJ_TREE_FIND_RESULT is-hidden",
|
|
17365
|
+
style: "display:flex; align-items:center; gap:.3rem; margin-right:.5rem; font-size:.85rem;"
|
|
17366
|
+
},
|
|
17367
|
+
[[
|
|
17368
|
+
"span",
|
|
17369
|
+
{ class: "GOBJ_TREE_FIND_COUNT" },
|
|
17370
|
+
""
|
|
17371
|
+
], [
|
|
17372
|
+
"span",
|
|
17373
|
+
{ i18n: "matches" },
|
|
17374
|
+
(0, i18next.t)("matches")
|
|
17375
|
+
]]
|
|
17376
|
+
]];
|
|
17377
|
+
}
|
|
17378
|
+
/************************************************************
|
|
17379
|
+
* Does this node answer the current find term?
|
|
17380
|
+
*
|
|
17381
|
+
* What a reader types is a gclass, a name or a state -- the
|
|
17382
|
+
* three things a card shows. The full name is searched too,
|
|
17383
|
+
* because it is what the popover shows and what a log line
|
|
17384
|
+
* carries.
|
|
17385
|
+
************************************************************/
|
|
17386
|
+
function node_matches(d, term) {
|
|
17387
|
+
if (!term) return false;
|
|
17388
|
+
return [
|
|
17389
|
+
d.gclass,
|
|
17390
|
+
d.name,
|
|
17391
|
+
d.fullname,
|
|
17392
|
+
d.state
|
|
17393
|
+
].filter((x) => typeof x === "string").join(" ").toLowerCase().indexOf(term) >= 0;
|
|
17394
|
+
}
|
|
17395
|
+
/************************************************************
|
|
17396
|
+
* Repaint the count chip. Hidden while nothing is typed:
|
|
17397
|
+
* "0 matches" beside an empty box says nothing.
|
|
17398
|
+
************************************************************/
|
|
17399
|
+
function update_find_result(gobj) {
|
|
17400
|
+
let priv = gobj.priv;
|
|
17401
|
+
if (!priv.$find_result || !priv.$find_count) return;
|
|
17402
|
+
if (!priv.search) {
|
|
17403
|
+
priv.$find_result.classList.add("is-hidden");
|
|
17404
|
+
priv.$find_count.textContent = "";
|
|
17405
|
+
return;
|
|
17406
|
+
}
|
|
17407
|
+
priv.$find_count.textContent = String(priv.match_count);
|
|
17408
|
+
priv.$find_result.classList.remove("is-hidden");
|
|
17409
|
+
}
|
|
17289
17410
|
function make_toolbar$1(gobj) {
|
|
17290
17411
|
let priv = gobj.priv;
|
|
17291
17412
|
let toolbar_wide = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "wide");
|
|
17292
17413
|
let current_layout = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "layout");
|
|
17293
|
-
let left_items = yui_graph_fold_items(gobj, toolbar_wide);
|
|
17414
|
+
let left_items = yui_graph_fold_items(gobj, toolbar_wide).concat(make_find_items(gobj));
|
|
17294
17415
|
let center_items = [];
|
|
17295
17416
|
let right_items = [];
|
|
17296
17417
|
center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide);
|
|
@@ -17341,6 +17462,8 @@ function make_toolbar$1(gobj) {
|
|
|
17341
17462
|
right_items
|
|
17342
17463
|
]
|
|
17343
17464
|
]);
|
|
17465
|
+
priv.$find_result = $toolbar.querySelector(".GOBJ_TREE_FIND_RESULT");
|
|
17466
|
+
priv.$find_count = $toolbar.querySelector(".GOBJ_TREE_FIND_COUNT");
|
|
17344
17467
|
(0, _yuneta_gobj_js.refresh_language)($toolbar, i18next.t);
|
|
17345
17468
|
return $toolbar;
|
|
17346
17469
|
}
|
|
@@ -17516,26 +17639,32 @@ function render_toggle_html(node_id, collapsed, num_children, cs) {
|
|
|
17516
17639
|
/************************************************************
|
|
17517
17640
|
* HTML for the status pill: what the gobj is DOING.
|
|
17518
17641
|
*
|
|
17519
|
-
* A
|
|
17520
|
-
* card where there is no room for it
|
|
17521
|
-
*
|
|
17642
|
+
* A SYMBOL plus a word, and the word is dropped in the
|
|
17643
|
+
* compact card where there is no room for it.
|
|
17644
|
+
*
|
|
17645
|
+
* The symbol carries the state on its own, which a dot never
|
|
17646
|
+
* did: a dot has one shape, so telling running from stopped
|
|
17647
|
+
* meant telling green from red at 10 pixels -- and stopped is
|
|
17648
|
+
* the one that has to be seen. Play, pause and stop are also
|
|
17649
|
+
* the framework's own three verbs, not a borrowed metaphor.
|
|
17522
17650
|
************************************************************/
|
|
17523
17651
|
function render_status_html(d, ss, labels, with_label) {
|
|
17524
17652
|
let full = d.disabled ? labels.field.disabled : labels.status[node_status_key(d)] || node_status_key(d);
|
|
17525
17653
|
let label = d.disabled ? labels.field.disabled : labels.status_short[node_status(d)] || node_status(d);
|
|
17654
|
+
let symbol = node_status_symbol(d);
|
|
17526
17655
|
if (!with_label) return `<span title="${(0, _yuneta_gobj_js.escapeHtml)(full)}" style="
|
|
17527
17656
|
flex: 0 0 auto;
|
|
17528
|
-
width: 10px; height: 10px; border-radius: 50%;
|
|
17529
|
-
background: ${ss.dot};
|
|
17530
|
-
box-shadow: 0 0 0 2px ${ss.bg};
|
|
17531
17657
|
margin-left: 6px;
|
|
17532
|
-
|
|
17658
|
+
color: ${ss.dot};
|
|
17659
|
+
font-size: 11px;
|
|
17660
|
+
line-height: 1;
|
|
17661
|
+
">${(0, _yuneta_gobj_js.escapeHtml)(symbol)}</span>`;
|
|
17533
17662
|
return `<span title="${(0, _yuneta_gobj_js.escapeHtml)(full)}" style="
|
|
17534
17663
|
flex: 0 0 auto;
|
|
17535
17664
|
display: inline-flex;
|
|
17536
17665
|
align-items: center;
|
|
17537
17666
|
gap: 4px;
|
|
17538
|
-
padding: 1px 7px 1px
|
|
17667
|
+
padding: 1px 7px 1px 6px;
|
|
17539
17668
|
background: ${ss.bg};
|
|
17540
17669
|
color: ${ss.fg};
|
|
17541
17670
|
border-radius: 9px;
|
|
@@ -17543,7 +17672,8 @@ function render_status_html(d, ss, labels, with_label) {
|
|
|
17543
17672
|
font-weight: 700;
|
|
17544
17673
|
line-height: 1.45;
|
|
17545
17674
|
white-space: nowrap;
|
|
17546
|
-
"><span style="
|
|
17675
|
+
"><span style="color:${ss.dot}; font-size:11px; line-height:1;"
|
|
17676
|
+
>${(0, _yuneta_gobj_js.escapeHtml)(symbol)}</span>${(0, _yuneta_gobj_js.escapeHtml)(label)}</span>`;
|
|
17547
17677
|
}
|
|
17548
17678
|
/************************************************************
|
|
17549
17679
|
* HTML for the badges: what the gobj IS, besides its colour.
|
|
@@ -17590,6 +17720,12 @@ function build_gobj_nodes(gobj, d, nodes, edges, parent_id, compact, labels) {
|
|
|
17590
17720
|
let height = size.h;
|
|
17591
17721
|
let border_width = category === "yuno" || category === "service" ? 2 : 1;
|
|
17592
17722
|
let border_style = d.disabled ? "dashed" : "solid";
|
|
17723
|
+
if (node_matches(d, priv.search)) {
|
|
17724
|
+
priv.match_count++;
|
|
17725
|
+
border_width = 3;
|
|
17726
|
+
border_style = "solid";
|
|
17727
|
+
cs = Object.assign({}, cs, { border: FIND_MATCH_COLOR });
|
|
17728
|
+
}
|
|
17593
17729
|
let children = d.children || [];
|
|
17594
17730
|
let num_children = children.length;
|
|
17595
17731
|
let has_children = num_children > 0;
|
|
@@ -17714,6 +17850,7 @@ function load_tree(gobj) {
|
|
|
17714
17850
|
}
|
|
17715
17851
|
let layout_cfg = get_layout_cfg((0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "layout"));
|
|
17716
17852
|
let labels = node_labels(i18next.t);
|
|
17853
|
+
priv.match_count = 0;
|
|
17717
17854
|
let anchor = priv.pending_anchor || null;
|
|
17718
17855
|
let preserve_view = !!priv.pending_preserve_view;
|
|
17719
17856
|
priv.pending_anchor = null;
|
|
@@ -18143,6 +18280,23 @@ var GobjLanesHLayout = class extends _antv_g6.BaseLayout {
|
|
|
18143
18280
|
/************************************************************
|
|
18144
18281
|
*
|
|
18145
18282
|
************************************************************/
|
|
18283
|
+
/************************************************************
|
|
18284
|
+
* EV_FIND_NODES {text}
|
|
18285
|
+
*
|
|
18286
|
+
* The cards carry the highlight in their own markup (an html
|
|
18287
|
+
* node paints no G6 state), so finding is a REBUILD -- which
|
|
18288
|
+
* is also why the box is rate-limited. The view is preserved:
|
|
18289
|
+
* a reader who typed a letter did not ask to be moved.
|
|
18290
|
+
************************************************************/
|
|
18291
|
+
function ac_find_nodes$2(gobj, event, kw, src) {
|
|
18292
|
+
let priv = gobj.priv;
|
|
18293
|
+
let text = String(kw && kw.text || "").trim().toLowerCase();
|
|
18294
|
+
if (text === priv.search) return 0;
|
|
18295
|
+
priv.search = text;
|
|
18296
|
+
refresh_tree(gobj, { preserve_view: true });
|
|
18297
|
+
update_find_result(gobj);
|
|
18298
|
+
return 0;
|
|
18299
|
+
}
|
|
18146
18300
|
function ac_refresh$3(gobj, event, kw, src) {
|
|
18147
18301
|
save_view_state(gobj);
|
|
18148
18302
|
refresh_tree(gobj);
|
|
@@ -18516,6 +18670,7 @@ function create_gclass$9(gclass_name) {
|
|
|
18516
18670
|
return -1;
|
|
18517
18671
|
}
|
|
18518
18672
|
__gclass__$9 = (0, _yuneta_gobj_js.gclass_create)(gclass_name, [
|
|
18673
|
+
["EV_FIND_NODES", 0],
|
|
18519
18674
|
["EV_REFRESH", 0],
|
|
18520
18675
|
["EV_THEME", 0],
|
|
18521
18676
|
["EV_CHANGE_LAYOUT", 0],
|
|
@@ -18536,6 +18691,11 @@ function create_gclass$9(gclass_name) {
|
|
|
18536
18691
|
["EV_GCLASS_CLOSED", 0],
|
|
18537
18692
|
["EV_LANGUAGE_CHANGED", 0]
|
|
18538
18693
|
], [["ST_IDLE", [
|
|
18694
|
+
[
|
|
18695
|
+
"EV_FIND_NODES",
|
|
18696
|
+
ac_find_nodes$2,
|
|
18697
|
+
null
|
|
18698
|
+
],
|
|
18539
18699
|
[
|
|
18540
18700
|
"EV_REFRESH",
|
|
18541
18701
|
ac_refresh$3,
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -11437,7 +11437,7 @@ function collect_foldable_paths(value, path, out, is_root) {
|
|
|
11437
11437
|
* move looks the same whether nothing matched or the match
|
|
11438
11438
|
* was already on screen.
|
|
11439
11439
|
************************************************************/
|
|
11440
|
-
function update_find_result(gobj) {
|
|
11440
|
+
function update_find_result$1(gobj) {
|
|
11441
11441
|
let priv = gobj.priv;
|
|
11442
11442
|
if (!priv.$find_result || !priv.$find_count) return;
|
|
11443
11443
|
if (empty_string(priv.search)) {
|
|
@@ -11702,7 +11702,7 @@ function ac_change_layout$2(gobj, event, kw, src) {
|
|
|
11702
11702
|
edge: { type: cfg.edge_type }
|
|
11703
11703
|
});
|
|
11704
11704
|
refresh_json(gobj);
|
|
11705
|
-
update_find_result(gobj);
|
|
11705
|
+
update_find_result$1(gobj);
|
|
11706
11706
|
return 0;
|
|
11707
11707
|
}
|
|
11708
11708
|
/************************************************************
|
|
@@ -11731,7 +11731,7 @@ function ac_toggle_fold(gobj, event, kw, src) {
|
|
|
11731
11731
|
}
|
|
11732
11732
|
priv.pending_keep = keep_of(gobj, kw && kw.keep || path);
|
|
11733
11733
|
refresh_json(gobj, { preserve_view: true });
|
|
11734
|
-
update_find_result(gobj);
|
|
11734
|
+
update_find_result$1(gobj);
|
|
11735
11735
|
return 0;
|
|
11736
11736
|
}
|
|
11737
11737
|
/************************************************************
|
|
@@ -11742,11 +11742,11 @@ function ac_toggle_fold(gobj, event, kw, src) {
|
|
|
11742
11742
|
* keystroke is unusable. The count says whether there is
|
|
11743
11743
|
* anything to look for elsewhere.
|
|
11744
11744
|
************************************************************/
|
|
11745
|
-
function ac_find_nodes$
|
|
11745
|
+
function ac_find_nodes$3(gobj, event, kw, src) {
|
|
11746
11746
|
let priv = gobj.priv;
|
|
11747
11747
|
priv.search = (kw && kw.text || "").trim().toLowerCase();
|
|
11748
11748
|
refresh_json(gobj, { preserve_view: true });
|
|
11749
|
-
update_find_result(gobj);
|
|
11749
|
+
update_find_result$1(gobj);
|
|
11750
11750
|
return 0;
|
|
11751
11751
|
}
|
|
11752
11752
|
/************************************************************
|
|
@@ -11758,7 +11758,7 @@ function ac_expand_all$2(gobj, event, kw, src) {
|
|
|
11758
11758
|
priv.collapsed.clear();
|
|
11759
11759
|
priv.reserved.clear();
|
|
11760
11760
|
refresh_json(gobj);
|
|
11761
|
-
update_find_result(gobj);
|
|
11761
|
+
update_find_result$1(gobj);
|
|
11762
11762
|
return 0;
|
|
11763
11763
|
}
|
|
11764
11764
|
/************************************************************
|
|
@@ -11775,7 +11775,7 @@ function ac_collapse_all$2(gobj, event, kw, src) {
|
|
|
11775
11775
|
priv.collapsed = new Set(paths);
|
|
11776
11776
|
priv.reserved.clear();
|
|
11777
11777
|
refresh_json(gobj);
|
|
11778
|
-
update_find_result(gobj);
|
|
11778
|
+
update_find_result$1(gobj);
|
|
11779
11779
|
return 0;
|
|
11780
11780
|
}
|
|
11781
11781
|
/************************************************************
|
|
@@ -12049,7 +12049,7 @@ function create_gclass$13(gclass_name) {
|
|
|
12049
12049
|
],
|
|
12050
12050
|
[
|
|
12051
12051
|
"EV_FIND_NODES",
|
|
12052
|
-
ac_find_nodes$
|
|
12052
|
+
ac_find_nodes$3,
|
|
12053
12053
|
null
|
|
12054
12054
|
],
|
|
12055
12055
|
[
|
|
@@ -16127,6 +16127,21 @@ function node_status(d) {
|
|
|
16127
16127
|
if (!d || !d.running) return "stopped";
|
|
16128
16128
|
return d.playing ? "playing" : "running";
|
|
16129
16129
|
}
|
|
16130
|
+
var STATUS_SYMBOLS = {
|
|
16131
|
+
"playing": "▶︎",
|
|
16132
|
+
"running": "‖",
|
|
16133
|
+
"stopped": "■︎"
|
|
16134
|
+
};
|
|
16135
|
+
var DISABLED_SYMBOL = "⊘";
|
|
16136
|
+
/************************************************************
|
|
16137
|
+
* The symbol of a node's status, which is what the reader
|
|
16138
|
+
* actually sees first. `disabled` overrides the rest: a
|
|
16139
|
+
* disabled gobj is not going to run whatever it is doing now.
|
|
16140
|
+
************************************************************/
|
|
16141
|
+
function node_status_symbol(d) {
|
|
16142
|
+
if (d && d.disabled) return DISABLED_SYMBOL;
|
|
16143
|
+
return STATUS_SYMBOLS[node_status(d)] || STATUS_SYMBOLS.stopped;
|
|
16144
|
+
}
|
|
16130
16145
|
/************************************************************
|
|
16131
16146
|
* The i18n key of the status, which is NOT the status itself:
|
|
16132
16147
|
* a gobj that runs and does not play is PAUSED to the reader,
|
|
@@ -16255,7 +16270,7 @@ function node_labels(t) {
|
|
|
16255
16270
|
full_name: t("full name"),
|
|
16256
16271
|
role: t("role"),
|
|
16257
16272
|
status: t("status"),
|
|
16258
|
-
|
|
16273
|
+
fsm_state: t("fsm state"),
|
|
16259
16274
|
disabled: t("disabled"),
|
|
16260
16275
|
parent: t("parent"),
|
|
16261
16276
|
bottom_gobj: t("bottom gobj"),
|
|
@@ -16284,8 +16299,8 @@ function node_info_rows(d, t) {
|
|
|
16284
16299
|
rows.push([f.name, d.name || "—"]);
|
|
16285
16300
|
if (d.fullname && d.fullname !== d.shortname) rows.push([f.full_name, d.fullname]);
|
|
16286
16301
|
rows.push([f.role, L.role[role_i18n_key(node_role(d))]]);
|
|
16287
|
-
rows.push([f.status, L.status[node_status_key(d)]]);
|
|
16288
|
-
rows.push([f.
|
|
16302
|
+
rows.push([f.status, node_status_symbol(d) + " " + L.status[node_status_key(d)]]);
|
|
16303
|
+
rows.push([f.fsm_state, d.state || "—"]);
|
|
16289
16304
|
if (d.disabled) rows.push([f.disabled, f.yes]);
|
|
16290
16305
|
if (d.parent) rows.push([f.parent, d.parent]);
|
|
16291
16306
|
if (d.bottom_gobj) rows.push([f.bottom_gobj, d.bottom_gobj]);
|
|
@@ -16553,6 +16568,9 @@ function open_gclass_view(host, gclass_name, opts) {
|
|
|
16553
16568
|
};
|
|
16554
16569
|
let $box = gobj_read_pointer_attr(jv, "$container");
|
|
16555
16570
|
let on_close = () => {
|
|
16571
|
+
handle.win = null;
|
|
16572
|
+
handle.modal = null;
|
|
16573
|
+
close_gclass_view(handle);
|
|
16556
16574
|
if (typeof opts.on_close === "function") opts.on_close();
|
|
16557
16575
|
};
|
|
16558
16576
|
if (is_mobile$3()) {
|
|
@@ -16764,6 +16782,12 @@ var PRIVATE_DATA$9 = {
|
|
|
16764
16782
|
theme: null,
|
|
16765
16783
|
theme_observer: null,
|
|
16766
16784
|
$layout_select: null,
|
|
16785
|
+
search: "",
|
|
16786
|
+
match_count: 0,
|
|
16787
|
+
find_timer: null,
|
|
16788
|
+
$find_input: null,
|
|
16789
|
+
$find_result: null,
|
|
16790
|
+
$find_count: null,
|
|
16767
16791
|
$popover: null,
|
|
16768
16792
|
$popover_title: null,
|
|
16769
16793
|
$popover_body: null,
|
|
@@ -16909,6 +16933,7 @@ var NODE_SIZES = {
|
|
|
16909
16933
|
/***************************************************************
|
|
16910
16934
|
* Shared card typography.
|
|
16911
16935
|
***************************************************************/
|
|
16936
|
+
var FIND_MATCH_COLOR = "#D9A23F";
|
|
16912
16937
|
var GT_FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
|
|
16913
16938
|
/***************************************************************
|
|
16914
16939
|
* Theme-aware card palette derived from the role colour.
|
|
@@ -17281,11 +17306,107 @@ function destroy_ui$4(gobj) {
|
|
|
17281
17306
|
/************************************************************
|
|
17282
17307
|
* Toolbar
|
|
17283
17308
|
************************************************************/
|
|
17309
|
+
/************************************************************
|
|
17310
|
+
* The find box and its count.
|
|
17311
|
+
*
|
|
17312
|
+
* A tree of a real yuno is a hundred cards, and the only way
|
|
17313
|
+
* to locate one was to read them all. The COUNT is what makes
|
|
17314
|
+
* it honest: a graph that did not move looks the same whether
|
|
17315
|
+
* nothing matched or the match was already on screen -- the
|
|
17316
|
+
* same reason the JSON graph carries one, and the same
|
|
17317
|
+
* drawing, because the two sit in the same console.
|
|
17318
|
+
************************************************************/
|
|
17319
|
+
function make_find_items(gobj) {
|
|
17320
|
+
let priv = gobj.priv;
|
|
17321
|
+
let $find_input = createElement2([
|
|
17322
|
+
"input",
|
|
17323
|
+
{
|
|
17324
|
+
class: "GOBJ_TREE_FIND_INPUT input",
|
|
17325
|
+
type: "text",
|
|
17326
|
+
placeholder: t("search"),
|
|
17327
|
+
"data-i18n-placeholder": "search",
|
|
17328
|
+
"aria-label": t("search"),
|
|
17329
|
+
"data-i18n-aria-label": "search"
|
|
17330
|
+
},
|
|
17331
|
+
[],
|
|
17332
|
+
{ input: (evt) => {
|
|
17333
|
+
evt.stopPropagation();
|
|
17334
|
+
let text = evt.target.value.trim();
|
|
17335
|
+
if (priv.find_timer) clearTimeout(priv.find_timer);
|
|
17336
|
+
priv.find_timer = setTimeout(function() {
|
|
17337
|
+
priv.find_timer = null;
|
|
17338
|
+
gobj_send_event(gobj, "EV_FIND_NODES", { text }, gobj);
|
|
17339
|
+
}, 250);
|
|
17340
|
+
} }
|
|
17341
|
+
]);
|
|
17342
|
+
let $find_control = createElement2([
|
|
17343
|
+
"div",
|
|
17344
|
+
{
|
|
17345
|
+
class: "GOBJ_TREE_FIND control has-icons-left",
|
|
17346
|
+
style: "margin-left:.5rem; margin-right:.5rem; max-width:12rem; min-width:7rem;"
|
|
17347
|
+
},
|
|
17348
|
+
[$find_input, [
|
|
17349
|
+
"span",
|
|
17350
|
+
{ class: "icon is-left" },
|
|
17351
|
+
[["i", { class: "yi-magnifying-glass" }]]
|
|
17352
|
+
]]
|
|
17353
|
+
]);
|
|
17354
|
+
attach_clear($find_control, $find_input);
|
|
17355
|
+
priv.$find_input = $find_input;
|
|
17356
|
+
return [$find_control, [
|
|
17357
|
+
"div",
|
|
17358
|
+
{
|
|
17359
|
+
class: "GOBJ_TREE_FIND_RESULT is-hidden",
|
|
17360
|
+
style: "display:flex; align-items:center; gap:.3rem; margin-right:.5rem; font-size:.85rem;"
|
|
17361
|
+
},
|
|
17362
|
+
[[
|
|
17363
|
+
"span",
|
|
17364
|
+
{ class: "GOBJ_TREE_FIND_COUNT" },
|
|
17365
|
+
""
|
|
17366
|
+
], [
|
|
17367
|
+
"span",
|
|
17368
|
+
{ i18n: "matches" },
|
|
17369
|
+
t("matches")
|
|
17370
|
+
]]
|
|
17371
|
+
]];
|
|
17372
|
+
}
|
|
17373
|
+
/************************************************************
|
|
17374
|
+
* Does this node answer the current find term?
|
|
17375
|
+
*
|
|
17376
|
+
* What a reader types is a gclass, a name or a state -- the
|
|
17377
|
+
* three things a card shows. The full name is searched too,
|
|
17378
|
+
* because it is what the popover shows and what a log line
|
|
17379
|
+
* carries.
|
|
17380
|
+
************************************************************/
|
|
17381
|
+
function node_matches(d, term) {
|
|
17382
|
+
if (!term) return false;
|
|
17383
|
+
return [
|
|
17384
|
+
d.gclass,
|
|
17385
|
+
d.name,
|
|
17386
|
+
d.fullname,
|
|
17387
|
+
d.state
|
|
17388
|
+
].filter((x) => typeof x === "string").join(" ").toLowerCase().indexOf(term) >= 0;
|
|
17389
|
+
}
|
|
17390
|
+
/************************************************************
|
|
17391
|
+
* Repaint the count chip. Hidden while nothing is typed:
|
|
17392
|
+
* "0 matches" beside an empty box says nothing.
|
|
17393
|
+
************************************************************/
|
|
17394
|
+
function update_find_result(gobj) {
|
|
17395
|
+
let priv = gobj.priv;
|
|
17396
|
+
if (!priv.$find_result || !priv.$find_count) return;
|
|
17397
|
+
if (!priv.search) {
|
|
17398
|
+
priv.$find_result.classList.add("is-hidden");
|
|
17399
|
+
priv.$find_count.textContent = "";
|
|
17400
|
+
return;
|
|
17401
|
+
}
|
|
17402
|
+
priv.$find_count.textContent = String(priv.match_count);
|
|
17403
|
+
priv.$find_result.classList.remove("is-hidden");
|
|
17404
|
+
}
|
|
17284
17405
|
function make_toolbar$1(gobj) {
|
|
17285
17406
|
let priv = gobj.priv;
|
|
17286
17407
|
let toolbar_wide = gobj_read_attr(gobj, "wide");
|
|
17287
17408
|
let current_layout = gobj_read_str_attr(gobj, "layout");
|
|
17288
|
-
let left_items = yui_graph_fold_items(gobj, toolbar_wide);
|
|
17409
|
+
let left_items = yui_graph_fold_items(gobj, toolbar_wide).concat(make_find_items(gobj));
|
|
17289
17410
|
let center_items = [];
|
|
17290
17411
|
let right_items = [];
|
|
17291
17412
|
center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide);
|
|
@@ -17336,6 +17457,8 @@ function make_toolbar$1(gobj) {
|
|
|
17336
17457
|
right_items
|
|
17337
17458
|
]
|
|
17338
17459
|
]);
|
|
17460
|
+
priv.$find_result = $toolbar.querySelector(".GOBJ_TREE_FIND_RESULT");
|
|
17461
|
+
priv.$find_count = $toolbar.querySelector(".GOBJ_TREE_FIND_COUNT");
|
|
17339
17462
|
refresh_language($toolbar, t);
|
|
17340
17463
|
return $toolbar;
|
|
17341
17464
|
}
|
|
@@ -17511,26 +17634,32 @@ function render_toggle_html(node_id, collapsed, num_children, cs) {
|
|
|
17511
17634
|
/************************************************************
|
|
17512
17635
|
* HTML for the status pill: what the gobj is DOING.
|
|
17513
17636
|
*
|
|
17514
|
-
* A
|
|
17515
|
-
* card where there is no room for it
|
|
17516
|
-
*
|
|
17637
|
+
* A SYMBOL plus a word, and the word is dropped in the
|
|
17638
|
+
* compact card where there is no room for it.
|
|
17639
|
+
*
|
|
17640
|
+
* The symbol carries the state on its own, which a dot never
|
|
17641
|
+
* did: a dot has one shape, so telling running from stopped
|
|
17642
|
+
* meant telling green from red at 10 pixels -- and stopped is
|
|
17643
|
+
* the one that has to be seen. Play, pause and stop are also
|
|
17644
|
+
* the framework's own three verbs, not a borrowed metaphor.
|
|
17517
17645
|
************************************************************/
|
|
17518
17646
|
function render_status_html(d, ss, labels, with_label) {
|
|
17519
17647
|
let full = d.disabled ? labels.field.disabled : labels.status[node_status_key(d)] || node_status_key(d);
|
|
17520
17648
|
let label = d.disabled ? labels.field.disabled : labels.status_short[node_status(d)] || node_status(d);
|
|
17649
|
+
let symbol = node_status_symbol(d);
|
|
17521
17650
|
if (!with_label) return `<span title="${escapeHtml(full)}" style="
|
|
17522
17651
|
flex: 0 0 auto;
|
|
17523
|
-
width: 10px; height: 10px; border-radius: 50%;
|
|
17524
|
-
background: ${ss.dot};
|
|
17525
|
-
box-shadow: 0 0 0 2px ${ss.bg};
|
|
17526
17652
|
margin-left: 6px;
|
|
17527
|
-
|
|
17653
|
+
color: ${ss.dot};
|
|
17654
|
+
font-size: 11px;
|
|
17655
|
+
line-height: 1;
|
|
17656
|
+
">${escapeHtml(symbol)}</span>`;
|
|
17528
17657
|
return `<span title="${escapeHtml(full)}" style="
|
|
17529
17658
|
flex: 0 0 auto;
|
|
17530
17659
|
display: inline-flex;
|
|
17531
17660
|
align-items: center;
|
|
17532
17661
|
gap: 4px;
|
|
17533
|
-
padding: 1px 7px 1px
|
|
17662
|
+
padding: 1px 7px 1px 6px;
|
|
17534
17663
|
background: ${ss.bg};
|
|
17535
17664
|
color: ${ss.fg};
|
|
17536
17665
|
border-radius: 9px;
|
|
@@ -17538,7 +17667,8 @@ function render_status_html(d, ss, labels, with_label) {
|
|
|
17538
17667
|
font-weight: 700;
|
|
17539
17668
|
line-height: 1.45;
|
|
17540
17669
|
white-space: nowrap;
|
|
17541
|
-
"><span style="
|
|
17670
|
+
"><span style="color:${ss.dot}; font-size:11px; line-height:1;"
|
|
17671
|
+
>${escapeHtml(symbol)}</span>${escapeHtml(label)}</span>`;
|
|
17542
17672
|
}
|
|
17543
17673
|
/************************************************************
|
|
17544
17674
|
* HTML for the badges: what the gobj IS, besides its colour.
|
|
@@ -17585,6 +17715,12 @@ function build_gobj_nodes(gobj, d, nodes, edges, parent_id, compact, labels) {
|
|
|
17585
17715
|
let height = size.h;
|
|
17586
17716
|
let border_width = category === "yuno" || category === "service" ? 2 : 1;
|
|
17587
17717
|
let border_style = d.disabled ? "dashed" : "solid";
|
|
17718
|
+
if (node_matches(d, priv.search)) {
|
|
17719
|
+
priv.match_count++;
|
|
17720
|
+
border_width = 3;
|
|
17721
|
+
border_style = "solid";
|
|
17722
|
+
cs = Object.assign({}, cs, { border: FIND_MATCH_COLOR });
|
|
17723
|
+
}
|
|
17588
17724
|
let children = d.children || [];
|
|
17589
17725
|
let num_children = children.length;
|
|
17590
17726
|
let has_children = num_children > 0;
|
|
@@ -17709,6 +17845,7 @@ function load_tree(gobj) {
|
|
|
17709
17845
|
}
|
|
17710
17846
|
let layout_cfg = get_layout_cfg(gobj_read_str_attr(gobj, "layout"));
|
|
17711
17847
|
let labels = node_labels(t);
|
|
17848
|
+
priv.match_count = 0;
|
|
17712
17849
|
let anchor = priv.pending_anchor || null;
|
|
17713
17850
|
let preserve_view = !!priv.pending_preserve_view;
|
|
17714
17851
|
priv.pending_anchor = null;
|
|
@@ -18138,6 +18275,23 @@ var GobjLanesHLayout = class extends BaseLayout {
|
|
|
18138
18275
|
/************************************************************
|
|
18139
18276
|
*
|
|
18140
18277
|
************************************************************/
|
|
18278
|
+
/************************************************************
|
|
18279
|
+
* EV_FIND_NODES {text}
|
|
18280
|
+
*
|
|
18281
|
+
* The cards carry the highlight in their own markup (an html
|
|
18282
|
+
* node paints no G6 state), so finding is a REBUILD -- which
|
|
18283
|
+
* is also why the box is rate-limited. The view is preserved:
|
|
18284
|
+
* a reader who typed a letter did not ask to be moved.
|
|
18285
|
+
************************************************************/
|
|
18286
|
+
function ac_find_nodes$2(gobj, event, kw, src) {
|
|
18287
|
+
let priv = gobj.priv;
|
|
18288
|
+
let text = String(kw && kw.text || "").trim().toLowerCase();
|
|
18289
|
+
if (text === priv.search) return 0;
|
|
18290
|
+
priv.search = text;
|
|
18291
|
+
refresh_tree(gobj, { preserve_view: true });
|
|
18292
|
+
update_find_result(gobj);
|
|
18293
|
+
return 0;
|
|
18294
|
+
}
|
|
18141
18295
|
function ac_refresh$3(gobj, event, kw, src) {
|
|
18142
18296
|
save_view_state(gobj);
|
|
18143
18297
|
refresh_tree(gobj);
|
|
@@ -18511,6 +18665,7 @@ function create_gclass$9(gclass_name) {
|
|
|
18511
18665
|
return -1;
|
|
18512
18666
|
}
|
|
18513
18667
|
__gclass__$9 = gclass_create(gclass_name, [
|
|
18668
|
+
["EV_FIND_NODES", 0],
|
|
18514
18669
|
["EV_REFRESH", 0],
|
|
18515
18670
|
["EV_THEME", 0],
|
|
18516
18671
|
["EV_CHANGE_LAYOUT", 0],
|
|
@@ -18531,6 +18686,11 @@ function create_gclass$9(gclass_name) {
|
|
|
18531
18686
|
["EV_GCLASS_CLOSED", 0],
|
|
18532
18687
|
["EV_LANGUAGE_CHANGED", 0]
|
|
18533
18688
|
], [["ST_IDLE", [
|
|
18689
|
+
[
|
|
18690
|
+
"EV_FIND_NODES",
|
|
18691
|
+
ac_find_nodes$2,
|
|
18692
|
+
null
|
|
18693
|
+
],
|
|
18534
18694
|
[
|
|
18535
18695
|
"EV_REFRESH",
|
|
18536
18696
|
ac_refresh$3,
|
package/package.json
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
node_role,
|
|
46
46
|
node_status,
|
|
47
47
|
node_status_key,
|
|
48
|
+
node_status_symbol,
|
|
48
49
|
} from "./gobj_tree_model.js";
|
|
49
50
|
import {
|
|
50
51
|
close_gclass_view,
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
import {yui_shell_of} from "./c_yui_shell.js";
|
|
55
56
|
|
|
56
57
|
import {yui_toolbar} from "./yui_toolbar.js";
|
|
58
|
+
import {attach_clear} from "./yui_inputs.js";
|
|
57
59
|
import {
|
|
58
60
|
yui_graph_camera_items,
|
|
59
61
|
yui_graph_anchor_item,
|
|
@@ -257,6 +259,15 @@ let PRIVATE_DATA = {
|
|
|
257
259
|
theme: null,
|
|
258
260
|
theme_observer: null, // MutationObserver on <html data-theme>
|
|
259
261
|
$layout_select: null,
|
|
262
|
+
|
|
263
|
+
/*---------------- find ----------------*/
|
|
264
|
+
search: "", // current find term, lower-cased
|
|
265
|
+
match_count: 0, // how many cards it matched, for the chip
|
|
266
|
+
find_timer: null, // rate-limits the find box
|
|
267
|
+
$find_input: null,
|
|
268
|
+
$find_result: null,
|
|
269
|
+
$find_count: null,
|
|
270
|
+
|
|
260
271
|
$popover: null,
|
|
261
272
|
$popover_title: null,
|
|
262
273
|
$popover_body: null,
|
|
@@ -351,6 +362,13 @@ const NODE_SIZES = {
|
|
|
351
362
|
/***************************************************************
|
|
352
363
|
* Shared card typography.
|
|
353
364
|
***************************************************************/
|
|
365
|
+
/*
|
|
366
|
+
* The ring a find match wears. Amber, which in this library means
|
|
367
|
+
* "here" -- the same colour the gclass viewer lights the current
|
|
368
|
+
* state with -- and not one of the role colours, which are taken.
|
|
369
|
+
*/
|
|
370
|
+
const FIND_MATCH_COLOR = "#D9A23F";
|
|
371
|
+
|
|
354
372
|
const GT_FONT =
|
|
355
373
|
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, " +
|
|
356
374
|
"Helvetica, Arial, sans-serif";
|
|
@@ -836,6 +854,120 @@ function destroy_ui(gobj)
|
|
|
836
854
|
/************************************************************
|
|
837
855
|
* Toolbar
|
|
838
856
|
************************************************************/
|
|
857
|
+
/************************************************************
|
|
858
|
+
* The find box and its count.
|
|
859
|
+
*
|
|
860
|
+
* A tree of a real yuno is a hundred cards, and the only way
|
|
861
|
+
* to locate one was to read them all. The COUNT is what makes
|
|
862
|
+
* it honest: a graph that did not move looks the same whether
|
|
863
|
+
* nothing matched or the match was already on screen -- the
|
|
864
|
+
* same reason the JSON graph carries one, and the same
|
|
865
|
+
* drawing, because the two sit in the same console.
|
|
866
|
+
************************************************************/
|
|
867
|
+
function make_find_items(gobj)
|
|
868
|
+
{
|
|
869
|
+
let priv = gobj.priv;
|
|
870
|
+
|
|
871
|
+
/* Materialised, not a spec: attach_clear() hangs the NORM clear (✕)
|
|
872
|
+
* on a real element. Clearing dispatches a synthetic `input`, which
|
|
873
|
+
* goes through the same rate-limited handler and fires
|
|
874
|
+
* EV_FIND_NODES with an empty term -- box and highlight clear
|
|
875
|
+
* together. */
|
|
876
|
+
let $find_input = createElement2(
|
|
877
|
+
['input', {
|
|
878
|
+
class: 'GOBJ_TREE_FIND_INPUT input',
|
|
879
|
+
type: 'text',
|
|
880
|
+
/* A placeholder is not a text node, so the data-i18n walk
|
|
881
|
+
* cannot reach it: it needs its own key. */
|
|
882
|
+
placeholder: t('search'),
|
|
883
|
+
'data-i18n-placeholder': 'search',
|
|
884
|
+
'aria-label': t('search'),
|
|
885
|
+
'data-i18n-aria-label': 'search'
|
|
886
|
+
}, [], {
|
|
887
|
+
/* Rate-limited, not delayed for effect: every keystroke
|
|
888
|
+
* REBUILDS the cards -- an html node paints no G6 state, so
|
|
889
|
+
* the highlight has to live in the card's own markup -- and
|
|
890
|
+
* the first letter typed matches most of a tree. */
|
|
891
|
+
input: (evt) => {
|
|
892
|
+
evt.stopPropagation();
|
|
893
|
+
let text = evt.target.value.trim();
|
|
894
|
+
if(priv.find_timer) {
|
|
895
|
+
clearTimeout(priv.find_timer);
|
|
896
|
+
}
|
|
897
|
+
priv.find_timer = setTimeout(function() {
|
|
898
|
+
priv.find_timer = null;
|
|
899
|
+
gobj_send_event(gobj, "EV_FIND_NODES", {text: text}, gobj);
|
|
900
|
+
}, 250);
|
|
901
|
+
}
|
|
902
|
+
}]);
|
|
903
|
+
|
|
904
|
+
let $find_control = createElement2(
|
|
905
|
+
['div', {class: 'GOBJ_TREE_FIND control has-icons-left',
|
|
906
|
+
style: 'margin-left:.5rem; margin-right:.5rem; ' +
|
|
907
|
+
'max-width:12rem; min-width:7rem;'}, [
|
|
908
|
+
$find_input,
|
|
909
|
+
['span', {class: 'icon is-left'}, [['i', {class: 'yi-magnifying-glass'}]]]
|
|
910
|
+
]]);
|
|
911
|
+
attach_clear($find_control, $find_input);
|
|
912
|
+
|
|
913
|
+
priv.$find_input = $find_input;
|
|
914
|
+
|
|
915
|
+
return [
|
|
916
|
+
$find_control,
|
|
917
|
+
/* Two spans, not one string: the number is DATA and "matches"
|
|
918
|
+
* is the word, so a language switch re-translates the half that
|
|
919
|
+
* is a word. `display:flex` inline and NOT the `is-flex` helper
|
|
920
|
+
* -- both Bulma helpers carry !important, so `is-hidden is-flex`
|
|
921
|
+
* on one element is decided by stylesheet order. */
|
|
922
|
+
['div', {class: 'GOBJ_TREE_FIND_RESULT is-hidden',
|
|
923
|
+
style: 'display:flex; align-items:center; gap:.3rem; ' +
|
|
924
|
+
'margin-right:.5rem; font-size:.85rem;'}, [
|
|
925
|
+
['span', {class: 'GOBJ_TREE_FIND_COUNT'}, ''],
|
|
926
|
+
['span', {i18n: 'matches'}, t('matches')]
|
|
927
|
+
]]
|
|
928
|
+
];
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/************************************************************
|
|
932
|
+
* Does this node answer the current find term?
|
|
933
|
+
*
|
|
934
|
+
* What a reader types is a gclass, a name or a state -- the
|
|
935
|
+
* three things a card shows. The full name is searched too,
|
|
936
|
+
* because it is what the popover shows and what a log line
|
|
937
|
+
* carries.
|
|
938
|
+
************************************************************/
|
|
939
|
+
function node_matches(d, term)
|
|
940
|
+
{
|
|
941
|
+
if(!term) {
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
944
|
+
let hay = [d.gclass, d.name, d.fullname, d.state]
|
|
945
|
+
.filter((x) => typeof x === "string")
|
|
946
|
+
.join(" ")
|
|
947
|
+
.toLowerCase();
|
|
948
|
+
return hay.indexOf(term) >= 0;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/************************************************************
|
|
952
|
+
* Repaint the count chip. Hidden while nothing is typed:
|
|
953
|
+
* "0 matches" beside an empty box says nothing.
|
|
954
|
+
************************************************************/
|
|
955
|
+
function update_find_result(gobj)
|
|
956
|
+
{
|
|
957
|
+
let priv = gobj.priv;
|
|
958
|
+
|
|
959
|
+
if(!priv.$find_result || !priv.$find_count) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
if(!priv.search) {
|
|
963
|
+
priv.$find_result.classList.add("is-hidden");
|
|
964
|
+
priv.$find_count.textContent = "";
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
priv.$find_count.textContent = String(priv.match_count);
|
|
968
|
+
priv.$find_result.classList.remove("is-hidden");
|
|
969
|
+
}
|
|
970
|
+
|
|
839
971
|
function make_toolbar(gobj)
|
|
840
972
|
{
|
|
841
973
|
let priv = gobj.priv;
|
|
@@ -846,7 +978,9 @@ function make_toolbar(gobj)
|
|
|
846
978
|
* and not a per-node one, so it sits on its own at the left — ahead
|
|
847
979
|
* of a find box where the toolbar has one. The per-node handles
|
|
848
980
|
* live inside the cards, on the right of each header. */
|
|
849
|
-
let left_items = yui_graph_fold_items(gobj, toolbar_wide)
|
|
981
|
+
let left_items = yui_graph_fold_items(gobj, toolbar_wide).concat(
|
|
982
|
+
make_find_items(gobj)
|
|
983
|
+
);
|
|
850
984
|
let center_items = [];
|
|
851
985
|
let right_items = [];
|
|
852
986
|
|
|
@@ -899,6 +1033,11 @@ function make_toolbar(gobj)
|
|
|
899
1033
|
['div', {class: 'yui-horizontal-toolbar-section right'}, right_items],
|
|
900
1034
|
]);
|
|
901
1035
|
|
|
1036
|
+
/* The count chip is built as a spec, so its elements only exist
|
|
1037
|
+
* once yui_toolbar() has materialised the row. */
|
|
1038
|
+
priv.$find_result = $toolbar.querySelector('.GOBJ_TREE_FIND_RESULT');
|
|
1039
|
+
priv.$find_count = $toolbar.querySelector('.GOBJ_TREE_FIND_COUNT');
|
|
1040
|
+
|
|
902
1041
|
refresh_language($toolbar, t);
|
|
903
1042
|
return $toolbar;
|
|
904
1043
|
}
|
|
@@ -1159,9 +1298,14 @@ function render_toggle_html(node_id, collapsed, num_children, cs)
|
|
|
1159
1298
|
/************************************************************
|
|
1160
1299
|
* HTML for the status pill: what the gobj is DOING.
|
|
1161
1300
|
*
|
|
1162
|
-
* A
|
|
1163
|
-
* card where there is no room for it
|
|
1164
|
-
*
|
|
1301
|
+
* A SYMBOL plus a word, and the word is dropped in the
|
|
1302
|
+
* compact card where there is no room for it.
|
|
1303
|
+
*
|
|
1304
|
+
* The symbol carries the state on its own, which a dot never
|
|
1305
|
+
* did: a dot has one shape, so telling running from stopped
|
|
1306
|
+
* meant telling green from red at 10 pixels -- and stopped is
|
|
1307
|
+
* the one that has to be seen. Play, pause and stop are also
|
|
1308
|
+
* the framework's own three verbs, not a borrowed metaphor.
|
|
1165
1309
|
************************************************************/
|
|
1166
1310
|
function render_status_html(d, ss, labels, with_label)
|
|
1167
1311
|
{
|
|
@@ -1171,15 +1315,16 @@ function render_status_html(d, ss, labels, with_label)
|
|
|
1171
1315
|
let label = d.disabled?
|
|
1172
1316
|
labels.field.disabled:
|
|
1173
1317
|
(labels.status_short[node_status(d)] || node_status(d));
|
|
1318
|
+
let symbol = node_status_symbol(d);
|
|
1174
1319
|
|
|
1175
1320
|
if(!with_label) {
|
|
1176
1321
|
return `<span title="${escapeHtml(full)}" style="
|
|
1177
1322
|
flex: 0 0 auto;
|
|
1178
|
-
width: 10px; height: 10px; border-radius: 50%;
|
|
1179
|
-
background: ${ss.dot};
|
|
1180
|
-
box-shadow: 0 0 0 2px ${ss.bg};
|
|
1181
1323
|
margin-left: 6px;
|
|
1182
|
-
|
|
1324
|
+
color: ${ss.dot};
|
|
1325
|
+
font-size: 11px;
|
|
1326
|
+
line-height: 1;
|
|
1327
|
+
">${escapeHtml(symbol)}</span>`;
|
|
1183
1328
|
}
|
|
1184
1329
|
|
|
1185
1330
|
return `<span title="${escapeHtml(full)}" style="
|
|
@@ -1187,7 +1332,7 @@ function render_status_html(d, ss, labels, with_label)
|
|
|
1187
1332
|
display: inline-flex;
|
|
1188
1333
|
align-items: center;
|
|
1189
1334
|
gap: 4px;
|
|
1190
|
-
padding: 1px 7px 1px
|
|
1335
|
+
padding: 1px 7px 1px 6px;
|
|
1191
1336
|
background: ${ss.bg};
|
|
1192
1337
|
color: ${ss.fg};
|
|
1193
1338
|
border-radius: 9px;
|
|
@@ -1195,7 +1340,8 @@ function render_status_html(d, ss, labels, with_label)
|
|
|
1195
1340
|
font-weight: 700;
|
|
1196
1341
|
line-height: 1.45;
|
|
1197
1342
|
white-space: nowrap;
|
|
1198
|
-
"><span style="
|
|
1343
|
+
"><span style="color:${ss.dot}; font-size:11px; line-height:1;"
|
|
1344
|
+
>${escapeHtml(symbol)}</span>${escapeHtml(label)}</span>`;
|
|
1199
1345
|
}
|
|
1200
1346
|
|
|
1201
1347
|
/************************************************************
|
|
@@ -1257,6 +1403,20 @@ function build_gobj_nodes(gobj, d, nodes, edges, parent_id, compact, labels)
|
|
|
1257
1403
|
* "this branch is out of the game" at any zoom. */
|
|
1258
1404
|
let border_style = d.disabled? "dashed": "solid";
|
|
1259
1405
|
|
|
1406
|
+
/*
|
|
1407
|
+
* A card the find box matched. The ring is drawn OVER the role
|
|
1408
|
+
* colour and not instead of it -- the card still has to say what
|
|
1409
|
+
* it is -- and it is counted here, while every card passes
|
|
1410
|
+
* through, so the chip and the drawing can never disagree.
|
|
1411
|
+
*/
|
|
1412
|
+
let matched = node_matches(d, priv.search);
|
|
1413
|
+
if(matched) {
|
|
1414
|
+
priv.match_count++;
|
|
1415
|
+
border_width = 3;
|
|
1416
|
+
border_style = "solid";
|
|
1417
|
+
cs = Object.assign({}, cs, {border: FIND_MATCH_COLOR});
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1260
1420
|
let children = d.children || [];
|
|
1261
1421
|
let num_children = children.length;
|
|
1262
1422
|
let has_children = num_children > 0;
|
|
@@ -1442,6 +1602,11 @@ function load_tree(gobj)
|
|
|
1442
1602
|
let layout_cfg = get_layout_cfg(gobj_read_str_attr(gobj, "layout"));
|
|
1443
1603
|
let labels = node_labels(t);
|
|
1444
1604
|
|
|
1605
|
+
/* Counted as the cards are built, so it is reset HERE and nowhere
|
|
1606
|
+
* else: a count kept by the action would drift the moment the tree
|
|
1607
|
+
* was rebuilt for any other reason. */
|
|
1608
|
+
priv.match_count = 0;
|
|
1609
|
+
|
|
1445
1610
|
let anchor = priv.pending_anchor || null;
|
|
1446
1611
|
let preserve_view = !!priv.pending_preserve_view;
|
|
1447
1612
|
priv.pending_anchor = null;
|
|
@@ -2074,6 +2239,29 @@ class GobjLanesHLayout extends BaseLayout {
|
|
|
2074
2239
|
/************************************************************
|
|
2075
2240
|
*
|
|
2076
2241
|
************************************************************/
|
|
2242
|
+
/************************************************************
|
|
2243
|
+
* EV_FIND_NODES {text}
|
|
2244
|
+
*
|
|
2245
|
+
* The cards carry the highlight in their own markup (an html
|
|
2246
|
+
* node paints no G6 state), so finding is a REBUILD -- which
|
|
2247
|
+
* is also why the box is rate-limited. The view is preserved:
|
|
2248
|
+
* a reader who typed a letter did not ask to be moved.
|
|
2249
|
+
************************************************************/
|
|
2250
|
+
function ac_find_nodes(gobj, event, kw, src)
|
|
2251
|
+
{
|
|
2252
|
+
let priv = gobj.priv;
|
|
2253
|
+
let text = String((kw && kw.text) || "").trim().toLowerCase();
|
|
2254
|
+
|
|
2255
|
+
if(text === priv.search) {
|
|
2256
|
+
return 0;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
priv.search = text;
|
|
2260
|
+
refresh_tree(gobj, {preserve_view: true});
|
|
2261
|
+
update_find_result(gobj);
|
|
2262
|
+
return 0;
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2077
2265
|
function ac_refresh(gobj, event, kw, src)
|
|
2078
2266
|
{
|
|
2079
2267
|
save_view_state(gobj);
|
|
@@ -2684,6 +2872,7 @@ function create_gclass(gclass_name)
|
|
|
2684
2872
|
*---------------------------------------------*/
|
|
2685
2873
|
const states = [
|
|
2686
2874
|
["ST_IDLE", [
|
|
2875
|
+
["EV_FIND_NODES", ac_find_nodes, null],
|
|
2687
2876
|
["EV_REFRESH", ac_refresh, null],
|
|
2688
2877
|
["EV_THEME", ac_theme, null],
|
|
2689
2878
|
["EV_CHANGE_LAYOUT", ac_change_layout, null],
|
|
@@ -2710,6 +2899,7 @@ function create_gclass(gclass_name)
|
|
|
2710
2899
|
* Events
|
|
2711
2900
|
*---------------------------------------------*/
|
|
2712
2901
|
const event_types = [
|
|
2902
|
+
["EV_FIND_NODES", 0],
|
|
2713
2903
|
["EV_REFRESH", 0],
|
|
2714
2904
|
["EV_THEME", 0],
|
|
2715
2905
|
["EV_CHANGE_LAYOUT", 0],
|
package/src/gobj_tree_model.js
CHANGED
|
@@ -88,6 +88,43 @@ function node_status(d)
|
|
|
88
88
|
return d.playing? "playing": "running";
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/*
|
|
92
|
+
* What the status LOOKS like, before any word is read.
|
|
93
|
+
*
|
|
94
|
+
* The transport vocabulary, because it is Yuneta's own: a yuno is RUN
|
|
95
|
+
* (the process), PLAYED (the services) and PAUSED, so play / pause /
|
|
96
|
+
* stop are not a metaphor here, they are the three verbs.
|
|
97
|
+
*
|
|
98
|
+
* A coloured dot was the whole encoding before, and a dot has one
|
|
99
|
+
* SHAPE: telling "running" from "stopped" meant telling green from
|
|
100
|
+
* red, at 10px, which is no distinction at all for a reader who does
|
|
101
|
+
* not see colour -- and stopped is the state that matters.
|
|
102
|
+
*
|
|
103
|
+
* U+FE0E asks for the TEXT presentation: bare, several of these
|
|
104
|
+
* render as colour emoji on some platforms, which would make the
|
|
105
|
+
* glyph the loudest thing on a card.
|
|
106
|
+
*/
|
|
107
|
+
const STATUS_SYMBOLS = {
|
|
108
|
+
"playing": "\u25B6\uFE0E", /* play */
|
|
109
|
+
"running": "\u2016", /* paused: two bars */
|
|
110
|
+
"stopped": "\u25A0\uFE0E", /* stop */
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const DISABLED_SYMBOL = "\u2298"; /* a slashed circle */
|
|
114
|
+
|
|
115
|
+
/************************************************************
|
|
116
|
+
* The symbol of a node's status, which is what the reader
|
|
117
|
+
* actually sees first. `disabled` overrides the rest: a
|
|
118
|
+
* disabled gobj is not going to run whatever it is doing now.
|
|
119
|
+
************************************************************/
|
|
120
|
+
function node_status_symbol(d)
|
|
121
|
+
{
|
|
122
|
+
if(d && d.disabled) {
|
|
123
|
+
return DISABLED_SYMBOL;
|
|
124
|
+
}
|
|
125
|
+
return STATUS_SYMBOLS[node_status(d)] || STATUS_SYMBOLS.stopped;
|
|
126
|
+
}
|
|
127
|
+
|
|
91
128
|
/************************************************************
|
|
92
129
|
* The i18n key of the status, which is NOT the status itself:
|
|
93
130
|
* a gobj that runs and does not play is PAUSED to the reader,
|
|
@@ -349,7 +386,7 @@ function node_labels(t)
|
|
|
349
386
|
full_name: t("full name"),
|
|
350
387
|
role: t("role"),
|
|
351
388
|
status: t("status"),
|
|
352
|
-
|
|
389
|
+
fsm_state: t("fsm state"),
|
|
353
390
|
disabled: t("disabled"),
|
|
354
391
|
parent: t("parent"),
|
|
355
392
|
bottom_gobj: t("bottom gobj"),
|
|
@@ -387,8 +424,14 @@ function node_info_rows(d, t)
|
|
|
387
424
|
}
|
|
388
425
|
|
|
389
426
|
rows.push([f.role, L.role[role_i18n_key(node_role(d))]]);
|
|
390
|
-
rows.push([
|
|
391
|
-
|
|
427
|
+
rows.push([
|
|
428
|
+
f.status,
|
|
429
|
+
node_status_symbol(d) + " " + L.status[node_status_key(d)]
|
|
430
|
+
]);
|
|
431
|
+
/* NOT `state`: both rows read "Estado" in Spanish, side by side,
|
|
432
|
+
* one saying "Parado" and the other "ST_IDLE". They are different
|
|
433
|
+
* questions -- what the gobj is DOING, and where its machine IS. */
|
|
434
|
+
rows.push([f.fsm_state, d.state || "\u2014"]);
|
|
392
435
|
|
|
393
436
|
if(d.disabled) {
|
|
394
437
|
rows.push([f.disabled, f.yes]);
|
|
@@ -423,9 +466,11 @@ function node_info_rows(d, t)
|
|
|
423
466
|
export {
|
|
424
467
|
ROLES,
|
|
425
468
|
STATUSES,
|
|
469
|
+
STATUS_SYMBOLS,
|
|
426
470
|
node_role,
|
|
427
471
|
node_status,
|
|
428
472
|
node_status_key,
|
|
473
|
+
node_status_symbol,
|
|
429
474
|
node_badge_keys,
|
|
430
475
|
node_labels,
|
|
431
476
|
node_info_rows,
|
|
@@ -2,6 +2,7 @@ import {describe, it, expect} from "vitest";
|
|
|
2
2
|
import {
|
|
3
3
|
node_role,
|
|
4
4
|
node_status,
|
|
5
|
+
node_status_symbol,
|
|
5
6
|
node_status_key,
|
|
6
7
|
node_badge_keys,
|
|
7
8
|
node_info_rows,
|
|
@@ -148,8 +149,10 @@ describe("node_info_rows", () => {
|
|
|
148
149
|
let timer = root.children.find(c => c.gclass === "C_TIMER");
|
|
149
150
|
let r = rows_of(timer);
|
|
150
151
|
expect(r["role"]).toBe("pure child");
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
/* The symbol travels with the word: the popover is where a
|
|
153
|
+
* reader learns which glyph means what. */
|
|
154
|
+
expect(r["status"]).toBe("\u2016 running (paused)");
|
|
155
|
+
expect(r["fsm state"]).toBe("ST_IDLE");
|
|
153
156
|
});
|
|
154
157
|
|
|
155
158
|
it("shows the facts a card has no room for", () => {
|
|
@@ -158,7 +161,7 @@ describe("node_info_rows", () => {
|
|
|
158
161
|
let r = rows_of(tcp);
|
|
159
162
|
expect(r["disabled"]).toBe("yes");
|
|
160
163
|
expect(r["bottom gobj"]).toBe("C_TCP^raw");
|
|
161
|
-
expect(r["status"]).toBe("stopped");
|
|
164
|
+
expect(r["status"]).toBe("\u2298 stopped");
|
|
162
165
|
});
|
|
163
166
|
|
|
164
167
|
it("carries the traces and the flags the backend reports", () => {
|
|
@@ -182,3 +185,21 @@ describe("node_info_rows", () => {
|
|
|
182
185
|
expect(rows_of(root)["children"]).toBe("2 (collapsed)");
|
|
183
186
|
});
|
|
184
187
|
});
|
|
188
|
+
|
|
189
|
+
describe("node_status_symbol", () => {
|
|
190
|
+
it("gives each status its own SHAPE, not only a colour", () => {
|
|
191
|
+
expect(node_status_symbol({running: true, playing: true})).toBe("\u25B6\uFE0E");
|
|
192
|
+
expect(node_status_symbol({running: true, playing: false})).toBe("\u2016");
|
|
193
|
+
expect(node_status_symbol({running: false})).toBe("\u25A0\uFE0E");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("says disabled over anything else", () => {
|
|
197
|
+
expect(node_status_symbol({running: true, playing: true, disabled: true}))
|
|
198
|
+
.toBe("\u2298");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("falls back to stopped for nothing at all", () => {
|
|
202
|
+
expect(node_status_symbol(null)).toBe("\u25A0\uFE0E");
|
|
203
|
+
expect(node_status_symbol(undefined)).toBe("\u25A0\uFE0E");
|
|
204
|
+
});
|
|
205
|
+
});
|
package/src/yui_gclass_view.js
CHANGED
|
@@ -158,7 +158,30 @@ function open_gclass_view(host, gclass_name, opts)
|
|
|
158
158
|
let handle = {json_gobj: jv, win: null, modal: null};
|
|
159
159
|
let $box = gobj_read_pointer_attr(jv, "$container");
|
|
160
160
|
|
|
161
|
+
/*
|
|
162
|
+
* The ✕ path, and the ONLY one that used to leave the viewer
|
|
163
|
+
* behind. `close_window()` calls this and THEN destroys the
|
|
164
|
+
* window -- but the viewer is not the window's child (it hangs
|
|
165
|
+
* from the host, so it can outlive a window that is only its
|
|
166
|
+
* frame), so nothing else took it down. Alive, it did two things:
|
|
167
|
+
*
|
|
168
|
+
* - it kept its service NAME, so the very next click on the
|
|
169
|
+
* `gclass` button answered "service ALREADY registered:
|
|
170
|
+
* C_YUI_GCLASS^gclass-view-<host>" and no second gclass could
|
|
171
|
+
* be opened for the rest of the session.
|
|
172
|
+
* - it was still RUNNING when the host's own window closed, and
|
|
173
|
+
* the framework logged "Destroying a RUNNING gobj" while
|
|
174
|
+
* taking it down with its parent.
|
|
175
|
+
*
|
|
176
|
+
* The window and the sheet are cleared FIRST: both are already
|
|
177
|
+
* tearing themselves down, and close_gclass_view() must not go
|
|
178
|
+
* back at them.
|
|
179
|
+
*/
|
|
161
180
|
let on_close = () => {
|
|
181
|
+
handle.win = null;
|
|
182
|
+
handle.modal = null;
|
|
183
|
+
close_gclass_view(handle);
|
|
184
|
+
|
|
162
185
|
if(typeof opts.on_close === "function") {
|
|
163
186
|
opts.on_close();
|
|
164
187
|
}
|