@yuneta/gobj-ui 7.23.29 → 7.23.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;
@@ -13205,7 +13205,7 @@ function ac_json_item_clicked(gobj, event, kw, src) {
13205
13205
  /************************************************************
13206
13206
  * EV_LANGUAGE_CHANGED — re-translate chrome + re-render
13207
13207
  ************************************************************/
13208
- function ac_language_changed$3(gobj, event, kw, src) {
13208
+ function ac_language_changed$4(gobj, event, kw, src) {
13209
13209
  let priv = gobj.priv;
13210
13210
  let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
13211
13211
  if ($container) {
@@ -13266,7 +13266,7 @@ function create_gclass$10(gclass_name) {
13266
13266
  ],
13267
13267
  [
13268
13268
  "EV_LANGUAGE_CHANGED",
13269
- ac_language_changed$3,
13269
+ ac_language_changed$4,
13270
13270
  null
13271
13271
  ],
13272
13272
  [
@@ -13342,7 +13342,7 @@ function create_gclass$10(gclass_name) {
13342
13342
  ],
13343
13343
  [
13344
13344
  "EV_LANGUAGE_CHANGED",
13345
- ac_language_changed$3,
13345
+ ac_language_changed$4,
13346
13346
  null
13347
13347
  ],
13348
13348
  [
@@ -13392,6 +13392,567 @@ function register_c_yui_json() {
13392
13392
  return create_gclass$10(GCLASS_NAME$10);
13393
13393
  }
13394
13394
  //#endregion
13395
+ //#region src/gobj_tree_model.js
13396
+ /***********************************************************************
13397
+ * gobj_tree_model.js
13398
+ *
13399
+ * The gobj tree of a yuno, as plain data.
13400
+ *
13401
+ * The view (C_YUI_GOBJ_TREE_JS) draws NODE DESCRIPTORS, never
13402
+ * gobjs. There are two producers of a descriptor, because the
13403
+ * view has to show the gobjs of the BACKEND too:
13404
+ *
13405
+ * - describe_js_gobj() the gobj tree of THIS browser yuno
13406
+ * - describe_backend_tree() the answer of the backend command
13407
+ * `view-gobj-tree` of another yuno
13408
+ *
13409
+ * Both answer the SAME shape, and that shape is the backend's:
13410
+ * the field names are the ones `gobj2json()` writes in
13411
+ * `kernel/c/gobj-c/src/gobj.c` (fullname, shortname, gclass,
13412
+ * name, parent, state, running, playing, service, disabled,
13413
+ * volatil, bottom_gobj, gobj_flags, commands, gobj_trace_level).
13414
+ * Anything the browser runtime cannot answer is simply absent,
13415
+ * and every reader here treats absent as "not known", never as
13416
+ * false.
13417
+ *
13418
+ * Copyright (c) 2026, ArtGins.
13419
+ * All Rights Reserved.
13420
+ ***********************************************************************/
13421
+ /************************************************************
13422
+ * The role of a node: "yuno" | "service" | "volatil" | "pure" | "child"
13423
+ ************************************************************/
13424
+ function node_role(d) {
13425
+ if (!d) return "child";
13426
+ if (d.is_yuno) return "yuno";
13427
+ if (d.service) return "service";
13428
+ if (d.volatil) return "volatil";
13429
+ if (d.pure_child) return "pure";
13430
+ return "child";
13431
+ }
13432
+ /************************************************************
13433
+ * What the node is doing: "stopped" | "running" | "playing"
13434
+ ************************************************************/
13435
+ function node_status(d) {
13436
+ if (!d || !d.running) return "stopped";
13437
+ return d.playing ? "playing" : "running";
13438
+ }
13439
+ /************************************************************
13440
+ * The i18n key of the status, which is NOT the status itself:
13441
+ * a gobj that runs and does not play is PAUSED to the reader,
13442
+ * and "running" alone never said which of the two it was.
13443
+ ************************************************************/
13444
+ function node_status_key(d) {
13445
+ switch (node_status(d)) {
13446
+ case "playing": return "playing";
13447
+ case "running": return "running (paused)";
13448
+ default: return "stopped";
13449
+ }
13450
+ }
13451
+ /******************************
13452
+ * Producers
13453
+ ******************************/
13454
+ /************************************************************
13455
+ * A descriptor of a LIVE gobj of this browser yuno, children
13456
+ * included (the whole subtree, depth-first).
13457
+ *
13458
+ * `is_root` marks the yuno itself: the JS runtime has no
13459
+ * gobj_flag_yuno test of its own.
13460
+ ************************************************************/
13461
+ function describe_js_gobj(target_gobj, is_root) {
13462
+ if (!target_gobj) return null;
13463
+ let parent_gobj = (0, _yuneta_gobj_js.gobj_parent)(target_gobj);
13464
+ let bottom = (0, _yuneta_gobj_js.gobj_bottom_gobj)(target_gobj);
13465
+ let d = {
13466
+ gclass: (0, _yuneta_gobj_js.gobj_gclass_name)(target_gobj) || "",
13467
+ name: (0, _yuneta_gobj_js.gobj_name)(target_gobj) || "",
13468
+ shortname: (0, _yuneta_gobj_js.gobj_short_name)(target_gobj) || "",
13469
+ fullname: (0, _yuneta_gobj_js.gobj_full_name)(target_gobj) || "",
13470
+ parent: parent_gobj ? (0, _yuneta_gobj_js.gobj_short_name)(parent_gobj) || "" : "",
13471
+ state: (0, _yuneta_gobj_js.gobj_current_state)(target_gobj) || "",
13472
+ running: !!(0, _yuneta_gobj_js.gobj_is_running)(target_gobj),
13473
+ playing: !!(0, _yuneta_gobj_js.gobj_is_playing)(target_gobj),
13474
+ service: !!(0, _yuneta_gobj_js.gobj_is_service)(target_gobj),
13475
+ volatil: !!(0, _yuneta_gobj_js.gobj_is_volatil)(target_gobj),
13476
+ pure_child: !!(0, _yuneta_gobj_js.gobj_is_pure_child)(target_gobj),
13477
+ disabled: !!target_gobj.disabled,
13478
+ bottom_gobj: bottom ? (0, _yuneta_gobj_js.gobj_short_name)(bottom) || "" : "",
13479
+ commands: !!(target_gobj.gclass && target_gobj.gclass.command_table),
13480
+ is_yuno: !!is_root,
13481
+ children: []
13482
+ };
13483
+ let children = target_gobj.dl_children || [];
13484
+ for (let child of children) {
13485
+ let cd = describe_js_gobj(child, false);
13486
+ if (cd) d.children.push(cd);
13487
+ }
13488
+ return d;
13489
+ }
13490
+ /******************************
13491
+ * Readouts
13492
+ ******************************/
13493
+ /************************************************************
13494
+ * The i18n key of a role. Two of the five roles are not the
13495
+ * word the reader wants ("pure" and "volatil" alone say
13496
+ * nothing), so the map is explicit.
13497
+ ************************************************************/
13498
+ function role_i18n_key(role) {
13499
+ switch (role) {
13500
+ case "yuno": return "yuno";
13501
+ case "service": return "service";
13502
+ case "volatil": return "volatil child";
13503
+ case "pure": return "pure child";
13504
+ default: return "child";
13505
+ }
13506
+ }
13507
+ /************************************************************
13508
+ * The marks a node carries besides its role: what the reader
13509
+ * would otherwise have to open the popover to learn.
13510
+ *
13511
+ * Returns i18n KEYS, in a fixed order, so the card and the
13512
+ * popover cannot disagree about what a node is.
13513
+ ************************************************************/
13514
+ function node_badge_keys(d) {
13515
+ let badges = [];
13516
+ if (!d) return badges;
13517
+ let role = node_role(d);
13518
+ if (role !== "child") badges.push(role_i18n_key(role));
13519
+ if (d.disabled) badges.push("disabled");
13520
+ if (d.bottom_gobj) badges.push("bottom");
13521
+ if (d.commands) badges.push("commands");
13522
+ return badges;
13523
+ }
13524
+ /************************************************************
13525
+ * The vocabulary of this view, translated.
13526
+ *
13527
+ * Written as literal t("...") calls and not as t(key): a key
13528
+ * reached through a variable is invisible to the locale
13529
+ * validator, which is how a fixed vocabulary ships in one
13530
+ * language and never changes it.
13531
+ ************************************************************/
13532
+ function node_labels(t) {
13533
+ if (typeof t !== "function") t = (k) => k;
13534
+ return {
13535
+ role: {
13536
+ "yuno": t("yuno"),
13537
+ "service": t("service"),
13538
+ "volatil child": t("volatil child"),
13539
+ "pure child": t("pure child"),
13540
+ "child": t("child")
13541
+ },
13542
+ status: {
13543
+ "playing": t("playing"),
13544
+ "running (paused)": t("running (paused)"),
13545
+ "stopped": t("stopped")
13546
+ },
13547
+ status_short: {
13548
+ "playing": t("playing"),
13549
+ "running": t("running"),
13550
+ "stopped": t("stopped")
13551
+ },
13552
+ badge: {
13553
+ "yuno": t("yuno"),
13554
+ "service": t("service"),
13555
+ "volatil child": t("volatil child"),
13556
+ "pure child": t("pure child"),
13557
+ "disabled": t("disabled"),
13558
+ "bottom": t("bottom"),
13559
+ "commands": t("commands")
13560
+ },
13561
+ field: {
13562
+ gclass: t("gclass"),
13563
+ name: t("name"),
13564
+ full_name: t("full name"),
13565
+ role: t("role"),
13566
+ status: t("status"),
13567
+ state: t("state"),
13568
+ disabled: t("disabled"),
13569
+ parent: t("parent"),
13570
+ bottom_gobj: t("bottom gobj"),
13571
+ commands: t("commands"),
13572
+ traces: t("traces"),
13573
+ flags: t("flags"),
13574
+ children: t("children"),
13575
+ yes: t("yes"),
13576
+ collapsed: t("(collapsed)")
13577
+ }
13578
+ };
13579
+ }
13580
+ /************************************************************
13581
+ * The rows of the popover: [label, value], both translated.
13582
+ *
13583
+ * `t` is the app's translator -- the library has none of its
13584
+ * own. Values that are a fixed vocabulary (the role, the
13585
+ * status, yes) go through it too; free text does not.
13586
+ ************************************************************/
13587
+ function node_info_rows(d, t) {
13588
+ let rows = [];
13589
+ if (!d) return rows;
13590
+ let L = node_labels(t);
13591
+ let f = L.field;
13592
+ rows.push([f.gclass, d.gclass || "—"]);
13593
+ rows.push([f.name, d.name || "—"]);
13594
+ if (d.fullname && d.fullname !== d.shortname) rows.push([f.full_name, d.fullname]);
13595
+ rows.push([f.role, L.role[role_i18n_key(node_role(d))]]);
13596
+ rows.push([f.status, L.status[node_status_key(d)]]);
13597
+ rows.push([f.state, d.state || "—"]);
13598
+ if (d.disabled) rows.push([f.disabled, f.yes]);
13599
+ if (d.parent) rows.push([f.parent, d.parent]);
13600
+ if (d.bottom_gobj) rows.push([f.bottom_gobj, d.bottom_gobj]);
13601
+ if (d.commands) rows.push([f.commands, f.yes]);
13602
+ if (Array.isArray(d.gobj_trace_level) && d.gobj_trace_level.length > 0) rows.push([f.traces, d.gobj_trace_level.join(", ")]);
13603
+ if (Array.isArray(d.gobj_flags) && d.gobj_flags.length > 0) rows.push([f.flags, d.gobj_flags.join(", ")]);
13604
+ let kids = Array.isArray(d.children) ? d.children.length : 0;
13605
+ if (kids > 0) rows.push([f.children, d.is_collapsed ? kids + " " + f.collapsed : String(kids)]);
13606
+ return rows;
13607
+ }
13608
+ //#endregion
13609
+ //#region src/gclass_describe.js
13610
+ /***********************************************************************
13611
+ * gclass_describe.js
13612
+ *
13613
+ * A gclass, as plain JSON, in the shape the BACKEND answers.
13614
+ *
13615
+ * There is no gclass viewer in the framework: the C kernel
13616
+ * answers `view-gclass` with `gclass2json()`
13617
+ * (`kernel/c/gobj-c/src/gobj.c`) and nothing draws it. This
13618
+ * module builds the same document for a gclass of the BROWSER
13619
+ * yuno, out of the gobj-js registry, so one viewer can show a
13620
+ * gclass of either side:
13621
+ *
13622
+ * {id, gcflag, attrs, commands, gclass_methods,
13623
+ * internal_methods, FSM: {events, states}}
13624
+ *
13625
+ * Two keys the backend does not carry are added at the end,
13626
+ * because the browser can answer them for free and the reader
13627
+ * asks them first: `instances` and `trace_levels`.
13628
+ *
13629
+ * Copyright (c) 2026, ArtGins.
13630
+ * All Rights Reserved.
13631
+ ***********************************************************************/
13632
+ /***************************************************************
13633
+ * Bit tables
13634
+ ***************************************************************/
13635
+ var DATA_TYPE_NAMES = {
13636
+ [_yuneta_gobj_js.data_type_t.DTP_STRING]: "DTP_STRING",
13637
+ [_yuneta_gobj_js.data_type_t.DTP_BOOLEAN]: "DTP_BOOLEAN",
13638
+ [_yuneta_gobj_js.data_type_t.DTP_INTEGER]: "DTP_INTEGER",
13639
+ [_yuneta_gobj_js.data_type_t.DTP_REAL]: "DTP_REAL",
13640
+ [_yuneta_gobj_js.data_type_t.DTP_LIST]: "DTP_LIST",
13641
+ [_yuneta_gobj_js.data_type_t.DTP_DICT]: "DTP_DICT",
13642
+ [_yuneta_gobj_js.data_type_t.DTP_JSON]: "DTP_JSON",
13643
+ [_yuneta_gobj_js.data_type_t.DTP_POINTER]: "DTP_POINTER"
13644
+ };
13645
+ /************************************************************
13646
+ * The names of the bits set in `bits`, in table order.
13647
+ ************************************************************/
13648
+ function bits2names(table, bits) {
13649
+ let names = [];
13650
+ let value = Number(bits) || 0;
13651
+ for (let name of Object.keys(table)) if ((value & table[name]) !== 0) names.push(name);
13652
+ return names;
13653
+ }
13654
+ /************************************************************
13655
+ * The name of a data type. An unknown type is reported as
13656
+ * the number it is -- silently drawing "DTP_STRING" for a
13657
+ * type nobody registered would be worse than saying "17".
13658
+ ************************************************************/
13659
+ function data_type_name(type) {
13660
+ return DATA_TYPE_NAMES[type] || String(type);
13661
+ }
13662
+ /************************************************************
13663
+ * One SDATA descriptor -> one row.
13664
+ *
13665
+ * A DTP_POINTER default is a live object (a DOM node, a
13666
+ * gobj): it is never printed, only named, because a viewer
13667
+ * that serializes it would either throw or dump the page.
13668
+ ************************************************************/
13669
+ function sdatadesc2json(it) {
13670
+ let row = {
13671
+ id: it.name,
13672
+ type: data_type_name(it.type),
13673
+ flag: bits2names(_yuneta_gobj_js.sdata_flag_t, it.flag),
13674
+ description: it.description || ""
13675
+ };
13676
+ 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>";
13677
+ else row.default_value = it.default_value === void 0 ? null : it.default_value;
13678
+ return row;
13679
+ }
13680
+ /************************************************************
13681
+ * The attrs table of a gclass.
13682
+ ************************************************************/
13683
+ function attrs2json(attrs_table) {
13684
+ let rows = [];
13685
+ if (!Array.isArray(attrs_table)) return rows;
13686
+ for (let it of attrs_table) {
13687
+ if (!it || !it.name) continue;
13688
+ rows.push(sdatadesc2json(it));
13689
+ }
13690
+ return rows;
13691
+ }
13692
+ /************************************************************
13693
+ * The command table of a gclass, with the parameters of
13694
+ * each command (the `schema` slot of SDATACM).
13695
+ ************************************************************/
13696
+ function commands2json(command_table) {
13697
+ let rows = [];
13698
+ if (!Array.isArray(command_table)) return rows;
13699
+ for (let it of command_table) {
13700
+ if (!it || !it.name) continue;
13701
+ let row = {
13702
+ id: it.name,
13703
+ alias: it.alias || [],
13704
+ description: it.description || ""
13705
+ };
13706
+ if (Array.isArray(it.schema)) row.parameters = attrs2json(it.schema);
13707
+ rows.push(row);
13708
+ }
13709
+ return rows;
13710
+ }
13711
+ /************************************************************
13712
+ * The methods the gclass actually implements. A table of
13713
+ * every possible one, mostly empty, tells the reader nothing.
13714
+ ************************************************************/
13715
+ function methods2json(mt) {
13716
+ let names = [];
13717
+ if (!mt || typeof mt !== "object") return names;
13718
+ for (let key of Object.keys(mt)) if (typeof mt[key] === "function") names.push(key);
13719
+ return names;
13720
+ }
13721
+ /************************************************************
13722
+ * The FSM: the events the gclass declares, and what each
13723
+ * state does with them.
13724
+ ************************************************************/
13725
+ function fsm2json(gclass) {
13726
+ let events = [];
13727
+ for (let ev of gclass.dl_events || []) events.push({
13728
+ event_name: ev.event_name,
13729
+ event_flag: bits2names(_yuneta_gobj_js.event_flag_t, ev.event_flag)
13730
+ });
13731
+ let states = {};
13732
+ for (let st of gclass.dl_states || []) {
13733
+ let actions = [];
13734
+ for (let ac of st.dl_actions || []) actions.push({
13735
+ event_name: ac.event_name,
13736
+ action: ac.action ? ac.action.name || "action" : "",
13737
+ next_state: ac.next_state || ""
13738
+ });
13739
+ states[st.state_name] = actions;
13740
+ }
13741
+ return {
13742
+ events,
13743
+ states
13744
+ };
13745
+ }
13746
+ /************************************************************
13747
+ * The description of a gclass REGISTERED IN THIS BROWSER YUNO,
13748
+ * or null when the registry does not know that name.
13749
+ *
13750
+ * Null is the answer for a gclass of a BACKEND yuno too: the
13751
+ * browser registry carries the GUI gclasses and nothing else,
13752
+ * so the caller asks the backend `view-gclass` for those and
13753
+ * shows whatever it answers -- this same shape.
13754
+ ************************************************************/
13755
+ function describe_local_gclass(gclass_name) {
13756
+ if (!gclass_name) return null;
13757
+ let gclass = (0, _yuneta_gobj_js.gclass_find_by_name)(gclass_name, false);
13758
+ if (!gclass) return null;
13759
+ return {
13760
+ id: gclass.gclass_name,
13761
+ gcflag: bits2names(_yuneta_gobj_js.gclass_flag_t, gclass.gclass_flag),
13762
+ attrs: attrs2json(gclass.attrs_table),
13763
+ commands: commands2json(gclass.command_table),
13764
+ gclass_methods: methods2json(gclass.gmt),
13765
+ internal_methods: methods2json(gclass.lmt),
13766
+ FSM: fsm2json(gclass),
13767
+ instances: Number(gclass.instances) || 0,
13768
+ trace_levels: Array.isArray(gclass.s_user_trace_level) ? gclass.s_user_trace_level : []
13769
+ };
13770
+ }
13771
+ //#endregion
13772
+ //#region src/yui_gclass_view.js
13773
+ /***********************************************************************
13774
+ * yui_gclass_view.js
13775
+ *
13776
+ * The gclass viewer: what a GClass IS, in a window.
13777
+ *
13778
+ * The framework had no such viewer. The C kernel answers
13779
+ * `view-gclass` with a full description (attrs, commands,
13780
+ * methods, FSM) and the only reader of it was a terminal.
13781
+ * This one draws that same document with C_YUI_JSON, so the
13782
+ * FSM of a gclass is a graph and its attrs table is a tree,
13783
+ * and it takes the document from either side:
13784
+ *
13785
+ * - a gclass of the browser yuno -> describe_local_gclass()
13786
+ * - a gclass of a backend yuno -> `opts.description`, the
13787
+ * answer of `view-gclass`
13788
+ *
13789
+ * One window at a time per host: the caller keeps the handle
13790
+ * and closes it, the way the treedb graph keeps its raw-JSON
13791
+ * viewer.
13792
+ *
13793
+ * Copyright (c) 2026, ArtGins.
13794
+ * All Rights Reserved.
13795
+ ***********************************************************************/
13796
+ /************************************************************
13797
+ * Can this app show a gclass at all?
13798
+ *
13799
+ * The viewer is C_YUI_JSON, and an app registers only the
13800
+ * gclasses it mounts. The caller asks this BEFORE drawing a
13801
+ * button, so an app that does not carry the viewer shows no
13802
+ * control instead of a control that fails.
13803
+ ************************************************************/
13804
+ function gclass_view_available() {
13805
+ return !!(0, _yuneta_gobj_js.gclass_find_by_name)("C_YUI_JSON", false);
13806
+ }
13807
+ /************************************************************
13808
+ * Open the viewer on `gclass_name`.
13809
+ *
13810
+ * opts:
13811
+ * description the document to draw. Omitted, it is
13812
+ * built from the browser registry.
13813
+ * title_prefix what the window says before the gclass
13814
+ * name (the yuno it belongs to).
13815
+ * on_close called when the reader dismisses it.
13816
+ *
13817
+ * Returns a handle for close_gclass_view(), or null (and a
13818
+ * logged reason) when there is nothing to show.
13819
+ ************************************************************/
13820
+ function open_gclass_view(host, gclass_name, opts) {
13821
+ opts = opts || {};
13822
+ if (!host || !gclass_name) {
13823
+ (0, _yuneta_gobj_js.log_error)(`yui_gclass_view: open without a host or a gclass name`);
13824
+ return null;
13825
+ }
13826
+ let shell = yui_shell_of(host);
13827
+ if (!gclass_view_available()) {
13828
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(host)}: C_YUI_JSON not registered by the app`);
13829
+ yui_shell_show_error(shell, "gclass viewer unavailable", { t: i18next.t });
13830
+ return null;
13831
+ }
13832
+ let description = opts.description || describe_local_gclass(gclass_name);
13833
+ if (!description) {
13834
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(host)}: gclass not registered here: ${gclass_name}`);
13835
+ yui_shell_show_error(shell, "gclass not registered in this yuno", { t: i18next.t });
13836
+ return null;
13837
+ }
13838
+ 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", {
13839
+ json_data: description,
13840
+ subscriber: null
13841
+ }, host);
13842
+ if (!jv) {
13843
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(host)}: cannot create the gclass viewer`);
13844
+ return null;
13845
+ }
13846
+ (0, _yuneta_gobj_js.gobj_start)(jv);
13847
+ let handle = {
13848
+ json_gobj: jv,
13849
+ win: null,
13850
+ modal: null
13851
+ };
13852
+ let $box = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(jv, "$container");
13853
+ let on_close = () => {
13854
+ if (typeof opts.on_close === "function") opts.on_close();
13855
+ };
13856
+ if (is_mobile$3()) {
13857
+ if (!shell) {
13858
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(host)}: no shell, cannot open the gclass sheet`);
13859
+ close_gclass_view(handle);
13860
+ return null;
13861
+ }
13862
+ handle.modal = yui_shell_show_modal(shell, $box, {
13863
+ dialog: true,
13864
+ logical_class: "GCLASS_VIEW_SHEET",
13865
+ title_prefix: opts.title_prefix || "",
13866
+ title: gclass_name,
13867
+ t: i18next.t,
13868
+ on_close
13869
+ });
13870
+ return handle;
13871
+ }
13872
+ let $win_parent = window_layer_of(host, shell);
13873
+ 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", {
13874
+ $parent: $win_parent,
13875
+ subscriber: null,
13876
+ modal: false,
13877
+ showMax: true,
13878
+ showFooter: false,
13879
+ resizable: true,
13880
+ center: true,
13881
+ auto_save_size_and_position: true,
13882
+ width: 720,
13883
+ height: 640,
13884
+ logical_class: "GCLASS_VIEW_WINDOW",
13885
+ title_prefix: opts.title_prefix || "",
13886
+ title: gclass_name,
13887
+ icon: "yi-code",
13888
+ body: $box,
13889
+ manager: (0, _yuneta_gobj_js.gobj_find_service)("__window_manager__", false) || null,
13890
+ on_close
13891
+ }, host);
13892
+ if (!handle.win) {
13893
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(host)}: cannot create the gclass window`);
13894
+ close_gclass_view(handle);
13895
+ return null;
13896
+ }
13897
+ (0, _yuneta_gobj_js.gobj_start)(handle.win);
13898
+ return handle;
13899
+ }
13900
+ /************************************************************
13901
+ * Close whatever open_gclass_view() opened. Safe on null and
13902
+ * on a handle whose window the reader already dismissed.
13903
+ ************************************************************/
13904
+ function close_gclass_view(handle) {
13905
+ if (!handle) return;
13906
+ let jv = handle.json_gobj;
13907
+ let win = handle.win;
13908
+ let modal = handle.modal;
13909
+ handle.json_gobj = null;
13910
+ handle.win = null;
13911
+ handle.modal = null;
13912
+ if (win && (0, _yuneta_gobj_js.is_gobj)(win)) try {
13913
+ if ((0, _yuneta_gobj_js.gobj_is_running)(win)) (0, _yuneta_gobj_js.gobj_stop)(win);
13914
+ (0, _yuneta_gobj_js.gobj_destroy)(win);
13915
+ } catch (e) {
13916
+ (0, _yuneta_gobj_js.log_warning)(`yui_gclass_view: window already gone: ${e}`);
13917
+ }
13918
+ if (modal && typeof modal.close === "function") try {
13919
+ modal.close();
13920
+ } catch (e) {
13921
+ (0, _yuneta_gobj_js.log_warning)(`yui_gclass_view: modal already gone: ${e}`);
13922
+ }
13923
+ if (jv && (0, _yuneta_gobj_js.is_gobj)(jv)) try {
13924
+ if ((0, _yuneta_gobj_js.gobj_is_running)(jv)) (0, _yuneta_gobj_js.gobj_stop)(jv);
13925
+ (0, _yuneta_gobj_js.gobj_destroy)(jv);
13926
+ } catch (e) {
13927
+ (0, _yuneta_gobj_js.log_warning)(`yui_gclass_view: viewer already gone: ${e}`);
13928
+ }
13929
+ }
13930
+ /************************************************************
13931
+ * Where to mount the window: the SAME element the caller's
13932
+ * own window hangs from.
13933
+ *
13934
+ * Not a preference -- a requirement. z-index only orders
13935
+ * siblings of one stacking context, so a viewer opened from a
13936
+ * window on `body` and mounted in the shell's popup layer
13937
+ * lands BEHIND its opener whatever number it is given, and
13938
+ * the button reads as one that did nothing.
13939
+ ************************************************************/
13940
+ function window_layer_of(host, shell) {
13941
+ let $c = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(host, "$container");
13942
+ if ($c && typeof $c.closest === "function") {
13943
+ let $win = $c.closest(".C_YUI_WINDOW");
13944
+ if ($win && $win.parentNode) return $win.parentNode;
13945
+ }
13946
+ return typeof document !== "undefined" && document.getElementById("top-layer") || shell && yui_shell_popup_layer(shell) || null;
13947
+ }
13948
+ /************************************************************
13949
+ * A window is not a sheet: below this width there is no room
13950
+ * for a title bar the reader can drag.
13951
+ ************************************************************/
13952
+ function is_mobile$3() {
13953
+ return typeof window !== "undefined" && window.innerWidth <= 768;
13954
+ }
13955
+ //#endregion
13395
13956
  //#region src/c_yui_gobj_tree_js.js
13396
13957
  /***********************************************************************
13397
13958
  * c_yui_gobj_tree_js.js
@@ -13502,6 +14063,9 @@ var PRIVATE_DATA$9 = {
13502
14063
  $popover: null,
13503
14064
  $popover_title: null,
13504
14065
  $popover_body: null,
14066
+ $popover_gclass: null,
14067
+ popover_node: null,
14068
+ gclass_view: null,
13505
14069
  node_by_id: null,
13506
14070
  collapse_state: null,
13507
14071
  resize_observer: null,
@@ -13516,34 +14080,92 @@ var PRIVATE_DATA$9 = {
13516
14080
  var __gclass__$9 = null;
13517
14081
  var __layout_registered__ = false;
13518
14082
  /***************************************************************
13519
- * Node colors by role
14083
+ * Node colours by role.
14084
+ *
14085
+ * Saturated hues, one family apart from the next, and the same
14086
+ * five everywhere the reader meets a gobj. `stroke` draws the
14087
+ * border and the edge; `accent` is the brighter sibling used
14088
+ * for the bar down the left of the card and the toggle badge,
14089
+ * so a card carries its colour twice at two intensities and is
14090
+ * readable at the zoom where the text is not.
13520
14091
  ***************************************************************/
13521
14092
  var ROLE_COLORS = {
13522
14093
  yuno: {
13523
- fill: "#E0F2FE",
13524
- stroke: "#075985"
14094
+ stroke: "#0284c7",
14095
+ accent: "#38bdf8"
13525
14096
  },
13526
14097
  service: {
13527
- fill: "#DCFCE7",
13528
- stroke: "#166534"
14098
+ stroke: "#059669",
14099
+ accent: "#34d399"
13529
14100
  },
13530
14101
  pure: {
13531
- fill: "#FEF9C3",
13532
- stroke: "#854D0E"
14102
+ stroke: "#d97706",
14103
+ accent: "#fbbf24"
13533
14104
  },
13534
14105
  volatil: {
13535
- fill: "#FCE7F3",
13536
- stroke: "#9D174D"
14106
+ stroke: "#db2777",
14107
+ accent: "#f472b6"
13537
14108
  },
13538
- default: {
13539
- fill: "#F3F4F6",
13540
- stroke: "#374151"
14109
+ child: {
14110
+ stroke: "#7c3aed",
14111
+ accent: "#a78bfa"
13541
14112
  }
13542
14113
  };
13543
- var STATE_COLORS = {
13544
- running_playing: "#15803D",
13545
- running: "#B45309",
13546
- stopped: "#B91C1C"
14114
+ /***************************************************************
14115
+ * Status colours: what the gobj is DOING.
14116
+ *
14117
+ * A pill and not a word: three states of one gobj are told
14118
+ * apart across a whole tree by colour long before anybody
14119
+ * reads them, and the dot alone never said which was which.
14120
+ * `disabled` is a fourth entry and not a status -- a disabled
14121
+ * gobj is still stopped or running -- but it takes the pill,
14122
+ * because it is the fact that explains the other one.
14123
+ ***************************************************************/
14124
+ var STATUS_COLORS = {
14125
+ light: {
14126
+ playing: {
14127
+ fg: "#065f46",
14128
+ bg: "#a7f3d0",
14129
+ dot: "#10b981"
14130
+ },
14131
+ running: {
14132
+ fg: "#92400e",
14133
+ bg: "#fde68a",
14134
+ dot: "#f59e0b"
14135
+ },
14136
+ stopped: {
14137
+ fg: "#991b1b",
14138
+ bg: "#fecaca",
14139
+ dot: "#ef4444"
14140
+ },
14141
+ disabled: {
14142
+ fg: "#334155",
14143
+ bg: "#e2e8f0",
14144
+ dot: "#94a3b8"
14145
+ }
14146
+ },
14147
+ dark: {
14148
+ playing: {
14149
+ fg: "#6ee7b7",
14150
+ bg: "rgba(16,185,129,0.28)",
14151
+ dot: "#34d399"
14152
+ },
14153
+ running: {
14154
+ fg: "#fcd34d",
14155
+ bg: "rgba(245,158,11,0.28)",
14156
+ dot: "#fbbf24"
14157
+ },
14158
+ stopped: {
14159
+ fg: "#fca5a5",
14160
+ bg: "rgba(239,68,68,0.28)",
14161
+ dot: "#f87171"
14162
+ },
14163
+ disabled: {
14164
+ fg: "#cbd5e1",
14165
+ bg: "rgba(148,163,184,0.28)",
14166
+ dot: "#94a3b8"
14167
+ }
14168
+ }
13547
14169
  };
13548
14170
  /***************************************************************
13549
14171
  * Fixed node sizes per role (px).
@@ -13553,16 +14175,16 @@ var STATE_COLORS = {
13553
14175
  var NODE_SIZES = {
13554
14176
  full: {
13555
14177
  yuno: {
13556
- w: 240,
13557
- h: 72
14178
+ w: 260,
14179
+ h: 96
13558
14180
  },
13559
14181
  service: {
13560
- w: 210,
13561
- h: 68
14182
+ w: 232,
14183
+ h: 92
13562
14184
  },
13563
14185
  child: {
13564
- w: 160,
13565
- h: 60
14186
+ w: 196,
14187
+ h: 86
13566
14188
  }
13567
14189
  },
13568
14190
  compact: {
@@ -13585,27 +14207,47 @@ var NODE_SIZES = {
13585
14207
  ***************************************************************/
13586
14208
  var GT_FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
13587
14209
  /***************************************************************
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.
14210
+ * Theme-aware card palette derived from the role colour.
14211
+ *
14212
+ * Brighter than a tint: at 9% of the role colour every card
14213
+ * was the same near-white rectangle and the tree read as a
14214
+ * wireframe. The fill carries enough of the hue to be named
14215
+ * from across the canvas, and the border and the accent bar
14216
+ * carry the rest.
13591
14217
  ***************************************************************/
13592
- function role_card_style(stroke, dark) {
14218
+ function role_card_style(colors, dark) {
14219
+ let stroke = colors.stroke;
14220
+ let accent = colors.accent;
13593
14221
  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)"
14222
+ bg: dark ? `color-mix(in srgb, ${stroke} 34%, #253044)` : `color-mix(in srgb, ${stroke} 13%, #ffffff)`,
14223
+ border: dark ? accent : stroke,
14224
+ accent: dark ? accent : stroke,
14225
+ title: dark ? "#f1f5f9" : "#0f172a",
14226
+ sub: dark ? "#a8b3c2" : "#475569",
14227
+ tagbg: dark ? `color-mix(in srgb, ${stroke} 48%, #253044)` : `color-mix(in srgb, ${stroke} 20%, #ffffff)`,
14228
+ tagfg: dark ? "#f1f5f9" : stroke,
14229
+ shadow: dark ? "0 2px 6px rgba(0,0,0,0.50)" : "0 2px 5px rgba(15,23,42,0.16)"
13600
14230
  };
13601
14231
  }
13602
14232
  /***************************************************************
13603
- * Map a gobj to its visual category.
14233
+ * The pill colours of a node, for the theme in force.
13604
14234
  ***************************************************************/
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";
14235
+ function status_card_style(d, dark) {
14236
+ let table = dark ? STATUS_COLORS.dark : STATUS_COLORS.light;
14237
+ if (d.disabled) return table.disabled;
14238
+ return table[node_status(d)] || table.stopped;
14239
+ }
14240
+ /***************************************************************
14241
+ * Map a node to its visual size class. Services are drawn
14242
+ * bigger than plain children so the service boundary of the
14243
+ * hierarchy is visible before anything is read.
14244
+ ***************************************************************/
14245
+ function get_node_category(d) {
14246
+ switch (node_role(d)) {
14247
+ case "yuno": return "yuno";
14248
+ case "service": return "service";
14249
+ default: return "child";
14250
+ }
13609
14251
  }
13610
14252
  /***************************************************************
13611
14253
  * Layouts registry.
@@ -13718,13 +14360,21 @@ function mt_create$9(gobj) {
13718
14360
  ***************************************************************/
13719
14361
  function mt_start$9(gobj) {
13720
14362
  restore_view_shape(gobj);
14363
+ let shell = yui_shell_of(gobj);
14364
+ if (shell) (0, _yuneta_gobj_js.gobj_subscribe_event)(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
13721
14365
  build_graph$2(gobj);
13722
14366
  load_tree(gobj);
13723
14367
  }
13724
14368
  /***************************************************************
13725
14369
  * Framework Method: Stop
13726
14370
  ***************************************************************/
13727
- function mt_stop$9(gobj) {}
14371
+ function mt_stop$9(gobj) {
14372
+ let priv = gobj.priv;
14373
+ let shell = yui_shell_of(gobj);
14374
+ if (shell) (0, _yuneta_gobj_js.gobj_unsubscribe_event)(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
14375
+ close_gclass_view(priv.gclass_view);
14376
+ priv.gclass_view = null;
14377
+ }
13728
14378
  /***************************************************************
13729
14379
  * Framework Method: Destroy
13730
14380
  ***************************************************************/
@@ -13750,6 +14400,8 @@ function mt_destroy$9(gobj) {
13750
14400
  priv.$popover = null;
13751
14401
  priv.$popover_title = null;
13752
14402
  priv.$popover_body = null;
14403
+ priv.$popover_gclass = null;
14404
+ priv.popover_node = null;
13753
14405
  priv.$layout_select = null;
13754
14406
  priv.node_by_id = null;
13755
14407
  priv.collapse_state = null;
@@ -13853,6 +14505,30 @@ function build_popover(gobj, $container) {
13853
14505
  text-overflow: ellipsis;
13854
14506
  `;
13855
14507
  $header.appendChild($title);
14508
+ if (gclass_view_available()) {
14509
+ let $gclass = document.createElement("button");
14510
+ $gclass.type = "button";
14511
+ $gclass.className = "GOBJ_TREE_POPOVER_GCLASS button is-small";
14512
+ $gclass.style.cssText = `
14513
+ margin-right: 6px;
14514
+ padding: 0 8px;
14515
+ height: 22px;
14516
+ font-size: 11px;
14517
+ white-space: nowrap;
14518
+ `;
14519
+ $gclass.textContent = (0, i18next.t)("gclass");
14520
+ $gclass.setAttribute("i18n", "gclass");
14521
+ $gclass.title = (0, i18next.t)("view gclass");
14522
+ $gclass.setAttribute("data-i18n-title", "view gclass");
14523
+ $gclass.setAttribute("aria-label", (0, i18next.t)("view gclass"));
14524
+ $gclass.setAttribute("data-i18n-aria-label", "view gclass");
14525
+ $gclass.addEventListener("click", (evt) => {
14526
+ evt.stopPropagation();
14527
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_OPEN_GCLASS", {}, gobj);
14528
+ });
14529
+ $header.appendChild($gclass);
14530
+ priv.$popover_gclass = $gclass;
14531
+ } 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
14532
  let $close = document.createElement("button");
13857
14533
  $close.type = "button";
13858
14534
  $close.className = "GOBJ_TREE_POPOVER_CLOSE";
@@ -14037,22 +14713,20 @@ function apply_layout(gobj) {
14037
14713
  });
14038
14714
  }
14039
14715
  /************************************************************
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
14716
+ * The role colours of a node.
14717
+ *
14718
+ * Note the ORDER: volatil before service. A volatil gobj that
14719
+ * is also a service is drawn volatil, because that is the fact
14720
+ * that decides how long it lives.
14051
14721
  ************************************************************/
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;
14722
+ function get_role_colors(d) {
14723
+ switch (node_role(d)) {
14724
+ case "yuno": return ROLE_COLORS.yuno;
14725
+ case "volatil": return ROLE_COLORS.volatil;
14726
+ case "service": return ROLE_COLORS.service;
14727
+ case "pure": return ROLE_COLORS.pure;
14728
+ default: return ROLE_COLORS.child;
14729
+ }
14056
14730
  }
14057
14731
  /************************************************************
14058
14732
  * Generate a unique node id
@@ -14084,72 +14758,135 @@ function is_node_collapsed(gobj, full_name, num_children) {
14084
14758
  ************************************************************/
14085
14759
  function render_toggle_html(node_id, collapsed, num_children, cs) {
14086
14760
  let label = collapsed ? "+" + num_children : "−";
14761
+ let title = collapsed ? (0, i18next.t)("expand children") : (0, i18next.t)("collapse children");
14087
14762
  return `<span
14088
14763
  data-gobj-toggle="true"
14089
14764
  data-node-id="${node_id}"
14090
- title="${collapsed ? "Expand children" : "Collapse children"}"
14765
+ title="${(0, _yuneta_gobj_js.escapeHtml)(title)}"
14091
14766
  style="
14092
14767
  flex: 0 0 auto;
14093
14768
  display: inline-flex;
14094
14769
  align-items: center;
14095
14770
  justify-content: center;
14096
- min-width: 22px;
14097
- height: 16px;
14098
- padding: 0 5px;
14771
+ min-width: 24px;
14772
+ height: 18px;
14773
+ padding: 0 6px;
14099
14774
  margin-left: 4px;
14100
- background: ${cs.tagbg};
14101
- color: ${cs.border};
14102
- border: 1px solid ${cs.border};
14103
- border-radius: 6px;
14775
+ background: ${cs.accent};
14776
+ color: #ffffff;
14777
+ border: 1px solid ${cs.accent};
14778
+ border-radius: 9px;
14104
14779
  font-size: 10px;
14105
- font-weight: 600;
14780
+ font-weight: 700;
14106
14781
  line-height: 1;
14107
14782
  cursor: pointer;
14108
14783
  user-select: none;
14109
14784
  ">${label}</span>`;
14110
14785
  }
14111
14786
  /************************************************************
14112
- * Recursively build nodes and edges from the gobj tree
14787
+ * HTML for the status pill: what the gobj is DOING.
14788
+ *
14789
+ * A dot plus a word, and the word is dropped in the compact
14790
+ * card where there is no room for it -- the colour still
14791
+ * carries it, and the `title` says it in full either way.
14792
+ ************************************************************/
14793
+ function render_status_html(d, ss, labels, with_label) {
14794
+ let full = d.disabled ? labels.field.disabled : labels.status[node_status_key(d)] || node_status_key(d);
14795
+ let label = d.disabled ? labels.field.disabled : labels.status_short[node_status(d)] || node_status(d);
14796
+ if (!with_label) return `<span title="${(0, _yuneta_gobj_js.escapeHtml)(full)}" style="
14797
+ flex: 0 0 auto;
14798
+ width: 10px; height: 10px; border-radius: 50%;
14799
+ background: ${ss.dot};
14800
+ box-shadow: 0 0 0 2px ${ss.bg};
14801
+ margin-left: 6px;
14802
+ "></span>`;
14803
+ return `<span title="${(0, _yuneta_gobj_js.escapeHtml)(full)}" style="
14804
+ flex: 0 0 auto;
14805
+ display: inline-flex;
14806
+ align-items: center;
14807
+ gap: 4px;
14808
+ padding: 1px 7px 1px 5px;
14809
+ background: ${ss.bg};
14810
+ color: ${ss.fg};
14811
+ border-radius: 9px;
14812
+ font-size: 10px;
14813
+ font-weight: 700;
14814
+ line-height: 1.45;
14815
+ white-space: nowrap;
14816
+ "><span style="width:7px; height:7px; border-radius:50%; background:${ss.dot};"></span>${(0, _yuneta_gobj_js.escapeHtml)(label)}</span>`;
14817
+ }
14818
+ /************************************************************
14819
+ * HTML for the badges: what the gobj IS, besides its colour.
14820
+ ************************************************************/
14821
+ function render_badges_html(d, cs, labels) {
14822
+ let html = "";
14823
+ for (let key of node_badge_keys(d)) {
14824
+ let label = labels.badge[key] || key;
14825
+ html += `<span style="
14826
+ font-size: 10px;
14827
+ font-weight: 600;
14828
+ padding: 0 6px;
14829
+ margin-right: 4px;
14830
+ background: ${cs.tagbg};
14831
+ color: ${cs.tagfg};
14832
+ border: 1px solid ${cs.accent};
14833
+ border-radius: 9px;
14834
+ white-space: nowrap;
14835
+ ">${(0, _yuneta_gobj_js.escapeHtml)(label)}</span>`;
14836
+ }
14837
+ return html;
14838
+ }
14839
+ /************************************************************
14840
+ * Recursively build nodes and edges from a NODE DESCRIPTOR
14841
+ * tree (gobj_tree_model.js), never from gobjs: the same
14842
+ * builder draws the browser yuno and a backend one.
14843
+ *
14844
+ * The descriptor is also what the popover reads, so the view
14845
+ * fields it needs are written onto it here (`full_name`,
14846
+ * `short_name`, `num_children`, `is_collapsed`,
14847
+ * `direct_children`) instead of copied into a second object
14848
+ * that can then disagree with the first.
14113
14849
  ************************************************************/
14114
- function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, compact) {
14850
+ function build_gobj_nodes(gobj, d, nodes, edges, parent_id, compact, labels) {
14115
14851
  let priv = gobj.priv;
14116
14852
  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
14853
  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);
14854
+ let colors = get_role_colors(d);
14855
+ let cs = role_card_style(colors, dark);
14856
+ let ss = status_card_style(d, dark);
14857
+ let category = get_node_category(d);
14129
14858
  let size = NODE_SIZES[compact ? "compact" : "full"][category];
14130
14859
  let width = size.w;
14131
14860
  let height = size.h;
14132
14861
  let border_width = category === "yuno" || category === "service" ? 2 : 1;
14133
- let children = target_gobj.dl_children || [];
14862
+ let border_style = d.disabled ? "dashed" : "solid";
14863
+ let children = d.children || [];
14134
14864
  let num_children = children.length;
14135
14865
  let has_children = num_children > 0;
14136
- let collapsed = has_children && is_node_collapsed(gobj, full_name, num_children);
14866
+ let collapsed = has_children && is_node_collapsed(gobj, d.fullname, num_children);
14137
14867
  let direct_children = [];
14138
14868
  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
14869
+ full_name: child.fullname,
14870
+ has_grandchildren: (child.children || []).length > 0
14141
14871
  });
14872
+ d.full_name = d.fullname;
14873
+ d.short_name = d.shortname;
14874
+ d.num_children = num_children;
14875
+ d.is_collapsed = collapsed;
14876
+ d.direct_children = direct_children;
14877
+ d.node_id = node_id;
14142
14878
  let toggle_html = has_children ? render_toggle_html(node_id, collapsed, num_children, cs) : "";
14879
+ let card_title = d.gclass + (d.name ? "^" + d.name : "");
14143
14880
  let node_html;
14144
14881
  if (compact) {
14145
- let compact_label = (0, _yuneta_gobj_js.escapeHtml)(instance_name ? gclass_name + "^" + instance_name : gclass_name);
14882
+ let compact_label = (0, _yuneta_gobj_js.escapeHtml)(card_title);
14146
14883
  node_html = `
14147
- <div class="GOBJ_CARD" data-gobj-name="${(0, _yuneta_gobj_js.escapeHtml)(full_name)}" style="
14884
+ <div class="GOBJ_CARD" data-gobj-name="${(0, _yuneta_gobj_js.escapeHtml)(d.fullname)}" style="
14148
14885
  width: ${width}px;
14149
14886
  height: ${height}px;
14150
14887
  background: ${cs.bg};
14151
- border: ${border_width}px solid ${cs.border};
14152
- border-left: 5px solid ${cs.border};
14888
+ border: ${border_width}px ${border_style} ${cs.border};
14889
+ border-left: 6px solid ${cs.accent};
14153
14890
  border-radius: 7px;
14154
14891
  box-shadow: ${cs.shadow};
14155
14892
  font-family: ${GT_FONT};
@@ -14158,34 +14895,24 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
14158
14895
  padding: 0 8px;
14159
14896
  box-sizing: border-box;
14160
14897
  overflow: hidden;
14898
+ opacity: ${d.disabled ? "0.72" : "1"};
14161
14899
  cursor: pointer;
14162
14900
  " title="${compact_label}">
14163
14901
  <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>
14902
+ ${render_status_html(d, ss, labels, false)}
14169
14903
  ${toggle_html}
14170
14904
  </div>`;
14171
14905
  } 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("");
14906
+ let header_label = (0, _yuneta_gobj_js.escapeHtml)(d.gclass);
14907
+ let name_label = (0, _yuneta_gobj_js.escapeHtml)(d.name);
14908
+ let state_label = (0, _yuneta_gobj_js.escapeHtml)(d.state);
14183
14909
  node_html = `
14184
- <div class="GOBJ_CARD" data-gobj-name="${(0, _yuneta_gobj_js.escapeHtml)(full_name)}" style="
14910
+ <div class="GOBJ_CARD" data-gobj-name="${(0, _yuneta_gobj_js.escapeHtml)(d.fullname)}" style="
14185
14911
  width: ${width}px;
14186
14912
  height: ${height}px;
14187
14913
  background: ${cs.bg};
14188
- border: ${border_width}px solid ${cs.border};
14914
+ border: ${border_width}px ${border_style} ${cs.border};
14915
+ border-left: 6px solid ${cs.accent};
14189
14916
  border-radius: 9px;
14190
14917
  box-shadow: ${cs.shadow};
14191
14918
  overflow: hidden;
@@ -14194,48 +14921,29 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
14194
14921
  display: flex;
14195
14922
  flex-direction: column;
14196
14923
  justify-content: center;
14197
- padding: 5px 9px;
14198
- gap: 1px;
14924
+ padding: 6px 9px;
14925
+ gap: 2px;
14926
+ opacity: ${d.disabled ? "0.72" : "1"};
14199
14927
  cursor: pointer;
14200
- " title="${(0, _yuneta_gobj_js.escapeHtml)(gclass_name + (instance_name ? "^" + instance_name : ""))}">
14928
+ " title="${(0, _yuneta_gobj_js.escapeHtml)(card_title)}">
14201
14929
  <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>
14930
+ <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
14931
  ${toggle_html}
14204
14932
  </div>
14205
14933
  <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
14934
  <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>
14935
+ <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>
14936
+ ${render_status_html(d, ss, labels, true)}
14211
14937
  </div>
14938
+ <div style="flex:0 0 auto; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;">${render_badges_html(d, cs, labels)}</div>
14212
14939
  </div>`;
14213
14940
  }
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
14941
  if (!priv.node_by_id) priv.node_by_id = {};
14234
- priv.node_by_id[node_id] = node_data;
14942
+ priv.node_by_id[node_id] = d;
14235
14943
  let node = {
14236
14944
  id: node_id,
14237
14945
  type: "html",
14238
- data: node_data,
14946
+ data: d,
14239
14947
  style: {
14240
14948
  innerHTML: node_html,
14241
14949
  size: [width, height],
@@ -14248,11 +14956,11 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
14248
14956
  source: parent_id,
14249
14957
  target: node_id,
14250
14958
  style: {
14251
- stroke: colors.stroke,
14252
- lineWidth: 1
14959
+ stroke: colors.accent,
14960
+ lineWidth: 1.4
14253
14961
  }
14254
14962
  });
14255
- if (!collapsed) for (let child of children) build_gobj_nodes(gobj, child, nodes, edges, node_id, false, compact);
14963
+ if (!collapsed) for (let child of children) build_gobj_nodes(gobj, child, nodes, edges, node_id, compact, labels);
14256
14964
  return node_id;
14257
14965
  }
14258
14966
  /************************************************************
@@ -14269,9 +14977,13 @@ function load_tree(gobj) {
14269
14977
  let priv = gobj.priv;
14270
14978
  let graph = priv.graph;
14271
14979
  if (!graph) return;
14272
- let yuno = (0, _yuneta_gobj_js.gobj_yuno)();
14273
- if (!yuno) return;
14980
+ let root = describe_js_gobj((0, _yuneta_gobj_js.gobj_yuno)(), true);
14981
+ if (!root) {
14982
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no yuno to draw`);
14983
+ return;
14984
+ }
14274
14985
  let layout_cfg = get_layout_cfg((0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "layout"));
14986
+ let labels = node_labels(i18next.t);
14275
14987
  let anchor = priv.pending_anchor || null;
14276
14988
  let preserve_view = !!priv.pending_preserve_view;
14277
14989
  priv.pending_anchor = null;
@@ -14280,7 +14992,7 @@ function load_tree(gobj) {
14280
14992
  priv.node_by_id = {};
14281
14993
  let nodes = [];
14282
14994
  let edges = [];
14283
- build_gobj_nodes(gobj, yuno, nodes, edges, null, true, layout_cfg.compact);
14995
+ build_gobj_nodes(gobj, root, nodes, edges, null, layout_cfg.compact, labels);
14284
14996
  if (nodes.length > 0) {
14285
14997
  let root_id = nodes[0].id;
14286
14998
  graph.setData({
@@ -14381,46 +15093,17 @@ function refresh_tree(gobj, opts) {
14381
15093
  }
14382
15094
  }
14383
15095
  /************************************************************
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
15096
  * Render popover body from the info rows of a node.
15097
+ *
15098
+ * The rows come from gobj_tree_model.js, so a node of the
15099
+ * browser yuno and a node of a backend one are read out with
15100
+ * the same words in the same order.
14418
15101
  ************************************************************/
14419
15102
  function render_popover_body(gobj, node_data) {
14420
15103
  let $body = gobj.priv.$popover_body;
14421
15104
  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;">
15105
+ $body.innerHTML = node_info_rows(node_data, i18next.t).map(([label, value]) => `
15106
+ <div style="display:grid; grid-template-columns: 96px 1fr; column-gap:10px; padding: 2px 0;">
14424
15107
  <span style="color:var(--bulma-text-weak, #6B7280); font-weight:500;">${(0, _yuneta_gobj_js.escapeHtml)(String(label))}</span>
14425
15108
  <span style="color:var(--bulma-text-strong, #1A1A1A); word-break:break-all;">${(0, _yuneta_gobj_js.escapeHtml)(String(value))}</span>
14426
15109
  </div>
@@ -14437,6 +15120,7 @@ function show_popover(gobj, node_data, client_x, client_y) {
14437
15120
  let $title = priv.$popover_title;
14438
15121
  let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
14439
15122
  if (!$popover || !$title || !$container) return;
15123
+ priv.popover_node = node_data;
14440
15124
  $title.textContent = node_data.short_name || node_data.gclass + (node_data.name ? "^" + node_data.name : "");
14441
15125
  $title.title = $title.textContent;
14442
15126
  render_popover_body(gobj, node_data);
@@ -14467,6 +15151,7 @@ function hide_popover(gobj) {
14467
15151
  if (priv.$popover) priv.$popover.style.display = "none";
14468
15152
  if (priv.$popover_body) priv.$popover_body.innerHTML = "";
14469
15153
  if (priv.$popover_title) priv.$popover_title.textContent = "";
15154
+ priv.popover_node = null;
14470
15155
  }
14471
15156
  /************************************************************
14472
15157
  * Generic tree layout engine.
@@ -15036,6 +15721,68 @@ function ac_hide$5(gobj, event, kw, src) {
15036
15721
  hide_popover(gobj);
15037
15722
  return 0;
15038
15723
  }
15724
+ /************************************************************
15725
+ * Open the gclass of the node the popover is showing.
15726
+ *
15727
+ * One window at a time: opening a second gclass replaces the
15728
+ * first rather than stacking windows nobody closes.
15729
+ ************************************************************/
15730
+ function ac_open_gclass(gobj, event, kw, src) {
15731
+ let priv = gobj.priv;
15732
+ let node_data = priv.popover_node;
15733
+ if (!node_data || !node_data.gclass) {
15734
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: EV_OPEN_GCLASS with no node`);
15735
+ return -1;
15736
+ }
15737
+ close_gclass_view(priv.gclass_view);
15738
+ priv.gclass_view = null;
15739
+ priv.gclass_view = open_gclass_view(gobj, node_data.gclass, {
15740
+ title_prefix: (0, _yuneta_gobj_js.gobj_yuno_name)() || "",
15741
+ on_close: () => {
15742
+ if ((0, _yuneta_gobj_js.gobj_is_destroying)(gobj)) return;
15743
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_GCLASS_CLOSED", {}, gobj);
15744
+ }
15745
+ });
15746
+ return 0;
15747
+ }
15748
+ /************************************************************
15749
+ * The reader dismissed the gclass window (its own ✕).
15750
+ *
15751
+ * C_YUI_WINDOW.close_window() calls on_close and THEN destroys
15752
+ * itself, so here we only drop our reference -- destroying it
15753
+ * again would destroy it twice.
15754
+ ************************************************************/
15755
+ function ac_gclass_closed(gobj, event, kw, src) {
15756
+ gobj.priv.gclass_view = null;
15757
+ return 0;
15758
+ }
15759
+ /************************************************************
15760
+ * The gclass viewer asked for a subtree.
15761
+ *
15762
+ * It cannot: the gclass description is a COMPLETE document,
15763
+ * it carries no `__collapsed__` stub, so nothing in it is
15764
+ * fetchable. The event is declared because the viewer is
15765
+ * hosted as a child and subscribes its host to everything it
15766
+ * publishes -- and it is logged, because arriving here means
15767
+ * the document was not the one we built.
15768
+ ************************************************************/
15769
+ function ac_gclass_expand_path(gobj, event, kw, src) {
15770
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: EV_EXPAND_PATH on a complete document: ${kw && kw.path}`);
15771
+ return -1;
15772
+ }
15773
+ /************************************************************
15774
+ * The app switched language.
15775
+ *
15776
+ * A rebuild, not a re-translation: the words live inside the
15777
+ * innerHTML of G6 nodes, where refresh_language() cannot go.
15778
+ ************************************************************/
15779
+ function ac_language_changed$3(gobj, event, kw, src) {
15780
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
15781
+ if ($container) (0, _yuneta_gobj_js.refresh_language)($container, i18next.t);
15782
+ hide_popover(gobj);
15783
+ refresh_tree(gobj);
15784
+ return 0;
15785
+ }
15039
15786
  /***************************
15040
15787
  * FSM
15041
15788
  ***************************/
@@ -15069,7 +15816,11 @@ function create_gclass$9(gclass_name) {
15069
15816
  ["EV_CENTER_ANCHOR", 0],
15070
15817
  ["EV_RESIZE", 0],
15071
15818
  ["EV_SHOW", 0],
15072
- ["EV_HIDE", 0]
15819
+ ["EV_HIDE", 0],
15820
+ ["EV_OPEN_GCLASS", 0],
15821
+ ["EV_GCLASS_CLOSED", 0],
15822
+ ["EV_EXPAND_PATH", 0],
15823
+ ["EV_LANGUAGE_CHANGED", 0]
15073
15824
  ], [["ST_IDLE", [
15074
15825
  [
15075
15826
  "EV_REFRESH",
@@ -15150,6 +15901,26 @@ function create_gclass$9(gclass_name) {
15150
15901
  "EV_HIDE",
15151
15902
  ac_hide$5,
15152
15903
  null
15904
+ ],
15905
+ [
15906
+ "EV_OPEN_GCLASS",
15907
+ ac_open_gclass,
15908
+ null
15909
+ ],
15910
+ [
15911
+ "EV_GCLASS_CLOSED",
15912
+ ac_gclass_closed,
15913
+ null
15914
+ ],
15915
+ [
15916
+ "EV_EXPAND_PATH",
15917
+ ac_gclass_expand_path,
15918
+ null
15919
+ ],
15920
+ [
15921
+ "EV_LANGUAGE_CHANGED",
15922
+ ac_language_changed$3,
15923
+ null
15153
15924
  ]
15154
15925
  ]]], gmt$9, 0, attrs_table$9, PRIVATE_DATA$9, 0, 0, 0, 0);
15155
15926
  if (!__gclass__$9) return -1;