@yuneta/gobj-ui 7.23.38 → 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 +20 -2
- package/dist/gobj-ui.cjs.js +178 -21
- package/dist/gobj-ui.es.js +178 -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/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
|
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]);
|
|
@@ -16772,6 +16787,12 @@ var PRIVATE_DATA$9 = {
|
|
|
16772
16787
|
theme: null,
|
|
16773
16788
|
theme_observer: null,
|
|
16774
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,
|
|
16775
16796
|
$popover: null,
|
|
16776
16797
|
$popover_title: null,
|
|
16777
16798
|
$popover_body: null,
|
|
@@ -16917,6 +16938,7 @@ var NODE_SIZES = {
|
|
|
16917
16938
|
/***************************************************************
|
|
16918
16939
|
* Shared card typography.
|
|
16919
16940
|
***************************************************************/
|
|
16941
|
+
var FIND_MATCH_COLOR = "#D9A23F";
|
|
16920
16942
|
var GT_FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
|
|
16921
16943
|
/***************************************************************
|
|
16922
16944
|
* Theme-aware card palette derived from the role colour.
|
|
@@ -17289,11 +17311,107 @@ function destroy_ui$4(gobj) {
|
|
|
17289
17311
|
/************************************************************
|
|
17290
17312
|
* Toolbar
|
|
17291
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
|
+
}
|
|
17292
17410
|
function make_toolbar$1(gobj) {
|
|
17293
17411
|
let priv = gobj.priv;
|
|
17294
17412
|
let toolbar_wide = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "wide");
|
|
17295
17413
|
let current_layout = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "layout");
|
|
17296
|
-
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));
|
|
17297
17415
|
let center_items = [];
|
|
17298
17416
|
let right_items = [];
|
|
17299
17417
|
center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide);
|
|
@@ -17344,6 +17462,8 @@ function make_toolbar$1(gobj) {
|
|
|
17344
17462
|
right_items
|
|
17345
17463
|
]
|
|
17346
17464
|
]);
|
|
17465
|
+
priv.$find_result = $toolbar.querySelector(".GOBJ_TREE_FIND_RESULT");
|
|
17466
|
+
priv.$find_count = $toolbar.querySelector(".GOBJ_TREE_FIND_COUNT");
|
|
17347
17467
|
(0, _yuneta_gobj_js.refresh_language)($toolbar, i18next.t);
|
|
17348
17468
|
return $toolbar;
|
|
17349
17469
|
}
|
|
@@ -17519,26 +17639,32 @@ function render_toggle_html(node_id, collapsed, num_children, cs) {
|
|
|
17519
17639
|
/************************************************************
|
|
17520
17640
|
* HTML for the status pill: what the gobj is DOING.
|
|
17521
17641
|
*
|
|
17522
|
-
* A
|
|
17523
|
-
* card where there is no room for it
|
|
17524
|
-
*
|
|
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.
|
|
17525
17650
|
************************************************************/
|
|
17526
17651
|
function render_status_html(d, ss, labels, with_label) {
|
|
17527
17652
|
let full = d.disabled ? labels.field.disabled : labels.status[node_status_key(d)] || node_status_key(d);
|
|
17528
17653
|
let label = d.disabled ? labels.field.disabled : labels.status_short[node_status(d)] || node_status(d);
|
|
17654
|
+
let symbol = node_status_symbol(d);
|
|
17529
17655
|
if (!with_label) return `<span title="${(0, _yuneta_gobj_js.escapeHtml)(full)}" style="
|
|
17530
17656
|
flex: 0 0 auto;
|
|
17531
|
-
width: 10px; height: 10px; border-radius: 50%;
|
|
17532
|
-
background: ${ss.dot};
|
|
17533
|
-
box-shadow: 0 0 0 2px ${ss.bg};
|
|
17534
17657
|
margin-left: 6px;
|
|
17535
|
-
|
|
17658
|
+
color: ${ss.dot};
|
|
17659
|
+
font-size: 11px;
|
|
17660
|
+
line-height: 1;
|
|
17661
|
+
">${(0, _yuneta_gobj_js.escapeHtml)(symbol)}</span>`;
|
|
17536
17662
|
return `<span title="${(0, _yuneta_gobj_js.escapeHtml)(full)}" style="
|
|
17537
17663
|
flex: 0 0 auto;
|
|
17538
17664
|
display: inline-flex;
|
|
17539
17665
|
align-items: center;
|
|
17540
17666
|
gap: 4px;
|
|
17541
|
-
padding: 1px 7px 1px
|
|
17667
|
+
padding: 1px 7px 1px 6px;
|
|
17542
17668
|
background: ${ss.bg};
|
|
17543
17669
|
color: ${ss.fg};
|
|
17544
17670
|
border-radius: 9px;
|
|
@@ -17546,7 +17672,8 @@ function render_status_html(d, ss, labels, with_label) {
|
|
|
17546
17672
|
font-weight: 700;
|
|
17547
17673
|
line-height: 1.45;
|
|
17548
17674
|
white-space: nowrap;
|
|
17549
|
-
"><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>`;
|
|
17550
17677
|
}
|
|
17551
17678
|
/************************************************************
|
|
17552
17679
|
* HTML for the badges: what the gobj IS, besides its colour.
|
|
@@ -17593,6 +17720,12 @@ function build_gobj_nodes(gobj, d, nodes, edges, parent_id, compact, labels) {
|
|
|
17593
17720
|
let height = size.h;
|
|
17594
17721
|
let border_width = category === "yuno" || category === "service" ? 2 : 1;
|
|
17595
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
|
+
}
|
|
17596
17729
|
let children = d.children || [];
|
|
17597
17730
|
let num_children = children.length;
|
|
17598
17731
|
let has_children = num_children > 0;
|
|
@@ -17717,6 +17850,7 @@ function load_tree(gobj) {
|
|
|
17717
17850
|
}
|
|
17718
17851
|
let layout_cfg = get_layout_cfg((0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "layout"));
|
|
17719
17852
|
let labels = node_labels(i18next.t);
|
|
17853
|
+
priv.match_count = 0;
|
|
17720
17854
|
let anchor = priv.pending_anchor || null;
|
|
17721
17855
|
let preserve_view = !!priv.pending_preserve_view;
|
|
17722
17856
|
priv.pending_anchor = null;
|
|
@@ -18146,6 +18280,23 @@ var GobjLanesHLayout = class extends _antv_g6.BaseLayout {
|
|
|
18146
18280
|
/************************************************************
|
|
18147
18281
|
*
|
|
18148
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
|
+
}
|
|
18149
18300
|
function ac_refresh$3(gobj, event, kw, src) {
|
|
18150
18301
|
save_view_state(gobj);
|
|
18151
18302
|
refresh_tree(gobj);
|
|
@@ -18519,6 +18670,7 @@ function create_gclass$9(gclass_name) {
|
|
|
18519
18670
|
return -1;
|
|
18520
18671
|
}
|
|
18521
18672
|
__gclass__$9 = (0, _yuneta_gobj_js.gclass_create)(gclass_name, [
|
|
18673
|
+
["EV_FIND_NODES", 0],
|
|
18522
18674
|
["EV_REFRESH", 0],
|
|
18523
18675
|
["EV_THEME", 0],
|
|
18524
18676
|
["EV_CHANGE_LAYOUT", 0],
|
|
@@ -18539,6 +18691,11 @@ function create_gclass$9(gclass_name) {
|
|
|
18539
18691
|
["EV_GCLASS_CLOSED", 0],
|
|
18540
18692
|
["EV_LANGUAGE_CHANGED", 0]
|
|
18541
18693
|
], [["ST_IDLE", [
|
|
18694
|
+
[
|
|
18695
|
+
"EV_FIND_NODES",
|
|
18696
|
+
ac_find_nodes$2,
|
|
18697
|
+
null
|
|
18698
|
+
],
|
|
18542
18699
|
[
|
|
18543
18700
|
"EV_REFRESH",
|
|
18544
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]);
|
|
@@ -16767,6 +16782,12 @@ var PRIVATE_DATA$9 = {
|
|
|
16767
16782
|
theme: null,
|
|
16768
16783
|
theme_observer: null,
|
|
16769
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,
|
|
16770
16791
|
$popover: null,
|
|
16771
16792
|
$popover_title: null,
|
|
16772
16793
|
$popover_body: null,
|
|
@@ -16912,6 +16933,7 @@ var NODE_SIZES = {
|
|
|
16912
16933
|
/***************************************************************
|
|
16913
16934
|
* Shared card typography.
|
|
16914
16935
|
***************************************************************/
|
|
16936
|
+
var FIND_MATCH_COLOR = "#D9A23F";
|
|
16915
16937
|
var GT_FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
|
|
16916
16938
|
/***************************************************************
|
|
16917
16939
|
* Theme-aware card palette derived from the role colour.
|
|
@@ -17284,11 +17306,107 @@ function destroy_ui$4(gobj) {
|
|
|
17284
17306
|
/************************************************************
|
|
17285
17307
|
* Toolbar
|
|
17286
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
|
+
}
|
|
17287
17405
|
function make_toolbar$1(gobj) {
|
|
17288
17406
|
let priv = gobj.priv;
|
|
17289
17407
|
let toolbar_wide = gobj_read_attr(gobj, "wide");
|
|
17290
17408
|
let current_layout = gobj_read_str_attr(gobj, "layout");
|
|
17291
|
-
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));
|
|
17292
17410
|
let center_items = [];
|
|
17293
17411
|
let right_items = [];
|
|
17294
17412
|
center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide);
|
|
@@ -17339,6 +17457,8 @@ function make_toolbar$1(gobj) {
|
|
|
17339
17457
|
right_items
|
|
17340
17458
|
]
|
|
17341
17459
|
]);
|
|
17460
|
+
priv.$find_result = $toolbar.querySelector(".GOBJ_TREE_FIND_RESULT");
|
|
17461
|
+
priv.$find_count = $toolbar.querySelector(".GOBJ_TREE_FIND_COUNT");
|
|
17342
17462
|
refresh_language($toolbar, t);
|
|
17343
17463
|
return $toolbar;
|
|
17344
17464
|
}
|
|
@@ -17514,26 +17634,32 @@ function render_toggle_html(node_id, collapsed, num_children, cs) {
|
|
|
17514
17634
|
/************************************************************
|
|
17515
17635
|
* HTML for the status pill: what the gobj is DOING.
|
|
17516
17636
|
*
|
|
17517
|
-
* A
|
|
17518
|
-
* card where there is no room for it
|
|
17519
|
-
*
|
|
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.
|
|
17520
17645
|
************************************************************/
|
|
17521
17646
|
function render_status_html(d, ss, labels, with_label) {
|
|
17522
17647
|
let full = d.disabled ? labels.field.disabled : labels.status[node_status_key(d)] || node_status_key(d);
|
|
17523
17648
|
let label = d.disabled ? labels.field.disabled : labels.status_short[node_status(d)] || node_status(d);
|
|
17649
|
+
let symbol = node_status_symbol(d);
|
|
17524
17650
|
if (!with_label) return `<span title="${escapeHtml(full)}" style="
|
|
17525
17651
|
flex: 0 0 auto;
|
|
17526
|
-
width: 10px; height: 10px; border-radius: 50%;
|
|
17527
|
-
background: ${ss.dot};
|
|
17528
|
-
box-shadow: 0 0 0 2px ${ss.bg};
|
|
17529
17652
|
margin-left: 6px;
|
|
17530
|
-
|
|
17653
|
+
color: ${ss.dot};
|
|
17654
|
+
font-size: 11px;
|
|
17655
|
+
line-height: 1;
|
|
17656
|
+
">${escapeHtml(symbol)}</span>`;
|
|
17531
17657
|
return `<span title="${escapeHtml(full)}" style="
|
|
17532
17658
|
flex: 0 0 auto;
|
|
17533
17659
|
display: inline-flex;
|
|
17534
17660
|
align-items: center;
|
|
17535
17661
|
gap: 4px;
|
|
17536
|
-
padding: 1px 7px 1px
|
|
17662
|
+
padding: 1px 7px 1px 6px;
|
|
17537
17663
|
background: ${ss.bg};
|
|
17538
17664
|
color: ${ss.fg};
|
|
17539
17665
|
border-radius: 9px;
|
|
@@ -17541,7 +17667,8 @@ function render_status_html(d, ss, labels, with_label) {
|
|
|
17541
17667
|
font-weight: 700;
|
|
17542
17668
|
line-height: 1.45;
|
|
17543
17669
|
white-space: nowrap;
|
|
17544
|
-
"><span style="
|
|
17670
|
+
"><span style="color:${ss.dot}; font-size:11px; line-height:1;"
|
|
17671
|
+
>${escapeHtml(symbol)}</span>${escapeHtml(label)}</span>`;
|
|
17545
17672
|
}
|
|
17546
17673
|
/************************************************************
|
|
17547
17674
|
* HTML for the badges: what the gobj IS, besides its colour.
|
|
@@ -17588,6 +17715,12 @@ function build_gobj_nodes(gobj, d, nodes, edges, parent_id, compact, labels) {
|
|
|
17588
17715
|
let height = size.h;
|
|
17589
17716
|
let border_width = category === "yuno" || category === "service" ? 2 : 1;
|
|
17590
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
|
+
}
|
|
17591
17724
|
let children = d.children || [];
|
|
17592
17725
|
let num_children = children.length;
|
|
17593
17726
|
let has_children = num_children > 0;
|
|
@@ -17712,6 +17845,7 @@ function load_tree(gobj) {
|
|
|
17712
17845
|
}
|
|
17713
17846
|
let layout_cfg = get_layout_cfg(gobj_read_str_attr(gobj, "layout"));
|
|
17714
17847
|
let labels = node_labels(t);
|
|
17848
|
+
priv.match_count = 0;
|
|
17715
17849
|
let anchor = priv.pending_anchor || null;
|
|
17716
17850
|
let preserve_view = !!priv.pending_preserve_view;
|
|
17717
17851
|
priv.pending_anchor = null;
|
|
@@ -18141,6 +18275,23 @@ var GobjLanesHLayout = class extends BaseLayout {
|
|
|
18141
18275
|
/************************************************************
|
|
18142
18276
|
*
|
|
18143
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
|
+
}
|
|
18144
18295
|
function ac_refresh$3(gobj, event, kw, src) {
|
|
18145
18296
|
save_view_state(gobj);
|
|
18146
18297
|
refresh_tree(gobj);
|
|
@@ -18514,6 +18665,7 @@ function create_gclass$9(gclass_name) {
|
|
|
18514
18665
|
return -1;
|
|
18515
18666
|
}
|
|
18516
18667
|
__gclass__$9 = gclass_create(gclass_name, [
|
|
18668
|
+
["EV_FIND_NODES", 0],
|
|
18517
18669
|
["EV_REFRESH", 0],
|
|
18518
18670
|
["EV_THEME", 0],
|
|
18519
18671
|
["EV_CHANGE_LAYOUT", 0],
|
|
@@ -18534,6 +18686,11 @@ function create_gclass$9(gclass_name) {
|
|
|
18534
18686
|
["EV_GCLASS_CLOSED", 0],
|
|
18535
18687
|
["EV_LANGUAGE_CHANGED", 0]
|
|
18536
18688
|
], [["ST_IDLE", [
|
|
18689
|
+
[
|
|
18690
|
+
"EV_FIND_NODES",
|
|
18691
|
+
ac_find_nodes$2,
|
|
18692
|
+
null
|
|
18693
|
+
],
|
|
18537
18694
|
[
|
|
18538
18695
|
"EV_REFRESH",
|
|
18539
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
|
+
});
|