@yuneta/gobj-ui 7.23.29 → 7.23.30
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 +163 -2
- package/dist/gobj-ui.cjs.js +950 -179
- package/dist/gobj-ui.es.js +951 -180
- package/package.json +1 -1
- package/src/c_yui_gobj_tree_js.js +435 -245
- package/src/gclass_describe.js +234 -0
- package/src/gclass_describe.test.js +125 -0
- package/src/gobj_tree_model.js +436 -0
- package/src/gobj_tree_model.test.js +184 -0
- package/src/yui_gclass_view.js +277 -0
|
@@ -26,22 +26,33 @@ import {
|
|
|
26
26
|
kw_set_local_storage_value,
|
|
27
27
|
gobj_write_str_attr,
|
|
28
28
|
gobj_read_str_attr,
|
|
29
|
-
gobj_publish_event,
|
|
30
29
|
gobj_yuno,
|
|
31
|
-
|
|
30
|
+
gobj_yuno_name,
|
|
32
31
|
gobj_short_name,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
gobj_is_running,
|
|
36
|
-
gobj_is_playing,
|
|
37
|
-
gobj_is_service,
|
|
38
|
-
gobj_is_pure_child,
|
|
39
|
-
gobj_is_volatil,
|
|
32
|
+
gobj_is_destroying,
|
|
33
|
+
gobj_unsubscribe_event,
|
|
40
34
|
createElement2,
|
|
41
35
|
escapeHtml,
|
|
36
|
+
log_warning,
|
|
42
37
|
refresh_language,
|
|
43
38
|
} from "@yuneta/gobj-js";
|
|
44
39
|
|
|
40
|
+
import {
|
|
41
|
+
describe_js_gobj,
|
|
42
|
+
node_badge_keys,
|
|
43
|
+
node_info_rows,
|
|
44
|
+
node_labels,
|
|
45
|
+
node_role,
|
|
46
|
+
node_status,
|
|
47
|
+
node_status_key,
|
|
48
|
+
} from "./gobj_tree_model.js";
|
|
49
|
+
import {
|
|
50
|
+
close_gclass_view,
|
|
51
|
+
gclass_view_available,
|
|
52
|
+
open_gclass_view,
|
|
53
|
+
} from "./yui_gclass_view.js";
|
|
54
|
+
import {yui_shell_of} from "./c_yui_shell.js";
|
|
55
|
+
|
|
45
56
|
import {yui_toolbar} from "./yui_toolbar.js";
|
|
46
57
|
import {
|
|
47
58
|
yui_graph_camera_items,
|
|
@@ -249,6 +260,9 @@ let PRIVATE_DATA = {
|
|
|
249
260
|
$popover: null,
|
|
250
261
|
$popover_title: null,
|
|
251
262
|
$popover_body: null,
|
|
263
|
+
$popover_gclass: null, // the "gclass" button, or null when unavailable
|
|
264
|
+
popover_node: null, // the descriptor the popover is showing
|
|
265
|
+
gclass_view: null, // handle of the open gclass window (or null)
|
|
252
266
|
node_by_id: null, // map of node_id -> node_data (for popover)
|
|
253
267
|
collapse_state: null, // map of full_name -> "collapsed" | "expanded"
|
|
254
268
|
resize_observer: null, // ResizeObserver on the canvas mount element
|
|
@@ -274,20 +288,46 @@ let __gclass__ = null;
|
|
|
274
288
|
let __layout_registered__ = false;
|
|
275
289
|
|
|
276
290
|
/***************************************************************
|
|
277
|
-
* Node
|
|
291
|
+
* Node colours by role.
|
|
292
|
+
*
|
|
293
|
+
* Saturated hues, one family apart from the next, and the same
|
|
294
|
+
* five everywhere the reader meets a gobj. `stroke` draws the
|
|
295
|
+
* border and the edge; `accent` is the brighter sibling used
|
|
296
|
+
* for the bar down the left of the card and the toggle badge,
|
|
297
|
+
* so a card carries its colour twice at two intensities and is
|
|
298
|
+
* readable at the zoom where the text is not.
|
|
278
299
|
***************************************************************/
|
|
279
300
|
const ROLE_COLORS = {
|
|
280
|
-
yuno: {
|
|
281
|
-
service: {
|
|
282
|
-
pure: {
|
|
283
|
-
volatil: {
|
|
284
|
-
|
|
301
|
+
yuno: {stroke: "#0284c7", accent: "#38bdf8"}, // sky
|
|
302
|
+
service: {stroke: "#059669", accent: "#34d399"}, // emerald
|
|
303
|
+
pure: {stroke: "#d97706", accent: "#fbbf24"}, // amber
|
|
304
|
+
volatil: {stroke: "#db2777", accent: "#f472b6"}, // pink
|
|
305
|
+
child: {stroke: "#7c3aed", accent: "#a78bfa"}, // violet
|
|
285
306
|
};
|
|
286
307
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
308
|
+
/***************************************************************
|
|
309
|
+
* Status colours: what the gobj is DOING.
|
|
310
|
+
*
|
|
311
|
+
* A pill and not a word: three states of one gobj are told
|
|
312
|
+
* apart across a whole tree by colour long before anybody
|
|
313
|
+
* reads them, and the dot alone never said which was which.
|
|
314
|
+
* `disabled` is a fourth entry and not a status -- a disabled
|
|
315
|
+
* gobj is still stopped or running -- but it takes the pill,
|
|
316
|
+
* because it is the fact that explains the other one.
|
|
317
|
+
***************************************************************/
|
|
318
|
+
const STATUS_COLORS = {
|
|
319
|
+
light: {
|
|
320
|
+
playing: {fg: "#065f46", bg: "#a7f3d0", dot: "#10b981"},
|
|
321
|
+
running: {fg: "#92400e", bg: "#fde68a", dot: "#f59e0b"},
|
|
322
|
+
stopped: {fg: "#991b1b", bg: "#fecaca", dot: "#ef4444"},
|
|
323
|
+
disabled: {fg: "#334155", bg: "#e2e8f0", dot: "#94a3b8"},
|
|
324
|
+
},
|
|
325
|
+
dark: {
|
|
326
|
+
playing: {fg: "#6ee7b7", bg: "rgba(16,185,129,0.28)", dot: "#34d399"},
|
|
327
|
+
running: {fg: "#fcd34d", bg: "rgba(245,158,11,0.28)", dot: "#fbbf24"},
|
|
328
|
+
stopped: {fg: "#fca5a5", bg: "rgba(239,68,68,0.28)", dot: "#f87171"},
|
|
329
|
+
disabled: {fg: "#cbd5e1", bg: "rgba(148,163,184,0.28)", dot: "#94a3b8"},
|
|
330
|
+
},
|
|
291
331
|
};
|
|
292
332
|
|
|
293
333
|
/***************************************************************
|
|
@@ -297,9 +337,9 @@ const STATE_COLORS = {
|
|
|
297
337
|
***************************************************************/
|
|
298
338
|
const NODE_SIZES = {
|
|
299
339
|
full: {
|
|
300
|
-
yuno: {w:
|
|
301
|
-
service: {w:
|
|
302
|
-
child: {w:
|
|
340
|
+
yuno: {w: 260, h: 96},
|
|
341
|
+
service: {w: 232, h: 92},
|
|
342
|
+
child: {w: 196, h: 86},
|
|
303
343
|
},
|
|
304
344
|
compact: {
|
|
305
345
|
yuno: {w: 210, h: 30},
|
|
@@ -316,45 +356,64 @@ const GT_FONT =
|
|
|
316
356
|
"Helvetica, Arial, sans-serif";
|
|
317
357
|
|
|
318
358
|
/***************************************************************
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
359
|
+
* Theme-aware card palette derived from the role colour.
|
|
360
|
+
*
|
|
361
|
+
* Brighter than a tint: at 9% of the role colour every card
|
|
362
|
+
* was the same near-white rectangle and the tree read as a
|
|
363
|
+
* wireframe. The fill carries enough of the hue to be named
|
|
364
|
+
* from across the canvas, and the border and the accent bar
|
|
365
|
+
* carry the rest.
|
|
322
366
|
***************************************************************/
|
|
323
|
-
function role_card_style(
|
|
367
|
+
function role_card_style(colors, dark)
|
|
324
368
|
{
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
369
|
+
let stroke = colors.stroke;
|
|
370
|
+
let accent = colors.accent;
|
|
371
|
+
|
|
328
372
|
return {
|
|
329
373
|
bg: dark
|
|
330
|
-
? `color-mix(in srgb, ${stroke}
|
|
331
|
-
: `color-mix(in srgb, ${stroke}
|
|
332
|
-
border: dark
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
sub: dark ? "#9aa4b2" : "#64748b",
|
|
374
|
+
? `color-mix(in srgb, ${stroke} 34%, #253044)`
|
|
375
|
+
: `color-mix(in srgb, ${stroke} 13%, #ffffff)`,
|
|
376
|
+
border: dark? accent: stroke,
|
|
377
|
+
accent: dark? accent: stroke,
|
|
378
|
+
title: dark ? "#f1f5f9" : "#0f172a",
|
|
379
|
+
sub: dark ? "#a8b3c2" : "#475569",
|
|
337
380
|
tagbg: dark
|
|
338
|
-
? `color-mix(in srgb, ${stroke}
|
|
339
|
-
: `color-mix(in srgb, ${stroke}
|
|
381
|
+
? `color-mix(in srgb, ${stroke} 48%, #253044)`
|
|
382
|
+
: `color-mix(in srgb, ${stroke} 20%, #ffffff)`,
|
|
383
|
+
tagfg: dark? "#f1f5f9": stroke,
|
|
340
384
|
shadow: dark
|
|
341
|
-
? "0
|
|
342
|
-
: "0
|
|
385
|
+
? "0 2px 6px rgba(0,0,0,0.50)"
|
|
386
|
+
: "0 2px 5px rgba(15,23,42,0.16)",
|
|
343
387
|
};
|
|
344
388
|
}
|
|
345
389
|
|
|
346
390
|
/***************************************************************
|
|
347
|
-
*
|
|
391
|
+
* The pill colours of a node, for the theme in force.
|
|
348
392
|
***************************************************************/
|
|
349
|
-
function
|
|
393
|
+
function status_card_style(d, dark)
|
|
350
394
|
{
|
|
351
|
-
|
|
352
|
-
|
|
395
|
+
let table = dark? STATUS_COLORS.dark: STATUS_COLORS.light;
|
|
396
|
+
if(d.disabled) {
|
|
397
|
+
return table.disabled;
|
|
353
398
|
}
|
|
354
|
-
|
|
355
|
-
|
|
399
|
+
return table[node_status(d)] || table.stopped;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/***************************************************************
|
|
403
|
+
* Map a node to its visual size class. Services are drawn
|
|
404
|
+
* bigger than plain children so the service boundary of the
|
|
405
|
+
* hierarchy is visible before anything is read.
|
|
406
|
+
***************************************************************/
|
|
407
|
+
function get_node_category(d)
|
|
408
|
+
{
|
|
409
|
+
switch(node_role(d)) {
|
|
410
|
+
case "yuno":
|
|
411
|
+
return "yuno";
|
|
412
|
+
case "service":
|
|
413
|
+
return "service";
|
|
414
|
+
default:
|
|
415
|
+
return "child";
|
|
356
416
|
}
|
|
357
|
-
return "child";
|
|
358
417
|
}
|
|
359
418
|
|
|
360
419
|
/***************************************************************
|
|
@@ -498,6 +557,17 @@ function mt_start(gobj)
|
|
|
498
557
|
*/
|
|
499
558
|
restore_view_shape(gobj);
|
|
500
559
|
|
|
560
|
+
/* The cards carry translated text now (the status pill, the
|
|
561
|
+
* badges), and G6 draws them as innerHTML inside its own
|
|
562
|
+
* canvas: refresh_language() cannot reach in there, so a
|
|
563
|
+
* language switch is a REBUILD. Subscribe to the shell
|
|
564
|
+
* directly, like C_YUI_PERIOD -- a host that mounts a bare
|
|
565
|
+
* tree gets the repaint without having to forward anything. */
|
|
566
|
+
let shell = yui_shell_of(gobj);
|
|
567
|
+
if(shell) {
|
|
568
|
+
gobj_subscribe_event(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
|
|
569
|
+
}
|
|
570
|
+
|
|
501
571
|
build_graph(gobj);
|
|
502
572
|
load_tree(gobj);
|
|
503
573
|
}
|
|
@@ -507,6 +577,18 @@ function mt_start(gobj)
|
|
|
507
577
|
***************************************************************/
|
|
508
578
|
function mt_stop(gobj)
|
|
509
579
|
{
|
|
580
|
+
let priv = gobj.priv;
|
|
581
|
+
|
|
582
|
+
let shell = yui_shell_of(gobj);
|
|
583
|
+
if(shell) {
|
|
584
|
+
gobj_unsubscribe_event(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/* In mt_stop and NOT in mt_destroy: gobj_destroy() destroys the
|
|
588
|
+
* children BEFORE calling mt_destroy, so a hosted gobj retired
|
|
589
|
+
* there is retired after the framework already took it down. */
|
|
590
|
+
close_gclass_view(priv.gclass_view);
|
|
591
|
+
priv.gclass_view = null;
|
|
510
592
|
}
|
|
511
593
|
|
|
512
594
|
/***************************************************************
|
|
@@ -539,6 +621,8 @@ function mt_destroy(gobj)
|
|
|
539
621
|
priv.$popover = null;
|
|
540
622
|
priv.$popover_title = null;
|
|
541
623
|
priv.$popover_body = null;
|
|
624
|
+
priv.$popover_gclass = null;
|
|
625
|
+
priv.popover_node = null;
|
|
542
626
|
priv.$layout_select = null;
|
|
543
627
|
priv.node_by_id = null;
|
|
544
628
|
priv.collapse_state = null;
|
|
@@ -657,6 +741,42 @@ function build_popover(gobj, $container)
|
|
|
657
741
|
`;
|
|
658
742
|
$header.appendChild($title);
|
|
659
743
|
|
|
744
|
+
/* What this gobj IS: its gclass, opened in the gclass viewer.
|
|
745
|
+
*
|
|
746
|
+
* Built only where the app registers C_YUI_JSON -- the viewer
|
|
747
|
+
* the window hosts. An app that does not carry it gets no
|
|
748
|
+
* button rather than a button that fails, and the reason is
|
|
749
|
+
* logged once, at build time. */
|
|
750
|
+
if(gclass_view_available()) {
|
|
751
|
+
let $gclass = document.createElement("button");
|
|
752
|
+
$gclass.type = "button";
|
|
753
|
+
$gclass.className = "GOBJ_TREE_POPOVER_GCLASS button is-small";
|
|
754
|
+
$gclass.style.cssText = `
|
|
755
|
+
margin-right: 6px;
|
|
756
|
+
padding: 0 8px;
|
|
757
|
+
height: 22px;
|
|
758
|
+
font-size: 11px;
|
|
759
|
+
white-space: nowrap;
|
|
760
|
+
`;
|
|
761
|
+
$gclass.textContent = t("gclass");
|
|
762
|
+
$gclass.setAttribute("i18n", "gclass");
|
|
763
|
+
$gclass.title = t("view gclass");
|
|
764
|
+
$gclass.setAttribute("data-i18n-title", "view gclass");
|
|
765
|
+
$gclass.setAttribute("aria-label", t("view gclass"));
|
|
766
|
+
$gclass.setAttribute("data-i18n-aria-label", "view gclass");
|
|
767
|
+
$gclass.addEventListener("click", (evt) => {
|
|
768
|
+
evt.stopPropagation();
|
|
769
|
+
gobj_send_event(gobj, "EV_OPEN_GCLASS", {}, gobj);
|
|
770
|
+
});
|
|
771
|
+
$header.appendChild($gclass);
|
|
772
|
+
priv.$popover_gclass = $gclass;
|
|
773
|
+
} else {
|
|
774
|
+
log_warning(
|
|
775
|
+
`${gobj_short_name(gobj)}: no C_YUI_JSON registered, ` +
|
|
776
|
+
`the gclass viewer is not offered`
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
660
780
|
let $close = document.createElement("button");
|
|
661
781
|
$close.type = "button";
|
|
662
782
|
/* Named so a stylesheet can reach it: the size a FINGER needs is a
|
|
@@ -917,37 +1037,26 @@ function apply_layout(gobj)
|
|
|
917
1037
|
}
|
|
918
1038
|
|
|
919
1039
|
/************************************************************
|
|
920
|
-
*
|
|
1040
|
+
* The role colours of a node.
|
|
1041
|
+
*
|
|
1042
|
+
* Note the ORDER: volatil before service. A volatil gobj that
|
|
1043
|
+
* is also a service is drawn volatil, because that is the fact
|
|
1044
|
+
* that decides how long it lives.
|
|
921
1045
|
************************************************************/
|
|
922
|
-
function get_role_colors(
|
|
1046
|
+
function get_role_colors(d)
|
|
923
1047
|
{
|
|
924
|
-
|
|
925
|
-
|
|
1048
|
+
switch(node_role(d)) {
|
|
1049
|
+
case "yuno":
|
|
1050
|
+
return ROLE_COLORS.yuno;
|
|
1051
|
+
case "volatil":
|
|
1052
|
+
return ROLE_COLORS.volatil;
|
|
1053
|
+
case "service":
|
|
1054
|
+
return ROLE_COLORS.service;
|
|
1055
|
+
case "pure":
|
|
1056
|
+
return ROLE_COLORS.pure;
|
|
1057
|
+
default:
|
|
1058
|
+
return ROLE_COLORS.child;
|
|
926
1059
|
}
|
|
927
|
-
if(gobj_is_volatil(target_gobj)) {
|
|
928
|
-
return ROLE_COLORS.volatil;
|
|
929
|
-
}
|
|
930
|
-
if(gobj_is_service(target_gobj)) {
|
|
931
|
-
return ROLE_COLORS.service;
|
|
932
|
-
}
|
|
933
|
-
if(gobj_is_pure_child(target_gobj)) {
|
|
934
|
-
return ROLE_COLORS.pure;
|
|
935
|
-
}
|
|
936
|
-
return ROLE_COLORS.default;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
/************************************************************
|
|
940
|
-
* Resolve state color
|
|
941
|
-
************************************************************/
|
|
942
|
-
function get_state_color(target_gobj)
|
|
943
|
-
{
|
|
944
|
-
if(!gobj_is_running(target_gobj)) {
|
|
945
|
-
return STATE_COLORS.stopped;
|
|
946
|
-
}
|
|
947
|
-
if(gobj_is_playing(target_gobj)) {
|
|
948
|
-
return STATE_COLORS.running_playing;
|
|
949
|
-
}
|
|
950
|
-
return STATE_COLORS.running;
|
|
951
1060
|
}
|
|
952
1061
|
|
|
953
1062
|
/************************************************************
|
|
@@ -994,27 +1103,27 @@ function is_node_collapsed(gobj, full_name, num_children)
|
|
|
994
1103
|
************************************************************/
|
|
995
1104
|
function render_toggle_html(node_id, collapsed, num_children, cs)
|
|
996
1105
|
{
|
|
997
|
-
let label = collapsed ? ("+" + num_children) : "
|
|
998
|
-
let title = collapsed ? "
|
|
1106
|
+
let label = collapsed ? ("+" + num_children) : "\u2212";
|
|
1107
|
+
let title = collapsed ? t("expand children") : t("collapse children");
|
|
999
1108
|
return `<span
|
|
1000
1109
|
data-gobj-toggle="true"
|
|
1001
1110
|
data-node-id="${node_id}"
|
|
1002
|
-
title="${title}"
|
|
1111
|
+
title="${escapeHtml(title)}"
|
|
1003
1112
|
style="
|
|
1004
1113
|
flex: 0 0 auto;
|
|
1005
1114
|
display: inline-flex;
|
|
1006
1115
|
align-items: center;
|
|
1007
1116
|
justify-content: center;
|
|
1008
|
-
min-width:
|
|
1009
|
-
height:
|
|
1010
|
-
padding: 0
|
|
1117
|
+
min-width: 24px;
|
|
1118
|
+
height: 18px;
|
|
1119
|
+
padding: 0 6px;
|
|
1011
1120
|
margin-left: 4px;
|
|
1012
|
-
background: ${cs.
|
|
1013
|
-
color:
|
|
1014
|
-
border: 1px solid ${cs.
|
|
1015
|
-
border-radius:
|
|
1121
|
+
background: ${cs.accent};
|
|
1122
|
+
color: #ffffff;
|
|
1123
|
+
border: 1px solid ${cs.accent};
|
|
1124
|
+
border-radius: 9px;
|
|
1016
1125
|
font-size: 10px;
|
|
1017
|
-
font-weight:
|
|
1126
|
+
font-weight: 700;
|
|
1018
1127
|
line-height: 1;
|
|
1019
1128
|
cursor: pointer;
|
|
1020
1129
|
user-select: none;
|
|
@@ -1022,27 +1131,92 @@ function render_toggle_html(node_id, collapsed, num_children, cs)
|
|
|
1022
1131
|
}
|
|
1023
1132
|
|
|
1024
1133
|
/************************************************************
|
|
1025
|
-
*
|
|
1134
|
+
* HTML for the status pill: what the gobj is DOING.
|
|
1135
|
+
*
|
|
1136
|
+
* A dot plus a word, and the word is dropped in the compact
|
|
1137
|
+
* card where there is no room for it -- the colour still
|
|
1138
|
+
* carries it, and the `title` says it in full either way.
|
|
1026
1139
|
************************************************************/
|
|
1027
|
-
function
|
|
1140
|
+
function render_status_html(d, ss, labels, with_label)
|
|
1141
|
+
{
|
|
1142
|
+
let full = d.disabled?
|
|
1143
|
+
labels.field.disabled:
|
|
1144
|
+
(labels.status[node_status_key(d)] || node_status_key(d));
|
|
1145
|
+
let label = d.disabled?
|
|
1146
|
+
labels.field.disabled:
|
|
1147
|
+
(labels.status_short[node_status(d)] || node_status(d));
|
|
1148
|
+
|
|
1149
|
+
if(!with_label) {
|
|
1150
|
+
return `<span title="${escapeHtml(full)}" style="
|
|
1151
|
+
flex: 0 0 auto;
|
|
1152
|
+
width: 10px; height: 10px; border-radius: 50%;
|
|
1153
|
+
background: ${ss.dot};
|
|
1154
|
+
box-shadow: 0 0 0 2px ${ss.bg};
|
|
1155
|
+
margin-left: 6px;
|
|
1156
|
+
"></span>`;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
return `<span title="${escapeHtml(full)}" style="
|
|
1160
|
+
flex: 0 0 auto;
|
|
1161
|
+
display: inline-flex;
|
|
1162
|
+
align-items: center;
|
|
1163
|
+
gap: 4px;
|
|
1164
|
+
padding: 1px 7px 1px 5px;
|
|
1165
|
+
background: ${ss.bg};
|
|
1166
|
+
color: ${ss.fg};
|
|
1167
|
+
border-radius: 9px;
|
|
1168
|
+
font-size: 10px;
|
|
1169
|
+
font-weight: 700;
|
|
1170
|
+
line-height: 1.45;
|
|
1171
|
+
white-space: nowrap;
|
|
1172
|
+
"><span style="width:7px; height:7px; border-radius:50%; background:${ss.dot};"></span>${escapeHtml(label)}</span>`;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/************************************************************
|
|
1176
|
+
* HTML for the badges: what the gobj IS, besides its colour.
|
|
1177
|
+
************************************************************/
|
|
1178
|
+
function render_badges_html(d, cs, labels)
|
|
1179
|
+
{
|
|
1180
|
+
let html = "";
|
|
1181
|
+
for(let key of node_badge_keys(d)) {
|
|
1182
|
+
let label = labels.badge[key] || key;
|
|
1183
|
+
html += `<span style="
|
|
1184
|
+
font-size: 10px;
|
|
1185
|
+
font-weight: 600;
|
|
1186
|
+
padding: 0 6px;
|
|
1187
|
+
margin-right: 4px;
|
|
1188
|
+
background: ${cs.tagbg};
|
|
1189
|
+
color: ${cs.tagfg};
|
|
1190
|
+
border: 1px solid ${cs.accent};
|
|
1191
|
+
border-radius: 9px;
|
|
1192
|
+
white-space: nowrap;
|
|
1193
|
+
">${escapeHtml(label)}</span>`;
|
|
1194
|
+
}
|
|
1195
|
+
return html;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
/************************************************************
|
|
1199
|
+
* Recursively build nodes and edges from a NODE DESCRIPTOR
|
|
1200
|
+
* tree (gobj_tree_model.js), never from gobjs: the same
|
|
1201
|
+
* builder draws the browser yuno and a backend one.
|
|
1202
|
+
*
|
|
1203
|
+
* The descriptor is also what the popover reads, so the view
|
|
1204
|
+
* fields it needs are written onto it here (`full_name`,
|
|
1205
|
+
* `short_name`, `num_children`, `is_collapsed`,
|
|
1206
|
+
* `direct_children`) instead of copied into a second object
|
|
1207
|
+
* that can then disagree with the first.
|
|
1208
|
+
************************************************************/
|
|
1209
|
+
function build_gobj_nodes(gobj, d, nodes, edges, parent_id, compact, labels)
|
|
1028
1210
|
{
|
|
1029
1211
|
let priv = gobj.priv;
|
|
1030
1212
|
let node_id = gen_node_id(gobj);
|
|
1031
1213
|
|
|
1032
|
-
let gclass_name = gobj_gclass_name(target_gobj) || "";
|
|
1033
|
-
let instance_name = gobj_name(target_gobj) || "";
|
|
1034
|
-
let state = gobj_current_state(target_gobj) || "";
|
|
1035
|
-
let full_name = gobj_full_name(target_gobj);
|
|
1036
|
-
let colors = get_role_colors(target_gobj, is_root);
|
|
1037
1214
|
let dark = yui_is_dark();
|
|
1038
|
-
let
|
|
1039
|
-
let
|
|
1040
|
-
|
|
1041
|
-
let running = gobj_is_running(target_gobj);
|
|
1042
|
-
let playing = gobj_is_playing(target_gobj);
|
|
1043
|
-
let status_text = !running ? "stopped" : (playing ? "playing" : "running");
|
|
1215
|
+
let colors = get_role_colors(d);
|
|
1216
|
+
let cs = role_card_style(colors, dark);
|
|
1217
|
+
let ss = status_card_style(d, dark);
|
|
1044
1218
|
|
|
1045
|
-
let category = get_node_category(
|
|
1219
|
+
let category = get_node_category(d);
|
|
1046
1220
|
let size = NODE_SIZES[compact ? "compact" : "full"][category];
|
|
1047
1221
|
let width = size.w;
|
|
1048
1222
|
let height = size.h;
|
|
@@ -1053,10 +1227,15 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1053
1227
|
*/
|
|
1054
1228
|
let border_width = (category === "yuno" || category === "service") ? 2 : 1;
|
|
1055
1229
|
|
|
1056
|
-
|
|
1230
|
+
/* A disabled gobj is drawn as one: a dashed border says
|
|
1231
|
+
* "this branch is out of the game" at any zoom. */
|
|
1232
|
+
let border_style = d.disabled? "dashed": "solid";
|
|
1233
|
+
|
|
1234
|
+
let children = d.children || [];
|
|
1057
1235
|
let num_children = children.length;
|
|
1058
1236
|
let has_children = num_children > 0;
|
|
1059
|
-
let collapsed = has_children &&
|
|
1237
|
+
let collapsed = has_children &&
|
|
1238
|
+
is_node_collapsed(gobj, d.fullname, num_children);
|
|
1060
1239
|
|
|
1061
1240
|
/*
|
|
1062
1241
|
* Direct-children index (collected even when this node is
|
|
@@ -1066,33 +1245,41 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1066
1245
|
let direct_children = [];
|
|
1067
1246
|
for(let child of children) {
|
|
1068
1247
|
direct_children.push({
|
|
1069
|
-
full_name:
|
|
1070
|
-
has_grandchildren: (child.
|
|
1248
|
+
full_name: child.fullname,
|
|
1249
|
+
has_grandchildren: (child.children || []).length > 0,
|
|
1071
1250
|
});
|
|
1072
1251
|
}
|
|
1073
1252
|
|
|
1253
|
+
/* What the VIEW needs on top of the descriptor. Written onto
|
|
1254
|
+
* it, because the popover reads the same object. */
|
|
1255
|
+
d.full_name = d.fullname;
|
|
1256
|
+
d.short_name = d.shortname;
|
|
1257
|
+
d.num_children = num_children;
|
|
1258
|
+
d.is_collapsed = collapsed;
|
|
1259
|
+
d.direct_children = direct_children;
|
|
1260
|
+
d.node_id = node_id;
|
|
1261
|
+
|
|
1074
1262
|
let toggle_html = has_children
|
|
1075
1263
|
? render_toggle_html(node_id, collapsed, num_children, cs)
|
|
1076
1264
|
: "";
|
|
1077
1265
|
|
|
1266
|
+
let card_title = d.gclass + (d.name? ("^" + d.name): "");
|
|
1078
1267
|
let node_html;
|
|
1079
1268
|
|
|
1080
1269
|
if(compact) {
|
|
1081
1270
|
/*
|
|
1082
|
-
* Compact one-line node: "gclass^name" with a left
|
|
1083
|
-
*
|
|
1271
|
+
* Compact one-line node: "gclass^name" with a left colour bar
|
|
1272
|
+
* and the status dot. Name truncates with "...".
|
|
1084
1273
|
*/
|
|
1085
|
-
let compact_label = escapeHtml(
|
|
1086
|
-
instance_name ? (gclass_name + "^" + instance_name) : gclass_name
|
|
1087
|
-
);
|
|
1274
|
+
let compact_label = escapeHtml(card_title);
|
|
1088
1275
|
|
|
1089
1276
|
node_html = `
|
|
1090
|
-
<div class="GOBJ_CARD" data-gobj-name="${escapeHtml(
|
|
1277
|
+
<div class="GOBJ_CARD" data-gobj-name="${escapeHtml(d.fullname)}" style="
|
|
1091
1278
|
width: ${width}px;
|
|
1092
1279
|
height: ${height}px;
|
|
1093
1280
|
background: ${cs.bg};
|
|
1094
|
-
border: ${border_width}px
|
|
1095
|
-
border-left:
|
|
1281
|
+
border: ${border_width}px ${border_style} ${cs.border};
|
|
1282
|
+
border-left: 6px solid ${cs.accent};
|
|
1096
1283
|
border-radius: 7px;
|
|
1097
1284
|
box-shadow: ${cs.shadow};
|
|
1098
1285
|
font-family: ${GT_FONT};
|
|
@@ -1101,49 +1288,30 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1101
1288
|
padding: 0 8px;
|
|
1102
1289
|
box-sizing: border-box;
|
|
1103
1290
|
overflow: hidden;
|
|
1291
|
+
opacity: ${d.disabled? "0.72": "1"};
|
|
1104
1292
|
cursor: pointer;
|
|
1105
1293
|
" title="${compact_label}">
|
|
1106
1294
|
<span style="flex:1 1 auto; min-width:0; font-size:11px; font-weight:600; color:${cs.title}; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${compact_label}</span>
|
|
1107
|
-
|
|
1108
|
-
flex: 0 0 auto;
|
|
1109
|
-
width: 8px; height: 8px; border-radius: 50%;
|
|
1110
|
-
background: ${state_color}; margin-left: 6px;
|
|
1111
|
-
"></span>
|
|
1295
|
+
${render_status_html(d, ss, labels, false)}
|
|
1112
1296
|
${toggle_html}
|
|
1113
1297
|
</div>`;
|
|
1114
1298
|
} else {
|
|
1115
1299
|
/*
|
|
1116
|
-
* Full node: header (gclass + optional toggle), name,
|
|
1117
|
-
* All rows truncate with "..."
|
|
1300
|
+
* Full node: header (gclass + optional toggle), name, state,
|
|
1301
|
+
* badges + status pill. All rows truncate with "..." -- the
|
|
1302
|
+
* width is fixed.
|
|
1118
1303
|
*/
|
|
1119
|
-
let header_label = escapeHtml(
|
|
1120
|
-
let name_label = escapeHtml(
|
|
1121
|
-
let state_label = escapeHtml(state);
|
|
1122
|
-
|
|
1123
|
-
let badges = [];
|
|
1124
|
-
if(is_root) {
|
|
1125
|
-
badges.push("yuno");
|
|
1126
|
-
} else {
|
|
1127
|
-
if(gobj_is_service(target_gobj)) {
|
|
1128
|
-
badges.push("service");
|
|
1129
|
-
}
|
|
1130
|
-
if(gobj_is_pure_child(target_gobj)) {
|
|
1131
|
-
badges.push("pure");
|
|
1132
|
-
}
|
|
1133
|
-
if(gobj_is_volatil(target_gobj)) {
|
|
1134
|
-
badges.push("volatil");
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
let badges_html = badges.map(b =>
|
|
1138
|
-
`<span style="font-size:10px; padding:0 5px; margin-right:4px; background:${cs.tagbg}; color:${cs.border}; border:1px solid ${cs.border}; border-radius:6px; white-space:nowrap;">${b}</span>`
|
|
1139
|
-
).join("");
|
|
1304
|
+
let header_label = escapeHtml(d.gclass);
|
|
1305
|
+
let name_label = escapeHtml(d.name);
|
|
1306
|
+
let state_label = escapeHtml(d.state);
|
|
1140
1307
|
|
|
1141
1308
|
node_html = `
|
|
1142
|
-
<div class="GOBJ_CARD" data-gobj-name="${escapeHtml(
|
|
1309
|
+
<div class="GOBJ_CARD" data-gobj-name="${escapeHtml(d.fullname)}" style="
|
|
1143
1310
|
width: ${width}px;
|
|
1144
1311
|
height: ${height}px;
|
|
1145
1312
|
background: ${cs.bg};
|
|
1146
|
-
border: ${border_width}px
|
|
1313
|
+
border: ${border_width}px ${border_style} ${cs.border};
|
|
1314
|
+
border-left: 6px solid ${cs.accent};
|
|
1147
1315
|
border-radius: 9px;
|
|
1148
1316
|
box-shadow: ${cs.shadow};
|
|
1149
1317
|
overflow: hidden;
|
|
@@ -1152,59 +1320,33 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1152
1320
|
display: flex;
|
|
1153
1321
|
flex-direction: column;
|
|
1154
1322
|
justify-content: center;
|
|
1155
|
-
padding:
|
|
1156
|
-
gap:
|
|
1323
|
+
padding: 6px 9px;
|
|
1324
|
+
gap: 2px;
|
|
1325
|
+
opacity: ${d.disabled? "0.72": "1"};
|
|
1157
1326
|
cursor: pointer;
|
|
1158
|
-
" title="${escapeHtml(
|
|
1327
|
+
" title="${escapeHtml(card_title)}">
|
|
1159
1328
|
<div style="flex:0 0 auto; display:flex; align-items:center; gap:6px; overflow:hidden;">
|
|
1160
|
-
<span style="flex:1 1 auto; min-width:0; font-size:12px; line-height:1.25; font-weight:
|
|
1329
|
+
<span style="flex:1 1 auto; min-width:0; font-size:12px; line-height:1.25; font-weight:700; color:${cs.title}; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${header_label}</span>
|
|
1161
1330
|
${toggle_html}
|
|
1162
1331
|
</div>
|
|
1163
1332
|
<div style="flex:0 0 auto; font-size:12px; line-height:1.25; color:${cs.sub}; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${name_label || " "}</div>
|
|
1164
1333
|
<div style="flex:0 0 auto; display:flex; justify-content:space-between; align-items:center; gap:6px; overflow:hidden;">
|
|
1165
|
-
<span style="flex:1 1 auto; min-width:0;
|
|
1166
|
-
|
|
1167
|
-
<span style="width:7px; height:7px; border-radius:50%; background:${state_color};"></span>${state_label || status_text}
|
|
1168
|
-
</span>
|
|
1334
|
+
<span style="flex:1 1 auto; min-width:0; font-size:10px; line-height:1.25; color:${cs.sub}; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${state_label || " "}</span>
|
|
1335
|
+
${render_status_html(d, ss, labels, true)}
|
|
1169
1336
|
</div>
|
|
1337
|
+
<div style="flex:0 0 auto; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;">${render_badges_html(d, cs, labels)}</div>
|
|
1170
1338
|
</div>`;
|
|
1171
1339
|
}
|
|
1172
1340
|
|
|
1173
|
-
let parent_gobj = gobj_parent(target_gobj);
|
|
1174
|
-
|
|
1175
|
-
/*
|
|
1176
|
-
* Node data kept for the popover. Extend this object when
|
|
1177
|
-
* you want to surface more attributes — then add a row in
|
|
1178
|
-
* get_node_info_rows() with the same key.
|
|
1179
|
-
*/
|
|
1180
|
-
let node_data = {
|
|
1181
|
-
gclass: gclass_name,
|
|
1182
|
-
name: instance_name,
|
|
1183
|
-
short_name: gobj_short_name(target_gobj),
|
|
1184
|
-
full_name: full_name,
|
|
1185
|
-
parent_short_name: parent_gobj ? gobj_short_name(parent_gobj) : "",
|
|
1186
|
-
state: state,
|
|
1187
|
-
category: category,
|
|
1188
|
-
is_yuno: is_root,
|
|
1189
|
-
is_service: gobj_is_service(target_gobj),
|
|
1190
|
-
is_pure_child: gobj_is_pure_child(target_gobj),
|
|
1191
|
-
is_volatil: gobj_is_volatil(target_gobj),
|
|
1192
|
-
is_running: running,
|
|
1193
|
-
is_playing: playing,
|
|
1194
|
-
num_children: num_children,
|
|
1195
|
-
is_collapsed: collapsed,
|
|
1196
|
-
direct_children: direct_children,
|
|
1197
|
-
};
|
|
1198
|
-
|
|
1199
1341
|
if(!priv.node_by_id) {
|
|
1200
1342
|
priv.node_by_id = {};
|
|
1201
1343
|
}
|
|
1202
|
-
priv.node_by_id[node_id] =
|
|
1344
|
+
priv.node_by_id[node_id] = d;
|
|
1203
1345
|
|
|
1204
1346
|
let node = {
|
|
1205
1347
|
id: node_id,
|
|
1206
1348
|
type: 'html',
|
|
1207
|
-
data:
|
|
1349
|
+
data: d,
|
|
1208
1350
|
style: {
|
|
1209
1351
|
innerHTML: node_html,
|
|
1210
1352
|
size: [width, height],
|
|
@@ -1219,8 +1361,8 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1219
1361
|
source: parent_id,
|
|
1220
1362
|
target: node_id,
|
|
1221
1363
|
style: {
|
|
1222
|
-
stroke: colors.
|
|
1223
|
-
lineWidth: 1,
|
|
1364
|
+
stroke: colors.accent,
|
|
1365
|
+
lineWidth: 1.4,
|
|
1224
1366
|
}
|
|
1225
1367
|
});
|
|
1226
1368
|
}
|
|
@@ -1230,7 +1372,7 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1230
1372
|
*/
|
|
1231
1373
|
if(!collapsed) {
|
|
1232
1374
|
for(let child of children) {
|
|
1233
|
-
build_gobj_nodes(gobj, child, nodes, edges, node_id,
|
|
1375
|
+
build_gobj_nodes(gobj, child, nodes, edges, node_id, compact, labels);
|
|
1234
1376
|
}
|
|
1235
1377
|
}
|
|
1236
1378
|
|
|
@@ -1256,12 +1398,23 @@ function load_tree(gobj)
|
|
|
1256
1398
|
return;
|
|
1257
1399
|
}
|
|
1258
1400
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1401
|
+
/*
|
|
1402
|
+
* The tree, as data.
|
|
1403
|
+
*
|
|
1404
|
+
* The graph draws DESCRIPTORS, so this is the one line that
|
|
1405
|
+
* says which yuno is on screen. A backend yuno enters here:
|
|
1406
|
+
* describe_backend_tree(<the `view-gobj-tree` answer>) in
|
|
1407
|
+
* place of describe_js_gobj(gobj_yuno(), true), and nothing
|
|
1408
|
+
* below this point changes.
|
|
1409
|
+
*/
|
|
1410
|
+
let root = describe_js_gobj(gobj_yuno(), true);
|
|
1411
|
+
if(!root) {
|
|
1412
|
+
log_error(`${gobj_short_name(gobj)}: no yuno to draw`);
|
|
1261
1413
|
return;
|
|
1262
1414
|
}
|
|
1263
1415
|
|
|
1264
1416
|
let layout_cfg = get_layout_cfg(gobj_read_str_attr(gobj, "layout"));
|
|
1417
|
+
let labels = node_labels(t);
|
|
1265
1418
|
|
|
1266
1419
|
let anchor = priv.pending_anchor || null;
|
|
1267
1420
|
let preserve_view = !!priv.pending_preserve_view;
|
|
@@ -1274,7 +1427,7 @@ function load_tree(gobj)
|
|
|
1274
1427
|
let nodes = [];
|
|
1275
1428
|
let edges = [];
|
|
1276
1429
|
|
|
1277
|
-
build_gobj_nodes(gobj,
|
|
1430
|
+
build_gobj_nodes(gobj, root, nodes, edges, null, layout_cfg.compact, labels);
|
|
1278
1431
|
|
|
1279
1432
|
if(nodes.length > 0) {
|
|
1280
1433
|
let root_id = nodes[0].id; /* built before its children */
|
|
@@ -1434,67 +1587,12 @@ function refresh_tree(gobj, opts)
|
|
|
1434
1587
|
}
|
|
1435
1588
|
}
|
|
1436
1589
|
|
|
1437
|
-
/************************************************************
|
|
1438
|
-
* Rows to show in the popover for a given node data.
|
|
1439
|
-
*
|
|
1440
|
-
* This is the extension point: to surface a new attribute,
|
|
1441
|
-
* collect it into `node_data` in build_gobj_nodes(), then
|
|
1442
|
-
* append a `[label, value]` pair here.
|
|
1443
|
-
************************************************************/
|
|
1444
|
-
function get_node_info_rows(node_data)
|
|
1445
|
-
{
|
|
1446
|
-
let rows = [];
|
|
1447
|
-
|
|
1448
|
-
rows.push([t("gclass"), node_data.gclass || "—"]);
|
|
1449
|
-
rows.push([t("name"), node_data.name || "—"]);
|
|
1450
|
-
|
|
1451
|
-
if(node_data.full_name && node_data.full_name !== node_data.short_name) {
|
|
1452
|
-
rows.push([t("full name"), node_data.full_name]);
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
let role;
|
|
1456
|
-
if(node_data.is_yuno) {
|
|
1457
|
-
role = "yuno";
|
|
1458
|
-
} else if(node_data.is_service) {
|
|
1459
|
-
role = "service";
|
|
1460
|
-
} else if(node_data.is_volatil) {
|
|
1461
|
-
role = "volatil child";
|
|
1462
|
-
} else if(node_data.is_pure_child) {
|
|
1463
|
-
role = "pure child";
|
|
1464
|
-
} else {
|
|
1465
|
-
role = "child";
|
|
1466
|
-
}
|
|
1467
|
-
rows.push([t("role"), role]);
|
|
1468
|
-
|
|
1469
|
-
let status;
|
|
1470
|
-
if(!node_data.is_running) {
|
|
1471
|
-
status = "stopped";
|
|
1472
|
-
} else if(node_data.is_playing) {
|
|
1473
|
-
status = "running + playing";
|
|
1474
|
-
} else {
|
|
1475
|
-
status = "running";
|
|
1476
|
-
}
|
|
1477
|
-
rows.push([t("status"), status]);
|
|
1478
|
-
rows.push([t("state"), node_data.state || "—"]);
|
|
1479
|
-
|
|
1480
|
-
if(node_data.parent_short_name) {
|
|
1481
|
-
rows.push([t("parent"), node_data.parent_short_name]);
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
if(typeof node_data.num_children === "number") {
|
|
1485
|
-
let kids = node_data.num_children;
|
|
1486
|
-
if(kids > 0 && node_data.is_collapsed) {
|
|
1487
|
-
rows.push([t("children"), kids + " " + t("(collapsed)")]);
|
|
1488
|
-
} else if(kids > 0) {
|
|
1489
|
-
rows.push([t("children"), String(kids)]);
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
return rows;
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
1590
|
/************************************************************
|
|
1497
1591
|
* Render popover body from the info rows of a node.
|
|
1592
|
+
*
|
|
1593
|
+
* The rows come from gobj_tree_model.js, so a node of the
|
|
1594
|
+
* browser yuno and a node of a backend one are read out with
|
|
1595
|
+
* the same words in the same order.
|
|
1498
1596
|
************************************************************/
|
|
1499
1597
|
function render_popover_body(gobj, node_data)
|
|
1500
1598
|
{
|
|
@@ -1504,14 +1602,14 @@ function render_popover_body(gobj, node_data)
|
|
|
1504
1602
|
return;
|
|
1505
1603
|
}
|
|
1506
1604
|
|
|
1507
|
-
let rows =
|
|
1605
|
+
let rows = node_info_rows(node_data, t);
|
|
1508
1606
|
|
|
1509
1607
|
/* Bulma scheme vars, like the popover chrome around it (build_popover):
|
|
1510
1608
|
* they flip with <html data-theme>, so the popover follows the theme
|
|
1511
1609
|
* with no redraw. Hardcoding them is what made the value near-black
|
|
1512
1610
|
* (#1A1A1A) on the near-black dark card background — invisible. */
|
|
1513
1611
|
let rows_html = rows.map(([label, value]) => `
|
|
1514
|
-
<div style="display:grid; grid-template-columns:
|
|
1612
|
+
<div style="display:grid; grid-template-columns: 96px 1fr; column-gap:10px; padding: 2px 0;">
|
|
1515
1613
|
<span style="color:var(--bulma-text-weak, #6B7280); font-weight:500;">${escapeHtml(String(label))}</span>
|
|
1516
1614
|
<span style="color:var(--bulma-text-strong, #1A1A1A); word-break:break-all;">${escapeHtml(String(value))}</span>
|
|
1517
1615
|
</div>
|
|
@@ -1535,6 +1633,8 @@ function show_popover(gobj, node_data, client_x, client_y)
|
|
|
1535
1633
|
return;
|
|
1536
1634
|
}
|
|
1537
1635
|
|
|
1636
|
+
priv.popover_node = node_data;
|
|
1637
|
+
|
|
1538
1638
|
$title.textContent = node_data.short_name ||
|
|
1539
1639
|
(node_data.gclass + (node_data.name ? "^" + node_data.name : ""));
|
|
1540
1640
|
$title.title = $title.textContent;
|
|
@@ -1588,6 +1688,7 @@ function hide_popover(gobj)
|
|
|
1588
1688
|
if(priv.$popover_title) {
|
|
1589
1689
|
priv.$popover_title.textContent = "";
|
|
1590
1690
|
}
|
|
1691
|
+
priv.popover_node = null;
|
|
1591
1692
|
}
|
|
1592
1693
|
|
|
1593
1694
|
/************************************************************
|
|
@@ -2455,6 +2556,87 @@ function ac_hide(gobj, event, kw, src)
|
|
|
2455
2556
|
return 0;
|
|
2456
2557
|
}
|
|
2457
2558
|
|
|
2559
|
+
/************************************************************
|
|
2560
|
+
* Open the gclass of the node the popover is showing.
|
|
2561
|
+
*
|
|
2562
|
+
* One window at a time: opening a second gclass replaces the
|
|
2563
|
+
* first rather than stacking windows nobody closes.
|
|
2564
|
+
************************************************************/
|
|
2565
|
+
function ac_open_gclass(gobj, event, kw, src)
|
|
2566
|
+
{
|
|
2567
|
+
let priv = gobj.priv;
|
|
2568
|
+
let node_data = priv.popover_node;
|
|
2569
|
+
|
|
2570
|
+
if(!node_data || !node_data.gclass) {
|
|
2571
|
+
log_error(`${gobj_short_name(gobj)}: EV_OPEN_GCLASS with no node`);
|
|
2572
|
+
return -1;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
close_gclass_view(priv.gclass_view);
|
|
2576
|
+
priv.gclass_view = null;
|
|
2577
|
+
|
|
2578
|
+
priv.gclass_view = open_gclass_view(gobj, node_data.gclass, {
|
|
2579
|
+
title_prefix: gobj_yuno_name() || "",
|
|
2580
|
+
on_close: () => {
|
|
2581
|
+
if(gobj_is_destroying(gobj)) {
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
gobj_send_event(gobj, "EV_GCLASS_CLOSED", {}, gobj);
|
|
2585
|
+
},
|
|
2586
|
+
});
|
|
2587
|
+
|
|
2588
|
+
return 0;
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
/************************************************************
|
|
2592
|
+
* The reader dismissed the gclass window (its own ✕).
|
|
2593
|
+
*
|
|
2594
|
+
* C_YUI_WINDOW.close_window() calls on_close and THEN destroys
|
|
2595
|
+
* itself, so here we only drop our reference -- destroying it
|
|
2596
|
+
* again would destroy it twice.
|
|
2597
|
+
************************************************************/
|
|
2598
|
+
function ac_gclass_closed(gobj, event, kw, src)
|
|
2599
|
+
{
|
|
2600
|
+
gobj.priv.gclass_view = null;
|
|
2601
|
+
return 0;
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
/************************************************************
|
|
2605
|
+
* The gclass viewer asked for a subtree.
|
|
2606
|
+
*
|
|
2607
|
+
* It cannot: the gclass description is a COMPLETE document,
|
|
2608
|
+
* it carries no `__collapsed__` stub, so nothing in it is
|
|
2609
|
+
* fetchable. The event is declared because the viewer is
|
|
2610
|
+
* hosted as a child and subscribes its host to everything it
|
|
2611
|
+
* publishes -- and it is logged, because arriving here means
|
|
2612
|
+
* the document was not the one we built.
|
|
2613
|
+
************************************************************/
|
|
2614
|
+
function ac_gclass_expand_path(gobj, event, kw, src)
|
|
2615
|
+
{
|
|
2616
|
+
log_error(
|
|
2617
|
+
`${gobj_short_name(gobj)}: EV_EXPAND_PATH on a complete document: ` +
|
|
2618
|
+
`${kw && kw.path}`
|
|
2619
|
+
);
|
|
2620
|
+
return -1;
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
/************************************************************
|
|
2624
|
+
* The app switched language.
|
|
2625
|
+
*
|
|
2626
|
+
* A rebuild, not a re-translation: the words live inside the
|
|
2627
|
+
* innerHTML of G6 nodes, where refresh_language() cannot go.
|
|
2628
|
+
************************************************************/
|
|
2629
|
+
function ac_language_changed(gobj, event, kw, src)
|
|
2630
|
+
{
|
|
2631
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
2632
|
+
if($container) {
|
|
2633
|
+
refresh_language($container, t);
|
|
2634
|
+
}
|
|
2635
|
+
hide_popover(gobj);
|
|
2636
|
+
refresh_tree(gobj);
|
|
2637
|
+
return 0;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2458
2640
|
|
|
2459
2641
|
|
|
2460
2642
|
|
|
@@ -2506,6 +2688,10 @@ function create_gclass(gclass_name)
|
|
|
2506
2688
|
["EV_RESIZE", ac_resize, null],
|
|
2507
2689
|
["EV_SHOW", ac_show, null],
|
|
2508
2690
|
["EV_HIDE", ac_hide, null],
|
|
2691
|
+
["EV_OPEN_GCLASS", ac_open_gclass, null],
|
|
2692
|
+
["EV_GCLASS_CLOSED", ac_gclass_closed, null],
|
|
2693
|
+
["EV_EXPAND_PATH", ac_gclass_expand_path, null],
|
|
2694
|
+
["EV_LANGUAGE_CHANGED", ac_language_changed, null],
|
|
2509
2695
|
]]
|
|
2510
2696
|
];
|
|
2511
2697
|
|
|
@@ -2529,6 +2715,10 @@ function create_gclass(gclass_name)
|
|
|
2529
2715
|
["EV_RESIZE", 0],
|
|
2530
2716
|
["EV_SHOW", 0],
|
|
2531
2717
|
["EV_HIDE", 0],
|
|
2718
|
+
["EV_OPEN_GCLASS", 0],
|
|
2719
|
+
["EV_GCLASS_CLOSED", 0],
|
|
2720
|
+
["EV_EXPAND_PATH", 0],
|
|
2721
|
+
["EV_LANGUAGE_CHANGED", 0],
|
|
2532
2722
|
];
|
|
2533
2723
|
|
|
2534
2724
|
__gclass__ = gclass_create(
|