@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.
@@ -3518,7 +3518,7 @@ function composed_title(gobj) {
3518
3518
  * Below the mobile breakpoint the window is a full-screen
3519
3519
  * sheet (no float / drag / resize).
3520
3520
  ************************************************************/
3521
- function is_mobile$3() {
3521
+ function is_mobile$4() {
3522
3522
  return (window.innerWidth || document.documentElement.offsetWidth) <= MOBILE_MAX;
3523
3523
  }
3524
3524
  /************************************************************
@@ -3551,7 +3551,7 @@ function do_minimize(gobj) {
3551
3551
  ************************************************************/
3552
3552
  function build_ui$15(gobj) {
3553
3553
  ensure_window_style();
3554
- let mobile = is_mobile$3();
3554
+ let mobile = is_mobile$4();
3555
3555
  let header = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "header");
3556
3556
  if ((0, _yuneta_gobj_js.is_gobj)(header)) header = (0, _yuneta_gobj_js.gobj_read_attr)(header, "$container");
3557
3557
  if (!header) header = build_default_header(gobj);
@@ -3889,7 +3889,7 @@ function min$1(gobj) {
3889
3889
  * handlers moving
3890
3890
  ************************************************************/
3891
3891
  function mvStart(gobj, evt) {
3892
- if (is_mobile$3()) return;
3892
+ if (is_mobile$4()) return;
3893
3893
  let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
3894
3894
  let window_rect = $container.getBoundingClientRect();
3895
3895
  let x = evt.screenX;
@@ -4048,7 +4048,7 @@ function do_center(gobj, x, y, width, height) {
4048
4048
  function handleResize(gobj) {
4049
4049
  let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
4050
4050
  if (!$container) return;
4051
- if (is_mobile$3()) {
4051
+ if (is_mobile$4()) {
4052
4052
  $container.classList.add("is-mobile-sheet");
4053
4053
  let vw = window.innerWidth || document.documentElement.offsetWidth;
4054
4054
  let vh = window.innerHeight || document.documentElement.offsetHeight;
@@ -10983,6 +10983,26 @@ function make_toolbar$3(gobj) {
10983
10983
  return $toolbar;
10984
10984
  }
10985
10985
  /************************************************************
10986
+ * The element G6 mounts its canvas in.
10987
+ *
10988
+ * Read from THIS gclass's own $container, never with
10989
+ * `document.getElementById()`: a view is not always in the
10990
+ * document when it is started -- a host that builds the
10991
+ * viewer and mounts it into a window afterwards starts it
10992
+ * DETACHED -- and a global lookup answers null there. It cost
10993
+ * a resize that never worked: the ResizeObserver below was
10994
+ * simply never attached, silently, and the canvas kept the
10995
+ * size it was born with for the life of the window.
10996
+ *
10997
+ * Null only if the UI was never built; that is a bug, and it
10998
+ * is logged where it matters rather than skipped.
10999
+ ************************************************************/
11000
+ function canvas_mount$1(gobj) {
11001
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
11002
+ if (!$container) return null;
11003
+ return $container.querySelector("#" + gobj.priv.canvas_id);
11004
+ }
11005
+ /************************************************************
10986
11006
  * Build a G6 graph instance
10987
11007
  ************************************************************/
10988
11008
  function build_graph$3(gobj) {
@@ -11026,8 +11046,9 @@ function build_graph$3(gobj) {
11026
11046
  });
11027
11047
  graph.on(_antv_g6.CanvasEvent.CLICK, (evt) => {});
11028
11048
  if (typeof ResizeObserver !== "undefined") {
11029
- let $canvas = document.getElementById(priv.canvas_id);
11030
- if ($canvas) {
11049
+ let $canvas = canvas_mount$1(gobj);
11050
+ if (!$canvas) (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no canvas mount, the graph will never resize`);
11051
+ else {
11031
11052
  let ro = new ResizeObserver(() => {
11032
11053
  if (priv._resize_raf) cancelAnimationFrame(priv._resize_raf);
11033
11054
  priv._resize_raf = requestAnimationFrame(() => {
@@ -11964,10 +11985,9 @@ function ac_node_click$4(gobj, event, kw, src) {
11964
11985
  *
11965
11986
  ************************************************************/
11966
11987
  function ac_show$8(gobj, event, kw, src) {
11967
- let priv = gobj.priv;
11968
- let graph = priv.graph;
11988
+ let graph = gobj.priv.graph;
11969
11989
  if (graph) {
11970
- let $canvas = document.getElementById(priv.canvas_id);
11990
+ let $canvas = canvas_mount$1(gobj);
11971
11991
  if ($canvas) {
11972
11992
  let cw = $canvas.clientWidth;
11973
11993
  let ch = $canvas.clientHeight;
@@ -11986,10 +12006,9 @@ function ac_show$8(gobj, event, kw, src) {
11986
12006
  * No fitView: preserve the user's current pan/zoom.
11987
12007
  ************************************************************/
11988
12008
  function ac_resize$2(gobj, event, kw, src) {
11989
- let priv = gobj.priv;
11990
- let graph = priv.graph;
12009
+ let graph = gobj.priv.graph;
11991
12010
  if (graph) {
11992
- let $canvas = document.getElementById(priv.canvas_id);
12011
+ let $canvas = canvas_mount$1(gobj);
11993
12012
  if ($canvas) {
11994
12013
  let cw = $canvas.clientWidth;
11995
12014
  let ch = $canvas.clientHeight;
@@ -13205,7 +13224,7 @@ function ac_json_item_clicked(gobj, event, kw, src) {
13205
13224
  /************************************************************
13206
13225
  * EV_LANGUAGE_CHANGED — re-translate chrome + re-render
13207
13226
  ************************************************************/
13208
- function ac_language_changed$3(gobj, event, kw, src) {
13227
+ function ac_language_changed$4(gobj, event, kw, src) {
13209
13228
  let priv = gobj.priv;
13210
13229
  let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
13211
13230
  if ($container) {
@@ -13266,7 +13285,7 @@ function create_gclass$10(gclass_name) {
13266
13285
  ],
13267
13286
  [
13268
13287
  "EV_LANGUAGE_CHANGED",
13269
- ac_language_changed$3,
13288
+ ac_language_changed$4,
13270
13289
  null
13271
13290
  ],
13272
13291
  [
@@ -13342,7 +13361,7 @@ function create_gclass$10(gclass_name) {
13342
13361
  ],
13343
13362
  [
13344
13363
  "EV_LANGUAGE_CHANGED",
13345
- ac_language_changed$3,
13364
+ ac_language_changed$4,
13346
13365
  null
13347
13366
  ],
13348
13367
  [
@@ -13392,6 +13411,568 @@ function register_c_yui_json() {
13392
13411
  return create_gclass$10(GCLASS_NAME$10);
13393
13412
  }
13394
13413
  //#endregion
13414
+ //#region src/gobj_tree_model.js
13415
+ /***********************************************************************
13416
+ * gobj_tree_model.js
13417
+ *
13418
+ * The gobj tree of a yuno, as plain data.
13419
+ *
13420
+ * The view (C_YUI_GOBJ_TREE_JS) draws NODE DESCRIPTORS, never
13421
+ * gobjs. There are two producers of a descriptor, because the
13422
+ * view has to show the gobjs of the BACKEND too:
13423
+ *
13424
+ * - describe_js_gobj() the gobj tree of THIS browser yuno
13425
+ * - describe_backend_tree() the answer of the backend command
13426
+ * `view-gobj-tree` of another yuno
13427
+ *
13428
+ * Both answer the SAME shape, and that shape is the backend's:
13429
+ * the field names are the ones `gobj2json()` writes in
13430
+ * `kernel/c/gobj-c/src/gobj.c` (fullname, shortname, gclass,
13431
+ * name, parent, state, running, playing, service, disabled,
13432
+ * volatil, bottom_gobj, gobj_flags, commands, gobj_trace_level).
13433
+ * Anything the browser runtime cannot answer is simply absent,
13434
+ * and every reader here treats absent as "not known", never as
13435
+ * false.
13436
+ *
13437
+ * Copyright (c) 2026, ArtGins.
13438
+ * All Rights Reserved.
13439
+ ***********************************************************************/
13440
+ /************************************************************
13441
+ * The role of a node: "yuno" | "service" | "volatil" | "pure" | "child"
13442
+ ************************************************************/
13443
+ function node_role(d) {
13444
+ if (!d) return "child";
13445
+ if (d.is_yuno) return "yuno";
13446
+ if (d.service) return "service";
13447
+ if (d.volatil) return "volatil";
13448
+ if (d.pure_child) return "pure";
13449
+ return "child";
13450
+ }
13451
+ /************************************************************
13452
+ * What the node is doing: "stopped" | "running" | "playing"
13453
+ ************************************************************/
13454
+ function node_status(d) {
13455
+ if (!d || !d.running) return "stopped";
13456
+ return d.playing ? "playing" : "running";
13457
+ }
13458
+ /************************************************************
13459
+ * The i18n key of the status, which is NOT the status itself:
13460
+ * a gobj that runs and does not play is PAUSED to the reader,
13461
+ * and "running" alone never said which of the two it was.
13462
+ ************************************************************/
13463
+ function node_status_key(d) {
13464
+ switch (node_status(d)) {
13465
+ case "playing": return "playing";
13466
+ case "running": return "running (paused)";
13467
+ default: return "stopped";
13468
+ }
13469
+ }
13470
+ /******************************
13471
+ * Producers
13472
+ ******************************/
13473
+ /************************************************************
13474
+ * A descriptor of a LIVE gobj of this browser yuno, children
13475
+ * included (the whole subtree, depth-first).
13476
+ *
13477
+ * `is_root` marks the yuno itself: the JS runtime has no
13478
+ * gobj_flag_yuno test of its own.
13479
+ ************************************************************/
13480
+ function describe_js_gobj(target_gobj, is_root) {
13481
+ if (!target_gobj) return null;
13482
+ let parent_gobj = (0, _yuneta_gobj_js.gobj_parent)(target_gobj);
13483
+ let bottom = (0, _yuneta_gobj_js.gobj_bottom_gobj)(target_gobj);
13484
+ let d = {
13485
+ gclass: (0, _yuneta_gobj_js.gobj_gclass_name)(target_gobj) || "",
13486
+ name: (0, _yuneta_gobj_js.gobj_name)(target_gobj) || "",
13487
+ shortname: (0, _yuneta_gobj_js.gobj_short_name)(target_gobj) || "",
13488
+ fullname: (0, _yuneta_gobj_js.gobj_full_name)(target_gobj) || "",
13489
+ parent: parent_gobj ? (0, _yuneta_gobj_js.gobj_short_name)(parent_gobj) || "" : "",
13490
+ state: (0, _yuneta_gobj_js.gobj_current_state)(target_gobj) || "",
13491
+ running: !!(0, _yuneta_gobj_js.gobj_is_running)(target_gobj),
13492
+ playing: !!(0, _yuneta_gobj_js.gobj_is_playing)(target_gobj),
13493
+ service: !!(0, _yuneta_gobj_js.gobj_is_service)(target_gobj),
13494
+ volatil: !!(0, _yuneta_gobj_js.gobj_is_volatil)(target_gobj),
13495
+ pure_child: !!(0, _yuneta_gobj_js.gobj_is_pure_child)(target_gobj),
13496
+ disabled: !!target_gobj.disabled,
13497
+ bottom_gobj: bottom ? (0, _yuneta_gobj_js.gobj_short_name)(bottom) || "" : "",
13498
+ commands: !!(target_gobj.gclass && target_gobj.gclass.command_table),
13499
+ is_yuno: !!is_root,
13500
+ children: []
13501
+ };
13502
+ let children = target_gobj.dl_children || [];
13503
+ for (let child of children) {
13504
+ let cd = describe_js_gobj(child, false);
13505
+ if (cd) d.children.push(cd);
13506
+ }
13507
+ return d;
13508
+ }
13509
+ /******************************
13510
+ * Readouts
13511
+ ******************************/
13512
+ /************************************************************
13513
+ * The i18n key of a role. Two of the five roles are not the
13514
+ * word the reader wants ("pure" and "volatil" alone say
13515
+ * nothing), so the map is explicit.
13516
+ ************************************************************/
13517
+ function role_i18n_key(role) {
13518
+ switch (role) {
13519
+ case "yuno": return "yuno";
13520
+ case "service": return "service";
13521
+ case "volatil": return "volatil child";
13522
+ case "pure": return "pure child";
13523
+ default: return "child";
13524
+ }
13525
+ }
13526
+ /************************************************************
13527
+ * The marks a node carries besides its role: what the reader
13528
+ * would otherwise have to open the popover to learn.
13529
+ *
13530
+ * Returns i18n KEYS, in a fixed order, so the card and the
13531
+ * popover cannot disagree about what a node is.
13532
+ ************************************************************/
13533
+ function node_badge_keys(d) {
13534
+ let badges = [];
13535
+ if (!d) return badges;
13536
+ let role = node_role(d);
13537
+ if (role !== "child") badges.push(role_i18n_key(role));
13538
+ if (d.disabled) badges.push("disabled");
13539
+ if (d.bottom_gobj) badges.push("bottom");
13540
+ if (d.commands) badges.push("commands");
13541
+ return badges;
13542
+ }
13543
+ /************************************************************
13544
+ * The vocabulary of this view, translated.
13545
+ *
13546
+ * Written as literal t("...") calls and not as t(key): a key
13547
+ * reached through a variable is invisible to the locale
13548
+ * validator, which is how a fixed vocabulary ships in one
13549
+ * language and never changes it.
13550
+ ************************************************************/
13551
+ function node_labels(t) {
13552
+ if (typeof t !== "function") t = (k) => k;
13553
+ return {
13554
+ role: {
13555
+ "yuno": t("yuno"),
13556
+ "service": t("service"),
13557
+ "volatil child": t("volatil child"),
13558
+ "pure child": t("pure child"),
13559
+ "child": t("child")
13560
+ },
13561
+ status: {
13562
+ "playing": t("playing"),
13563
+ "running (paused)": t("running (paused)"),
13564
+ "stopped": t("stopped")
13565
+ },
13566
+ status_short: {
13567
+ "playing": t("playing"),
13568
+ "running": t("running"),
13569
+ "stopped": t("stopped")
13570
+ },
13571
+ badge: {
13572
+ "yuno": t("yuno"),
13573
+ "service": t("service"),
13574
+ "volatil child": t("volatil child"),
13575
+ "pure child": t("pure child"),
13576
+ "disabled": t("disabled"),
13577
+ "bottom": t("bottom"),
13578
+ "commands": t("commands")
13579
+ },
13580
+ field: {
13581
+ gclass: t("gclass"),
13582
+ name: t("name"),
13583
+ full_name: t("full name"),
13584
+ role: t("role"),
13585
+ status: t("status"),
13586
+ state: t("state"),
13587
+ disabled: t("disabled"),
13588
+ parent: t("parent"),
13589
+ bottom_gobj: t("bottom gobj"),
13590
+ commands: t("commands"),
13591
+ traces: t("traces"),
13592
+ flags: t("flags"),
13593
+ children: t("children"),
13594
+ yes: t("yes"),
13595
+ collapsed: t("(collapsed)")
13596
+ }
13597
+ };
13598
+ }
13599
+ /************************************************************
13600
+ * The rows of the popover: [label, value], both translated.
13601
+ *
13602
+ * `t` is the app's translator -- the library has none of its
13603
+ * own. Values that are a fixed vocabulary (the role, the
13604
+ * status, yes) go through it too; free text does not.
13605
+ ************************************************************/
13606
+ function node_info_rows(d, t) {
13607
+ let rows = [];
13608
+ if (!d) return rows;
13609
+ let L = node_labels(t);
13610
+ let f = L.field;
13611
+ rows.push([f.gclass, d.gclass || "—"]);
13612
+ rows.push([f.name, d.name || "—"]);
13613
+ if (d.fullname && d.fullname !== d.shortname) rows.push([f.full_name, d.fullname]);
13614
+ rows.push([f.role, L.role[role_i18n_key(node_role(d))]]);
13615
+ rows.push([f.status, L.status[node_status_key(d)]]);
13616
+ rows.push([f.state, d.state || "—"]);
13617
+ if (d.disabled) rows.push([f.disabled, f.yes]);
13618
+ if (d.parent) rows.push([f.parent, d.parent]);
13619
+ if (d.bottom_gobj) rows.push([f.bottom_gobj, d.bottom_gobj]);
13620
+ if (d.commands) rows.push([f.commands, f.yes]);
13621
+ if (Array.isArray(d.gobj_trace_level) && d.gobj_trace_level.length > 0) rows.push([f.traces, d.gobj_trace_level.join(", ")]);
13622
+ if (Array.isArray(d.gobj_flags) && d.gobj_flags.length > 0) rows.push([f.flags, d.gobj_flags.join(", ")]);
13623
+ let kids = Array.isArray(d.children) ? d.children.length : 0;
13624
+ if (kids > 0) rows.push([f.children, d.is_collapsed ? kids + " " + f.collapsed : String(kids)]);
13625
+ return rows;
13626
+ }
13627
+ //#endregion
13628
+ //#region src/gclass_describe.js
13629
+ /***********************************************************************
13630
+ * gclass_describe.js
13631
+ *
13632
+ * A gclass, as plain JSON, in the shape the BACKEND answers.
13633
+ *
13634
+ * There is no gclass viewer in the framework: the C kernel
13635
+ * answers `view-gclass` with `gclass2json()`
13636
+ * (`kernel/c/gobj-c/src/gobj.c`) and nothing draws it. This
13637
+ * module builds the same document for a gclass of the BROWSER
13638
+ * yuno, out of the gobj-js registry, so one viewer can show a
13639
+ * gclass of either side:
13640
+ *
13641
+ * {id, gcflag, attrs, commands, gclass_methods,
13642
+ * internal_methods, FSM: {events, states}}
13643
+ *
13644
+ * Two keys the backend does not carry are added at the end,
13645
+ * because the browser can answer them for free and the reader
13646
+ * asks them first: `instances` and `trace_levels`.
13647
+ *
13648
+ * Copyright (c) 2026, ArtGins.
13649
+ * All Rights Reserved.
13650
+ ***********************************************************************/
13651
+ /***************************************************************
13652
+ * Bit tables
13653
+ ***************************************************************/
13654
+ var DATA_TYPE_NAMES = {
13655
+ [_yuneta_gobj_js.data_type_t.DTP_STRING]: "DTP_STRING",
13656
+ [_yuneta_gobj_js.data_type_t.DTP_BOOLEAN]: "DTP_BOOLEAN",
13657
+ [_yuneta_gobj_js.data_type_t.DTP_INTEGER]: "DTP_INTEGER",
13658
+ [_yuneta_gobj_js.data_type_t.DTP_REAL]: "DTP_REAL",
13659
+ [_yuneta_gobj_js.data_type_t.DTP_LIST]: "DTP_LIST",
13660
+ [_yuneta_gobj_js.data_type_t.DTP_DICT]: "DTP_DICT",
13661
+ [_yuneta_gobj_js.data_type_t.DTP_JSON]: "DTP_JSON",
13662
+ [_yuneta_gobj_js.data_type_t.DTP_POINTER]: "DTP_POINTER"
13663
+ };
13664
+ /************************************************************
13665
+ * The names of the bits set in `bits`, in table order.
13666
+ ************************************************************/
13667
+ function bits2names(table, bits) {
13668
+ let names = [];
13669
+ let value = Number(bits) || 0;
13670
+ for (let name of Object.keys(table)) if ((value & table[name]) !== 0) names.push(name);
13671
+ return names;
13672
+ }
13673
+ /************************************************************
13674
+ * The name of a data type. An unknown type is reported as
13675
+ * the number it is -- silently drawing "DTP_STRING" for a
13676
+ * type nobody registered would be worse than saying "17".
13677
+ ************************************************************/
13678
+ function data_type_name(type) {
13679
+ return DATA_TYPE_NAMES[type] || String(type);
13680
+ }
13681
+ /************************************************************
13682
+ * One SDATA descriptor -> one row.
13683
+ *
13684
+ * A DTP_POINTER default is a live object (a DOM node, a
13685
+ * gobj): it is never printed, only named, because a viewer
13686
+ * that serializes it would either throw or dump the page.
13687
+ ************************************************************/
13688
+ function sdatadesc2json(it) {
13689
+ let row = {
13690
+ id: it.name,
13691
+ type: data_type_name(it.type),
13692
+ flag: bits2names(_yuneta_gobj_js.sdata_flag_t, it.flag),
13693
+ description: it.description || ""
13694
+ };
13695
+ if (it.type === _yuneta_gobj_js.data_type_t.DTP_POINTER) row.default_value = it.default_value === null || it.default_value === void 0 ? null : "<pointer>";
13696
+ else row.default_value = it.default_value === void 0 ? null : it.default_value;
13697
+ return row;
13698
+ }
13699
+ /************************************************************
13700
+ * The attrs table of a gclass.
13701
+ ************************************************************/
13702
+ function attrs2json(attrs_table) {
13703
+ let rows = [];
13704
+ if (!Array.isArray(attrs_table)) return rows;
13705
+ for (let it of attrs_table) {
13706
+ if (!it || !it.name) continue;
13707
+ rows.push(sdatadesc2json(it));
13708
+ }
13709
+ return rows;
13710
+ }
13711
+ /************************************************************
13712
+ * The command table of a gclass, with the parameters of
13713
+ * each command (the `schema` slot of SDATACM).
13714
+ ************************************************************/
13715
+ function commands2json(command_table) {
13716
+ let rows = [];
13717
+ if (!Array.isArray(command_table)) return rows;
13718
+ for (let it of command_table) {
13719
+ if (!it || !it.name) continue;
13720
+ let row = {
13721
+ id: it.name,
13722
+ alias: it.alias || [],
13723
+ description: it.description || ""
13724
+ };
13725
+ if (Array.isArray(it.schema)) row.parameters = attrs2json(it.schema);
13726
+ rows.push(row);
13727
+ }
13728
+ return rows;
13729
+ }
13730
+ /************************************************************
13731
+ * The methods the gclass actually implements. A table of
13732
+ * every possible one, mostly empty, tells the reader nothing.
13733
+ ************************************************************/
13734
+ function methods2json(mt) {
13735
+ let names = [];
13736
+ if (!mt || typeof mt !== "object") return names;
13737
+ for (let key of Object.keys(mt)) if (typeof mt[key] === "function") names.push(key);
13738
+ return names;
13739
+ }
13740
+ /************************************************************
13741
+ * The FSM: the events the gclass declares, and what each
13742
+ * state does with them.
13743
+ ************************************************************/
13744
+ function fsm2json(gclass) {
13745
+ let events = [];
13746
+ for (let ev of gclass.dl_events || []) events.push({
13747
+ event_name: ev.event_name,
13748
+ event_flag: bits2names(_yuneta_gobj_js.event_flag_t, ev.event_flag)
13749
+ });
13750
+ let states = {};
13751
+ for (let st of gclass.dl_states || []) {
13752
+ let actions = [];
13753
+ for (let ac of st.dl_actions || []) actions.push({
13754
+ event_name: ac.event_name,
13755
+ action: ac.action ? ac.action.name || "action" : "",
13756
+ next_state: ac.next_state || ""
13757
+ });
13758
+ states[st.state_name] = actions;
13759
+ }
13760
+ return {
13761
+ events,
13762
+ states
13763
+ };
13764
+ }
13765
+ /************************************************************
13766
+ * The description of a gclass REGISTERED IN THIS BROWSER YUNO,
13767
+ * or null when the registry does not know that name.
13768
+ *
13769
+ * Null is the answer for a gclass of a BACKEND yuno too: the
13770
+ * browser registry carries the GUI gclasses and nothing else,
13771
+ * so the caller asks the backend `view-gclass` for those and
13772
+ * shows whatever it answers -- this same shape.
13773
+ ************************************************************/
13774
+ function describe_local_gclass(gclass_name) {
13775
+ if (!gclass_name) return null;
13776
+ let gclass = (0, _yuneta_gobj_js.gclass_find_by_name)(gclass_name, false);
13777
+ if (!gclass) return null;
13778
+ return {
13779
+ id: gclass.gclass_name,
13780
+ gcflag: bits2names(_yuneta_gobj_js.gclass_flag_t, gclass.gclass_flag),
13781
+ attrs: attrs2json(gclass.attrs_table),
13782
+ commands: commands2json(gclass.command_table),
13783
+ gclass_methods: methods2json(gclass.gmt),
13784
+ internal_methods: methods2json(gclass.lmt),
13785
+ FSM: fsm2json(gclass),
13786
+ instances: Number(gclass.instances) || 0,
13787
+ trace_levels: Array.isArray(gclass.s_user_trace_level) ? gclass.s_user_trace_level : []
13788
+ };
13789
+ }
13790
+ //#endregion
13791
+ //#region src/yui_gclass_view.js
13792
+ /***********************************************************************
13793
+ * yui_gclass_view.js
13794
+ *
13795
+ * The gclass viewer: what a GClass IS, in a window.
13796
+ *
13797
+ * The framework had no such viewer. The C kernel answers
13798
+ * `view-gclass` with a full description (attrs, commands,
13799
+ * methods, FSM) and the only reader of it was a terminal.
13800
+ * This one draws that same document with C_YUI_JSON, so the
13801
+ * FSM of a gclass is a graph and its attrs table is a tree,
13802
+ * and it takes the document from either side:
13803
+ *
13804
+ * - a gclass of the browser yuno -> describe_local_gclass()
13805
+ * - a gclass of a backend yuno -> `opts.description`, the
13806
+ * answer of `view-gclass`
13807
+ *
13808
+ * One window at a time per host: the caller keeps the handle
13809
+ * and closes it, the way the treedb graph keeps its raw-JSON
13810
+ * viewer.
13811
+ *
13812
+ * Copyright (c) 2026, ArtGins.
13813
+ * All Rights Reserved.
13814
+ ***********************************************************************/
13815
+ /************************************************************
13816
+ * Can this app show a gclass at all?
13817
+ *
13818
+ * The viewer is C_YUI_JSON, and an app registers only the
13819
+ * gclasses it mounts. The caller asks this BEFORE drawing a
13820
+ * button, so an app that does not carry the viewer shows no
13821
+ * control instead of a control that fails.
13822
+ ************************************************************/
13823
+ function gclass_view_available() {
13824
+ return !!(0, _yuneta_gobj_js.gclass_find_by_name)("C_YUI_JSON", false);
13825
+ }
13826
+ /************************************************************
13827
+ * Open the viewer on `gclass_name`.
13828
+ *
13829
+ * opts:
13830
+ * description the document to draw. Omitted, it is
13831
+ * built from the browser registry.
13832
+ * title_prefix what the window says before the gclass
13833
+ * name (the yuno it belongs to).
13834
+ * on_close called when the reader dismisses it.
13835
+ *
13836
+ * Returns a handle for close_gclass_view(), or null (and a
13837
+ * logged reason) when there is nothing to show.
13838
+ ************************************************************/
13839
+ function open_gclass_view(host, gclass_name, opts) {
13840
+ opts = opts || {};
13841
+ if (!host || !gclass_name) {
13842
+ (0, _yuneta_gobj_js.log_error)(`yui_gclass_view: open without a host or a gclass name`);
13843
+ return null;
13844
+ }
13845
+ let shell = yui_shell_of(host);
13846
+ if (!gclass_view_available()) {
13847
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(host)}: C_YUI_JSON not registered by the app`);
13848
+ yui_shell_show_error(shell, "gclass viewer unavailable", { t: i18next.t });
13849
+ return null;
13850
+ }
13851
+ let description = opts.description || describe_local_gclass(gclass_name);
13852
+ if (!description) {
13853
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(host)}: gclass not registered here: ${gclass_name}`);
13854
+ yui_shell_show_error(shell, "gclass not registered in this yuno", { t: i18next.t });
13855
+ return null;
13856
+ }
13857
+ let jv = (0, _yuneta_gobj_js.gobj_create_service)(`gclass-view-${(0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(host))}`, "C_YUI_JSON", {
13858
+ json_data: description,
13859
+ subscriber: null
13860
+ }, host);
13861
+ if (!jv) {
13862
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(host)}: cannot create the gclass viewer`);
13863
+ return null;
13864
+ }
13865
+ let handle = {
13866
+ json_gobj: jv,
13867
+ win: null,
13868
+ modal: null
13869
+ };
13870
+ let $box = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(jv, "$container");
13871
+ let on_close = () => {
13872
+ if (typeof opts.on_close === "function") opts.on_close();
13873
+ };
13874
+ if (is_mobile$3()) {
13875
+ if (!shell) {
13876
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(host)}: no shell, cannot open the gclass sheet`);
13877
+ close_gclass_view(handle);
13878
+ return null;
13879
+ }
13880
+ handle.modal = yui_shell_show_modal(shell, $box, {
13881
+ dialog: true,
13882
+ logical_class: "GCLASS_VIEW_SHEET",
13883
+ title_prefix: opts.title_prefix || "",
13884
+ title: gclass_name,
13885
+ t: i18next.t,
13886
+ on_close
13887
+ });
13888
+ (0, _yuneta_gobj_js.gobj_start)(jv);
13889
+ return handle;
13890
+ }
13891
+ let $win_parent = window_layer_of(host, shell);
13892
+ handle.win = (0, _yuneta_gobj_js.gobj_create_service)(`gclass-win-${(0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(host))}`, "C_YUI_WINDOW", {
13893
+ $parent: $win_parent,
13894
+ subscriber: null,
13895
+ modal: false,
13896
+ showMax: true,
13897
+ showFooter: false,
13898
+ resizable: true,
13899
+ center: true,
13900
+ auto_save_size_and_position: true,
13901
+ width: 720,
13902
+ height: 640,
13903
+ logical_class: "GCLASS_VIEW_WINDOW",
13904
+ title_prefix: opts.title_prefix || "",
13905
+ title: gclass_name,
13906
+ icon: "yi-code",
13907
+ body: $box,
13908
+ manager: (0, _yuneta_gobj_js.gobj_find_service)("__window_manager__", false) || null,
13909
+ on_close
13910
+ }, host);
13911
+ if (!handle.win) {
13912
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(host)}: cannot create the gclass window`);
13913
+ close_gclass_view(handle);
13914
+ return null;
13915
+ }
13916
+ (0, _yuneta_gobj_js.gobj_start)(handle.win);
13917
+ (0, _yuneta_gobj_js.gobj_start)(jv);
13918
+ return handle;
13919
+ }
13920
+ /************************************************************
13921
+ * Close whatever open_gclass_view() opened. Safe on null and
13922
+ * on a handle whose window the reader already dismissed.
13923
+ ************************************************************/
13924
+ function close_gclass_view(handle) {
13925
+ if (!handle) return;
13926
+ let jv = handle.json_gobj;
13927
+ let win = handle.win;
13928
+ let modal = handle.modal;
13929
+ handle.json_gobj = null;
13930
+ handle.win = null;
13931
+ handle.modal = null;
13932
+ if (win && (0, _yuneta_gobj_js.is_gobj)(win)) try {
13933
+ if ((0, _yuneta_gobj_js.gobj_is_running)(win)) (0, _yuneta_gobj_js.gobj_stop)(win);
13934
+ (0, _yuneta_gobj_js.gobj_destroy)(win);
13935
+ } catch (e) {
13936
+ (0, _yuneta_gobj_js.log_warning)(`yui_gclass_view: window already gone: ${e}`);
13937
+ }
13938
+ if (modal && typeof modal.close === "function") try {
13939
+ modal.close();
13940
+ } catch (e) {
13941
+ (0, _yuneta_gobj_js.log_warning)(`yui_gclass_view: modal already gone: ${e}`);
13942
+ }
13943
+ if (jv && (0, _yuneta_gobj_js.is_gobj)(jv)) try {
13944
+ if ((0, _yuneta_gobj_js.gobj_is_running)(jv)) (0, _yuneta_gobj_js.gobj_stop)(jv);
13945
+ (0, _yuneta_gobj_js.gobj_destroy)(jv);
13946
+ } catch (e) {
13947
+ (0, _yuneta_gobj_js.log_warning)(`yui_gclass_view: viewer already gone: ${e}`);
13948
+ }
13949
+ }
13950
+ /************************************************************
13951
+ * Where to mount the window: the SAME element the caller's
13952
+ * own window hangs from.
13953
+ *
13954
+ * Not a preference -- a requirement. z-index only orders
13955
+ * siblings of one stacking context, so a viewer opened from a
13956
+ * window on `body` and mounted in the shell's popup layer
13957
+ * lands BEHIND its opener whatever number it is given, and
13958
+ * the button reads as one that did nothing.
13959
+ ************************************************************/
13960
+ function window_layer_of(host, shell) {
13961
+ let $c = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(host, "$container");
13962
+ if ($c && typeof $c.closest === "function") {
13963
+ let $win = $c.closest(".C_YUI_WINDOW");
13964
+ if ($win && $win.parentNode) return $win.parentNode;
13965
+ }
13966
+ return typeof document !== "undefined" && document.getElementById("top-layer") || shell && yui_shell_popup_layer(shell) || null;
13967
+ }
13968
+ /************************************************************
13969
+ * A window is not a sheet: below this width there is no room
13970
+ * for a title bar the reader can drag.
13971
+ ************************************************************/
13972
+ function is_mobile$3() {
13973
+ return typeof window !== "undefined" && window.innerWidth <= 768;
13974
+ }
13975
+ //#endregion
13395
13976
  //#region src/c_yui_gobj_tree_js.js
13396
13977
  /***********************************************************************
13397
13978
  * c_yui_gobj_tree_js.js
@@ -13502,6 +14083,9 @@ var PRIVATE_DATA$9 = {
13502
14083
  $popover: null,
13503
14084
  $popover_title: null,
13504
14085
  $popover_body: null,
14086
+ $popover_gclass: null,
14087
+ popover_node: null,
14088
+ gclass_view: null,
13505
14089
  node_by_id: null,
13506
14090
  collapse_state: null,
13507
14091
  resize_observer: null,
@@ -13516,34 +14100,92 @@ var PRIVATE_DATA$9 = {
13516
14100
  var __gclass__$9 = null;
13517
14101
  var __layout_registered__ = false;
13518
14102
  /***************************************************************
13519
- * Node colors by role
14103
+ * Node colours by role.
14104
+ *
14105
+ * Saturated hues, one family apart from the next, and the same
14106
+ * five everywhere the reader meets a gobj. `stroke` draws the
14107
+ * border and the edge; `accent` is the brighter sibling used
14108
+ * for the bar down the left of the card and the toggle badge,
14109
+ * so a card carries its colour twice at two intensities and is
14110
+ * readable at the zoom where the text is not.
13520
14111
  ***************************************************************/
13521
14112
  var ROLE_COLORS = {
13522
14113
  yuno: {
13523
- fill: "#E0F2FE",
13524
- stroke: "#075985"
14114
+ stroke: "#0284c7",
14115
+ accent: "#38bdf8"
13525
14116
  },
13526
14117
  service: {
13527
- fill: "#DCFCE7",
13528
- stroke: "#166534"
14118
+ stroke: "#059669",
14119
+ accent: "#34d399"
13529
14120
  },
13530
14121
  pure: {
13531
- fill: "#FEF9C3",
13532
- stroke: "#854D0E"
14122
+ stroke: "#d97706",
14123
+ accent: "#fbbf24"
13533
14124
  },
13534
14125
  volatil: {
13535
- fill: "#FCE7F3",
13536
- stroke: "#9D174D"
14126
+ stroke: "#db2777",
14127
+ accent: "#f472b6"
13537
14128
  },
13538
- default: {
13539
- fill: "#F3F4F6",
13540
- stroke: "#374151"
14129
+ child: {
14130
+ stroke: "#7c3aed",
14131
+ accent: "#a78bfa"
13541
14132
  }
13542
14133
  };
13543
- var STATE_COLORS = {
13544
- running_playing: "#15803D",
13545
- running: "#B45309",
13546
- stopped: "#B91C1C"
14134
+ /***************************************************************
14135
+ * Status colours: what the gobj is DOING.
14136
+ *
14137
+ * A pill and not a word: three states of one gobj are told
14138
+ * apart across a whole tree by colour long before anybody
14139
+ * reads them, and the dot alone never said which was which.
14140
+ * `disabled` is a fourth entry and not a status -- a disabled
14141
+ * gobj is still stopped or running -- but it takes the pill,
14142
+ * because it is the fact that explains the other one.
14143
+ ***************************************************************/
14144
+ var STATUS_COLORS = {
14145
+ light: {
14146
+ playing: {
14147
+ fg: "#065f46",
14148
+ bg: "#a7f3d0",
14149
+ dot: "#10b981"
14150
+ },
14151
+ running: {
14152
+ fg: "#92400e",
14153
+ bg: "#fde68a",
14154
+ dot: "#f59e0b"
14155
+ },
14156
+ stopped: {
14157
+ fg: "#991b1b",
14158
+ bg: "#fecaca",
14159
+ dot: "#ef4444"
14160
+ },
14161
+ disabled: {
14162
+ fg: "#334155",
14163
+ bg: "#e2e8f0",
14164
+ dot: "#94a3b8"
14165
+ }
14166
+ },
14167
+ dark: {
14168
+ playing: {
14169
+ fg: "#6ee7b7",
14170
+ bg: "rgba(16,185,129,0.28)",
14171
+ dot: "#34d399"
14172
+ },
14173
+ running: {
14174
+ fg: "#fcd34d",
14175
+ bg: "rgba(245,158,11,0.28)",
14176
+ dot: "#fbbf24"
14177
+ },
14178
+ stopped: {
14179
+ fg: "#fca5a5",
14180
+ bg: "rgba(239,68,68,0.28)",
14181
+ dot: "#f87171"
14182
+ },
14183
+ disabled: {
14184
+ fg: "#cbd5e1",
14185
+ bg: "rgba(148,163,184,0.28)",
14186
+ dot: "#94a3b8"
14187
+ }
14188
+ }
13547
14189
  };
13548
14190
  /***************************************************************
13549
14191
  * Fixed node sizes per role (px).
@@ -13553,16 +14195,16 @@ var STATE_COLORS = {
13553
14195
  var NODE_SIZES = {
13554
14196
  full: {
13555
14197
  yuno: {
13556
- w: 240,
13557
- h: 72
14198
+ w: 260,
14199
+ h: 96
13558
14200
  },
13559
14201
  service: {
13560
- w: 210,
13561
- h: 68
14202
+ w: 232,
14203
+ h: 92
13562
14204
  },
13563
14205
  child: {
13564
- w: 160,
13565
- h: 60
14206
+ w: 196,
14207
+ h: 86
13566
14208
  }
13567
14209
  },
13568
14210
  compact: {
@@ -13585,27 +14227,47 @@ var NODE_SIZES = {
13585
14227
  ***************************************************************/
13586
14228
  var GT_FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
13587
14229
  /***************************************************************
13588
- * Soft, theme-aware card palette derived from the role colour
13589
- * (same visual language as the treedb graph cards): light tint
13590
- * fill + role-colour border, brightened on dark.
14230
+ * Theme-aware card palette derived from the role colour.
14231
+ *
14232
+ * Brighter than a tint: at 9% of the role colour every card
14233
+ * was the same near-white rectangle and the tree read as a
14234
+ * wireframe. The fill carries enough of the hue to be named
14235
+ * from across the canvas, and the border and the accent bar
14236
+ * carry the rest.
13591
14237
  ***************************************************************/
13592
- function role_card_style(stroke, dark) {
14238
+ function role_card_style(colors, dark) {
14239
+ let stroke = colors.stroke;
14240
+ let accent = colors.accent;
13593
14241
  return {
13594
- bg: dark ? `color-mix(in srgb, ${stroke} 30%, #2c3542)` : `color-mix(in srgb, ${stroke} 9%, #ffffff)`,
13595
- border: dark ? `color-mix(in srgb, ${stroke} 85%, #ffffff)` : stroke,
13596
- title: dark ? "#e8eaed" : "#0f172a",
13597
- sub: dark ? "#9aa4b2" : "#64748b",
13598
- tagbg: dark ? `color-mix(in srgb, ${stroke} 42%, #2c3542)` : `color-mix(in srgb, ${stroke} 14%, #ffffff)`,
13599
- shadow: dark ? "0 1px 3px rgba(0,0,0,0.45)" : "0 1px 3px rgba(15,23,42,0.12)"
14242
+ bg: dark ? `color-mix(in srgb, ${stroke} 34%, #253044)` : `color-mix(in srgb, ${stroke} 13%, #ffffff)`,
14243
+ border: dark ? accent : stroke,
14244
+ accent: dark ? accent : stroke,
14245
+ title: dark ? "#f1f5f9" : "#0f172a",
14246
+ sub: dark ? "#a8b3c2" : "#475569",
14247
+ tagbg: dark ? `color-mix(in srgb, ${stroke} 48%, #253044)` : `color-mix(in srgb, ${stroke} 20%, #ffffff)`,
14248
+ tagfg: dark ? "#f1f5f9" : stroke,
14249
+ shadow: dark ? "0 2px 6px rgba(0,0,0,0.50)" : "0 2px 5px rgba(15,23,42,0.16)"
13600
14250
  };
13601
14251
  }
13602
14252
  /***************************************************************
13603
- * Map a gobj to its visual category.
14253
+ * The pill colours of a node, for the theme in force.
13604
14254
  ***************************************************************/
13605
- function get_node_category(target_gobj, is_root) {
13606
- if (is_root) return "yuno";
13607
- if ((0, _yuneta_gobj_js.gobj_is_service)(target_gobj)) return "service";
13608
- return "child";
14255
+ function status_card_style(d, dark) {
14256
+ let table = dark ? STATUS_COLORS.dark : STATUS_COLORS.light;
14257
+ if (d.disabled) return table.disabled;
14258
+ return table[node_status(d)] || table.stopped;
14259
+ }
14260
+ /***************************************************************
14261
+ * Map a node to its visual size class. Services are drawn
14262
+ * bigger than plain children so the service boundary of the
14263
+ * hierarchy is visible before anything is read.
14264
+ ***************************************************************/
14265
+ function get_node_category(d) {
14266
+ switch (node_role(d)) {
14267
+ case "yuno": return "yuno";
14268
+ case "service": return "service";
14269
+ default: return "child";
14270
+ }
13609
14271
  }
13610
14272
  /***************************************************************
13611
14273
  * Layouts registry.
@@ -13718,13 +14380,21 @@ function mt_create$9(gobj) {
13718
14380
  ***************************************************************/
13719
14381
  function mt_start$9(gobj) {
13720
14382
  restore_view_shape(gobj);
14383
+ let shell = yui_shell_of(gobj);
14384
+ if (shell) (0, _yuneta_gobj_js.gobj_subscribe_event)(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
13721
14385
  build_graph$2(gobj);
13722
14386
  load_tree(gobj);
13723
14387
  }
13724
14388
  /***************************************************************
13725
14389
  * Framework Method: Stop
13726
14390
  ***************************************************************/
13727
- function mt_stop$9(gobj) {}
14391
+ function mt_stop$9(gobj) {
14392
+ let priv = gobj.priv;
14393
+ let shell = yui_shell_of(gobj);
14394
+ if (shell) (0, _yuneta_gobj_js.gobj_unsubscribe_event)(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
14395
+ close_gclass_view(priv.gclass_view);
14396
+ priv.gclass_view = null;
14397
+ }
13728
14398
  /***************************************************************
13729
14399
  * Framework Method: Destroy
13730
14400
  ***************************************************************/
@@ -13750,6 +14420,8 @@ function mt_destroy$9(gobj) {
13750
14420
  priv.$popover = null;
13751
14421
  priv.$popover_title = null;
13752
14422
  priv.$popover_body = null;
14423
+ priv.$popover_gclass = null;
14424
+ priv.popover_node = null;
13753
14425
  priv.$layout_select = null;
13754
14426
  priv.node_by_id = null;
13755
14427
  priv.collapse_state = null;
@@ -13853,6 +14525,30 @@ function build_popover(gobj, $container) {
13853
14525
  text-overflow: ellipsis;
13854
14526
  `;
13855
14527
  $header.appendChild($title);
14528
+ if (gclass_view_available()) {
14529
+ let $gclass = document.createElement("button");
14530
+ $gclass.type = "button";
14531
+ $gclass.className = "GOBJ_TREE_POPOVER_GCLASS button is-small";
14532
+ $gclass.style.cssText = `
14533
+ margin-right: 6px;
14534
+ padding: 0 8px;
14535
+ height: 22px;
14536
+ font-size: 11px;
14537
+ white-space: nowrap;
14538
+ `;
14539
+ $gclass.textContent = (0, i18next.t)("gclass");
14540
+ $gclass.setAttribute("i18n", "gclass");
14541
+ $gclass.title = (0, i18next.t)("view gclass");
14542
+ $gclass.setAttribute("data-i18n-title", "view gclass");
14543
+ $gclass.setAttribute("aria-label", (0, i18next.t)("view gclass"));
14544
+ $gclass.setAttribute("data-i18n-aria-label", "view gclass");
14545
+ $gclass.addEventListener("click", (evt) => {
14546
+ evt.stopPropagation();
14547
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_OPEN_GCLASS", {}, gobj);
14548
+ });
14549
+ $header.appendChild($gclass);
14550
+ priv.$popover_gclass = $gclass;
14551
+ } else (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no C_YUI_JSON registered, the gclass viewer is not offered`);
13856
14552
  let $close = document.createElement("button");
13857
14553
  $close.type = "button";
13858
14554
  $close.className = "GOBJ_TREE_POPOVER_CLOSE";
@@ -13960,6 +14656,26 @@ function make_toolbar$1(gobj) {
13960
14656
  return $toolbar;
13961
14657
  }
13962
14658
  /************************************************************
14659
+ * The element G6 mounts its canvas in.
14660
+ *
14661
+ * Read from THIS gclass's own $container, never with
14662
+ * `document.getElementById()`: a view is not always in the
14663
+ * document when it is started -- a host that builds the
14664
+ * viewer and mounts it into a window afterwards starts it
14665
+ * DETACHED -- and a global lookup answers null there. It cost
14666
+ * a resize that never worked: the ResizeObserver below was
14667
+ * simply never attached, silently, and the canvas kept the
14668
+ * size it was born with for the life of the window.
14669
+ *
14670
+ * Null only if the UI was never built; that is a bug, and it
14671
+ * is logged where it matters rather than skipped.
14672
+ ************************************************************/
14673
+ function canvas_mount(gobj) {
14674
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
14675
+ if (!$container) return null;
14676
+ return $container.querySelector("#" + gobj.priv.canvas_id);
14677
+ }
14678
+ /************************************************************
13963
14679
  * Build a G6 graph instance
13964
14680
  ************************************************************/
13965
14681
  function build_graph$2(gobj) {
@@ -14003,8 +14719,9 @@ function build_graph$2(gobj) {
14003
14719
  hide_popover(gobj);
14004
14720
  });
14005
14721
  if (typeof ResizeObserver !== "undefined") {
14006
- let $canvas = document.getElementById(priv.canvas_id);
14007
- if ($canvas) {
14722
+ let $canvas = canvas_mount(gobj);
14723
+ if (!$canvas) (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no canvas mount, the graph will never resize`);
14724
+ else {
14008
14725
  let ro = new ResizeObserver(() => {
14009
14726
  if (priv._resize_raf) cancelAnimationFrame(priv._resize_raf);
14010
14727
  priv._resize_raf = requestAnimationFrame(() => {
@@ -14037,22 +14754,20 @@ function apply_layout(gobj) {
14037
14754
  });
14038
14755
  }
14039
14756
  /************************************************************
14040
- * Resolve role colors for a gobj
14041
- ************************************************************/
14042
- function get_role_colors(target_gobj, is_root) {
14043
- if (is_root) return ROLE_COLORS.yuno;
14044
- if ((0, _yuneta_gobj_js.gobj_is_volatil)(target_gobj)) return ROLE_COLORS.volatil;
14045
- if ((0, _yuneta_gobj_js.gobj_is_service)(target_gobj)) return ROLE_COLORS.service;
14046
- if ((0, _yuneta_gobj_js.gobj_is_pure_child)(target_gobj)) return ROLE_COLORS.pure;
14047
- return ROLE_COLORS.default;
14048
- }
14049
- /************************************************************
14050
- * Resolve state color
14757
+ * The role colours of a node.
14758
+ *
14759
+ * Note the ORDER: volatil before service. A volatil gobj that
14760
+ * is also a service is drawn volatil, because that is the fact
14761
+ * that decides how long it lives.
14051
14762
  ************************************************************/
14052
- function get_state_color(target_gobj) {
14053
- if (!(0, _yuneta_gobj_js.gobj_is_running)(target_gobj)) return STATE_COLORS.stopped;
14054
- if ((0, _yuneta_gobj_js.gobj_is_playing)(target_gobj)) return STATE_COLORS.running_playing;
14055
- return STATE_COLORS.running;
14763
+ function get_role_colors(d) {
14764
+ switch (node_role(d)) {
14765
+ case "yuno": return ROLE_COLORS.yuno;
14766
+ case "volatil": return ROLE_COLORS.volatil;
14767
+ case "service": return ROLE_COLORS.service;
14768
+ case "pure": return ROLE_COLORS.pure;
14769
+ default: return ROLE_COLORS.child;
14770
+ }
14056
14771
  }
14057
14772
  /************************************************************
14058
14773
  * Generate a unique node id
@@ -14084,72 +14799,135 @@ function is_node_collapsed(gobj, full_name, num_children) {
14084
14799
  ************************************************************/
14085
14800
  function render_toggle_html(node_id, collapsed, num_children, cs) {
14086
14801
  let label = collapsed ? "+" + num_children : "−";
14802
+ let title = collapsed ? (0, i18next.t)("expand children") : (0, i18next.t)("collapse children");
14087
14803
  return `<span
14088
14804
  data-gobj-toggle="true"
14089
14805
  data-node-id="${node_id}"
14090
- title="${collapsed ? "Expand children" : "Collapse children"}"
14806
+ title="${(0, _yuneta_gobj_js.escapeHtml)(title)}"
14091
14807
  style="
14092
14808
  flex: 0 0 auto;
14093
14809
  display: inline-flex;
14094
14810
  align-items: center;
14095
14811
  justify-content: center;
14096
- min-width: 22px;
14097
- height: 16px;
14098
- padding: 0 5px;
14812
+ min-width: 24px;
14813
+ height: 18px;
14814
+ padding: 0 6px;
14099
14815
  margin-left: 4px;
14100
- background: ${cs.tagbg};
14101
- color: ${cs.border};
14102
- border: 1px solid ${cs.border};
14103
- border-radius: 6px;
14816
+ background: ${cs.accent};
14817
+ color: #ffffff;
14818
+ border: 1px solid ${cs.accent};
14819
+ border-radius: 9px;
14104
14820
  font-size: 10px;
14105
- font-weight: 600;
14821
+ font-weight: 700;
14106
14822
  line-height: 1;
14107
14823
  cursor: pointer;
14108
14824
  user-select: none;
14109
14825
  ">${label}</span>`;
14110
14826
  }
14111
14827
  /************************************************************
14112
- * Recursively build nodes and edges from the gobj tree
14828
+ * HTML for the status pill: what the gobj is DOING.
14829
+ *
14830
+ * A dot plus a word, and the word is dropped in the compact
14831
+ * card where there is no room for it -- the colour still
14832
+ * carries it, and the `title` says it in full either way.
14113
14833
  ************************************************************/
14114
- function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, compact) {
14834
+ function render_status_html(d, ss, labels, with_label) {
14835
+ let full = d.disabled ? labels.field.disabled : labels.status[node_status_key(d)] || node_status_key(d);
14836
+ let label = d.disabled ? labels.field.disabled : labels.status_short[node_status(d)] || node_status(d);
14837
+ if (!with_label) return `<span title="${(0, _yuneta_gobj_js.escapeHtml)(full)}" style="
14838
+ flex: 0 0 auto;
14839
+ width: 10px; height: 10px; border-radius: 50%;
14840
+ background: ${ss.dot};
14841
+ box-shadow: 0 0 0 2px ${ss.bg};
14842
+ margin-left: 6px;
14843
+ "></span>`;
14844
+ return `<span title="${(0, _yuneta_gobj_js.escapeHtml)(full)}" style="
14845
+ flex: 0 0 auto;
14846
+ display: inline-flex;
14847
+ align-items: center;
14848
+ gap: 4px;
14849
+ padding: 1px 7px 1px 5px;
14850
+ background: ${ss.bg};
14851
+ color: ${ss.fg};
14852
+ border-radius: 9px;
14853
+ font-size: 10px;
14854
+ font-weight: 700;
14855
+ line-height: 1.45;
14856
+ white-space: nowrap;
14857
+ "><span style="width:7px; height:7px; border-radius:50%; background:${ss.dot};"></span>${(0, _yuneta_gobj_js.escapeHtml)(label)}</span>`;
14858
+ }
14859
+ /************************************************************
14860
+ * HTML for the badges: what the gobj IS, besides its colour.
14861
+ ************************************************************/
14862
+ function render_badges_html(d, cs, labels) {
14863
+ let html = "";
14864
+ for (let key of node_badge_keys(d)) {
14865
+ let label = labels.badge[key] || key;
14866
+ html += `<span style="
14867
+ font-size: 10px;
14868
+ font-weight: 600;
14869
+ padding: 0 6px;
14870
+ margin-right: 4px;
14871
+ background: ${cs.tagbg};
14872
+ color: ${cs.tagfg};
14873
+ border: 1px solid ${cs.accent};
14874
+ border-radius: 9px;
14875
+ white-space: nowrap;
14876
+ ">${(0, _yuneta_gobj_js.escapeHtml)(label)}</span>`;
14877
+ }
14878
+ return html;
14879
+ }
14880
+ /************************************************************
14881
+ * Recursively build nodes and edges from a NODE DESCRIPTOR
14882
+ * tree (gobj_tree_model.js), never from gobjs: the same
14883
+ * builder draws the browser yuno and a backend one.
14884
+ *
14885
+ * The descriptor is also what the popover reads, so the view
14886
+ * fields it needs are written onto it here (`full_name`,
14887
+ * `short_name`, `num_children`, `is_collapsed`,
14888
+ * `direct_children`) instead of copied into a second object
14889
+ * that can then disagree with the first.
14890
+ ************************************************************/
14891
+ function build_gobj_nodes(gobj, d, nodes, edges, parent_id, compact, labels) {
14115
14892
  let priv = gobj.priv;
14116
14893
  let node_id = gen_node_id(gobj);
14117
- let gclass_name = (0, _yuneta_gobj_js.gobj_gclass_name)(target_gobj) || "";
14118
- let instance_name = (0, _yuneta_gobj_js.gobj_name)(target_gobj) || "";
14119
- let state = (0, _yuneta_gobj_js.gobj_current_state)(target_gobj) || "";
14120
- let full_name = (0, _yuneta_gobj_js.gobj_full_name)(target_gobj);
14121
- let colors = get_role_colors(target_gobj, is_root);
14122
14894
  let dark = yui_is_dark();
14123
- let cs = role_card_style(colors.stroke, dark);
14124
- let state_color = get_state_color(target_gobj);
14125
- let running = (0, _yuneta_gobj_js.gobj_is_running)(target_gobj);
14126
- let playing = (0, _yuneta_gobj_js.gobj_is_playing)(target_gobj);
14127
- let status_text = !running ? "stopped" : playing ? "playing" : "running";
14128
- let category = get_node_category(target_gobj, is_root);
14895
+ let colors = get_role_colors(d);
14896
+ let cs = role_card_style(colors, dark);
14897
+ let ss = status_card_style(d, dark);
14898
+ let category = get_node_category(d);
14129
14899
  let size = NODE_SIZES[compact ? "compact" : "full"][category];
14130
14900
  let width = size.w;
14131
14901
  let height = size.h;
14132
14902
  let border_width = category === "yuno" || category === "service" ? 2 : 1;
14133
- let children = target_gobj.dl_children || [];
14903
+ let border_style = d.disabled ? "dashed" : "solid";
14904
+ let children = d.children || [];
14134
14905
  let num_children = children.length;
14135
14906
  let has_children = num_children > 0;
14136
- let collapsed = has_children && is_node_collapsed(gobj, full_name, num_children);
14907
+ let collapsed = has_children && is_node_collapsed(gobj, d.fullname, num_children);
14137
14908
  let direct_children = [];
14138
14909
  for (let child of children) direct_children.push({
14139
- full_name: (0, _yuneta_gobj_js.gobj_full_name)(child),
14140
- has_grandchildren: (child.dl_children || []).length > 0
14910
+ full_name: child.fullname,
14911
+ has_grandchildren: (child.children || []).length > 0
14141
14912
  });
14913
+ d.full_name = d.fullname;
14914
+ d.short_name = d.shortname;
14915
+ d.num_children = num_children;
14916
+ d.is_collapsed = collapsed;
14917
+ d.direct_children = direct_children;
14918
+ d.node_id = node_id;
14142
14919
  let toggle_html = has_children ? render_toggle_html(node_id, collapsed, num_children, cs) : "";
14920
+ let card_title = d.gclass + (d.name ? "^" + d.name : "");
14143
14921
  let node_html;
14144
14922
  if (compact) {
14145
- let compact_label = (0, _yuneta_gobj_js.escapeHtml)(instance_name ? gclass_name + "^" + instance_name : gclass_name);
14923
+ let compact_label = (0, _yuneta_gobj_js.escapeHtml)(card_title);
14146
14924
  node_html = `
14147
- <div class="GOBJ_CARD" data-gobj-name="${(0, _yuneta_gobj_js.escapeHtml)(full_name)}" style="
14925
+ <div class="GOBJ_CARD" data-gobj-name="${(0, _yuneta_gobj_js.escapeHtml)(d.fullname)}" style="
14148
14926
  width: ${width}px;
14149
14927
  height: ${height}px;
14150
14928
  background: ${cs.bg};
14151
- border: ${border_width}px solid ${cs.border};
14152
- border-left: 5px solid ${cs.border};
14929
+ border: ${border_width}px ${border_style} ${cs.border};
14930
+ border-left: 6px solid ${cs.accent};
14153
14931
  border-radius: 7px;
14154
14932
  box-shadow: ${cs.shadow};
14155
14933
  font-family: ${GT_FONT};
@@ -14158,34 +14936,24 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
14158
14936
  padding: 0 8px;
14159
14937
  box-sizing: border-box;
14160
14938
  overflow: hidden;
14939
+ opacity: ${d.disabled ? "0.72" : "1"};
14161
14940
  cursor: pointer;
14162
14941
  " title="${compact_label}">
14163
14942
  <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>
14164
- <span title="${(0, _yuneta_gobj_js.escapeHtml)(state || status_text)}" style="
14165
- flex: 0 0 auto;
14166
- width: 8px; height: 8px; border-radius: 50%;
14167
- background: ${state_color}; margin-left: 6px;
14168
- "></span>
14943
+ ${render_status_html(d, ss, labels, false)}
14169
14944
  ${toggle_html}
14170
14945
  </div>`;
14171
14946
  } else {
14172
- let header_label = (0, _yuneta_gobj_js.escapeHtml)(gclass_name);
14173
- let name_label = (0, _yuneta_gobj_js.escapeHtml)(instance_name);
14174
- let state_label = (0, _yuneta_gobj_js.escapeHtml)(state);
14175
- let badges = [];
14176
- if (is_root) badges.push("yuno");
14177
- else {
14178
- if ((0, _yuneta_gobj_js.gobj_is_service)(target_gobj)) badges.push("service");
14179
- if ((0, _yuneta_gobj_js.gobj_is_pure_child)(target_gobj)) badges.push("pure");
14180
- if ((0, _yuneta_gobj_js.gobj_is_volatil)(target_gobj)) badges.push("volatil");
14181
- }
14182
- let badges_html = badges.map((b) => `<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>`).join("");
14947
+ let header_label = (0, _yuneta_gobj_js.escapeHtml)(d.gclass);
14948
+ let name_label = (0, _yuneta_gobj_js.escapeHtml)(d.name);
14949
+ let state_label = (0, _yuneta_gobj_js.escapeHtml)(d.state);
14183
14950
  node_html = `
14184
- <div class="GOBJ_CARD" data-gobj-name="${(0, _yuneta_gobj_js.escapeHtml)(full_name)}" style="
14951
+ <div class="GOBJ_CARD" data-gobj-name="${(0, _yuneta_gobj_js.escapeHtml)(d.fullname)}" style="
14185
14952
  width: ${width}px;
14186
14953
  height: ${height}px;
14187
14954
  background: ${cs.bg};
14188
- border: ${border_width}px solid ${cs.border};
14955
+ border: ${border_width}px ${border_style} ${cs.border};
14956
+ border-left: 6px solid ${cs.accent};
14189
14957
  border-radius: 9px;
14190
14958
  box-shadow: ${cs.shadow};
14191
14959
  overflow: hidden;
@@ -14194,48 +14962,29 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
14194
14962
  display: flex;
14195
14963
  flex-direction: column;
14196
14964
  justify-content: center;
14197
- padding: 5px 9px;
14198
- gap: 1px;
14965
+ padding: 6px 9px;
14966
+ gap: 2px;
14967
+ opacity: ${d.disabled ? "0.72" : "1"};
14199
14968
  cursor: pointer;
14200
- " title="${(0, _yuneta_gobj_js.escapeHtml)(gclass_name + (instance_name ? "^" + instance_name : ""))}">
14969
+ " title="${(0, _yuneta_gobj_js.escapeHtml)(card_title)}">
14201
14970
  <div style="flex:0 0 auto; display:flex; align-items:center; gap:6px; overflow:hidden;">
14202
- <span style="flex:1 1 auto; min-width:0; font-size:12px; line-height:1.25; font-weight:600; color:${cs.title}; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${header_label}</span>
14971
+ <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>
14203
14972
  ${toggle_html}
14204
14973
  </div>
14205
14974
  <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 || "&nbsp;"}</div>
14206
14975
  <div style="flex:0 0 auto; display:flex; justify-content:space-between; align-items:center; gap:6px; overflow:hidden;">
14207
- <span style="flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${badges_html}</span>
14208
- <span style="flex:0 0 auto; display:flex; align-items:center; gap:4px; color:${state_color}; font-weight:600; font-size:11px; white-space:nowrap;">
14209
- <span style="width:7px; height:7px; border-radius:50%; background:${state_color};"></span>${state_label || status_text}
14210
- </span>
14976
+ <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 || "&nbsp;"}</span>
14977
+ ${render_status_html(d, ss, labels, true)}
14211
14978
  </div>
14979
+ <div style="flex:0 0 auto; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;">${render_badges_html(d, cs, labels)}</div>
14212
14980
  </div>`;
14213
14981
  }
14214
- let parent_gobj = (0, _yuneta_gobj_js.gobj_parent)(target_gobj);
14215
- let node_data = {
14216
- gclass: gclass_name,
14217
- name: instance_name,
14218
- short_name: (0, _yuneta_gobj_js.gobj_short_name)(target_gobj),
14219
- full_name,
14220
- parent_short_name: parent_gobj ? (0, _yuneta_gobj_js.gobj_short_name)(parent_gobj) : "",
14221
- state,
14222
- category,
14223
- is_yuno: is_root,
14224
- is_service: (0, _yuneta_gobj_js.gobj_is_service)(target_gobj),
14225
- is_pure_child: (0, _yuneta_gobj_js.gobj_is_pure_child)(target_gobj),
14226
- is_volatil: (0, _yuneta_gobj_js.gobj_is_volatil)(target_gobj),
14227
- is_running: running,
14228
- is_playing: playing,
14229
- num_children,
14230
- is_collapsed: collapsed,
14231
- direct_children
14232
- };
14233
14982
  if (!priv.node_by_id) priv.node_by_id = {};
14234
- priv.node_by_id[node_id] = node_data;
14983
+ priv.node_by_id[node_id] = d;
14235
14984
  let node = {
14236
14985
  id: node_id,
14237
14986
  type: "html",
14238
- data: node_data,
14987
+ data: d,
14239
14988
  style: {
14240
14989
  innerHTML: node_html,
14241
14990
  size: [width, height],
@@ -14248,11 +14997,11 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
14248
14997
  source: parent_id,
14249
14998
  target: node_id,
14250
14999
  style: {
14251
- stroke: colors.stroke,
14252
- lineWidth: 1
15000
+ stroke: colors.accent,
15001
+ lineWidth: 1.4
14253
15002
  }
14254
15003
  });
14255
- if (!collapsed) for (let child of children) build_gobj_nodes(gobj, child, nodes, edges, node_id, false, compact);
15004
+ if (!collapsed) for (let child of children) build_gobj_nodes(gobj, child, nodes, edges, node_id, compact, labels);
14256
15005
  return node_id;
14257
15006
  }
14258
15007
  /************************************************************
@@ -14269,9 +15018,13 @@ function load_tree(gobj) {
14269
15018
  let priv = gobj.priv;
14270
15019
  let graph = priv.graph;
14271
15020
  if (!graph) return;
14272
- let yuno = (0, _yuneta_gobj_js.gobj_yuno)();
14273
- if (!yuno) return;
15021
+ let root = describe_js_gobj((0, _yuneta_gobj_js.gobj_yuno)(), true);
15022
+ if (!root) {
15023
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no yuno to draw`);
15024
+ return;
15025
+ }
14274
15026
  let layout_cfg = get_layout_cfg((0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "layout"));
15027
+ let labels = node_labels(i18next.t);
14275
15028
  let anchor = priv.pending_anchor || null;
14276
15029
  let preserve_view = !!priv.pending_preserve_view;
14277
15030
  priv.pending_anchor = null;
@@ -14280,7 +15033,7 @@ function load_tree(gobj) {
14280
15033
  priv.node_by_id = {};
14281
15034
  let nodes = [];
14282
15035
  let edges = [];
14283
- build_gobj_nodes(gobj, yuno, nodes, edges, null, true, layout_cfg.compact);
15036
+ build_gobj_nodes(gobj, root, nodes, edges, null, layout_cfg.compact, labels);
14284
15037
  if (nodes.length > 0) {
14285
15038
  let root_id = nodes[0].id;
14286
15039
  graph.setData({
@@ -14381,46 +15134,17 @@ function refresh_tree(gobj, opts) {
14381
15134
  }
14382
15135
  }
14383
15136
  /************************************************************
14384
- * Rows to show in the popover for a given node data.
14385
- *
14386
- * This is the extension point: to surface a new attribute,
14387
- * collect it into `node_data` in build_gobj_nodes(), then
14388
- * append a `[label, value]` pair here.
14389
- ************************************************************/
14390
- function get_node_info_rows(node_data) {
14391
- let rows = [];
14392
- rows.push([(0, i18next.t)("gclass"), node_data.gclass || "—"]);
14393
- rows.push([(0, i18next.t)("name"), node_data.name || "—"]);
14394
- if (node_data.full_name && node_data.full_name !== node_data.short_name) rows.push([(0, i18next.t)("full name"), node_data.full_name]);
14395
- let role;
14396
- if (node_data.is_yuno) role = "yuno";
14397
- else if (node_data.is_service) role = "service";
14398
- else if (node_data.is_volatil) role = "volatil child";
14399
- else if (node_data.is_pure_child) role = "pure child";
14400
- else role = "child";
14401
- rows.push([(0, i18next.t)("role"), role]);
14402
- let status;
14403
- if (!node_data.is_running) status = "stopped";
14404
- else if (node_data.is_playing) status = "running + playing";
14405
- else status = "running";
14406
- rows.push([(0, i18next.t)("status"), status]);
14407
- rows.push([(0, i18next.t)("state"), node_data.state || "—"]);
14408
- if (node_data.parent_short_name) rows.push([(0, i18next.t)("parent"), node_data.parent_short_name]);
14409
- if (typeof node_data.num_children === "number") {
14410
- let kids = node_data.num_children;
14411
- if (kids > 0 && node_data.is_collapsed) rows.push([(0, i18next.t)("children"), kids + " " + (0, i18next.t)("(collapsed)")]);
14412
- else if (kids > 0) rows.push([(0, i18next.t)("children"), String(kids)]);
14413
- }
14414
- return rows;
14415
- }
14416
- /************************************************************
14417
15137
  * Render popover body from the info rows of a node.
15138
+ *
15139
+ * The rows come from gobj_tree_model.js, so a node of the
15140
+ * browser yuno and a node of a backend one are read out with
15141
+ * the same words in the same order.
14418
15142
  ************************************************************/
14419
15143
  function render_popover_body(gobj, node_data) {
14420
15144
  let $body = gobj.priv.$popover_body;
14421
15145
  if (!$body) return;
14422
- $body.innerHTML = get_node_info_rows(node_data).map(([label, value]) => `
14423
- <div style="display:grid; grid-template-columns: 90px 1fr; column-gap:10px; padding: 2px 0;">
15146
+ $body.innerHTML = node_info_rows(node_data, i18next.t).map(([label, value]) => `
15147
+ <div style="display:grid; grid-template-columns: 96px 1fr; column-gap:10px; padding: 2px 0;">
14424
15148
  <span style="color:var(--bulma-text-weak, #6B7280); font-weight:500;">${(0, _yuneta_gobj_js.escapeHtml)(String(label))}</span>
14425
15149
  <span style="color:var(--bulma-text-strong, #1A1A1A); word-break:break-all;">${(0, _yuneta_gobj_js.escapeHtml)(String(value))}</span>
14426
15150
  </div>
@@ -14437,6 +15161,7 @@ function show_popover(gobj, node_data, client_x, client_y) {
14437
15161
  let $title = priv.$popover_title;
14438
15162
  let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
14439
15163
  if (!$popover || !$title || !$container) return;
15164
+ priv.popover_node = node_data;
14440
15165
  $title.textContent = node_data.short_name || node_data.gclass + (node_data.name ? "^" + node_data.name : "");
14441
15166
  $title.title = $title.textContent;
14442
15167
  render_popover_body(gobj, node_data);
@@ -14467,6 +15192,7 @@ function hide_popover(gobj) {
14467
15192
  if (priv.$popover) priv.$popover.style.display = "none";
14468
15193
  if (priv.$popover_body) priv.$popover_body.innerHTML = "";
14469
15194
  if (priv.$popover_title) priv.$popover_title.textContent = "";
15195
+ priv.popover_node = null;
14470
15196
  }
14471
15197
  /************************************************************
14472
15198
  * Generic tree layout engine.
@@ -14993,12 +15719,11 @@ function ac_node_click$3(gobj, event, kw, src) {
14993
15719
  *
14994
15720
  ************************************************************/
14995
15721
  function ac_show$6(gobj, event, kw, src) {
14996
- let priv = gobj.priv;
14997
- let graph = priv.graph;
15722
+ let graph = gobj.priv.graph;
14998
15723
  save_view_state(gobj);
14999
15724
  refresh_tree(gobj);
15000
15725
  if (graph) {
15001
- let $canvas = document.getElementById(priv.canvas_id);
15726
+ let $canvas = canvas_mount(gobj);
15002
15727
  if ($canvas) {
15003
15728
  let cw = $canvas.clientWidth;
15004
15729
  let ch = $canvas.clientHeight;
@@ -15017,10 +15742,9 @@ function ac_show$6(gobj, event, kw, src) {
15017
15742
  * No fitView: preserve the user's current pan/zoom.
15018
15743
  ************************************************************/
15019
15744
  function ac_resize$1(gobj, event, kw, src) {
15020
- let priv = gobj.priv;
15021
- let graph = priv.graph;
15745
+ let graph = gobj.priv.graph;
15022
15746
  if (graph) {
15023
- let $canvas = document.getElementById(priv.canvas_id);
15747
+ let $canvas = canvas_mount(gobj);
15024
15748
  if ($canvas) {
15025
15749
  let cw = $canvas.clientWidth;
15026
15750
  let ch = $canvas.clientHeight;
@@ -15036,6 +15760,68 @@ function ac_hide$5(gobj, event, kw, src) {
15036
15760
  hide_popover(gobj);
15037
15761
  return 0;
15038
15762
  }
15763
+ /************************************************************
15764
+ * Open the gclass of the node the popover is showing.
15765
+ *
15766
+ * One window at a time: opening a second gclass replaces the
15767
+ * first rather than stacking windows nobody closes.
15768
+ ************************************************************/
15769
+ function ac_open_gclass(gobj, event, kw, src) {
15770
+ let priv = gobj.priv;
15771
+ let node_data = priv.popover_node;
15772
+ if (!node_data || !node_data.gclass) {
15773
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: EV_OPEN_GCLASS with no node`);
15774
+ return -1;
15775
+ }
15776
+ close_gclass_view(priv.gclass_view);
15777
+ priv.gclass_view = null;
15778
+ priv.gclass_view = open_gclass_view(gobj, node_data.gclass, {
15779
+ title_prefix: (0, _yuneta_gobj_js.gobj_yuno_name)() || "",
15780
+ on_close: () => {
15781
+ if ((0, _yuneta_gobj_js.gobj_is_destroying)(gobj)) return;
15782
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_GCLASS_CLOSED", {}, gobj);
15783
+ }
15784
+ });
15785
+ return 0;
15786
+ }
15787
+ /************************************************************
15788
+ * The reader dismissed the gclass window (its own ✕).
15789
+ *
15790
+ * C_YUI_WINDOW.close_window() calls on_close and THEN destroys
15791
+ * itself, so here we only drop our reference -- destroying it
15792
+ * again would destroy it twice.
15793
+ ************************************************************/
15794
+ function ac_gclass_closed(gobj, event, kw, src) {
15795
+ gobj.priv.gclass_view = null;
15796
+ return 0;
15797
+ }
15798
+ /************************************************************
15799
+ * The gclass viewer asked for a subtree.
15800
+ *
15801
+ * It cannot: the gclass description is a COMPLETE document,
15802
+ * it carries no `__collapsed__` stub, so nothing in it is
15803
+ * fetchable. The event is declared because the viewer is
15804
+ * hosted as a child and subscribes its host to everything it
15805
+ * publishes -- and it is logged, because arriving here means
15806
+ * the document was not the one we built.
15807
+ ************************************************************/
15808
+ function ac_gclass_expand_path(gobj, event, kw, src) {
15809
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: EV_EXPAND_PATH on a complete document: ${kw && kw.path}`);
15810
+ return -1;
15811
+ }
15812
+ /************************************************************
15813
+ * The app switched language.
15814
+ *
15815
+ * A rebuild, not a re-translation: the words live inside the
15816
+ * innerHTML of G6 nodes, where refresh_language() cannot go.
15817
+ ************************************************************/
15818
+ function ac_language_changed$3(gobj, event, kw, src) {
15819
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
15820
+ if ($container) (0, _yuneta_gobj_js.refresh_language)($container, i18next.t);
15821
+ hide_popover(gobj);
15822
+ refresh_tree(gobj);
15823
+ return 0;
15824
+ }
15039
15825
  /***************************
15040
15826
  * FSM
15041
15827
  ***************************/
@@ -15069,7 +15855,11 @@ function create_gclass$9(gclass_name) {
15069
15855
  ["EV_CENTER_ANCHOR", 0],
15070
15856
  ["EV_RESIZE", 0],
15071
15857
  ["EV_SHOW", 0],
15072
- ["EV_HIDE", 0]
15858
+ ["EV_HIDE", 0],
15859
+ ["EV_OPEN_GCLASS", 0],
15860
+ ["EV_GCLASS_CLOSED", 0],
15861
+ ["EV_EXPAND_PATH", 0],
15862
+ ["EV_LANGUAGE_CHANGED", 0]
15073
15863
  ], [["ST_IDLE", [
15074
15864
  [
15075
15865
  "EV_REFRESH",
@@ -15150,6 +15940,26 @@ function create_gclass$9(gclass_name) {
15150
15940
  "EV_HIDE",
15151
15941
  ac_hide$5,
15152
15942
  null
15943
+ ],
15944
+ [
15945
+ "EV_OPEN_GCLASS",
15946
+ ac_open_gclass,
15947
+ null
15948
+ ],
15949
+ [
15950
+ "EV_GCLASS_CLOSED",
15951
+ ac_gclass_closed,
15952
+ null
15953
+ ],
15954
+ [
15955
+ "EV_EXPAND_PATH",
15956
+ ac_gclass_expand_path,
15957
+ null
15958
+ ],
15959
+ [
15960
+ "EV_LANGUAGE_CHANGED",
15961
+ ac_language_changed$3,
15962
+ null
15153
15963
  ]
15154
15964
  ]]], gmt$9, 0, attrs_table$9, PRIVATE_DATA$9, 0, 0, 0, 0);
15155
15965
  if (!__gclass__$9) return -1;
@@ -54595,7 +55405,6 @@ function open_json_viewer(gobj) {
54595
55405
  return;
54596
55406
  }
54597
55407
  priv.json_gobj = jv;
54598
- (0, _yuneta_gobj_js.gobj_start)(jv);
54599
55408
  let $box = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(jv, "$container");
54600
55409
  if (mobile) {
54601
55410
  if (!shell) {
@@ -54614,6 +55423,7 @@ function open_json_viewer(gobj) {
54614
55423
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_JSON_CLOSED", {}, gobj);
54615
55424
  }
54616
55425
  });
55426
+ (0, _yuneta_gobj_js.gobj_start)(jv);
54617
55427
  } else {
54618
55428
  let $win_parent = shell && yui_shell_popup_layer(shell) || typeof document !== "undefined" && document.getElementById("top-layer") || null;
54619
55429
  priv.json_win = (0, _yuneta_gobj_js.gobj_create_service)(`treedb-jsonwin-${(0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj))}`, "C_YUI_WINDOW", {
@@ -54644,6 +55454,7 @@ function open_json_viewer(gobj) {
54644
55454
  return;
54645
55455
  }
54646
55456
  (0, _yuneta_gobj_js.gobj_start)(priv.json_win);
55457
+ (0, _yuneta_gobj_js.gobj_start)(jv);
54647
55458
  }
54648
55459
  request_print_tranger(gobj, "");
54649
55460
  }
@@ -54671,7 +55482,7 @@ function close_json_viewer(gobj) {
54671
55482
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: already gone: ${e}`);
54672
55483
  }
54673
55484
  if (jv && (0, _yuneta_gobj_js.is_gobj)(jv)) try {
54674
- (0, _yuneta_gobj_js.gobj_stop)(jv);
55485
+ if ((0, _yuneta_gobj_js.gobj_is_running)(jv)) (0, _yuneta_gobj_js.gobj_stop)(jv);
54675
55486
  (0, _yuneta_gobj_js.gobj_destroy)(jv);
54676
55487
  } catch (e) {
54677
55488
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: already gone: ${e}`);