@yuneta/gobj-ui 7.23.73 → 7.23.75

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.
@@ -27368,7 +27368,7 @@ function fold_reveal(model, state, node_key) {
27368
27368
  * Two layouts made for a treedb, and nothing else.
27369
27369
  *
27370
27370
  * What a treedb graph IS, once it is folded (treedb_fold_model.js):
27371
- * a forest read left to right. A main topic is a tree (places:
27371
+ * a forest read top to bottom. A main topic is a tree (places:
27372
27372
  * country, region, site, hall), the other topics hang from its
27373
27373
  * nodes through hooks (devices, users, controllers), a record may
27374
27374
  * hang from two parents (a device from its place AND its
@@ -27389,8 +27389,8 @@ function fold_reveal(model, state, node_key) {
27389
27389
  * - the force family scatters a tree, which is the pile with
27390
27390
  * physics.
27391
27391
  *
27392
- * So: a tidy tree (`tree`) and an outline (`outline`), both fed by
27393
- * the SAME spanning tree, chosen deterministically:
27392
+ * So: a tidy tree (`tree`) and a radial tree (`radial`), both fed
27393
+ * by the SAME spanning tree, chosen deterministically:
27394
27394
  *
27395
27395
  * - roots are the nodes with no incoming edge, in node order;
27396
27396
  * - a node belongs to the FIRST parent that reaches it in a
@@ -27402,16 +27402,21 @@ function fold_reveal(model, state, node_key) {
27402
27402
  * and then by node order (record order, the pages' order);
27403
27403
  * - a node nothing reaches (a cycle) is a root too.
27404
27404
  *
27405
- * `tree`: the classic tidy tree, left to right. Each depth is a
27406
- * column as wide as its widest card; a node is centred on the
27407
- * block of its children; siblings stack with `nodesep` between
27408
- * them. O(n), and opening a hook moves NOTHING that is not below
27409
- * or beside it -- the cards keep their order, the eye keeps its
27410
- * place.
27411
- *
27412
- * `outline`: one node per row, indented by depth -- what a JSON
27413
- * viewer draws, and what a treedb is when read as one. Tall, but
27414
- * nothing is ever beside anything: the reader scrolls a list.
27405
+ * `tree`: the classic tidy tree, top to bottom. Each depth is a
27406
+ * row as tall as its tallest card; a node is centred over the
27407
+ * block of its children; siblings sit side by side with `nodesep`
27408
+ * between them. O(n), and opening a hook moves NOTHING that is
27409
+ * not below or beside it -- the cards keep their order, the eye
27410
+ * keeps its place. It reads DOWN because that is the direction a
27411
+ * tree with many children has room in: a hall with a hundred
27412
+ * devices is a wide row, not a tall column beside a card. (The
27413
+ * left-to-right reading is `direction: "LR"`, kept for a host
27414
+ * that wants it; `dagre` reads that way in the graph.)
27415
+ *
27416
+ * `radial`: the root in the middle and a ring per depth, each
27417
+ * subtree owning a sector proportional to its leaves; the radius
27418
+ * of a ring grows until its neighbours sit side by side, so
27419
+ * nothing overlaps by construction (see `layout_radial`).
27415
27420
  *
27416
27421
  * Pure: takes plain nodes and edges, returns positions. The G6
27417
27422
  * classes in c_g6_nodes_tree.js are two thin adapters over it.
@@ -27421,8 +27426,8 @@ function fold_reveal(model, state, node_key) {
27421
27426
  ***********************************************************************/
27422
27427
  var DEFAULTS = {
27423
27428
  nodesep: 18,
27424
- ranksep: 110,
27425
- indent: 48
27429
+ ranksep: 90,
27430
+ direction: "TB"
27426
27431
  };
27427
27432
  /************************************************************
27428
27433
  * The spanning tree of a graph, deterministic.
@@ -27509,13 +27514,33 @@ function walk_order(t) {
27509
27514
  return order;
27510
27515
  }
27511
27516
  /************************************************************
27512
- * The tidy tree, left to right.
27517
+ * The tidy tree.
27513
27518
  *
27514
27519
  * Returns Map<id, {x, y}> with the CENTRE of each node, the way G6
27515
27520
  * positions a node.
27521
+ *
27522
+ * The algorithm is written ONCE, reading left to right (depths are
27523
+ * columns, siblings stack down). Top to bottom -- the default -- is
27524
+ * the same tree with the axes swapped: every card is fed in
27525
+ * transposed (its height as its width), and every position comes
27526
+ * back transposed. That is the whole difference, and it is the
27527
+ * reason the code is not duplicated for a second direction.
27516
27528
  ************************************************************/
27517
27529
  function layout_tree(nodes, edges, opts) {
27518
27530
  let o = Object.assign({}, DEFAULTS, opts || {});
27531
+ if (o.direction === "LR") return tidy_tree_lr(nodes, edges, o);
27532
+ let lr = tidy_tree_lr(nodes.map((n) => Object.assign({}, n, {
27533
+ w: n.h,
27534
+ h: n.w
27535
+ })), edges, o);
27536
+ let pos = /* @__PURE__ */ new Map();
27537
+ for (let [id, p] of lr) pos.set(id, {
27538
+ x: p.y,
27539
+ y: p.x
27540
+ });
27541
+ return pos;
27542
+ }
27543
+ function tidy_tree_lr(nodes, edges, o) {
27519
27544
  let t = spanning_tree(nodes, edges);
27520
27545
  let pos = /* @__PURE__ */ new Map();
27521
27546
  let col_w = [];
@@ -27681,37 +27706,6 @@ function layout_radial(nodes, edges, opts) {
27681
27706
  }
27682
27707
  return pos;
27683
27708
  }
27684
- /************************************************************
27685
- * The outline: one node per row, indented by depth, in the
27686
- * pre-order of the spanning tree. Rows are left-aligned on
27687
- * their indent, so a column of chips reads as a list.
27688
- ************************************************************/
27689
- function layout_outline(nodes, edges, opts) {
27690
- let o = Object.assign({}, DEFAULTS, opts || {});
27691
- let t = spanning_tree(nodes, edges);
27692
- let pos = /* @__PURE__ */ new Map();
27693
- let y = 0;
27694
- let visit = (root_id) => {
27695
- let stack = [root_id];
27696
- while (stack.length) {
27697
- let id = stack.pop();
27698
- let n = t.by_id.get(id);
27699
- let d = t.depth.get(id);
27700
- pos.set(id, {
27701
- x: d * o.indent + n.w / 2,
27702
- y: y + n.h / 2
27703
- });
27704
- y += n.h + o.nodesep;
27705
- let kids = t.children.get(id) || [];
27706
- for (let i = kids.length - 1; i >= 0; i--) stack.push(kids[i]);
27707
- }
27708
- };
27709
- for (let root of t.roots) {
27710
- visit(root);
27711
- y += o.nodesep;
27712
- }
27713
- return pos;
27714
- }
27715
27709
  //#endregion
27716
27710
  //#region node_modules/@babel/runtime/helpers/esm/typeof.js
27717
27711
  function _typeof(o) {
@@ -54528,6 +54522,14 @@ var GCLASS_NAME$4 = "C_G6_NODES_TREE";
54528
54522
  var HIGHLIGHT_COLOR = "#f0a020";
54529
54523
  var HIGHLIGHT_HALO = "rgba(240,160,32,0.35)";
54530
54524
  var SELECT_RING = "rgba(59,130,246,0.95)";
54525
+ var PORT_R_ENTITY = 10;
54526
+ var PORT_R_CHILD = 5;
54527
+ var PORT_LINE_WIDTH = 2;
54528
+ var COMPACT_SIZE = {
54529
+ hierarchical: 32,
54530
+ extended: 28,
54531
+ child: 22
54532
+ };
54531
54533
  /***************************************************************
54532
54534
  * Internal layout and operation mode definitions
54533
54535
  ***************************************************************/
@@ -54535,13 +54537,9 @@ var _layouts = {
54535
54537
  "manual": { type: "manual" },
54536
54538
  "treedb-tree": {
54537
54539
  type: "treedb-tree",
54540
+ direction: "TB",
54538
54541
  nodesep: 18,
54539
- ranksep: 110
54540
- },
54541
- "treedb-outline": {
54542
- type: "treedb-outline",
54543
- nodesep: 12,
54544
- indent: 48
54542
+ ranksep: 90
54545
54543
  },
54546
54544
  "dagre": {
54547
54545
  type: "dagre",
@@ -54612,6 +54610,8 @@ var attrs_table$4 = [
54612
54610
  SDATA(data_type_t.DTP_LIST, "hidden_topics", 0, "[]", "Topics left out of the tree: no card, no pill counts them, no edge reaches them"),
54613
54611
  SDATA(data_type_t.DTP_STRING, "main_topic", 0, "", "The topic the tree hangs from; empty = deduced (the one whose hooks reach the most others). Its parentless records are the roots; a parentless record of a topic linked to it is LOOSE and shown only on request"),
54614
54612
  SDATA(data_type_t.DTP_LIST, "loose_topics", 0, "[]", "Topics whose loose records (no parent, in a topic the schema hangs from the main one) are shown as roots"),
54613
+ SDATA(data_type_t.DTP_STRING, "node_mode", 0, "expanded", "How a record is drawn: `expanded` = the card with its ports (the only shape a link can be edited on); `compact` = a rounded square of the topic's colour, no ports, no text -- the topology alone. One node can be toggled against it (EV_TOGGLE_NODE_MODE)"),
54614
+ SDATA(data_type_t.DTP_BOOLEAN, "node_labels", 0, true, "A compact node says its name under the square. Off, it is a coloured square and nothing else"),
54615
54615
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
54616
54616
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
54617
54617
  SDATA(data_type_t.DTP_STRING, "wide", 0, "40px", "Height of header"),
@@ -54695,6 +54695,7 @@ var PRIVATE_DATA$4 = {
54695
54695
  _fold_state: null,
54696
54696
  _fold_listener: null,
54697
54697
  _pill_sig: null,
54698
+ _open_nodes: null,
54698
54699
  _fold_busy: false
54699
54700
  };
54700
54701
  var __gclass__$4 = null;
@@ -54840,7 +54841,6 @@ function register_layouts(gobj) {
54840
54841
  _g6_extensions_registered = true;
54841
54842
  register(ExtensionCategory.LAYOUT, "manual", ManualLayout);
54842
54843
  register(ExtensionCategory.LAYOUT, "treedb-tree", TreedbTreeLayout);
54843
- register(ExtensionCategory.LAYOUT, "treedb-outline", TreedbOutlineLayout);
54844
54844
  register(ExtensionCategory.LAYOUT, "treedb-radial", TreedbRadialLayout);
54845
54845
  register(ExtensionCategory.NODE, "light", LightNode);
54846
54846
  }
@@ -54923,6 +54923,9 @@ function configure_events(gobj) {
54923
54923
  graph.on(NodeEvent.CONTEXT_MENU, (evt) => {
54924
54924
  gobj_send_event(gobj, "EV_NODE_CONTEXT_MENU", { evt }, gobj);
54925
54925
  });
54926
+ graph.on(NodeEvent.DBLCLICK, (evt) => {
54927
+ gobj_send_event(gobj, "EV_NODE_DBLCLICK", { evt }, gobj);
54928
+ });
54926
54929
  graph.on(EdgeEvent.CLICK, (evt) => {
54927
54930
  if (consume_long_press_click(graph)) return;
54928
54931
  gobj_send_event(gobj, "EV_EDGE_CLICK", { evt }, gobj);
@@ -55347,7 +55350,7 @@ function configure_behaviour(gobj) {
55347
55350
  case "writing":
55348
55351
  case "reading":
55349
55352
  priv.edit_mode = false;
55350
- behaviors = ["drag-canvas", "zoom-canvas"];
55353
+ behaviors = ["drag-canvas", ...camera_behaviors()];
55351
55354
  break;
55352
55355
  case "edition":
55353
55356
  priv.edit_mode = true;
@@ -55361,7 +55364,7 @@ function configure_behaviour(gobj) {
55361
55364
  return true;
55362
55365
  }
55363
55366
  },
55364
- "zoom-canvas",
55367
+ ...camera_behaviors(),
55365
55368
  "drag-element",
55366
55369
  {
55367
55370
  type: BRUSH_SELECT_OWNED,
@@ -55379,7 +55382,7 @@ function configure_behaviour(gobj) {
55379
55382
  break;
55380
55383
  case "operation":
55381
55384
  priv.edit_mode = false;
55382
- behaviors = ["drag-canvas", "zoom-canvas"];
55385
+ behaviors = ["drag-canvas", ...camera_behaviors()];
55383
55386
  break;
55384
55387
  default: log_error(`operation mode unknown: ${operation_mode}`);
55385
55388
  }
@@ -55451,13 +55454,33 @@ function build_ports(gobj, desc) {
55451
55454
  * Which way the graph reads. `dagre` is the left-to-right
55452
55455
  * layout; everything else keeps the top-down reading, `manual`
55453
55456
  * included, because the saved arrangements were made with the
55454
- * ports on the top and bottom edges.
55455
- ************************************************************/
55456
- var LR_LAYOUTS = [
55457
- "dagre",
55458
- "treedb-tree",
55459
- "treedb-outline"
55460
- ];
55457
+ * ports on the top and bottom edges. The treedb tree reads down
55458
+ * too, since 7.23.75: read right it was dagre's twin.
55459
+ ************************************************************/
55460
+ var LR_LAYOUTS = ["dagre"];
55461
+ /************************************************************
55462
+ * The camera behaviors every operation mode shares.
55463
+ *
55464
+ * The wheel SCROLLS, as it does on a map and on every page: a
55465
+ * graph of many nodes is taller than the screen, and a wheel
55466
+ * that zoomed instead made it a thing to be looked at from afar
55467
+ * or read through a keyhole -- never scrolled. Zoom is Ctrl +
55468
+ * wheel (which is also what a trackpad pinch arrives as), the
55469
+ * toolbar's +/-, and the two-finger pinch of g6_touch_gestures.
55470
+ * `enable` on the scroll keeps the two apart: a Ctrl wheel is
55471
+ * the zoom's, so the scroll stands aside for it.
55472
+ ************************************************************/
55473
+ function camera_behaviors() {
55474
+ return [{
55475
+ type: "scroll-canvas",
55476
+ key: "scroll-canvas",
55477
+ enable: (event) => !(event && (event.ctrlKey || event.metaKey))
55478
+ }, {
55479
+ type: "zoom-canvas",
55480
+ key: "zoom-canvas",
55481
+ trigger: ["Control"]
55482
+ }];
55483
+ }
55461
55484
  function layout_direction(gobj) {
55462
55485
  return str_in_list(LR_LAYOUTS, gobj.priv.layout) ? "LR" : "TB";
55463
55486
  }
@@ -55525,83 +55548,101 @@ function apply_layout_direction(gobj) {
55525
55548
  * Create a topic node in the G6 graph
55526
55549
  ************************************************************/
55527
55550
  function create_topic_node(gobj, desc, record) {
55528
- let priv = gobj.priv;
55529
- const graph = priv.graph;
55551
+ const graph = gobj.priv.graph;
55530
55552
  let node_name = build_node_name(gobj, desc.topic_name, record.id);
55531
55553
  let xy = get_default_ne_xy(gobj);
55532
55554
  let geometry = get_node_graph_props(gobj, desc.topic_name, record.id, record);
55533
55555
  let x = kw_get_int(gobj, geometry, "x", xy, kw_flag_t.KW_CREATE);
55534
55556
  let y = kw_get_int(gobj, geometry, "y", xy, kw_flag_t.KW_CREATE);
55535
- let ports = build_ports(gobj, desc);
55536
- let node_graph_type = null;
55557
+ let shape = node_shape_of(gobj, node_name, desc, record, geometry, pills_html_of_key(gobj, fold_node_key(desc.topic_name, String(record.id))));
55558
+ let node_def = {
55559
+ id: node_name,
55560
+ type: shape.type,
55561
+ style: Object.assign({
55562
+ x,
55563
+ y
55564
+ }, shape.style),
55565
+ data: {
55566
+ topic_name: desc.topic_name,
55567
+ desc,
55568
+ record,
55569
+ graph_props: geometry,
55570
+ port_keys: build_ports(gobj, desc).map((p) => p.key)
55571
+ }
55572
+ };
55573
+ graph.addNodeData([node_def]);
55574
+ }
55575
+ /************************************************************
55576
+ * The shape of a record node: OPEN (the card, with its ports)
55577
+ * or CLOSED (a coloured square), by `node_mode` and the nodes
55578
+ * toggled against it.
55579
+ *
55580
+ * Both answer {type, style} WITHOUT x/y, which belong to the node
55581
+ * and not to its shape, and a node moves between the two on the
55582
+ * fly: G6 rebuilds the element when `type` changes, and the
55583
+ * style is MERGED, which is why each shape also writes the keys
55584
+ * the other one would leave behind (`labelText`, `ports`).
55585
+ ************************************************************/
55586
+ function node_shape_of(gobj, node_id, desc, record, geometry, pills_html) {
55587
+ if (node_is_compact(gobj, node_id)) return compact_shape_of(gobj, desc, record, {});
55588
+ return card_shape_of(gobj, desc, record, geometry, pills_html);
55589
+ }
55590
+ function node_is_compact(gobj, node_id) {
55591
+ let priv = gobj.priv;
55592
+ if (!priv._open_nodes) priv._open_nodes = /* @__PURE__ */ new Set();
55593
+ let compact = gobj_read_str_attr(gobj, "node_mode") === "compact";
55594
+ return priv._open_nodes.has(node_id) ? !compact : compact;
55595
+ }
55596
+ /************************************************************
55597
+ * The CARD: an html node, the tier's size, the ports, the
55598
+ * saved geometry on top. The flags (focus, selection, anchor)
55599
+ * are not painted here: repaint_cards() reads them where they
55600
+ * live and rebuilds the html.
55601
+ ************************************************************/
55602
+ function card_shape_of(gobj, desc, record, geometry, pills_html) {
55603
+ let priv = gobj.priv;
55537
55604
  let node_treedb_type = desc.node_treedb_type;
55605
+ let label = node_label(desc, record);
55606
+ let ports = build_ports(gobj, desc);
55538
55607
  let style = {
55539
- x,
55540
- y,
55541
55608
  fill: desc.color,
55542
55609
  stroke: getStrokeColor(desc.color),
55543
- lineWidth: 1
55610
+ lineWidth: 1,
55611
+ lineDash: [],
55612
+ halo: false,
55613
+ labelText: ""
55544
55614
  };
55545
- let pills_html = pills_html_of_key(gobj, fold_node_key(desc.topic_name, String(record.id)));
55546
55615
  let size = card_size_for(node_treedb_type, !!pills_html);
55547
- if (node_treedb_type === "child") {
55548
- node_graph_type = "html";
55549
- style.size = size;
55550
- style.dx = -size[0] / 2;
55551
- style.dy = -size[1] / 2;
55552
- style.innerHTML = build_chip_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), record.id);
55553
- } else if (node_treedb_type === "extended") {
55554
- node_graph_type = "html";
55555
- style.size = size;
55556
- style.dx = -size[0] / 2;
55557
- style.dy = -size[1] / 2;
55558
- style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, true, record.id, false, false, false, pills_html);
55559
- } else {
55560
- node_graph_type = "html";
55561
- style.size = size;
55562
- style.dx = -size[0] / 2;
55563
- style.dy = -size[1] / 2;
55564
- style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, false, record.id, false, false, false, pills_html);
55565
- }
55616
+ style.size = size;
55617
+ style.dx = -size[0] / 2;
55618
+ style.dy = -size[1] / 2;
55619
+ if (node_treedb_type === "child") style.innerHTML = build_chip_innerHTML(desc.color, priv.theme, record.icon, label, record.id);
55620
+ else if (node_treedb_type === "extended") style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, label, desc.topic_name, true, record.id, false, false, false, pills_html);
55621
+ else style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, label, desc.topic_name, false, record.id, false, false, false, pills_html);
55566
55622
  let topic_props = priv._graph_properties[desc.topic_name];
55567
55623
  let topic_defaults = is_object(topic_props) && is_object(topic_props.defaults) ? topic_props.defaults : null;
55568
55624
  if (topic_defaults) {
55569
55625
  let def_size = topic_defaults.size;
55570
55626
  if (!geometry.size && Array.isArray(def_size) && def_size.length > 0) {
55571
55627
  style.size = [...def_size];
55572
- if (node_graph_type === "html") {
55573
- style.dx = -def_size[0] / 2;
55574
- style.dy = -(def_size.length > 1 ? def_size[1] : def_size[0]) / 2;
55575
- }
55628
+ style.dx = -def_size[0] / 2;
55629
+ style.dy = -(def_size.length > 1 ? def_size[1] : def_size[0]) / 2;
55576
55630
  }
55577
55631
  if (!geometry.portR && topic_defaults.portR > 0) style.portR = topic_defaults.portR;
55578
55632
  }
55579
55633
  let saved_size = geometry.size;
55580
55634
  if (Array.isArray(saved_size) && saved_size.length > 0) {
55581
55635
  style.size = saved_size;
55582
- if (node_graph_type === "html") {
55583
- style.dx = -saved_size[0] / 2;
55584
- style.dy = -(saved_size.length > 1 ? saved_size[1] : saved_size[0]) / 2;
55585
- }
55636
+ style.dx = -saved_size[0] / 2;
55637
+ style.dy = -(saved_size.length > 1 ? saved_size[1] : saved_size[0]) / 2;
55586
55638
  }
55587
55639
  if (geometry.portR > 0) style.portR = geometry.portR;
55588
- let node_def = {
55589
- id: node_name,
55590
- type: node_graph_type,
55591
- style,
55592
- data: {
55593
- topic_name: desc.topic_name,
55594
- desc,
55595
- record,
55596
- graph_props: geometry
55597
- }
55598
- };
55599
55640
  if (json_size(ports)) {
55600
55641
  json_object_update_missing(style, {
55601
55642
  port: true,
55602
55643
  ports,
55603
- portR: node_treedb_type === "child" ? 2 : 6,
55604
- portLineWidth: 1
55644
+ portR: node_treedb_type === "child" ? PORT_R_CHILD : PORT_R_ENTITY,
55645
+ portLineWidth: PORT_LINE_WIDTH
55605
55646
  });
55606
55647
  let port_sizes = geometry.port_sizes;
55607
55648
  let default_port_sizes = topic_defaults ? topic_defaults.port_sizes : null;
@@ -55611,8 +55652,117 @@ function create_topic_node(gobj, desc, record) {
55611
55652
  if (r == null && is_object(default_port_sizes)) r = default_port_sizes[key];
55612
55653
  if (r != null) style.ports[i].r = r;
55613
55654
  }
55655
+ } else {
55656
+ style.port = false;
55657
+ style.ports = [];
55658
+ }
55659
+ return {
55660
+ type: "html",
55661
+ style
55662
+ };
55663
+ }
55664
+ /************************************************************
55665
+ * The SQUARE: a G6 `rect` of the topic's colour, no ports, the
55666
+ * name as a label under it when `node_labels` says so. A native
55667
+ * shape and not html on purpose: G6's own stroke and halo paint
55668
+ * on it, so the focus, the selection and the anchor are three
55669
+ * keys of the style instead of a rebuilt innerHTML.
55670
+ *
55671
+ * flags {highlight, selected, anchored}
55672
+ ************************************************************/
55673
+ function compact_shape_of(gobj, desc, record, flags) {
55674
+ let dark = gobj.priv.theme === "dark";
55675
+ let f = flags || {};
55676
+ let tier = desc.node_treedb_type;
55677
+ let side = COMPACT_SIZE[tier] || COMPACT_SIZE.hierarchical;
55678
+ let labels = gobj_read_bool_attr(gobj, "node_labels");
55679
+ let stroke = getStrokeColor(desc.color);
55680
+ let line_width = 1.5;
55681
+ if (f.selected) {
55682
+ stroke = SELECT_RING;
55683
+ line_width = 3;
55684
+ }
55685
+ if (f.highlight) {
55686
+ stroke = HIGHLIGHT_COLOR;
55687
+ line_width = 3;
55688
+ }
55689
+ let dash = [];
55690
+ if (tier === "extended") dash = [4, 3];
55691
+ if (f.anchored) {
55692
+ dash = [2, 2];
55693
+ line_width = Math.max(line_width, 2.5);
55694
+ }
55695
+ return {
55696
+ type: "rect",
55697
+ style: {
55698
+ size: [side, side],
55699
+ radius: Math.round(side / 5),
55700
+ fill: desc.color,
55701
+ stroke,
55702
+ lineWidth: line_width,
55703
+ lineDash: dash,
55704
+ halo: !!f.highlight,
55705
+ haloStroke: HIGHLIGHT_HALO,
55706
+ haloLineWidth: 8,
55707
+ port: false,
55708
+ ports: [],
55709
+ labelText: labels ? node_label(desc, record) : "",
55710
+ labelPlacement: "bottom",
55711
+ labelFontSize: 11,
55712
+ labelFill: dark ? "#e6e9ef" : "#2e333d",
55713
+ labelMaxWidth: 150,
55714
+ labelWordWrap: true,
55715
+ labelMaxLines: 1,
55716
+ labelTextOverflow: "ellipsis",
55717
+ labelBackground: true,
55718
+ labelBackgroundFill: dark ? "#1b2230" : "#ffffff",
55719
+ labelBackgroundOpacity: .85,
55720
+ labelBackgroundRadius: 3,
55721
+ labelPadding: [1, 3]
55722
+ }
55723
+ };
55724
+ }
55725
+ /************************************************************
55726
+ * Give these record nodes the shape their mode says, then
55727
+ * repaint them with the flags they carry. A `+N` chip is not a
55728
+ * record and keeps its own shape.
55729
+ ************************************************************/
55730
+ function reshape_nodes(gobj, ids) {
55731
+ let graph = gobj.priv.graph;
55732
+ if (!graph) return;
55733
+ let updates = [];
55734
+ let touched = /* @__PURE__ */ new Set();
55735
+ for (let id of ids) {
55736
+ let nd;
55737
+ try {
55738
+ nd = graph.getNodeData(id);
55739
+ } catch (e) {
55740
+ continue;
55741
+ }
55742
+ if (!nd || !nd.data || !nd.data.desc || is_more_node(nd)) continue;
55743
+ let geometry = nd.data.graph_props || {};
55744
+ let shape = node_shape_of(gobj, id, nd.data.desc, nd.data.record || {}, geometry, pills_html_of(gobj, nd));
55745
+ updates.push({
55746
+ id,
55747
+ type: shape.type,
55748
+ style: shape.style
55749
+ });
55750
+ touched.add(id);
55751
+ }
55752
+ if (!updates.length) return;
55753
+ try {
55754
+ graph.updateNodeData(updates);
55755
+ } catch (e) {
55756
+ log_error(`${gobj_short_name(gobj)}: cannot reshape the nodes: ${e}`);
55757
+ return;
55614
55758
  }
55615
- if (node_graph_type) graph.addNodeData([node_def]);
55759
+ repaint_cards(gobj, touched);
55760
+ }
55761
+ function all_record_node_ids(gobj) {
55762
+ let graph = gobj.priv.graph;
55763
+ let ids = [];
55764
+ for (let nd of graph && graph.getNodeData() || []) if (nd && nd.data && nd.data.desc && !is_more_node(nd)) ids.push(nd.id);
55765
+ return ids;
55616
55766
  }
55617
55767
  /************************************************************
55618
55768
  * Update topic node data
@@ -56459,7 +56609,7 @@ var ManualLayout = class extends BaseLayout {
56459
56609
  }
56460
56610
  };
56461
56611
  /************************************************************
56462
- * Custom G6 layouts: TreedbTreeLayout / TreedbOutlineLayout.
56612
+ * Custom G6 layouts: TreedbTreeLayout / TreedbRadialLayout.
56463
56613
  *
56464
56614
  * Thin adapters over treedb_layout.js: the nodes with their sizes,
56465
56615
  * the edges with the RANK of the hook they leave by (the position
@@ -56477,7 +56627,8 @@ function treedb_layout_input(data) {
56477
56627
  let w = is_array(size) ? size[0] : size;
56478
56628
  let h = is_array(size) ? size.length > 1 ? size[1] : size[0] : size;
56479
56629
  let ports = node.style && is_array(node.style.ports) ? node.style.ports : [];
56480
- ports_of.set(node.id, ports.map((p) => p.key));
56630
+ let keys = node.data && is_array(node.data.port_keys) ? node.data.port_keys : ports.map((p) => p.key);
56631
+ ports_of.set(node.id, keys);
56481
56632
  return {
56482
56633
  id: node.id,
56483
56634
  w,
@@ -56513,12 +56664,6 @@ var TreedbTreeLayout = class extends BaseLayout {
56513
56664
  return treedb_layout_output(layout_tree(input.nodes, input.edges, options));
56514
56665
  }
56515
56666
  };
56516
- var TreedbOutlineLayout = class extends BaseLayout {
56517
- async execute(data, options) {
56518
- let input = treedb_layout_input(data);
56519
- return treedb_layout_output(layout_outline(input.nodes, input.edges, options));
56520
- }
56521
- };
56522
56667
  var TreedbRadialLayout = class extends BaseLayout {
56523
56668
  async execute(data, options) {
56524
56669
  let input = treedb_layout_input(data);
@@ -57210,7 +57355,7 @@ function get_port_radius(gobj, node_id, port_key) {
57210
57355
  if (ports[i].r != null) return ports[i].r;
57211
57356
  break;
57212
57357
  }
57213
- return style.portR || 6;
57358
+ return style.portR || PORT_R_ENTITY;
57214
57359
  }
57215
57360
  /************************************************************
57216
57361
  * How much wider than the port itself its hit area is,
@@ -57242,7 +57387,7 @@ function detect_port_click(gobj, node_id, canvasX, canvasY) {
57242
57387
  const style = graph.getNodeData(node_id).style || {};
57243
57388
  const { w, h } = extract_size(style);
57244
57389
  const ports = style.ports || [];
57245
- const defaultR = style.portR || 6;
57390
+ const defaultR = style.portR || PORT_R_ENTITY;
57246
57391
  let best_key = null;
57247
57392
  let best_dist = Infinity;
57248
57393
  let slop = port_hit_slop_world(graph);
@@ -57399,7 +57544,7 @@ function enter_linking_mode(gobj) {
57399
57544
  p.stroke = "#52c41a";
57400
57545
  p.lineWidth = 3;
57401
57546
  if (p.r != null) p.r = p.r + 4;
57402
- else p.r = (style.portR || 6) + 4;
57547
+ else p.r = (style.portR || PORT_R_ENTITY) + 4;
57403
57548
  }
57404
57549
  graph.updateNodeData([{
57405
57550
  id: vh.node_id,
@@ -57551,7 +57696,7 @@ function find_hook_at_point(gobj, canvasX, canvasY) {
57551
57696
  let pl = p.placement || [.5, .5];
57552
57697
  let px = pos[0] + (pl[0] - .5) * w;
57553
57698
  let py = pos[1] + (pl[1] - .5) * h;
57554
- let r = p.r != null ? p.r : style.portR || 6;
57699
+ let r = p.r != null ? p.r : style.portR || PORT_R_ENTITY;
57555
57700
  let dx = canvasX - px;
57556
57701
  let dy = canvasY - py;
57557
57702
  if (Math.sqrt(dx * dx + dy * dy) <= r + 6) return {
@@ -58309,7 +58454,19 @@ function repaint_cards(gobj, ids) {
58309
58454
  } catch (e) {
58310
58455
  continue;
58311
58456
  }
58312
- let html = node_innerHTML_of(nd, priv.theme, focus.has(id), selected.has(id), priv.anchor_state === "on" && id === priv.anchor_id, pills_html_of(gobj, nd));
58457
+ let anchored = priv.anchor_state === "on" && id === priv.anchor_id;
58458
+ if (nd && nd.data && nd.data.desc && node_is_compact(gobj, id)) {
58459
+ updates.push({
58460
+ id,
58461
+ style: compact_shape_of(gobj, nd.data.desc, nd.data.record || {}, {
58462
+ highlight: focus.has(id),
58463
+ selected: selected.has(id),
58464
+ anchored
58465
+ }).style
58466
+ });
58467
+ continue;
58468
+ }
58469
+ let html = node_innerHTML_of(nd, priv.theme, focus.has(id), selected.has(id), anchored, pills_html_of(gobj, nd));
58313
58470
  if (html !== null) updates.push({
58314
58471
  id,
58315
58472
  style: { innerHTML: html }
@@ -58346,7 +58503,19 @@ function refresh_html_nodes_theme(gobj, theme) {
58346
58503
  for (let i = 0; i < nodes.length; i++) {
58347
58504
  let id = nodes[i].id;
58348
58505
  let nd = graph.getNodeData(id);
58349
- let html = node_innerHTML_of(nd, theme, highlighted.has(id), selected.has(id), priv.anchor_state === "on" && id === priv.anchor_id, pills_html_of(gobj, nd));
58506
+ let anchored = priv.anchor_state === "on" && id === priv.anchor_id;
58507
+ if (nd && nd.data && nd.data.desc && node_is_compact(gobj, id)) {
58508
+ updates.push({
58509
+ id,
58510
+ style: compact_shape_of(gobj, nd.data.desc, nd.data.record || {}, {
58511
+ highlight: highlighted.has(id),
58512
+ selected: selected.has(id),
58513
+ anchored
58514
+ }).style
58515
+ });
58516
+ continue;
58517
+ }
58518
+ let html = node_innerHTML_of(nd, theme, highlighted.has(id), selected.has(id), anchored, pills_html_of(gobj, nd));
58350
58519
  if (html === null) html = more_innerHTML_of(gobj, nd, theme);
58351
58520
  if (html !== null) updates.push({
58352
58521
  id,
@@ -58966,8 +59135,10 @@ function ctx_item(icon_id, label, value) {
58966
59135
  ************************************************************/
58967
59136
  function build_node_context_menu(gobj, node_id) {
58968
59137
  let items = [];
58969
- if (gobj.priv.edit_mode) {
58970
- inject_svg_icons();
59138
+ inject_svg_icons();
59139
+ if (node_is_compact(gobj, node_id)) items.push(ctx_item("g6-icon-fullscreen", t("open node"), "toggle_node_mode"));
59140
+ else items.push(ctx_item("g6-icon-fullscreen-exit", t("close node"), "toggle_node_mode"));
59141
+ if (gobj.priv.edit_mode && !node_is_compact(gobj, node_id)) {
58971
59142
  items.push(ctx_item("g6-icon-resize", t("resize all"), "resize_all_nodes"));
58972
59143
  items.push(ctx_item("g6-icon-resize", t("resize topic nodes"), "resize_topic_nodes"));
58973
59144
  }
@@ -58995,8 +59166,11 @@ function build_edge_context_menu(gobj) {
58995
59166
  * Handle context menu click: dispatch by value.
58996
59167
  ************************************************************/
58997
59168
  function handle_context_menu_click(gobj, value) {
58998
- gobj.priv;
59169
+ let priv = gobj.priv;
58999
59170
  switch (value) {
59171
+ case "toggle_node_mode":
59172
+ if (priv._context_node_id) gobj_send_event(gobj, "EV_TOGGLE_NODE_MODE", { id: priv._context_node_id }, gobj);
59173
+ break;
59000
59174
  case "resize_all_nodes":
59001
59175
  copy_size_to_nodes(gobj, false);
59002
59176
  break;
@@ -59527,7 +59701,7 @@ async function reconcile_fold_apply(gobj, opts, visible) {
59527
59701
  let nd = graph.getNodeData(id);
59528
59702
  let desc = nd && nd.data && nd.data.desc;
59529
59703
  let geometry = nd && nd.data && nd.data.graph_props || {};
59530
- if (desc && !is_array(geometry.size)) {
59704
+ if (desc && !is_array(geometry.size) && !node_is_compact(gobj, id)) {
59531
59705
  let size = card_size_for(desc.node_treedb_type, !!sig);
59532
59706
  resize.push({
59533
59707
  id,
@@ -59612,6 +59786,7 @@ function ac_clear_data(gobj, event, kw, src) {
59612
59786
  priv._fold_model = null;
59613
59787
  priv._fold_state = null;
59614
59788
  priv._pill_sig = null;
59789
+ priv._open_nodes = null;
59615
59790
  graph_remove_plugin(gobj, "history");
59616
59791
  update_history_buttons(gobj);
59617
59792
  graph_clear(gobj);
@@ -60110,6 +60285,79 @@ function ac_set_loose_topics(gobj, event, kw, src) {
60110
60285
  return 0;
60111
60286
  }
60112
60287
  /************************************************************
60288
+ * The shape of every record: open cards or closed squares.
60289
+ * A change of mode forgets the nodes toggled against the old
60290
+ * one -- they were exceptions to a rule that no longer holds --
60291
+ * and lays the graph out again, because a square and a card
60292
+ * do not occupy the same room.
60293
+ ************************************************************/
60294
+ function ac_set_node_mode(gobj, event, kw, src) {
60295
+ let priv = gobj.priv;
60296
+ let mode = kw && kw.node_mode === "compact" ? "compact" : "expanded";
60297
+ gobj_write_str_attr(gobj, "node_mode", mode);
60298
+ priv._open_nodes = /* @__PURE__ */ new Set();
60299
+ if (!priv.graph || !priv._fold_model) return 0;
60300
+ reshape_nodes(gobj, all_record_node_ids(gobj));
60301
+ reconcile_fold(gobj, {
60302
+ relayout: true,
60303
+ fit: true
60304
+ });
60305
+ return 0;
60306
+ }
60307
+ /************************************************************
60308
+ * The name under a closed square, on or off. Only the closed
60309
+ * nodes change, and nothing moves: the label hangs outside the
60310
+ * square and the layout measures the square.
60311
+ ************************************************************/
60312
+ function ac_set_node_labels(gobj, event, kw, src) {
60313
+ let priv = gobj.priv;
60314
+ gobj_write_bool_attr(gobj, "node_labels", !!(kw && kw.node_labels));
60315
+ if (!priv.graph) return 0;
60316
+ reshape_nodes(gobj, all_record_node_ids(gobj).filter((id) => node_is_compact(gobj, id)));
60317
+ return 0;
60318
+ }
60319
+ /************************************************************
60320
+ * ONE node opened or closed against the mode (a double click,
60321
+ * or the context menu). It holds still on screen while the
60322
+ * rest of the graph makes room, as a fold does.
60323
+ ************************************************************/
60324
+ function ac_toggle_node_mode$1(gobj, event, kw, src) {
60325
+ let priv = gobj.priv;
60326
+ let node_id = kw && kw.id || "";
60327
+ if (!priv.graph || !node_id) {
60328
+ log_error(`${gobj_short_name(gobj)}: toggle node mode without a node`);
60329
+ return -1;
60330
+ }
60331
+ if (!priv._open_nodes) priv._open_nodes = /* @__PURE__ */ new Set();
60332
+ if (priv._open_nodes.has(node_id)) priv._open_nodes.delete(node_id);
60333
+ else priv._open_nodes.add(node_id);
60334
+ let vp = yui_graph_viewport_of(priv.graph, node_id);
60335
+ reshape_nodes(gobj, [node_id]);
60336
+ if (priv._fold_model) reconcile_fold(gobj, {
60337
+ relayout: true,
60338
+ keep: node_id,
60339
+ keep_vp: vp
60340
+ });
60341
+ return 0;
60342
+ }
60343
+ /************************************************************
60344
+ * A double click on a node: open it if closed, close it if open.
60345
+ * A `+N` chip is not a record and has no shape to toggle.
60346
+ ************************************************************/
60347
+ function ac_node_dblclick(gobj, event, kw, src) {
60348
+ let priv = gobj.priv;
60349
+ let node_id = kw && kw.evt && kw.evt.target ? kw.evt.target.id : "";
60350
+ let nd = null;
60351
+ try {
60352
+ nd = priv.graph.getNodeData(node_id);
60353
+ } catch (e) {
60354
+ nd = null;
60355
+ }
60356
+ if (!nd || !nd.data || !nd.data.desc || is_more_node(nd)) return 0;
60357
+ gobj_send_event(gobj, "EV_TOGGLE_NODE_MODE", { id: node_id }, gobj);
60358
+ return 0;
60359
+ }
60360
+ /************************************************************
60113
60361
  * Tell whoever asked how many nodes the term matched. The count is
60114
60362
  * the answer to the question the box asks; without it "nothing moved"
60115
60363
  * and "nothing matched" look the same.
@@ -60599,6 +60847,26 @@ function create_gclass$4(gclass_name) {
60599
60847
  ac_set_loose_topics,
60600
60848
  null
60601
60849
  ],
60850
+ [
60851
+ "EV_SET_NODE_MODE",
60852
+ ac_set_node_mode,
60853
+ null
60854
+ ],
60855
+ [
60856
+ "EV_SET_NODE_LABELS",
60857
+ ac_set_node_labels,
60858
+ null
60859
+ ],
60860
+ [
60861
+ "EV_TOGGLE_NODE_MODE",
60862
+ ac_toggle_node_mode$1,
60863
+ null
60864
+ ],
60865
+ [
60866
+ "EV_NODE_DBLCLICK",
60867
+ ac_node_dblclick,
60868
+ null
60869
+ ],
60602
60870
  [
60603
60871
  "EV_CENTER",
60604
60872
  ac_center,
@@ -60703,6 +60971,10 @@ function create_gclass$4(gclass_name) {
60703
60971
  ["EV_SET_HIDDEN_TOPICS", 0],
60704
60972
  ["EV_SET_MAIN_TOPIC", 0],
60705
60973
  ["EV_SET_LOOSE_TOPICS", 0],
60974
+ ["EV_SET_NODE_MODE", 0],
60975
+ ["EV_SET_NODE_LABELS", 0],
60976
+ ["EV_TOGGLE_NODE_MODE", 0],
60977
+ ["EV_NODE_DBLCLICK", 0],
60706
60978
  ["EV_LEGEND_STATE", event_flag_t.EVF_OUTPUT_EVENT],
60707
60979
  ["EV_CENTER", 0],
60708
60980
  ["EV_FULLSCREEN", 0],
@@ -60768,6 +61040,8 @@ var attrs_table$3 = [
60768
61040
  SDATA(data_type_t.DTP_LIST, "hidden_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics hidden from the graph (legend). User preference, per treedb"),
60769
61041
  SDATA(data_type_t.DTP_STRING, "main_topic", sdata_flag_t.SDF_PERSIST, "", "The topic the tree hangs from; empty = deduced by the graph. User preference, per treedb"),
60770
61042
  SDATA(data_type_t.DTP_LIST, "loose_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics whose LOOSE records (no parent) are shown. User preference, per treedb"),
61043
+ SDATA(data_type_t.DTP_STRING, "node_mode", sdata_flag_t.SDF_PERSIST, "expanded", "How the records are drawn: `expanded` = the cards with their ports, `compact` = coloured squares, the topology alone. User preference"),
61044
+ SDATA(data_type_t.DTP_BOOLEAN, "node_labels", sdata_flag_t.SDF_PERSIST, true, "A compact node says its name under its square. User preference"),
60771
61045
  SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
60772
61046
  SDATA(data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
60773
61047
  SDATA(data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
@@ -60845,7 +61119,9 @@ function mt_create$3(gobj) {
60845
61119
  fold_page_size: gobj_read_integer_attr(gobj, "fold_page_size"),
60846
61120
  hidden_topics: gobj_read_attr(gobj, "hidden_topics") || [],
60847
61121
  main_topic: gobj_read_str_attr(gobj, "main_topic") || "",
60848
- loose_topics: gobj_read_attr(gobj, "loose_topics") || []
61122
+ loose_topics: gobj_read_attr(gobj, "loose_topics") || [],
61123
+ node_mode: gobj_read_str_attr(gobj, "node_mode") || "expanded",
61124
+ node_labels: gobj_read_bool_attr(gobj, "node_labels")
60849
61125
  }, gobj);
60850
61126
  populate_nodes_tree_options(gobj);
60851
61127
  if (priv.with_treedb_tables) {}
@@ -60896,6 +61172,7 @@ function build_ui$3(gobj) {
60896
61172
  let priv = gobj.priv;
60897
61173
  let padding = gobj_read_attr(gobj, "padding");
60898
61174
  let $toolbar = make_toolbar(gobj);
61175
+ refresh_node_shape_buttons(gobj, $toolbar);
60899
61176
  let $back = make_back_to_topics(gobj);
60900
61177
  let $container = createElement2([
60901
61178
  "div",
@@ -61037,6 +61314,57 @@ function option_label(name) {
61037
61314
  /************************************************************
61038
61315
  *
61039
61316
  ************************************************************/
61317
+ /************************************************************
61318
+ * The two toggles of the nodes' shape.
61319
+ *
61320
+ * Closed nodes: every record a rounded square of its topic's
61321
+ * colour, no ports, no text -- the topology of a graph of many
61322
+ * nodes, which the cards hide behind their own size. Labels:
61323
+ * the name under each square, when the squares alone do not say
61324
+ * enough. Both persist, both are forwarded to the G6 child, and
61325
+ * one node can be opened against the rule with a double click or
61326
+ * its context menu (the child's business).
61327
+ ************************************************************/
61328
+ function node_shape_items(gobj, wide) {
61329
+ let toggle = (cls, icon, event_name, label_key) => {
61330
+ return [
61331
+ "button",
61332
+ {
61333
+ class: `${cls} button`,
61334
+ type: "button",
61335
+ style: {
61336
+ height: wide,
61337
+ width: "2.5em"
61338
+ },
61339
+ title: t(label_key),
61340
+ "data-i18n-title": label_key,
61341
+ "aria-label": t(label_key),
61342
+ "data-i18n-aria-label": label_key,
61343
+ "aria-pressed": "false"
61344
+ },
61345
+ ["i", {
61346
+ style: "font-size:1.5em; color:inherit;",
61347
+ class: icon
61348
+ }],
61349
+ { click: (evt) => {
61350
+ evt.stopPropagation();
61351
+ gobj_send_event(gobj, event_name, {}, gobj);
61352
+ } }
61353
+ ];
61354
+ };
61355
+ return [toggle("GRAPH_NODE_MODE", "yi-hexagon-nodes", "EV_TOGGLE_NODE_MODE", "closed nodes"), toggle("GRAPH_NODE_LABELS", "yi-font", "EV_TOGGLE_NODE_LABELS", "node labels")];
61356
+ }
61357
+ function refresh_node_shape_buttons(gobj, $root) {
61358
+ if (!$root) return;
61359
+ let compact = gobj_read_str_attr(gobj, "node_mode") === "compact";
61360
+ let labels = gobj_read_bool_attr(gobj, "node_labels");
61361
+ set_pressed_state($root, ".GRAPH_NODE_MODE", compact);
61362
+ set_pressed_state($root, ".GRAPH_NODE_LABELS", labels);
61363
+ let $mode = $root.querySelector(".GRAPH_NODE_MODE");
61364
+ if ($mode) $mode.setAttribute("aria-pressed", compact ? "true" : "false");
61365
+ let $labels = $root.querySelector(".GRAPH_NODE_LABELS");
61366
+ if ($labels) $labels.setAttribute("aria-pressed", labels ? "true" : "false");
61367
+ }
61040
61368
  function make_toolbar(gobj) {
61041
61369
  let priv = gobj.priv;
61042
61370
  let modes = gobj_read_attr(gobj, "operation_modes");
@@ -61107,6 +61435,7 @@ function make_toolbar(gobj) {
61107
61435
  } }
61108
61436
  ],
61109
61437
  ...yui_graph_fold_items(gobj, gobj_read_str_attr(gobj, "wide")),
61438
+ ...node_shape_items(gobj, gobj_read_str_attr(gobj, "wide")),
61110
61439
  [
61111
61440
  "button",
61112
61441
  {
@@ -62170,9 +62499,9 @@ function refresh_legend(gobj) {
62170
62499
  hidden ? `${entry.total}` : `${entry.visible}/${entry.total}`
62171
62500
  ]);
62172
62501
  let body_attrs = {
62173
- class: "GRAPH_LEGEND_ITEM button is-small" + (focused ? " pressed_state" : "") + (is_main ? " GRAPH_LEGEND_MAIN" : ""),
62502
+ class: "GRAPH_LEGEND_ITEM button is-small" + (focused ? " GRAPH_LEGEND_FOCUSED" : "") + (is_main ? " GRAPH_LEGEND_MAIN" : ""),
62174
62503
  type: "button",
62175
- style: "gap:0; flex:0 0 auto;",
62504
+ style: "gap:0; flex:0 0 auto;" + (focused ? " box-shadow: inset 0 0 0 2px var(--bulma-text, #2e333d);" : ""),
62176
62505
  title: is_main ? t("main topic") : hidden ? t("show topic") : t("hide topic"),
62177
62506
  "aria-label": topic_name,
62178
62507
  "aria-pressed": hidden ? "false" : "true"
@@ -62421,6 +62750,29 @@ function ac_collapse_all(gobj, event, kw, src) {
62421
62750
  return 0;
62422
62751
  }
62423
62752
  /************************************************************
62753
+ * The shape pair of the toolbar: the attr flips, persists,
62754
+ * shows on the button, and reaches the G6 child, which owns
62755
+ * the nodes.
62756
+ ************************************************************/
62757
+ function ac_toggle_node_mode(gobj, event, kw, src) {
62758
+ let priv = gobj.priv;
62759
+ let mode = gobj_read_str_attr(gobj, "node_mode") === "compact" ? "expanded" : "compact";
62760
+ gobj_write_str_attr(gobj, "node_mode", mode);
62761
+ gobj_save_persistent_attrs(gobj, "node_mode");
62762
+ refresh_node_shape_buttons(gobj, priv.$container);
62763
+ if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_SET_NODE_MODE", { node_mode: mode }, gobj);
62764
+ return 0;
62765
+ }
62766
+ function ac_toggle_node_labels(gobj, event, kw, src) {
62767
+ let priv = gobj.priv;
62768
+ let labels = !gobj_read_bool_attr(gobj, "node_labels");
62769
+ gobj_write_bool_attr(gobj, "node_labels", labels);
62770
+ gobj_save_persistent_attrs(gobj, "node_labels");
62771
+ refresh_node_shape_buttons(gobj, priv.$container);
62772
+ if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_SET_NODE_LABELS", { node_labels: labels }, gobj);
62773
+ return 0;
62774
+ }
62775
+ /************************************************************
62424
62776
  * How many nodes the term matched, from the graph child. Zero with a
62425
62777
  * term typed is an ANSWER and is shown; an empty box shows nothing.
62426
62778
  ************************************************************/
@@ -62634,6 +62986,16 @@ function create_gclass$3(gclass_name) {
62634
62986
  ac_collapse_all,
62635
62987
  null
62636
62988
  ],
62989
+ [
62990
+ "EV_TOGGLE_NODE_MODE",
62991
+ ac_toggle_node_mode,
62992
+ null
62993
+ ],
62994
+ [
62995
+ "EV_TOGGLE_NODE_LABELS",
62996
+ ac_toggle_node_labels,
62997
+ null
62998
+ ],
62637
62999
  [
62638
63000
  "EV_LAYOUT_AUTOSET",
62639
63001
  ac_layout_autoset,
@@ -62698,6 +63060,8 @@ function create_gclass$3(gclass_name) {
62698
63060
  ["EV_FIND_NODES", 0],
62699
63061
  ["EV_EXPAND_ALL", 0],
62700
63062
  ["EV_COLLAPSE_ALL", 0],
63063
+ ["EV_TOGGLE_NODE_MODE", 0],
63064
+ ["EV_TOGGLE_NODE_LABELS", 0],
62701
63065
  ["EV_FIND_RESULT", 0],
62702
63066
  ["EV_LAYOUT_AUTOSET", 0],
62703
63067
  ["EV_LEGEND_TOPIC", 0],