@yuneta/gobj-ui 7.23.29 → 7.23.31
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 +175 -2
- package/dist/gobj-ui.cjs.js +1008 -197
- package/dist/gobj-ui.es.js +1009 -198
- package/package.json +1 -1
- package/src/c_yui_gobj_tree_js.js +465 -249
- package/src/c_yui_json_graph.js +30 -4
- package/src/c_yui_treedb_graph.js +20 -6
- 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 +290 -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
|
|
@@ -783,6 +903,30 @@ function make_toolbar(gobj)
|
|
|
783
903
|
return $toolbar;
|
|
784
904
|
}
|
|
785
905
|
|
|
906
|
+
/************************************************************
|
|
907
|
+
* The element G6 mounts its canvas in.
|
|
908
|
+
*
|
|
909
|
+
* Read from THIS gclass's own $container, never with
|
|
910
|
+
* `document.getElementById()`: a view is not always in the
|
|
911
|
+
* document when it is started -- a host that builds the
|
|
912
|
+
* viewer and mounts it into a window afterwards starts it
|
|
913
|
+
* DETACHED -- and a global lookup answers null there. It cost
|
|
914
|
+
* a resize that never worked: the ResizeObserver below was
|
|
915
|
+
* simply never attached, silently, and the canvas kept the
|
|
916
|
+
* size it was born with for the life of the window.
|
|
917
|
+
*
|
|
918
|
+
* Null only if the UI was never built; that is a bug, and it
|
|
919
|
+
* is logged where it matters rather than skipped.
|
|
920
|
+
************************************************************/
|
|
921
|
+
function canvas_mount(gobj)
|
|
922
|
+
{
|
|
923
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
924
|
+
if(!$container) {
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
return $container.querySelector("#" + gobj.priv.canvas_id);
|
|
928
|
+
}
|
|
929
|
+
|
|
786
930
|
/************************************************************
|
|
787
931
|
* Build a G6 graph instance
|
|
788
932
|
************************************************************/
|
|
@@ -871,8 +1015,10 @@ function build_graph(gobj)
|
|
|
871
1015
|
* the observed box so there is no feedback loop.
|
|
872
1016
|
*/
|
|
873
1017
|
if(typeof ResizeObserver !== "undefined") {
|
|
874
|
-
let $canvas =
|
|
875
|
-
if(
|
|
1018
|
+
let $canvas = canvas_mount(gobj);
|
|
1019
|
+
if(!$canvas) {
|
|
1020
|
+
log_error(`${gobj_short_name(gobj)}: no canvas mount, the graph will never resize`);
|
|
1021
|
+
} else {
|
|
876
1022
|
let ro = new ResizeObserver(() => {
|
|
877
1023
|
if(priv._resize_raf) {
|
|
878
1024
|
cancelAnimationFrame(priv._resize_raf);
|
|
@@ -917,37 +1063,26 @@ function apply_layout(gobj)
|
|
|
917
1063
|
}
|
|
918
1064
|
|
|
919
1065
|
/************************************************************
|
|
920
|
-
*
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
return ROLE_COLORS.yuno;
|
|
926
|
-
}
|
|
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
|
|
1066
|
+
* The role colours of a node.
|
|
1067
|
+
*
|
|
1068
|
+
* Note the ORDER: volatil before service. A volatil gobj that
|
|
1069
|
+
* is also a service is drawn volatil, because that is the fact
|
|
1070
|
+
* that decides how long it lives.
|
|
941
1071
|
************************************************************/
|
|
942
|
-
function
|
|
1072
|
+
function get_role_colors(d)
|
|
943
1073
|
{
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1074
|
+
switch(node_role(d)) {
|
|
1075
|
+
case "yuno":
|
|
1076
|
+
return ROLE_COLORS.yuno;
|
|
1077
|
+
case "volatil":
|
|
1078
|
+
return ROLE_COLORS.volatil;
|
|
1079
|
+
case "service":
|
|
1080
|
+
return ROLE_COLORS.service;
|
|
1081
|
+
case "pure":
|
|
1082
|
+
return ROLE_COLORS.pure;
|
|
1083
|
+
default:
|
|
1084
|
+
return ROLE_COLORS.child;
|
|
949
1085
|
}
|
|
950
|
-
return STATE_COLORS.running;
|
|
951
1086
|
}
|
|
952
1087
|
|
|
953
1088
|
/************************************************************
|
|
@@ -994,27 +1129,27 @@ function is_node_collapsed(gobj, full_name, num_children)
|
|
|
994
1129
|
************************************************************/
|
|
995
1130
|
function render_toggle_html(node_id, collapsed, num_children, cs)
|
|
996
1131
|
{
|
|
997
|
-
let label = collapsed ? ("+" + num_children) : "
|
|
998
|
-
let title = collapsed ? "
|
|
1132
|
+
let label = collapsed ? ("+" + num_children) : "\u2212";
|
|
1133
|
+
let title = collapsed ? t("expand children") : t("collapse children");
|
|
999
1134
|
return `<span
|
|
1000
1135
|
data-gobj-toggle="true"
|
|
1001
1136
|
data-node-id="${node_id}"
|
|
1002
|
-
title="${title}"
|
|
1137
|
+
title="${escapeHtml(title)}"
|
|
1003
1138
|
style="
|
|
1004
1139
|
flex: 0 0 auto;
|
|
1005
1140
|
display: inline-flex;
|
|
1006
1141
|
align-items: center;
|
|
1007
1142
|
justify-content: center;
|
|
1008
|
-
min-width:
|
|
1009
|
-
height:
|
|
1010
|
-
padding: 0
|
|
1143
|
+
min-width: 24px;
|
|
1144
|
+
height: 18px;
|
|
1145
|
+
padding: 0 6px;
|
|
1011
1146
|
margin-left: 4px;
|
|
1012
|
-
background: ${cs.
|
|
1013
|
-
color:
|
|
1014
|
-
border: 1px solid ${cs.
|
|
1015
|
-
border-radius:
|
|
1147
|
+
background: ${cs.accent};
|
|
1148
|
+
color: #ffffff;
|
|
1149
|
+
border: 1px solid ${cs.accent};
|
|
1150
|
+
border-radius: 9px;
|
|
1016
1151
|
font-size: 10px;
|
|
1017
|
-
font-weight:
|
|
1152
|
+
font-weight: 700;
|
|
1018
1153
|
line-height: 1;
|
|
1019
1154
|
cursor: pointer;
|
|
1020
1155
|
user-select: none;
|
|
@@ -1022,27 +1157,92 @@ function render_toggle_html(node_id, collapsed, num_children, cs)
|
|
|
1022
1157
|
}
|
|
1023
1158
|
|
|
1024
1159
|
/************************************************************
|
|
1025
|
-
*
|
|
1160
|
+
* HTML for the status pill: what the gobj is DOING.
|
|
1161
|
+
*
|
|
1162
|
+
* A dot plus a word, and the word is dropped in the compact
|
|
1163
|
+
* card where there is no room for it -- the colour still
|
|
1164
|
+
* carries it, and the `title` says it in full either way.
|
|
1026
1165
|
************************************************************/
|
|
1027
|
-
function
|
|
1166
|
+
function render_status_html(d, ss, labels, with_label)
|
|
1167
|
+
{
|
|
1168
|
+
let full = d.disabled?
|
|
1169
|
+
labels.field.disabled:
|
|
1170
|
+
(labels.status[node_status_key(d)] || node_status_key(d));
|
|
1171
|
+
let label = d.disabled?
|
|
1172
|
+
labels.field.disabled:
|
|
1173
|
+
(labels.status_short[node_status(d)] || node_status(d));
|
|
1174
|
+
|
|
1175
|
+
if(!with_label) {
|
|
1176
|
+
return `<span title="${escapeHtml(full)}" style="
|
|
1177
|
+
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
|
+
margin-left: 6px;
|
|
1182
|
+
"></span>`;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
return `<span title="${escapeHtml(full)}" style="
|
|
1186
|
+
flex: 0 0 auto;
|
|
1187
|
+
display: inline-flex;
|
|
1188
|
+
align-items: center;
|
|
1189
|
+
gap: 4px;
|
|
1190
|
+
padding: 1px 7px 1px 5px;
|
|
1191
|
+
background: ${ss.bg};
|
|
1192
|
+
color: ${ss.fg};
|
|
1193
|
+
border-radius: 9px;
|
|
1194
|
+
font-size: 10px;
|
|
1195
|
+
font-weight: 700;
|
|
1196
|
+
line-height: 1.45;
|
|
1197
|
+
white-space: nowrap;
|
|
1198
|
+
"><span style="width:7px; height:7px; border-radius:50%; background:${ss.dot};"></span>${escapeHtml(label)}</span>`;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/************************************************************
|
|
1202
|
+
* HTML for the badges: what the gobj IS, besides its colour.
|
|
1203
|
+
************************************************************/
|
|
1204
|
+
function render_badges_html(d, cs, labels)
|
|
1205
|
+
{
|
|
1206
|
+
let html = "";
|
|
1207
|
+
for(let key of node_badge_keys(d)) {
|
|
1208
|
+
let label = labels.badge[key] || key;
|
|
1209
|
+
html += `<span style="
|
|
1210
|
+
font-size: 10px;
|
|
1211
|
+
font-weight: 600;
|
|
1212
|
+
padding: 0 6px;
|
|
1213
|
+
margin-right: 4px;
|
|
1214
|
+
background: ${cs.tagbg};
|
|
1215
|
+
color: ${cs.tagfg};
|
|
1216
|
+
border: 1px solid ${cs.accent};
|
|
1217
|
+
border-radius: 9px;
|
|
1218
|
+
white-space: nowrap;
|
|
1219
|
+
">${escapeHtml(label)}</span>`;
|
|
1220
|
+
}
|
|
1221
|
+
return html;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/************************************************************
|
|
1225
|
+
* Recursively build nodes and edges from a NODE DESCRIPTOR
|
|
1226
|
+
* tree (gobj_tree_model.js), never from gobjs: the same
|
|
1227
|
+
* builder draws the browser yuno and a backend one.
|
|
1228
|
+
*
|
|
1229
|
+
* The descriptor is also what the popover reads, so the view
|
|
1230
|
+
* fields it needs are written onto it here (`full_name`,
|
|
1231
|
+
* `short_name`, `num_children`, `is_collapsed`,
|
|
1232
|
+
* `direct_children`) instead of copied into a second object
|
|
1233
|
+
* that can then disagree with the first.
|
|
1234
|
+
************************************************************/
|
|
1235
|
+
function build_gobj_nodes(gobj, d, nodes, edges, parent_id, compact, labels)
|
|
1028
1236
|
{
|
|
1029
1237
|
let priv = gobj.priv;
|
|
1030
1238
|
let node_id = gen_node_id(gobj);
|
|
1031
1239
|
|
|
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
1240
|
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");
|
|
1241
|
+
let colors = get_role_colors(d);
|
|
1242
|
+
let cs = role_card_style(colors, dark);
|
|
1243
|
+
let ss = status_card_style(d, dark);
|
|
1044
1244
|
|
|
1045
|
-
let category = get_node_category(
|
|
1245
|
+
let category = get_node_category(d);
|
|
1046
1246
|
let size = NODE_SIZES[compact ? "compact" : "full"][category];
|
|
1047
1247
|
let width = size.w;
|
|
1048
1248
|
let height = size.h;
|
|
@@ -1053,10 +1253,15 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1053
1253
|
*/
|
|
1054
1254
|
let border_width = (category === "yuno" || category === "service") ? 2 : 1;
|
|
1055
1255
|
|
|
1056
|
-
|
|
1256
|
+
/* A disabled gobj is drawn as one: a dashed border says
|
|
1257
|
+
* "this branch is out of the game" at any zoom. */
|
|
1258
|
+
let border_style = d.disabled? "dashed": "solid";
|
|
1259
|
+
|
|
1260
|
+
let children = d.children || [];
|
|
1057
1261
|
let num_children = children.length;
|
|
1058
1262
|
let has_children = num_children > 0;
|
|
1059
|
-
let collapsed = has_children &&
|
|
1263
|
+
let collapsed = has_children &&
|
|
1264
|
+
is_node_collapsed(gobj, d.fullname, num_children);
|
|
1060
1265
|
|
|
1061
1266
|
/*
|
|
1062
1267
|
* Direct-children index (collected even when this node is
|
|
@@ -1066,33 +1271,41 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1066
1271
|
let direct_children = [];
|
|
1067
1272
|
for(let child of children) {
|
|
1068
1273
|
direct_children.push({
|
|
1069
|
-
full_name:
|
|
1070
|
-
has_grandchildren: (child.
|
|
1274
|
+
full_name: child.fullname,
|
|
1275
|
+
has_grandchildren: (child.children || []).length > 0,
|
|
1071
1276
|
});
|
|
1072
1277
|
}
|
|
1073
1278
|
|
|
1279
|
+
/* What the VIEW needs on top of the descriptor. Written onto
|
|
1280
|
+
* it, because the popover reads the same object. */
|
|
1281
|
+
d.full_name = d.fullname;
|
|
1282
|
+
d.short_name = d.shortname;
|
|
1283
|
+
d.num_children = num_children;
|
|
1284
|
+
d.is_collapsed = collapsed;
|
|
1285
|
+
d.direct_children = direct_children;
|
|
1286
|
+
d.node_id = node_id;
|
|
1287
|
+
|
|
1074
1288
|
let toggle_html = has_children
|
|
1075
1289
|
? render_toggle_html(node_id, collapsed, num_children, cs)
|
|
1076
1290
|
: "";
|
|
1077
1291
|
|
|
1292
|
+
let card_title = d.gclass + (d.name? ("^" + d.name): "");
|
|
1078
1293
|
let node_html;
|
|
1079
1294
|
|
|
1080
1295
|
if(compact) {
|
|
1081
1296
|
/*
|
|
1082
|
-
* Compact one-line node: "gclass^name" with a left
|
|
1083
|
-
*
|
|
1297
|
+
* Compact one-line node: "gclass^name" with a left colour bar
|
|
1298
|
+
* and the status dot. Name truncates with "...".
|
|
1084
1299
|
*/
|
|
1085
|
-
let compact_label = escapeHtml(
|
|
1086
|
-
instance_name ? (gclass_name + "^" + instance_name) : gclass_name
|
|
1087
|
-
);
|
|
1300
|
+
let compact_label = escapeHtml(card_title);
|
|
1088
1301
|
|
|
1089
1302
|
node_html = `
|
|
1090
|
-
<div class="GOBJ_CARD" data-gobj-name="${escapeHtml(
|
|
1303
|
+
<div class="GOBJ_CARD" data-gobj-name="${escapeHtml(d.fullname)}" style="
|
|
1091
1304
|
width: ${width}px;
|
|
1092
1305
|
height: ${height}px;
|
|
1093
1306
|
background: ${cs.bg};
|
|
1094
|
-
border: ${border_width}px
|
|
1095
|
-
border-left:
|
|
1307
|
+
border: ${border_width}px ${border_style} ${cs.border};
|
|
1308
|
+
border-left: 6px solid ${cs.accent};
|
|
1096
1309
|
border-radius: 7px;
|
|
1097
1310
|
box-shadow: ${cs.shadow};
|
|
1098
1311
|
font-family: ${GT_FONT};
|
|
@@ -1101,49 +1314,30 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1101
1314
|
padding: 0 8px;
|
|
1102
1315
|
box-sizing: border-box;
|
|
1103
1316
|
overflow: hidden;
|
|
1317
|
+
opacity: ${d.disabled? "0.72": "1"};
|
|
1104
1318
|
cursor: pointer;
|
|
1105
1319
|
" title="${compact_label}">
|
|
1106
1320
|
<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>
|
|
1321
|
+
${render_status_html(d, ss, labels, false)}
|
|
1112
1322
|
${toggle_html}
|
|
1113
1323
|
</div>`;
|
|
1114
1324
|
} else {
|
|
1115
1325
|
/*
|
|
1116
|
-
* Full node: header (gclass + optional toggle), name,
|
|
1117
|
-
* All rows truncate with "..."
|
|
1326
|
+
* Full node: header (gclass + optional toggle), name, state,
|
|
1327
|
+
* badges + status pill. All rows truncate with "..." -- the
|
|
1328
|
+
* width is fixed.
|
|
1118
1329
|
*/
|
|
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("");
|
|
1330
|
+
let header_label = escapeHtml(d.gclass);
|
|
1331
|
+
let name_label = escapeHtml(d.name);
|
|
1332
|
+
let state_label = escapeHtml(d.state);
|
|
1140
1333
|
|
|
1141
1334
|
node_html = `
|
|
1142
|
-
<div class="GOBJ_CARD" data-gobj-name="${escapeHtml(
|
|
1335
|
+
<div class="GOBJ_CARD" data-gobj-name="${escapeHtml(d.fullname)}" style="
|
|
1143
1336
|
width: ${width}px;
|
|
1144
1337
|
height: ${height}px;
|
|
1145
1338
|
background: ${cs.bg};
|
|
1146
|
-
border: ${border_width}px
|
|
1339
|
+
border: ${border_width}px ${border_style} ${cs.border};
|
|
1340
|
+
border-left: 6px solid ${cs.accent};
|
|
1147
1341
|
border-radius: 9px;
|
|
1148
1342
|
box-shadow: ${cs.shadow};
|
|
1149
1343
|
overflow: hidden;
|
|
@@ -1152,59 +1346,33 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1152
1346
|
display: flex;
|
|
1153
1347
|
flex-direction: column;
|
|
1154
1348
|
justify-content: center;
|
|
1155
|
-
padding:
|
|
1156
|
-
gap:
|
|
1349
|
+
padding: 6px 9px;
|
|
1350
|
+
gap: 2px;
|
|
1351
|
+
opacity: ${d.disabled? "0.72": "1"};
|
|
1157
1352
|
cursor: pointer;
|
|
1158
|
-
" title="${escapeHtml(
|
|
1353
|
+
" title="${escapeHtml(card_title)}">
|
|
1159
1354
|
<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:
|
|
1355
|
+
<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
1356
|
${toggle_html}
|
|
1162
1357
|
</div>
|
|
1163
1358
|
<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
1359
|
<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>
|
|
1360
|
+
<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>
|
|
1361
|
+
${render_status_html(d, ss, labels, true)}
|
|
1169
1362
|
</div>
|
|
1363
|
+
<div style="flex:0 0 auto; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;">${render_badges_html(d, cs, labels)}</div>
|
|
1170
1364
|
</div>`;
|
|
1171
1365
|
}
|
|
1172
1366
|
|
|
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
1367
|
if(!priv.node_by_id) {
|
|
1200
1368
|
priv.node_by_id = {};
|
|
1201
1369
|
}
|
|
1202
|
-
priv.node_by_id[node_id] =
|
|
1370
|
+
priv.node_by_id[node_id] = d;
|
|
1203
1371
|
|
|
1204
1372
|
let node = {
|
|
1205
1373
|
id: node_id,
|
|
1206
1374
|
type: 'html',
|
|
1207
|
-
data:
|
|
1375
|
+
data: d,
|
|
1208
1376
|
style: {
|
|
1209
1377
|
innerHTML: node_html,
|
|
1210
1378
|
size: [width, height],
|
|
@@ -1219,8 +1387,8 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1219
1387
|
source: parent_id,
|
|
1220
1388
|
target: node_id,
|
|
1221
1389
|
style: {
|
|
1222
|
-
stroke: colors.
|
|
1223
|
-
lineWidth: 1,
|
|
1390
|
+
stroke: colors.accent,
|
|
1391
|
+
lineWidth: 1.4,
|
|
1224
1392
|
}
|
|
1225
1393
|
});
|
|
1226
1394
|
}
|
|
@@ -1230,7 +1398,7 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
1230
1398
|
*/
|
|
1231
1399
|
if(!collapsed) {
|
|
1232
1400
|
for(let child of children) {
|
|
1233
|
-
build_gobj_nodes(gobj, child, nodes, edges, node_id,
|
|
1401
|
+
build_gobj_nodes(gobj, child, nodes, edges, node_id, compact, labels);
|
|
1234
1402
|
}
|
|
1235
1403
|
}
|
|
1236
1404
|
|
|
@@ -1256,12 +1424,23 @@ function load_tree(gobj)
|
|
|
1256
1424
|
return;
|
|
1257
1425
|
}
|
|
1258
1426
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1427
|
+
/*
|
|
1428
|
+
* The tree, as data.
|
|
1429
|
+
*
|
|
1430
|
+
* The graph draws DESCRIPTORS, so this is the one line that
|
|
1431
|
+
* says which yuno is on screen. A backend yuno enters here:
|
|
1432
|
+
* describe_backend_tree(<the `view-gobj-tree` answer>) in
|
|
1433
|
+
* place of describe_js_gobj(gobj_yuno(), true), and nothing
|
|
1434
|
+
* below this point changes.
|
|
1435
|
+
*/
|
|
1436
|
+
let root = describe_js_gobj(gobj_yuno(), true);
|
|
1437
|
+
if(!root) {
|
|
1438
|
+
log_error(`${gobj_short_name(gobj)}: no yuno to draw`);
|
|
1261
1439
|
return;
|
|
1262
1440
|
}
|
|
1263
1441
|
|
|
1264
1442
|
let layout_cfg = get_layout_cfg(gobj_read_str_attr(gobj, "layout"));
|
|
1443
|
+
let labels = node_labels(t);
|
|
1265
1444
|
|
|
1266
1445
|
let anchor = priv.pending_anchor || null;
|
|
1267
1446
|
let preserve_view = !!priv.pending_preserve_view;
|
|
@@ -1274,7 +1453,7 @@ function load_tree(gobj)
|
|
|
1274
1453
|
let nodes = [];
|
|
1275
1454
|
let edges = [];
|
|
1276
1455
|
|
|
1277
|
-
build_gobj_nodes(gobj,
|
|
1456
|
+
build_gobj_nodes(gobj, root, nodes, edges, null, layout_cfg.compact, labels);
|
|
1278
1457
|
|
|
1279
1458
|
if(nodes.length > 0) {
|
|
1280
1459
|
let root_id = nodes[0].id; /* built before its children */
|
|
@@ -1434,67 +1613,12 @@ function refresh_tree(gobj, opts)
|
|
|
1434
1613
|
}
|
|
1435
1614
|
}
|
|
1436
1615
|
|
|
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
1616
|
/************************************************************
|
|
1497
1617
|
* Render popover body from the info rows of a node.
|
|
1618
|
+
*
|
|
1619
|
+
* The rows come from gobj_tree_model.js, so a node of the
|
|
1620
|
+
* browser yuno and a node of a backend one are read out with
|
|
1621
|
+
* the same words in the same order.
|
|
1498
1622
|
************************************************************/
|
|
1499
1623
|
function render_popover_body(gobj, node_data)
|
|
1500
1624
|
{
|
|
@@ -1504,14 +1628,14 @@ function render_popover_body(gobj, node_data)
|
|
|
1504
1628
|
return;
|
|
1505
1629
|
}
|
|
1506
1630
|
|
|
1507
|
-
let rows =
|
|
1631
|
+
let rows = node_info_rows(node_data, t);
|
|
1508
1632
|
|
|
1509
1633
|
/* Bulma scheme vars, like the popover chrome around it (build_popover):
|
|
1510
1634
|
* they flip with <html data-theme>, so the popover follows the theme
|
|
1511
1635
|
* with no redraw. Hardcoding them is what made the value near-black
|
|
1512
1636
|
* (#1A1A1A) on the near-black dark card background — invisible. */
|
|
1513
1637
|
let rows_html = rows.map(([label, value]) => `
|
|
1514
|
-
<div style="display:grid; grid-template-columns:
|
|
1638
|
+
<div style="display:grid; grid-template-columns: 96px 1fr; column-gap:10px; padding: 2px 0;">
|
|
1515
1639
|
<span style="color:var(--bulma-text-weak, #6B7280); font-weight:500;">${escapeHtml(String(label))}</span>
|
|
1516
1640
|
<span style="color:var(--bulma-text-strong, #1A1A1A); word-break:break-all;">${escapeHtml(String(value))}</span>
|
|
1517
1641
|
</div>
|
|
@@ -1535,6 +1659,8 @@ function show_popover(gobj, node_data, client_x, client_y)
|
|
|
1535
1659
|
return;
|
|
1536
1660
|
}
|
|
1537
1661
|
|
|
1662
|
+
priv.popover_node = node_data;
|
|
1663
|
+
|
|
1538
1664
|
$title.textContent = node_data.short_name ||
|
|
1539
1665
|
(node_data.gclass + (node_data.name ? "^" + node_data.name : ""));
|
|
1540
1666
|
$title.title = $title.textContent;
|
|
@@ -1588,6 +1714,7 @@ function hide_popover(gobj)
|
|
|
1588
1714
|
if(priv.$popover_title) {
|
|
1589
1715
|
priv.$popover_title.textContent = "";
|
|
1590
1716
|
}
|
|
1717
|
+
priv.popover_node = null;
|
|
1591
1718
|
}
|
|
1592
1719
|
|
|
1593
1720
|
/************************************************************
|
|
@@ -2400,7 +2527,7 @@ function ac_show(gobj, event, kw, src)
|
|
|
2400
2527
|
refresh_tree(gobj);
|
|
2401
2528
|
|
|
2402
2529
|
if(graph) {
|
|
2403
|
-
let $canvas =
|
|
2530
|
+
let $canvas = canvas_mount(gobj);
|
|
2404
2531
|
if($canvas) {
|
|
2405
2532
|
/*
|
|
2406
2533
|
* Content box (clientWidth/Height), not getBoundingClientRect:
|
|
@@ -2433,7 +2560,7 @@ function ac_resize(gobj, event, kw, src)
|
|
|
2433
2560
|
let graph = priv.graph;
|
|
2434
2561
|
|
|
2435
2562
|
if(graph) {
|
|
2436
|
-
let $canvas =
|
|
2563
|
+
let $canvas = canvas_mount(gobj);
|
|
2437
2564
|
if($canvas) {
|
|
2438
2565
|
let cw = $canvas.clientWidth;
|
|
2439
2566
|
let ch = $canvas.clientHeight;
|
|
@@ -2455,6 +2582,87 @@ function ac_hide(gobj, event, kw, src)
|
|
|
2455
2582
|
return 0;
|
|
2456
2583
|
}
|
|
2457
2584
|
|
|
2585
|
+
/************************************************************
|
|
2586
|
+
* Open the gclass of the node the popover is showing.
|
|
2587
|
+
*
|
|
2588
|
+
* One window at a time: opening a second gclass replaces the
|
|
2589
|
+
* first rather than stacking windows nobody closes.
|
|
2590
|
+
************************************************************/
|
|
2591
|
+
function ac_open_gclass(gobj, event, kw, src)
|
|
2592
|
+
{
|
|
2593
|
+
let priv = gobj.priv;
|
|
2594
|
+
let node_data = priv.popover_node;
|
|
2595
|
+
|
|
2596
|
+
if(!node_data || !node_data.gclass) {
|
|
2597
|
+
log_error(`${gobj_short_name(gobj)}: EV_OPEN_GCLASS with no node`);
|
|
2598
|
+
return -1;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
close_gclass_view(priv.gclass_view);
|
|
2602
|
+
priv.gclass_view = null;
|
|
2603
|
+
|
|
2604
|
+
priv.gclass_view = open_gclass_view(gobj, node_data.gclass, {
|
|
2605
|
+
title_prefix: gobj_yuno_name() || "",
|
|
2606
|
+
on_close: () => {
|
|
2607
|
+
if(gobj_is_destroying(gobj)) {
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
gobj_send_event(gobj, "EV_GCLASS_CLOSED", {}, gobj);
|
|
2611
|
+
},
|
|
2612
|
+
});
|
|
2613
|
+
|
|
2614
|
+
return 0;
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
/************************************************************
|
|
2618
|
+
* The reader dismissed the gclass window (its own ✕).
|
|
2619
|
+
*
|
|
2620
|
+
* C_YUI_WINDOW.close_window() calls on_close and THEN destroys
|
|
2621
|
+
* itself, so here we only drop our reference -- destroying it
|
|
2622
|
+
* again would destroy it twice.
|
|
2623
|
+
************************************************************/
|
|
2624
|
+
function ac_gclass_closed(gobj, event, kw, src)
|
|
2625
|
+
{
|
|
2626
|
+
gobj.priv.gclass_view = null;
|
|
2627
|
+
return 0;
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
/************************************************************
|
|
2631
|
+
* The gclass viewer asked for a subtree.
|
|
2632
|
+
*
|
|
2633
|
+
* It cannot: the gclass description is a COMPLETE document,
|
|
2634
|
+
* it carries no `__collapsed__` stub, so nothing in it is
|
|
2635
|
+
* fetchable. The event is declared because the viewer is
|
|
2636
|
+
* hosted as a child and subscribes its host to everything it
|
|
2637
|
+
* publishes -- and it is logged, because arriving here means
|
|
2638
|
+
* the document was not the one we built.
|
|
2639
|
+
************************************************************/
|
|
2640
|
+
function ac_gclass_expand_path(gobj, event, kw, src)
|
|
2641
|
+
{
|
|
2642
|
+
log_error(
|
|
2643
|
+
`${gobj_short_name(gobj)}: EV_EXPAND_PATH on a complete document: ` +
|
|
2644
|
+
`${kw && kw.path}`
|
|
2645
|
+
);
|
|
2646
|
+
return -1;
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
/************************************************************
|
|
2650
|
+
* The app switched language.
|
|
2651
|
+
*
|
|
2652
|
+
* A rebuild, not a re-translation: the words live inside the
|
|
2653
|
+
* innerHTML of G6 nodes, where refresh_language() cannot go.
|
|
2654
|
+
************************************************************/
|
|
2655
|
+
function ac_language_changed(gobj, event, kw, src)
|
|
2656
|
+
{
|
|
2657
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
2658
|
+
if($container) {
|
|
2659
|
+
refresh_language($container, t);
|
|
2660
|
+
}
|
|
2661
|
+
hide_popover(gobj);
|
|
2662
|
+
refresh_tree(gobj);
|
|
2663
|
+
return 0;
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2458
2666
|
|
|
2459
2667
|
|
|
2460
2668
|
|
|
@@ -2506,6 +2714,10 @@ function create_gclass(gclass_name)
|
|
|
2506
2714
|
["EV_RESIZE", ac_resize, null],
|
|
2507
2715
|
["EV_SHOW", ac_show, null],
|
|
2508
2716
|
["EV_HIDE", ac_hide, null],
|
|
2717
|
+
["EV_OPEN_GCLASS", ac_open_gclass, null],
|
|
2718
|
+
["EV_GCLASS_CLOSED", ac_gclass_closed, null],
|
|
2719
|
+
["EV_EXPAND_PATH", ac_gclass_expand_path, null],
|
|
2720
|
+
["EV_LANGUAGE_CHANGED", ac_language_changed, null],
|
|
2509
2721
|
]]
|
|
2510
2722
|
];
|
|
2511
2723
|
|
|
@@ -2529,6 +2741,10 @@ function create_gclass(gclass_name)
|
|
|
2529
2741
|
["EV_RESIZE", 0],
|
|
2530
2742
|
["EV_SHOW", 0],
|
|
2531
2743
|
["EV_HIDE", 0],
|
|
2744
|
+
["EV_OPEN_GCLASS", 0],
|
|
2745
|
+
["EV_GCLASS_CLOSED", 0],
|
|
2746
|
+
["EV_EXPAND_PATH", 0],
|
|
2747
|
+
["EV_LANGUAGE_CHANGED", 0],
|
|
2532
2748
|
];
|
|
2533
2749
|
|
|
2534
2750
|
__gclass__ = gclass_create(
|