@yuneta/gobj-ui 7.23.38 → 7.23.40
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 +179 -37
- package/dist/gobj-ui.es.js +179 -37
- package/package.json +1 -1
- package/src/c_yui_gobj_tree_js.js +212 -10
- package/src/c_yui_json_graph.js +17 -9
- package/src/c_yui_treedb_graph.js +10 -6
- package/src/gobj_tree_model.js +48 -3
- package/src/gobj_tree_model.test.js +24 -3
- package/src/shell_route_map.js +13 -3
|
@@ -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,129 @@ 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
|
+
/* ONE counted span, not a number beside a word: "1 matches"
|
|
918
|
+
* is what two spans always produce, because nothing there can
|
|
919
|
+
* see the count. `t(key, {count})` picks `matches_one` for
|
|
920
|
+
* one and falls back to the base key for the rest, in both
|
|
921
|
+
* languages.
|
|
922
|
+
*
|
|
923
|
+
* And NO `i18n` attribute on it: refresh_language()
|
|
924
|
+
* re-translates whatever carries one by calling t() WITHOUT
|
|
925
|
+
* the count, which would put the plural back over the
|
|
926
|
+
* singular. A counted word is re-rendered by its owner
|
|
927
|
+
* instead -- which is why the language action below repaints
|
|
928
|
+
* this chip. */
|
|
929
|
+
/* `display:flex` inline and NOT the `is-flex` helper -- both
|
|
930
|
+
* Bulma helpers carry !important, so `is-hidden is-flex` on one
|
|
931
|
+
* element is decided by stylesheet order. */
|
|
932
|
+
['div', {class: 'GOBJ_TREE_FIND_RESULT is-hidden',
|
|
933
|
+
style: 'display:flex; align-items:center; gap:.3rem; ' +
|
|
934
|
+
'margin-right:.5rem; font-size:.85rem;'}, [
|
|
935
|
+
['span', {class: 'GOBJ_TREE_FIND_COUNT'}, '']
|
|
936
|
+
]]
|
|
937
|
+
];
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/************************************************************
|
|
941
|
+
* Does this node answer the current find term?
|
|
942
|
+
*
|
|
943
|
+
* What a reader types is a gclass, a name or a state -- the
|
|
944
|
+
* three things a card shows. The full name is searched too,
|
|
945
|
+
* because it is what the popover shows and what a log line
|
|
946
|
+
* carries.
|
|
947
|
+
************************************************************/
|
|
948
|
+
function node_matches(d, term)
|
|
949
|
+
{
|
|
950
|
+
if(!term) {
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
let hay = [d.gclass, d.name, d.fullname, d.state]
|
|
954
|
+
.filter((x) => typeof x === "string")
|
|
955
|
+
.join(" ")
|
|
956
|
+
.toLowerCase();
|
|
957
|
+
return hay.indexOf(term) >= 0;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/************************************************************
|
|
961
|
+
* Repaint the count chip. Hidden while nothing is typed:
|
|
962
|
+
* "0 matches" beside an empty box says nothing.
|
|
963
|
+
************************************************************/
|
|
964
|
+
function update_find_result(gobj)
|
|
965
|
+
{
|
|
966
|
+
let priv = gobj.priv;
|
|
967
|
+
|
|
968
|
+
if(!priv.$find_result || !priv.$find_count) {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
if(!priv.search) {
|
|
972
|
+
priv.$find_result.classList.add("is-hidden");
|
|
973
|
+
priv.$find_count.textContent = "";
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
priv.$find_count.textContent = t("matches", {count: priv.match_count});
|
|
977
|
+
priv.$find_result.classList.remove("is-hidden");
|
|
978
|
+
}
|
|
979
|
+
|
|
839
980
|
function make_toolbar(gobj)
|
|
840
981
|
{
|
|
841
982
|
let priv = gobj.priv;
|
|
@@ -846,7 +987,9 @@ function make_toolbar(gobj)
|
|
|
846
987
|
* and not a per-node one, so it sits on its own at the left — ahead
|
|
847
988
|
* of a find box where the toolbar has one. The per-node handles
|
|
848
989
|
* live inside the cards, on the right of each header. */
|
|
849
|
-
let left_items = yui_graph_fold_items(gobj, toolbar_wide)
|
|
990
|
+
let left_items = yui_graph_fold_items(gobj, toolbar_wide).concat(
|
|
991
|
+
make_find_items(gobj)
|
|
992
|
+
);
|
|
850
993
|
let center_items = [];
|
|
851
994
|
let right_items = [];
|
|
852
995
|
|
|
@@ -899,6 +1042,11 @@ function make_toolbar(gobj)
|
|
|
899
1042
|
['div', {class: 'yui-horizontal-toolbar-section right'}, right_items],
|
|
900
1043
|
]);
|
|
901
1044
|
|
|
1045
|
+
/* The count chip is built as a spec, so its elements only exist
|
|
1046
|
+
* once yui_toolbar() has materialised the row. */
|
|
1047
|
+
priv.$find_result = $toolbar.querySelector('.GOBJ_TREE_FIND_RESULT');
|
|
1048
|
+
priv.$find_count = $toolbar.querySelector('.GOBJ_TREE_FIND_COUNT');
|
|
1049
|
+
|
|
902
1050
|
refresh_language($toolbar, t);
|
|
903
1051
|
return $toolbar;
|
|
904
1052
|
}
|
|
@@ -1159,9 +1307,14 @@ function render_toggle_html(node_id, collapsed, num_children, cs)
|
|
|
1159
1307
|
/************************************************************
|
|
1160
1308
|
* HTML for the status pill: what the gobj is DOING.
|
|
1161
1309
|
*
|
|
1162
|
-
* A
|
|
1163
|
-
* card where there is no room for it
|
|
1164
|
-
*
|
|
1310
|
+
* A SYMBOL plus a word, and the word is dropped in the
|
|
1311
|
+
* compact card where there is no room for it.
|
|
1312
|
+
*
|
|
1313
|
+
* The symbol carries the state on its own, which a dot never
|
|
1314
|
+
* did: a dot has one shape, so telling running from stopped
|
|
1315
|
+
* meant telling green from red at 10 pixels -- and stopped is
|
|
1316
|
+
* the one that has to be seen. Play, pause and stop are also
|
|
1317
|
+
* the framework's own three verbs, not a borrowed metaphor.
|
|
1165
1318
|
************************************************************/
|
|
1166
1319
|
function render_status_html(d, ss, labels, with_label)
|
|
1167
1320
|
{
|
|
@@ -1171,15 +1324,16 @@ function render_status_html(d, ss, labels, with_label)
|
|
|
1171
1324
|
let label = d.disabled?
|
|
1172
1325
|
labels.field.disabled:
|
|
1173
1326
|
(labels.status_short[node_status(d)] || node_status(d));
|
|
1327
|
+
let symbol = node_status_symbol(d);
|
|
1174
1328
|
|
|
1175
1329
|
if(!with_label) {
|
|
1176
1330
|
return `<span title="${escapeHtml(full)}" style="
|
|
1177
1331
|
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
1332
|
margin-left: 6px;
|
|
1182
|
-
|
|
1333
|
+
color: ${ss.dot};
|
|
1334
|
+
font-size: 11px;
|
|
1335
|
+
line-height: 1;
|
|
1336
|
+
">${escapeHtml(symbol)}</span>`;
|
|
1183
1337
|
}
|
|
1184
1338
|
|
|
1185
1339
|
return `<span title="${escapeHtml(full)}" style="
|
|
@@ -1187,7 +1341,7 @@ function render_status_html(d, ss, labels, with_label)
|
|
|
1187
1341
|
display: inline-flex;
|
|
1188
1342
|
align-items: center;
|
|
1189
1343
|
gap: 4px;
|
|
1190
|
-
padding: 1px 7px 1px
|
|
1344
|
+
padding: 1px 7px 1px 6px;
|
|
1191
1345
|
background: ${ss.bg};
|
|
1192
1346
|
color: ${ss.fg};
|
|
1193
1347
|
border-radius: 9px;
|
|
@@ -1195,7 +1349,8 @@ function render_status_html(d, ss, labels, with_label)
|
|
|
1195
1349
|
font-weight: 700;
|
|
1196
1350
|
line-height: 1.45;
|
|
1197
1351
|
white-space: nowrap;
|
|
1198
|
-
"><span style="
|
|
1352
|
+
"><span style="color:${ss.dot}; font-size:11px; line-height:1;"
|
|
1353
|
+
>${escapeHtml(symbol)}</span>${escapeHtml(label)}</span>`;
|
|
1199
1354
|
}
|
|
1200
1355
|
|
|
1201
1356
|
/************************************************************
|
|
@@ -1257,6 +1412,20 @@ function build_gobj_nodes(gobj, d, nodes, edges, parent_id, compact, labels)
|
|
|
1257
1412
|
* "this branch is out of the game" at any zoom. */
|
|
1258
1413
|
let border_style = d.disabled? "dashed": "solid";
|
|
1259
1414
|
|
|
1415
|
+
/*
|
|
1416
|
+
* A card the find box matched. The ring is drawn OVER the role
|
|
1417
|
+
* colour and not instead of it -- the card still has to say what
|
|
1418
|
+
* it is -- and it is counted here, while every card passes
|
|
1419
|
+
* through, so the chip and the drawing can never disagree.
|
|
1420
|
+
*/
|
|
1421
|
+
let matched = node_matches(d, priv.search);
|
|
1422
|
+
if(matched) {
|
|
1423
|
+
priv.match_count++;
|
|
1424
|
+
border_width = 3;
|
|
1425
|
+
border_style = "solid";
|
|
1426
|
+
cs = Object.assign({}, cs, {border: FIND_MATCH_COLOR});
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1260
1429
|
let children = d.children || [];
|
|
1261
1430
|
let num_children = children.length;
|
|
1262
1431
|
let has_children = num_children > 0;
|
|
@@ -1442,6 +1611,11 @@ function load_tree(gobj)
|
|
|
1442
1611
|
let layout_cfg = get_layout_cfg(gobj_read_str_attr(gobj, "layout"));
|
|
1443
1612
|
let labels = node_labels(t);
|
|
1444
1613
|
|
|
1614
|
+
/* Counted as the cards are built, so it is reset HERE and nowhere
|
|
1615
|
+
* else: a count kept by the action would drift the moment the tree
|
|
1616
|
+
* was rebuilt for any other reason. */
|
|
1617
|
+
priv.match_count = 0;
|
|
1618
|
+
|
|
1445
1619
|
let anchor = priv.pending_anchor || null;
|
|
1446
1620
|
let preserve_view = !!priv.pending_preserve_view;
|
|
1447
1621
|
priv.pending_anchor = null;
|
|
@@ -2074,6 +2248,29 @@ class GobjLanesHLayout extends BaseLayout {
|
|
|
2074
2248
|
/************************************************************
|
|
2075
2249
|
*
|
|
2076
2250
|
************************************************************/
|
|
2251
|
+
/************************************************************
|
|
2252
|
+
* EV_FIND_NODES {text}
|
|
2253
|
+
*
|
|
2254
|
+
* The cards carry the highlight in their own markup (an html
|
|
2255
|
+
* node paints no G6 state), so finding is a REBUILD -- which
|
|
2256
|
+
* is also why the box is rate-limited. The view is preserved:
|
|
2257
|
+
* a reader who typed a letter did not ask to be moved.
|
|
2258
|
+
************************************************************/
|
|
2259
|
+
function ac_find_nodes(gobj, event, kw, src)
|
|
2260
|
+
{
|
|
2261
|
+
let priv = gobj.priv;
|
|
2262
|
+
let text = String((kw && kw.text) || "").trim().toLowerCase();
|
|
2263
|
+
|
|
2264
|
+
if(text === priv.search) {
|
|
2265
|
+
return 0;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
priv.search = text;
|
|
2269
|
+
refresh_tree(gobj, {preserve_view: true});
|
|
2270
|
+
update_find_result(gobj);
|
|
2271
|
+
return 0;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2077
2274
|
function ac_refresh(gobj, event, kw, src)
|
|
2078
2275
|
{
|
|
2079
2276
|
save_view_state(gobj);
|
|
@@ -2646,6 +2843,9 @@ function ac_language_changed(gobj, event, kw, src)
|
|
|
2646
2843
|
}
|
|
2647
2844
|
hide_popover(gobj);
|
|
2648
2845
|
refresh_tree(gobj);
|
|
2846
|
+
/* The count chip carries a COUNTED word, which has no key for
|
|
2847
|
+
* refresh_language() to re-translate: its owner has to repaint it. */
|
|
2848
|
+
update_find_result(gobj);
|
|
2649
2849
|
return 0;
|
|
2650
2850
|
}
|
|
2651
2851
|
|
|
@@ -2684,6 +2884,7 @@ function create_gclass(gclass_name)
|
|
|
2684
2884
|
*---------------------------------------------*/
|
|
2685
2885
|
const states = [
|
|
2686
2886
|
["ST_IDLE", [
|
|
2887
|
+
["EV_FIND_NODES", ac_find_nodes, null],
|
|
2687
2888
|
["EV_REFRESH", ac_refresh, null],
|
|
2688
2889
|
["EV_THEME", ac_theme, null],
|
|
2689
2890
|
["EV_CHANGE_LAYOUT", ac_change_layout, null],
|
|
@@ -2710,6 +2911,7 @@ function create_gclass(gclass_name)
|
|
|
2710
2911
|
* Events
|
|
2711
2912
|
*---------------------------------------------*/
|
|
2712
2913
|
const event_types = [
|
|
2914
|
+
["EV_FIND_NODES", 0],
|
|
2713
2915
|
["EV_REFRESH", 0],
|
|
2714
2916
|
["EV_THEME", 0],
|
|
2715
2917
|
["EV_CHANGE_LAYOUT", 0],
|
package/src/c_yui_json_graph.js
CHANGED
|
@@ -599,17 +599,25 @@ function make_toolbar(gobj)
|
|
|
599
599
|
*/
|
|
600
600
|
let left_items = yui_graph_fold_items(gobj, toolbar_wide).concat([
|
|
601
601
|
$find_control,
|
|
602
|
-
/*
|
|
603
|
-
* is
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
602
|
+
/* ONE counted span, not a number beside a word: "1 matches"
|
|
603
|
+
* is what two spans always produce, because nothing there can
|
|
604
|
+
* see the count. `t(key, {count})` picks `matches_one` for
|
|
605
|
+
* one and falls back to the base key for the rest, in both
|
|
606
|
+
* languages.
|
|
607
|
+
*
|
|
608
|
+
* And NO `i18n` attribute on it: refresh_language()
|
|
609
|
+
* re-translates whatever carries one by calling t() WITHOUT
|
|
610
|
+
* the count, which would put the plural back over the
|
|
611
|
+
* singular. Nothing re-translates it, so it is repainted on
|
|
612
|
+
* the next find -- which is the next keystroke, and it is
|
|
613
|
+
* hidden while the box is empty. */
|
|
614
|
+
/* `display:flex` inline and NOT the `is-flex` helper — both
|
|
615
|
+
* Bulma helpers carry !important, so `is-hidden is-flex` on one
|
|
616
|
+
* element is decided by stylesheet order. */
|
|
608
617
|
['div', {class: 'JSON_GRAPH_FIND_RESULT is-hidden',
|
|
609
618
|
style: 'display:flex; align-items:center; gap:.3rem; ' +
|
|
610
619
|
'margin-right:.5rem; font-size:.85rem;'}, [
|
|
611
|
-
['span', {class: 'JSON_GRAPH_FIND_COUNT'}, '']
|
|
612
|
-
['span', {i18n: 'matches'}, 'matches']
|
|
620
|
+
['span', {class: 'JSON_GRAPH_FIND_COUNT'}, '']
|
|
613
621
|
]]
|
|
614
622
|
]);
|
|
615
623
|
let center_items = [];
|
|
@@ -1537,7 +1545,7 @@ function update_find_result(gobj)
|
|
|
1537
1545
|
priv.$find_count.textContent = "";
|
|
1538
1546
|
return;
|
|
1539
1547
|
}
|
|
1540
|
-
priv.$find_count.textContent =
|
|
1548
|
+
priv.$find_count.textContent = t("matches", {count: priv.match_count});
|
|
1541
1549
|
priv.$find_result.classList.remove("is-hidden");
|
|
1542
1550
|
}
|
|
1543
1551
|
|
|
@@ -649,9 +649,14 @@ function make_toolbar(gobj)
|
|
|
649
649
|
|
|
650
650
|
let center_items = [
|
|
651
651
|
$find_control,
|
|
652
|
-
/*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
652
|
+
/* ONE counted span, not a number beside a word: "1 matches" is
|
|
653
|
+
* what two spans always produce, because nothing there can see
|
|
654
|
+
* the count. `t(key, {count})` picks `matches_one` for one and
|
|
655
|
+
* falls back to the base key for the rest, in both languages.
|
|
656
|
+
* No `i18n` attribute on it: refresh_language() would call t()
|
|
657
|
+
* WITHOUT the count and put the plural back over the singular.
|
|
658
|
+
* Nothing re-translates it, so it is repainted on the next
|
|
659
|
+
* find, and it is hidden while the box is empty. */
|
|
655
660
|
/* `display:flex` inline and NOT the `is-flex` helper: both Bulma
|
|
656
661
|
* helpers carry !important, so `is-hidden is-flex` on one element
|
|
657
662
|
* is decided by whichever lands later in the stylesheet — and the
|
|
@@ -661,8 +666,7 @@ function make_toolbar(gobj)
|
|
|
661
666
|
['div', {class: 'GRAPH_FIND_RESULT is-hidden',
|
|
662
667
|
style: 'display:flex; align-items:center; gap:.3rem; ' +
|
|
663
668
|
'margin-right:.5rem; font-size:.85rem;'}, [
|
|
664
|
-
['span', {class: 'GRAPH_FIND_COUNT'}, '']
|
|
665
|
-
['span', {i18n: 'matches'}, 'matches']
|
|
669
|
+
['span', {class: 'GRAPH_FIND_COUNT'}, '']
|
|
666
670
|
]]
|
|
667
671
|
];
|
|
668
672
|
|
|
@@ -2485,7 +2489,7 @@ function ac_find_result(gobj, event, kw, src)
|
|
|
2485
2489
|
return 0;
|
|
2486
2490
|
}
|
|
2487
2491
|
|
|
2488
|
-
$count.textContent =
|
|
2492
|
+
$count.textContent = t("matches", {count: (kw && kw.matches) || 0});
|
|
2489
2493
|
$result.classList.remove("is-hidden");
|
|
2490
2494
|
return 0;
|
|
2491
2495
|
}
|
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/shell_route_map.js
CHANGED
|
@@ -257,11 +257,19 @@ function build_body(shell, t)
|
|
|
257
257
|
);
|
|
258
258
|
attach_clear($search_ctrl, $search);
|
|
259
259
|
|
|
260
|
-
/* Live match counter next to the filter.
|
|
260
|
+
/* Live match counter next to the filter.
|
|
261
|
+
*
|
|
262
|
+
* ONE counted span, not a number beside a word: "1 matches" is what
|
|
263
|
+
* two spans always produce, because nothing there can see the
|
|
264
|
+
* count. `t(key, {count})` picks `matches_one` for one and falls
|
|
265
|
+
* back to the base key for the rest. No `i18n` attribute on it --
|
|
266
|
+
* refresh_language() would call t() WITHOUT the count and put the
|
|
267
|
+
* plural back over the singular -- so the filter, which runs on
|
|
268
|
+
* every keystroke, is what repaints it. */
|
|
261
269
|
let $count_n = createElement2(["span", {class: "ROUTEMAP_COUNT_N"}, ""]);
|
|
262
270
|
let $count = createElement2(
|
|
263
271
|
["span", {class: "ROUTEMAP_COUNT is-size-7 has-text-grey is-hidden"},
|
|
264
|
-
[$count_n
|
|
272
|
+
[$count_n]]
|
|
265
273
|
);
|
|
266
274
|
/* References are hidden by default and revealed by this toggle.
|
|
267
275
|
* Remembered for the page session (not localStorage): it is a
|
|
@@ -294,7 +302,9 @@ function build_body(shell, t)
|
|
|
294
302
|
if(q === "") {
|
|
295
303
|
$count.classList.add("is-hidden");
|
|
296
304
|
} else {
|
|
297
|
-
$count_n.textContent =
|
|
305
|
+
$count_n.textContent = t("matches", {
|
|
306
|
+
count: $tree.querySelectorAll(".ROUTEMAP_MATCH").length
|
|
307
|
+
});
|
|
298
308
|
$count.classList.remove("is-hidden");
|
|
299
309
|
}
|
|
300
310
|
};
|