@yuneta/gobj-ui 7.23.140 → 7.23.142

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.
@@ -13111,7 +13111,7 @@ function ac_find_nodes$3(gobj, event, kw, src) {
13111
13111
  /************************************************************
13112
13112
  * EV_EXPAND_ALL
13113
13113
  ************************************************************/
13114
- function ac_expand_all$4(gobj, event, kw, src) {
13114
+ function ac_expand_all$2(gobj, event, kw, src) {
13115
13115
  let priv = gobj.priv;
13116
13116
  if (priv.collapsed.size === 0) return 0;
13117
13117
  priv.collapsed.clear();
@@ -13123,7 +13123,7 @@ function ac_expand_all$4(gobj, event, kw, src) {
13123
13123
  /************************************************************
13124
13124
  * EV_COLLAPSE_ALL — fold every card but the root.
13125
13125
  ************************************************************/
13126
- function ac_collapse_all$4(gobj, event, kw, src) {
13126
+ function ac_collapse_all$2(gobj, event, kw, src) {
13127
13127
  let priv = gobj.priv;
13128
13128
  let json_data = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "json_data");
13129
13129
  if (!json_data) return 0;
@@ -13423,12 +13423,12 @@ function create_gclass$13(gclass_name) {
13423
13423
  ],
13424
13424
  [
13425
13425
  "EV_EXPAND_ALL",
13426
- ac_expand_all$4,
13426
+ ac_expand_all$2,
13427
13427
  null
13428
13428
  ],
13429
13429
  [
13430
13430
  "EV_COLLAPSE_ALL",
13431
- ac_collapse_all$4,
13431
+ ac_collapse_all$2,
13432
13432
  null
13433
13433
  ],
13434
13434
  [
@@ -14500,7 +14500,7 @@ function ac_search$2(gobj, event, kw, src) {
14500
14500
  /************************************************************
14501
14501
  * EV_EXPAND_ALL — expand every already-loaded container
14502
14502
  ************************************************************/
14503
- function ac_expand_all$3(gobj, event, kw, src) {
14503
+ function ac_expand_all$1(gobj, event, kw, src) {
14504
14504
  let priv = gobj.priv;
14505
14505
  if (priv.root === null || priv.root === void 0) return 0;
14506
14506
  let paths = [];
@@ -14518,7 +14518,7 @@ function ac_expand_all$3(gobj, event, kw, src) {
14518
14518
  /************************************************************
14519
14519
  * EV_COLLAPSE_ALL
14520
14520
  ************************************************************/
14521
- function ac_collapse_all$3(gobj, event, kw, src) {
14521
+ function ac_collapse_all$1(gobj, event, kw, src) {
14522
14522
  gobj.priv.expanded.clear();
14523
14523
  render_view$1(gobj);
14524
14524
  return 0;
@@ -14703,12 +14703,12 @@ function create_gclass$12(gclass_name) {
14703
14703
  ],
14704
14704
  [
14705
14705
  "EV_EXPAND_ALL",
14706
- ac_expand_all$3,
14706
+ ac_expand_all$1,
14707
14707
  null
14708
14708
  ],
14709
14709
  [
14710
14710
  "EV_COLLAPSE_ALL",
14711
- ac_collapse_all$3,
14711
+ ac_collapse_all$1,
14712
14712
  null
14713
14713
  ],
14714
14714
  [
@@ -19730,7 +19730,7 @@ function ac_toggle_collapse(gobj, event, kw, src) {
19730
19730
  /************************************************************
19731
19731
  * Force every node visible (overrides threshold).
19732
19732
  ************************************************************/
19733
- function ac_expand_all$2(gobj, event, kw, src) {
19733
+ function ac_expand_all(gobj, event, kw, src) {
19734
19734
  let priv = gobj.priv;
19735
19735
  if (!priv.collapse_state) priv.collapse_state = {};
19736
19736
  if (priv.node_by_id) for (let id of Object.keys(priv.node_by_id)) {
@@ -19743,7 +19743,7 @@ function ac_expand_all$2(gobj, event, kw, src) {
19743
19743
  /************************************************************
19744
19744
  * Collapse every node that has children.
19745
19745
  ************************************************************/
19746
- function ac_collapse_all$2(gobj, event, kw, src) {
19746
+ function ac_collapse_all(gobj, event, kw, src) {
19747
19747
  let priv = gobj.priv;
19748
19748
  if (!priv.collapse_state) priv.collapse_state = {};
19749
19749
  if (priv.node_by_id) for (let id of Object.keys(priv.node_by_id)) {
@@ -20095,12 +20095,12 @@ function create_gclass$9(gclass_name) {
20095
20095
  ],
20096
20096
  [
20097
20097
  "EV_EXPAND_ALL",
20098
- ac_expand_all$2,
20098
+ ac_expand_all,
20099
20099
  null
20100
20100
  ],
20101
20101
  [
20102
20102
  "EV_COLLAPSE_ALL",
20103
- ac_collapse_all$2,
20103
+ ac_collapse_all,
20104
20104
  null
20105
20105
  ],
20106
20106
  [
@@ -27675,7 +27675,9 @@ function fold_build_model(descs, records, opts) {
27675
27675
  opts = (0, _yuneta_gobj_js.is_object)(opts) ? opts : {};
27676
27676
  let hidden = new Set((0, _yuneta_gobj_js.is_array)(opts.hidden_topics) ? opts.hidden_topics : []);
27677
27677
  let loose_shown = new Set((0, _yuneta_gobj_js.is_array)(opts.loose_topics) ? opts.loose_topics : []);
27678
- let main_topic = opts.main_topic || fold_main_topic(descs);
27678
+ let main_topic = opts.main_topic || "";
27679
+ if (main_topic && !fold_is_hierarchical(descs, main_topic)) main_topic = "";
27680
+ if (!main_topic) main_topic = fold_main_topic(descs);
27679
27681
  if (main_topic && (!descs[main_topic] || hidden.has(main_topic))) main_topic = "";
27680
27682
  let linked = fold_linked_topics(descs, main_topic);
27681
27683
  let totals = {};
@@ -27739,7 +27741,7 @@ function fold_build_model(descs, records, opts) {
27739
27741
  for (let k of reach_from(nodes, [node.key])) reached.add(k);
27740
27742
  }
27741
27743
  for (let topic_name of topics) if (roots[topic_name].length > 0) groups.set(fold_root_group_key(topic_name), roots[topic_name]);
27742
- return {
27744
+ let model = {
27743
27745
  nodes,
27744
27746
  groups,
27745
27747
  topics,
@@ -27748,32 +27750,97 @@ function fold_build_model(descs, records, opts) {
27748
27750
  main_topic,
27749
27751
  linked,
27750
27752
  loose,
27751
- loose_shown
27753
+ loose_shown,
27754
+ level_of: null,
27755
+ levels: 0
27752
27756
  };
27757
+ count_levels(model);
27758
+ return model;
27753
27759
  }
27754
27760
  /************************************************************
27755
- * The topic the schema hangs the others from: the one whose hooks
27756
- * reach the most OTHER topics, a self-referent hook breaking a tie
27757
- * (a tree of places over a flat list of groups), schema order
27758
- * breaking the rest. "" when no topic reaches another -- a treedb of
27759
- * unrelated lists has no trunk, and every topic is a tree of its own.
27761
+ * The LEVELS of the main tree: the roots of the main topic --
27762
+ * and of the topics hung from it, whose roots are the loose
27763
+ * records the reader asked for -- are level 1, their children
27764
+ * level 2, and so on down every hook of every topic that is not
27765
+ * hidden. A node reached by two paths takes the shallowest.
27766
+ *
27767
+ * With no main topic every topic is a tree of its own and all
27768
+ * of them count. A topic the schema does not tie to the main
27769
+ * one is a separate tree, and has no level: the stepper of the
27770
+ * toolbar speaks for the main tree and nothing else, those
27771
+ * trees are opened by their own pills.
27772
+ ************************************************************/
27773
+ function count_levels(model) {
27774
+ let level_of = /* @__PURE__ */ new Map();
27775
+ let queue = [];
27776
+ let deepest = 0;
27777
+ for (let topic_name of model.topics) {
27778
+ if (model.main_topic && !model.linked.has(topic_name)) continue;
27779
+ let roots = model.groups.get(fold_root_group_key(topic_name)) || [];
27780
+ for (let key of roots) if (!level_of.has(key)) {
27781
+ level_of.set(key, 1);
27782
+ queue.push(key);
27783
+ }
27784
+ }
27785
+ let head = 0;
27786
+ while (head < queue.length) {
27787
+ let key = queue[head++];
27788
+ let lv = level_of.get(key);
27789
+ if (lv > deepest) deepest = lv;
27790
+ let node = model.nodes.get(key);
27791
+ if (!node) continue;
27792
+ for (let hook of Object.keys(node.hooks)) for (let child of node.hooks[hook]) if (!level_of.has(child)) {
27793
+ level_of.set(child, lv + 1);
27794
+ queue.push(child);
27795
+ }
27796
+ }
27797
+ model.level_of = level_of;
27798
+ model.levels = deepest;
27799
+ }
27800
+ /************************************************************
27801
+ * A HIERARCHICAL topic: one hooked to ITSELF -- places inside
27802
+ * places, groups inside groups, roles inside roles. It is the
27803
+ * only kind of topic a tree can hang from: a topic that only
27804
+ * reaches OTHER topics is a list with children, and making it
27805
+ * the trunk drew the agent's treedb from `yunos` instead of
27806
+ * `realms`.
27807
+ *
27808
+ * (Not the engine's `entity` tier, which is any topic with
27809
+ * hooks AND fkeys: that one sizes a card and says nothing about
27810
+ * where a tree starts.)
27811
+ ************************************************************/
27812
+ function fold_is_hierarchical(descs, topic_name) {
27813
+ if (!(0, _yuneta_gobj_js.is_object)(descs) || !descs[topic_name]) return false;
27814
+ return hook_targets(descs, topic_name).has(topic_name);
27815
+ }
27816
+ /************************************************************
27817
+ * The topic the tree hangs from. Only a HIERARCHICAL topic can
27818
+ * be it, and in this order:
27819
+ *
27820
+ * the one the schema MARKS (`main_topic: true` on the topic,
27821
+ * sent by the backend in its desc since SDK 7.19; the first
27822
+ * one in schema order if a schema marks two);
27823
+ * else the one whose hooks reach the most OTHER topics,
27824
+ * schema order breaking a tie.
27825
+ *
27826
+ * "" when no topic is hooked to itself: there is no trunk, and
27827
+ * every topic is a tree of its own. (A pick by the reader comes
27828
+ * first of all, and is fold_build_model's business.)
27760
27829
  ************************************************************/
27761
27830
  function fold_main_topic(descs) {
27762
27831
  descs = (0, _yuneta_gobj_js.is_object)(descs) ? descs : {};
27832
+ let candidates = Object.keys(descs).filter((topic_name) => {
27833
+ return topic_name.substring(0, 2) !== "__" && fold_is_hierarchical(descs, topic_name);
27834
+ });
27835
+ for (let topic_name of candidates) if (descs[topic_name].main_topic === true) return topic_name;
27763
27836
  let best = "";
27764
- let best_score = 0;
27765
- let best_self = false;
27766
- for (let topic_name of Object.keys(descs)) {
27767
- if (topic_name.substring(0, 2) === "__") continue;
27837
+ let best_score = -1;
27838
+ for (let topic_name of candidates) {
27768
27839
  let reach = hook_targets(descs, topic_name);
27769
- let self = reach.has(topic_name);
27770
27840
  reach.delete(topic_name);
27771
- let score = reach.size;
27772
- if (score === 0) continue;
27773
- if (score > best_score || score === best_score && self && !best_self) {
27841
+ if (reach.size > best_score) {
27774
27842
  best = topic_name;
27775
- best_score = score;
27776
- best_self = self;
27843
+ best_score = reach.size;
27777
27844
  }
27778
27845
  }
27779
27846
  return best;
@@ -27875,14 +27942,6 @@ function fold_show_more(model, state, group_key) {
27875
27942
  return n;
27876
27943
  }
27877
27944
  /************************************************************
27878
- * Every group open on ALL its children. This is the reader
27879
- * asking for the whole thing; the pages are for the other way in.
27880
- ************************************************************/
27881
- function fold_expand_all(model, state) {
27882
- state.shown.clear();
27883
- for (let [group_key, list] of model.groups) if (list.length > 0) state.shown.set(group_key, list.length);
27884
- }
27885
- /************************************************************
27886
27945
  * The roots and nothing else -- the first page of each topic's
27887
27946
  * roots, the way the graph looks before anything is opened.
27888
27947
  ************************************************************/
@@ -27894,40 +27953,51 @@ function fold_collapse_all(model, state) {
27894
27953
  }
27895
27954
  }
27896
27955
  /************************************************************
27897
- * Open the tree down to `depth` levels: the roots are level 1,
27898
- * their children level 2. A node reached by two paths takes the
27899
- * shallowest. Each group opens on its first page. `depth` of 1
27900
- * is the roots alone; anything below that is treated as 1.
27956
+ * The toolbar's STEPPER over the levels of the main tree (see
27957
+ * count_levels): level N shows the roots and N-1 levels under
27958
+ * them. A level is a floor of what is open, not a picture that
27959
+ * replaces it:
27960
+ *
27961
+ * opening to N opens, on its first page, every hook of
27962
+ * every node above level N that is folded;
27963
+ * what the reader opened, and every page
27964
+ * already shown, stays
27965
+ * closing to N folds every hook of every node at level N
27966
+ * or below, and touches nothing above it
27967
+ *
27968
+ * A separate tree (a topic not tied to the main one) has no
27969
+ * level, so neither ever touches it.
27901
27970
  ************************************************************/
27902
- function fold_expand_to_depth(model, state, depth) {
27903
- fold_collapse_all(model, state);
27904
- if (!(depth > 1)) return;
27905
- let level = /* @__PURE__ */ new Map();
27906
- let queue = [];
27907
- for (let topic_name of model.topics) {
27908
- let roots = model.groups.get(fold_root_group_key(topic_name)) || [];
27909
- for (let key of roots) if (!level.has(key)) {
27910
- level.set(key, 1);
27911
- queue.push(key);
27912
- }
27913
- }
27914
- while (queue.length) {
27915
- let key = queue.shift();
27916
- let lv = level.get(key);
27917
- if (lv >= depth) continue;
27971
+ function fold_level_clamp(model, level) {
27972
+ let top = model.levels > 0 ? model.levels : 1;
27973
+ level = Math.floor(Number(level)) || 1;
27974
+ if (level < 1) return 1;
27975
+ if (level > top) return top;
27976
+ return level;
27977
+ }
27978
+ function fold_open_levels(model, state, level) {
27979
+ for (let [key, lv] of model.level_of) {
27980
+ if (lv >= level) continue;
27918
27981
  let node = model.nodes.get(key);
27919
27982
  if (!node) continue;
27920
27983
  for (let hook of Object.keys(node.hooks)) {
27921
- let children = node.hooks[hook];
27922
- if (children.length === 0) continue;
27923
- state.shown.set(fold_group_key(key, hook), state.page_size);
27924
- for (let child of children) if (!level.has(child)) {
27925
- level.set(child, lv + 1);
27926
- queue.push(child);
27927
- }
27984
+ let group_key = fold_group_key(key, hook);
27985
+ if (node.hooks[hook].length > 0 && !fold_is_open(state, group_key)) state.shown.set(group_key, state.page_size);
27928
27986
  }
27929
27987
  }
27930
27988
  }
27989
+ function fold_close_levels(model, state, level) {
27990
+ for (let [key, lv] of model.level_of) {
27991
+ if (lv < level) continue;
27992
+ let node = model.nodes.get(key);
27993
+ if (!node) continue;
27994
+ for (let hook of Object.keys(node.hooks)) state.shown.delete(fold_group_key(key, hook));
27995
+ }
27996
+ }
27997
+ function fold_expand_to_level(model, state, level) {
27998
+ fold_collapse_all(model, state);
27999
+ fold_open_levels(model, state, fold_level_clamp(model, level));
28000
+ }
27931
28001
  /************************************************************
27932
28002
  * The set of node keys on screen, from the roots down through
27933
28003
  * every open group -- and only through open groups whose parent
@@ -55170,8 +55240,9 @@ function ensure_brush_select_owned() {
55170
55240
  * The graph opens FOLDED, like a JSON viewer.
55171
55241
  *
55172
55242
  * Every record of every topic is fetched, but only the ones on
55173
- * screen become G6 nodes: the roots, `expand_depth` levels under
55174
- * them, one page (`fold_page_size`) of children per hook. Each
55243
+ * screen become G6 nodes: the roots, and the MAIN tree open to
55244
+ * the reader's level (`fold_level`, `expand_depth` until they
55245
+ * step it), one page (`fold_page_size`) of children per hook. Each
55175
55246
  * card carries a pill per hook that has children -- `▸ devices
55176
55247
  * 142` -- which opens or folds that hook; an open hook with more
55177
55248
  * children than its page ends in a `+N` chip that opens the next
@@ -55255,13 +55326,13 @@ var NODE_MODES$1 = [
55255
55326
  "shape"
55256
55327
  ];
55257
55328
  var PILL_SIZE = {
55258
- hierarchical: [200, 30],
55329
+ entity: [200, 30],
55259
55330
  extended: [180, 28],
55260
55331
  child: [160, 26]
55261
55332
  };
55262
55333
  var PORT_R_PILL = 6;
55263
55334
  var SHAPE_SIZE = {
55264
- hierarchical: 32,
55335
+ entity: 32,
55265
55336
  extended: 28,
55266
55337
  child: 22
55267
55338
  };
@@ -55349,7 +55420,8 @@ var attrs_table$4 = [
55349
55420
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_LIST, "layout_names", _yuneta_gobj_js.sdata_flag_t.SDF_RD, JSON.stringify(Object.keys(_layouts)), "Available layout names (read-only, for parent to query)"),
55350
55421
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "hook_port_position", 0, "bottom", "Hook port position"),
55351
55422
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "fkey_port_position", 0, "top", "Fkey port position"),
55352
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "expand_depth", 0, 2, "Levels open when a treedb loads: 1 = the roots alone, 2 = the roots and their children. Every open hook shows its first page"),
55423
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "expand_depth", 0, 1, "Levels open when a treedb loads, until the reader steps (`fold_level`): 1 = the roots alone (the default), 2 = the roots and their children. Every open hook shows its first page"),
55424
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "fold_level", 0, 0, "The level of the MAIN tree the toolbar's stepper stands on: 1 = its roots, N = N-1 levels under them; 0 = `expand_depth`. The host persists it and sends EV_SET_FOLD_LEVEL; it is published back in EV_LEGEND_STATE (`fold`) with the levels the tree has"),
55353
55425
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "fold_page_size", 0, 24, "Children of one hook shown per page; the `+N` chip after the page opens the next one"),
55354
55426
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_LIST, "hidden_topics", 0, "[]", "Topics left out of the tree: no card, no pill counts them, no edge reaches them"),
55355
55427
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.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"),
@@ -55440,7 +55512,8 @@ var PRIVATE_DATA$4 = {
55440
55512
  _layout_asked: "",
55441
55513
  _nodes_total: 0,
55442
55514
  _nodes_placed: 0,
55443
- expand_depth: 2,
55515
+ expand_depth: 1,
55516
+ fold_level: 0,
55444
55517
  fold_page_size: 24,
55445
55518
  hidden_topics: null,
55446
55519
  main_topic: "",
@@ -56163,7 +56236,12 @@ function configure_behaviour(gobj) {
56163
56236
  graph_write_behaviors(gobj, behaviors);
56164
56237
  }
56165
56238
  /************************************************************
56166
- * Count hooks and fkeys in topic desc, classify node type
56239
+ * Count hooks and fkeys in topic desc, classify node type:
56240
+ * `child` (no hooks), `extended` (hooks, no fkeys), `entity`
56241
+ * (both). It sizes the card and nothing else. It used to be
56242
+ * called `hierarchical`, and that word means something else now:
56243
+ * a topic hooked to ITSELF, the only kind a tree can hang from
56244
+ * (treedb_fold_model).
56167
56245
  ************************************************************/
56168
56246
  function calculate_hooks_fkeys_counter(desc) {
56169
56247
  let cols = desc.cols;
@@ -56180,7 +56258,7 @@ function calculate_hooks_fkeys_counter(desc) {
56180
56258
  }
56181
56259
  if (desc.hooks_counter === 0) desc.node_treedb_type = "child";
56182
56260
  else if (desc.fkeys_counter === 0) desc.node_treedb_type = "extended";
56183
- else desc.node_treedb_type = "hierarchical";
56261
+ else desc.node_treedb_type = "entity";
56184
56262
  }
56185
56263
  /************************************************************
56186
56264
  * Build ports for a topic desc
@@ -56553,7 +56631,7 @@ function dress_ports(gobj, style, ports, geometry, topic_defaults, r_default, fi
56553
56631
  ************************************************************/
56554
56632
  function pill_shape_of(gobj, desc, record, geometry) {
56555
56633
  let priv = gobj.priv;
56556
- let size = PILL_SIZE[desc.node_treedb_type] || PILL_SIZE.hierarchical;
56634
+ let size = PILL_SIZE[desc.node_treedb_type] || PILL_SIZE.entity;
56557
56635
  let ports = build_ports(gobj, desc);
56558
56636
  let paint = node_paint_of(gobj, desc, geometry);
56559
56637
  let style = {
@@ -56602,7 +56680,7 @@ function figure_shape_of(gobj, desc, record, geometry, flags) {
56602
56680
  let dark = priv.theme === "dark";
56603
56681
  let f = flags || {};
56604
56682
  let tier = desc.node_treedb_type;
56605
- let side = SHAPE_SIZE[tier] || SHAPE_SIZE.hierarchical;
56683
+ let side = SHAPE_SIZE[tier] || SHAPE_SIZE.entity;
56606
56684
  let labels = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "node_labels");
56607
56685
  let figure = node_figure_of(gobj, desc, geometry || {});
56608
56686
  let paint = node_paint_of(gobj, desc, geometry);
@@ -59668,7 +59746,7 @@ function show_node_detail_popover(gobj, node_id) {
59668
59746
  clamp_popover_position(gobj, popover);
59669
59747
  }
59670
59748
  /************************************************************
59671
- * Re-render every HTML (hierarchical) node so its card adopts
59749
+ * Re-render every HTML (entity) node so its card adopts
59672
59750
  * the given theme: entity cards and leaf chips (baked innerHTML,
59673
59751
  * not re-themed by setTheme()) are regenerated; structural
59674
59752
  * junction diamonds get their neutral fill/stroke re-themed.
@@ -59787,7 +59865,7 @@ function refresh_minimap(gobj) {
59787
59865
  }
59788
59866
  /************************************************************
59789
59867
  * The innerHTML of ONE node, in the given theme and highlight state.
59790
- * The three treedb tiers (hierarchical / extended / child) each have
59868
+ * The three treedb tiers (entity / extended / child) each have
59791
59869
  * their own card, and both the theme refresh and the highlight need the
59792
59870
  * same three-way choice — it lived inline in the theme refresh and is
59793
59871
  * shared now. Returns null for a node that carries no desc.
@@ -59804,7 +59882,7 @@ function node_innerHTML_of(nd, theme, highlight, selected, anchored, pills_html)
59804
59882
  switch (desc.node_treedb_type) {
59805
59883
  case "child": return build_chip_innerHTML(fill, theme, record.icon, label, record.id, highlight, selected, anchored);
59806
59884
  case "extended": return build_node_innerHTML(fill, theme, record.icon, label, desc.topic_name, true, record.id, highlight, selected, anchored, pills_html);
59807
- case "hierarchical": return build_node_innerHTML(fill, theme, record.icon, label, desc.topic_name, false, record.id, highlight, selected, anchored, pills_html);
59885
+ case "entity": return build_node_innerHTML(fill, theme, record.icon, label, desc.topic_name, false, record.id, highlight, selected, anchored, pills_html);
59808
59886
  }
59809
59887
  return null;
59810
59888
  }
@@ -60001,7 +60079,7 @@ function build_chip_innerHTML(color, theme, icon, label, key, highlight, selecte
60001
60079
  `;
60002
60080
  }
60003
60081
  /************************************************************
60004
- * Build innerHTML for hierarchical (HTML) nodes.
60082
+ * Build innerHTML for entity (HTML) nodes.
60005
60083
  *
60006
60084
  * Card style modelled on the documentation schema graphs
60007
60085
  * (docs gen_treedb_graphs.py): rounded card, soft tint of the
@@ -61319,13 +61397,19 @@ function publish_legend_state(gobj, visible) {
61319
61397
  loose: loose.length,
61320
61398
  loose_shown: model.loose_shown.has(topic_name),
61321
61399
  hidden: model.hidden.has(topic_name),
61322
- linked: model.linked.has(topic_name)
61400
+ linked: model.linked.has(topic_name),
61401
+ hierarchical: fold_is_hierarchical(priv.descs, topic_name)
61323
61402
  });
61324
61403
  }
61325
61404
  (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_LEGEND_STATE", {
61326
61405
  main_topic: model.main_topic,
61327
- main_chosen: !!(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "main_topic"),
61328
- topics
61406
+ main_chosen: !!model.main_topic && (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "main_topic") === model.main_topic,
61407
+ topics,
61408
+ fold: {
61409
+ topic: model.main_topic,
61410
+ level: current_fold_level(gobj),
61411
+ levels: model.levels
61412
+ }
61329
61413
  });
61330
61414
  }
61331
61415
  async function reconcile_fold_apply(gobj, opts, visible) {
@@ -61630,7 +61714,7 @@ function ac_load_data(gobj, event, kw, src) {
61630
61714
  }
61631
61715
  if (all_loaded && priv.graph) {
61632
61716
  rebuild_fold_model(gobj);
61633
- fold_expand_to_depth(priv._fold_model, priv._fold_state, (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "expand_depth"));
61717
+ fold_expand_to_level(priv._fold_model, priv._fold_state, current_fold_level(gobj));
61634
61718
  count_placed_records(gobj);
61635
61719
  let auto_laid = false;
61636
61720
  try {
@@ -62034,19 +62118,46 @@ function ac_show_more(gobj, event, kw, src) {
62034
62118
  return 0;
62035
62119
  }
62036
62120
  /************************************************************
62037
- * The whole treedb, every hook open on all its children. This
62038
- * is the reader asking for the pile on purpose; the pages are
62039
- * the other way in.
62121
+ * The level of the main tree the toolbar's stepper stands on:
62122
+ * `fold_level` once the reader has stepped, `expand_depth`
62123
+ * until then, clamped to the levels the tree has.
62040
62124
  ************************************************************/
62041
- function ac_expand_all$1(gobj, event, kw, src) {
62125
+ function current_fold_level(gobj) {
62042
62126
  let priv = gobj.priv;
62043
- if (!priv._fold_model) {
62044
- (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: expand all with no tree loaded`);
62127
+ let level = (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "fold_level");
62128
+ if (!(level > 0)) level = (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "expand_depth");
62129
+ if (!priv._fold_model) return level;
62130
+ return fold_level_clamp(priv._fold_model, level);
62131
+ }
62132
+ /************************************************************
62133
+ * The toolbar's stepper: one level more or one less of the
62134
+ * MAIN tree (the host owns the number and persists it; this
62135
+ * applies it). Deeper opens the hooks above the new level,
62136
+ * shallower folds from it down -- a level is a floor, not a
62137
+ * picture, so what the reader opened by its pill stays (see
62138
+ * treedb_fold_model). Replaces expand all / collapse all
62139
+ * (7.23.141): the whole treedb at once was the pile, and
62140
+ * closing threw away everything the reader had opened.
62141
+ ************************************************************/
62142
+ function ac_set_fold_level(gobj, event, kw, src) {
62143
+ let priv = gobj.priv;
62144
+ let level = Number(kw && kw.level);
62145
+ if (!(level > 0)) {
62146
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: fold level '${kw && kw.level}' is not a level`);
62045
62147
  return -1;
62046
62148
  }
62149
+ if (!priv._fold_model) {
62150
+ (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "fold_level", level);
62151
+ return 0;
62152
+ }
62153
+ let from = current_fold_level(gobj);
62154
+ level = fold_level_clamp(priv._fold_model, level);
62155
+ (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "fold_level", level);
62156
+ if (level === from) return 0;
62047
62157
  let keep = fold_keep_node(gobj);
62048
62158
  let vp = yui_graph_viewport_of(priv.graph, keep);
62049
- fold_expand_all(priv._fold_model, priv._fold_state);
62159
+ if (level > from) fold_open_levels(priv._fold_model, priv._fold_state, level);
62160
+ else fold_close_levels(priv._fold_model, priv._fold_state, level);
62050
62161
  reconcile_fold(gobj, {
62051
62162
  keep,
62052
62163
  keep_vp: vp
@@ -62074,29 +62185,11 @@ function fold_keep_node(gobj) {
62074
62185
  return "";
62075
62186
  }
62076
62187
  /************************************************************
62077
- * The roots alone.
62078
- ************************************************************/
62079
- function ac_collapse_all$1(gobj, event, kw, src) {
62080
- let priv = gobj.priv;
62081
- if (!priv._fold_model) {
62082
- (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: collapse all with no tree loaded`);
62083
- return -1;
62084
- }
62085
- let keep = fold_keep_node(gobj);
62086
- let vp = yui_graph_viewport_of(priv.graph, keep);
62087
- fold_collapse_all(priv._fold_model, priv._fold_state);
62088
- reconcile_fold(gobj, {
62089
- keep,
62090
- keep_vp: vp
62091
- });
62092
- return 0;
62093
- }
62094
- /************************************************************
62095
62188
  * The legend's three settings, from the host, which persists
62096
62189
  * them. Each rebuilds the tree; the fold state is kept -- it is
62097
62190
  * keyed by group, and a group that is gone is never read -- except
62098
62191
  * for a new MAIN topic, whose roots are other roots: the tree
62099
- * opens again at `expand_depth`.
62192
+ * opens again at the reader's level, now counted from them.
62100
62193
  ************************************************************/
62101
62194
  function ac_set_hidden_topics(gobj, event, kw, src) {
62102
62195
  let priv = gobj.priv;
@@ -62114,7 +62207,7 @@ function ac_set_main_topic(gobj, event, kw, src) {
62114
62207
  let keep = fold_keep_node(gobj);
62115
62208
  let vp = yui_graph_viewport_of(priv.graph, keep);
62116
62209
  rebuild_fold_model(gobj);
62117
- fold_expand_to_depth(priv._fold_model, priv._fold_state, (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "expand_depth"));
62210
+ fold_expand_to_level(priv._fold_model, priv._fold_state, current_fold_level(gobj));
62118
62211
  reconcile_fold(gobj, {
62119
62212
  relayout: true,
62120
62213
  keep,
@@ -62675,13 +62768,8 @@ function create_gclass$4(gclass_name) {
62675
62768
  null
62676
62769
  ],
62677
62770
  [
62678
- "EV_EXPAND_ALL",
62679
- ac_expand_all$1,
62680
- null
62681
- ],
62682
- [
62683
- "EV_COLLAPSE_ALL",
62684
- ac_collapse_all$1,
62771
+ "EV_SET_FOLD_LEVEL",
62772
+ ac_set_fold_level,
62685
62773
  null
62686
62774
  ],
62687
62775
  [
@@ -62824,8 +62912,7 @@ function create_gclass$4(gclass_name) {
62824
62912
  ["EV_CAMERA_CHANGED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
62825
62913
  ["EV_TOGGLE_FOLD", 0],
62826
62914
  ["EV_SHOW_MORE", 0],
62827
- ["EV_EXPAND_ALL", 0],
62828
- ["EV_COLLAPSE_ALL", 0],
62915
+ ["EV_SET_FOLD_LEVEL", 0],
62829
62916
  ["EV_SET_HIDDEN_TOPICS", 0],
62830
62917
  ["EV_SET_MAIN_TOPIC", 0],
62831
62918
  ["EV_SET_LOOSE_TOPICS", 0],
@@ -62892,7 +62979,7 @@ var attrs_table$3 = [
62892
62979
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
62893
62980
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_LIST, "operation_modes", 0, "[\"reading\", \"operation\", \"writing\", \"edition\"]", "Available **permission** or behaviour modes. These operation modes are required to be accomplish by the graph handler (G6 child). TODO permissions must match treedb permissions."),
62894
62981
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_treedb_tables", 0, false, "Include treedb tables"),
62895
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "expand_depth", 0, 2, "Levels of the treedb open when it loads: 1 = the roots alone, 2 = the roots and their children. Forwarded to the G6 child"),
62982
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "expand_depth", 0, 1, "Levels of the treedb open when it loads, until the reader steps (`fold_level`): 1 = the roots alone (the default), 2 = the roots and their children. Forwarded to the G6 child"),
62896
62983
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "fold_page_size", 0, 24, "Children of one hook shown per page in the graph; a `+N` chip opens the next page. Forwarded to the G6 child"),
62897
62984
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "minimap_min_nodes", 0, 30, "Show the minimap from this many nodes on (0 = never). Forwarded to the G6 child"),
62898
62985
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_gridline", 0, true, "Draw the grid under the graph. Forwarded to the G6 child"),
@@ -62909,6 +62996,7 @@ var attrs_table$3 = [
62909
62996
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "node_mode", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, "expanded", "How the records are drawn: `expanded` = the cards with their pills and ports, `compact` = one-line pills with the name, `shape` = coloured figures, the topology alone. User preference"),
62910
62997
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "node_labels", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, true, "A `shape` node says its name under its figure. User preference"),
62911
62998
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "camera", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, "{}", "Where the reader left the viewport: {zoom, x, y}. Handed to the engine on creation and written back when a move settles, so the graph opens where it was left. User preference, per treedb"),
62999
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "fold_level", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, 0, "The level of the MAIN tree the toolbar's stepper stands on: 1 = the main topic's roots, N = N-1 levels under them; 0 = never stepped, `expand_depth`. Handed to the engine on creation, so the graph opens at the level it was left. User preference, per treedb"),
62912
63000
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
62913
63001
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
62914
63002
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
@@ -62984,6 +63072,7 @@ function mt_create$3(gobj) {
62984
63072
  hook_port_position: "bottom",
62985
63073
  fkey_port_position: "top",
62986
63074
  expand_depth: (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "expand_depth"),
63075
+ fold_level: (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "fold_level"),
62987
63076
  fold_page_size: (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "fold_page_size"),
62988
63077
  minimap_min_nodes: (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "minimap_min_nodes"),
62989
63078
  with_gridline: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_gridline"),
@@ -63291,6 +63380,127 @@ function refresh_node_shape_buttons(gobj, $root) {
63291
63380
  $labels.disabled = mode !== "shape";
63292
63381
  }
63293
63382
  }
63383
+ /************************************************************
63384
+ * The fold STEPPER: one level less, where the tree stands, one
63385
+ * level more.
63386
+ *
63387
+ * It replaces the fold pair (7.23.141). `expand all` opened every
63388
+ * hook of the treedb -- the pile, on purpose, which on a treedb
63389
+ * of thousands is a pile nobody reads -- and `collapse all` threw
63390
+ * away everything the reader had opened. The stepper walks the
63391
+ * MAIN tree a level at a time, the way the frontend view opens
63392
+ * one level per press: `+` opens the next level under the main
63393
+ * topic's roots, through every topic that is not hidden; `-`
63394
+ * folds the deepest one. Pages and pills the reader opened stay.
63395
+ *
63396
+ * The readout says what the two buttons act on, which no chevron
63397
+ * can: the main topic -- its swatch and its name, which is DATA
63398
+ * and never translated -- and `level/levels`. On a phone the name
63399
+ * goes and the numbers stay. The level is the reader's, per
63400
+ * treedb: persisted (`fold_level`) and handed to the engine on
63401
+ * creation, so a reload opens where it was left.
63402
+ *
63403
+ * Both buttons are born disabled: nothing can be stepped until
63404
+ * the engine has said how deep the tree goes (EV_LEGEND_STATE).
63405
+ ************************************************************/
63406
+ function fold_level_items(gobj, wide) {
63407
+ let step_button = (cls, icon, key, event_name) => {
63408
+ return [
63409
+ "button",
63410
+ {
63411
+ class: `${cls} button`,
63412
+ type: "button",
63413
+ style: {
63414
+ height: wide,
63415
+ width: "2.5em"
63416
+ },
63417
+ disabled: true,
63418
+ title: (0, i18next.t)(key),
63419
+ "data-i18n-title": key,
63420
+ "aria-label": (0, i18next.t)(key),
63421
+ "data-i18n-aria-label": key
63422
+ },
63423
+ yui_toolbar_icon(icon),
63424
+ { click: (evt) => {
63425
+ evt.stopPropagation();
63426
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, event_name, {}, gobj);
63427
+ } }
63428
+ ];
63429
+ };
63430
+ return [[
63431
+ "div",
63432
+ {
63433
+ class: "GRAPH_FOLD_STEPPER buttons has-addons",
63434
+ style: "margin:0; flex:0 0 auto; flex-wrap:nowrap; align-items:stretch;"
63435
+ },
63436
+ [
63437
+ step_button("GRAPH_FOLD_COLLAPSE", "yi-chevron-right", "collapse one level", "EV_COLLAPSE_LEVEL"),
63438
+ [
63439
+ "div",
63440
+ {
63441
+ class: "GRAPH_FOLD_LEVEL button",
63442
+ style: {
63443
+ height: wide,
63444
+ cursor: "default",
63445
+ gap: ".35rem"
63446
+ },
63447
+ role: "status",
63448
+ title: (0, i18next.t)("fold level"),
63449
+ "data-i18n-title": "fold level"
63450
+ },
63451
+ [
63452
+ ["span", {
63453
+ class: "GRAPH_FOLD_SWATCH",
63454
+ style: "display:none; width:.8rem; height:.8rem; border-radius:3px; flex:0 0 auto; border:1px solid rgba(0,0,0,.25);"
63455
+ }],
63456
+ ["span", {
63457
+ class: "GRAPH_FOLD_TOPIC is-hidden-mobile",
63458
+ style: "max-width:10em; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:600;"
63459
+ }],
63460
+ [
63461
+ "span",
63462
+ {
63463
+ class: "GRAPH_FOLD_WORD",
63464
+ i18n: "level"
63465
+ },
63466
+ "level"
63467
+ ],
63468
+ [
63469
+ "span",
63470
+ {
63471
+ class: "GRAPH_FOLD_COUNT",
63472
+ style: "font-variant-numeric:tabular-nums;"
63473
+ },
63474
+ "–"
63475
+ ]
63476
+ ]
63477
+ ],
63478
+ step_button("GRAPH_FOLD_EXPAND", "yi-chevron-down", "expand one level", "EV_EXPAND_LEVEL")
63479
+ ]
63480
+ ]];
63481
+ }
63482
+ function refresh_fold_level(gobj) {
63483
+ let priv = gobj.priv;
63484
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
63485
+ if (!$container) return;
63486
+ let $readout = $container.querySelector(".GRAPH_FOLD_LEVEL");
63487
+ if (!$readout) return;
63488
+ let state = priv._legend_state || {};
63489
+ let fold = state.fold || {};
63490
+ let levels = fold.levels > 0 ? fold.levels : 0;
63491
+ let level = fold.level > 0 ? fold.level : 0;
63492
+ let topic = fold.topic || "";
63493
+ let entry = ((0, _yuneta_gobj_js.is_array)(state.topics) ? state.topics : []).find((e) => e.topic === topic);
63494
+ let $swatch = $readout.querySelector(".GRAPH_FOLD_SWATCH");
63495
+ $swatch.style.display = topic ? "inline-block" : "none";
63496
+ $swatch.style.background = entry && entry.color || "#94a3b8";
63497
+ $readout.querySelector(".GRAPH_FOLD_TOPIC").textContent = topic;
63498
+ $readout.querySelector(".GRAPH_FOLD_COUNT").textContent = levels ? `${level}/${levels}` : "–";
63499
+ let $collapse = $container.querySelector(".GRAPH_FOLD_COLLAPSE");
63500
+ let $expand = $container.querySelector(".GRAPH_FOLD_EXPAND");
63501
+ if ($collapse) $collapse.disabled = !(levels && level > 1);
63502
+ if ($expand) $expand.disabled = !(levels && level < levels);
63503
+ }
63294
63504
  function make_toolbar(gobj) {
63295
63505
  let priv = gobj.priv;
63296
63506
  let modes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "operation_modes");
@@ -63357,7 +63567,7 @@ function make_toolbar(gobj) {
63357
63567
  } }
63358
63568
  ]
63359
63569
  ];
63360
- let center_items = [...yui_graph_fold_items(gobj, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "wide")), ...node_shape_items(gobj, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "wide"))];
63570
+ let center_items = [...fold_level_items(gobj, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "wide")), ...node_shape_items(gobj, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "wide"))];
63361
63571
  let backend_items = [[
63362
63572
  "button",
63363
63573
  {
@@ -64420,6 +64630,7 @@ function refresh_legend(gobj) {
64420
64630
  let topics = state.topics.slice().sort((a, b) => {
64421
64631
  return String(a.topic).localeCompare(String(b.topic));
64422
64632
  });
64633
+ let hierarchical_count = topics.filter((e) => e.hierarchical && !e.hidden).length;
64423
64634
  for (let entry of topics) {
64424
64635
  let topic_name = entry.topic;
64425
64636
  let is_main = topic_name === state.main_topic;
@@ -64514,7 +64725,7 @@ function refresh_legend(gobj) {
64514
64725
  "★"
64515
64726
  ]]
64516
64727
  ]);
64517
- else if (!hidden) extras.push([
64728
+ else if (!hidden && entry.hierarchical && hierarchical_count > 1) extras.push([
64518
64729
  "button",
64519
64730
  {
64520
64731
  class: "GRAPH_LEGEND_STAR button",
@@ -64613,6 +64824,7 @@ function ac_legend_state(gobj, event, kw, src) {
64613
64824
  let priv = gobj.priv;
64614
64825
  priv._legend_state = kw || null;
64615
64826
  refresh_legend(gobj);
64827
+ refresh_fold_level(gobj);
64616
64828
  return 0;
64617
64829
  }
64618
64830
  /************************************************************
@@ -64725,17 +64937,33 @@ function ac_find_nodes(gobj, event, kw, src) {
64725
64937
  return 0;
64726
64938
  }
64727
64939
  /************************************************************
64728
- * The fold pair of the toolbar, forwarded to the G6 child,
64729
- * which owns the tree.
64940
+ * One press of the fold stepper (EV_EXPAND_LEVEL /
64941
+ * EV_COLLAPSE_LEVEL): the level moves by one, is SAVED (the
64942
+ * reader's, per treedb) and reaches the G6 child, which owns
64943
+ * the tree.
64944
+ *
64945
+ * The readout moves at once, not when the engine speaks again:
64946
+ * a reconcile is asynchronous, and a second press that came
64947
+ * before it would step from the level already left -- two
64948
+ * presses, one level.
64730
64949
  ************************************************************/
64731
- function ac_expand_all(gobj, event, kw, src) {
64732
- let priv = gobj.priv;
64733
- if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_EXPAND_ALL", {}, gobj);
64734
- return 0;
64735
- }
64736
- function ac_collapse_all(gobj, event, kw, src) {
64950
+ function ac_fold_level_step(gobj, event, kw, src) {
64737
64951
  let priv = gobj.priv;
64738
- if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_COLLAPSE_ALL", {}, gobj);
64952
+ let fold = priv._legend_state && priv._legend_state.fold;
64953
+ if (!fold || !(fold.levels > 0)) {
64954
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} with no tree to step through`);
64955
+ return -1;
64956
+ }
64957
+ let level = fold.level + (event === "EV_EXPAND_LEVEL" ? 1 : -1);
64958
+ if (level < 1 || level > fold.levels) {
64959
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event}: level ${level} out of 1..${fold.levels}`);
64960
+ return -1;
64961
+ }
64962
+ (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "fold_level", level);
64963
+ (0, _yuneta_gobj_js.gobj_save_persistent_attrs)(gobj, "fold_level");
64964
+ fold.level = level;
64965
+ refresh_fold_level(gobj);
64966
+ if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_SET_FOLD_LEVEL", { level }, gobj);
64739
64967
  return 0;
64740
64968
  }
64741
64969
  /************************************************************
@@ -64972,13 +65200,13 @@ function create_gclass$3(gclass_name) {
64972
65200
  null
64973
65201
  ],
64974
65202
  [
64975
- "EV_EXPAND_ALL",
64976
- ac_expand_all,
65203
+ "EV_EXPAND_LEVEL",
65204
+ ac_fold_level_step,
64977
65205
  null
64978
65206
  ],
64979
65207
  [
64980
- "EV_COLLAPSE_ALL",
64981
- ac_collapse_all,
65208
+ "EV_COLLAPSE_LEVEL",
65209
+ ac_fold_level_step,
64982
65210
  null
64983
65211
  ],
64984
65212
  [
@@ -65063,8 +65291,8 @@ function create_gclass$3(gclass_name) {
65063
65291
  ["EV_SET_OPERATION_MODE", 0],
65064
65292
  ["EV_SET_FOCUS_TOPIC", 0],
65065
65293
  ["EV_FIND_NODES", 0],
65066
- ["EV_EXPAND_ALL", 0],
65067
- ["EV_COLLAPSE_ALL", 0],
65294
+ ["EV_EXPAND_LEVEL", 0],
65295
+ ["EV_COLLAPSE_LEVEL", 0],
65068
65296
  ["EV_SET_NODE_MODE", 0],
65069
65297
  ["EV_TOGGLE_NODE_LABELS", 0],
65070
65298
  ["EV_FIND_RESULT", 0],
@@ -65846,7 +66074,7 @@ function sort_by_order(list) {
65846
66074
  * schema_version, c_schema_version, system_schema_version,
65847
66075
  * topics: [{
65848
66076
  * id, name, order, record,
65849
- * pkey, topic_version, system_topic,
66077
+ * pkey, topic_version, system_topic, main_topic,
65850
66078
  * cols: [{id, name, order, record}]
65851
66079
  * }]
65852
66080
  * }],
@@ -65895,6 +66123,7 @@ function build_schema_model(records) {
65895
66123
  tkey: record.tkey || "",
65896
66124
  topic_version: record.topic_version,
65897
66125
  system_topic: !!record.system_topic,
66126
+ main_topic: !!record.main_topic,
65898
66127
  cols: []
65899
66128
  };
65900
66129
  by_topic_id[entry.id] = entry;
@@ -66191,6 +66420,12 @@ function validate_schema(treedb, options) {
66191
66420
  if (is_fkey && FKEY_TYPES.indexOf(type) < 0) add("error", "fkey has a bad type", topic.name, col.name, type);
66192
66421
  }
66193
66422
  }
66423
+ let marked = treedb.topics.filter((topic) => topic.main_topic);
66424
+ if (marked.length > 1) add("error", "more than one main topic", marked[1].name, "", marked.map((topic) => topic.name).join(", "));
66425
+ for (let topic of marked) if (!topic.cols.some((col) => {
66426
+ let hook = col_hook(col.record);
66427
+ return col_flags(col.record).indexOf("hook") >= 0 && !!hook && Object.prototype.hasOwnProperty.call(hook, topic.name);
66428
+ })) add("error", "main topic not hooked to itself", topic.name, "", "");
66194
66429
  let hooked = {};
66195
66430
  for (let topic of treedb.topics) for (let col of topic.cols) {
66196
66431
  let hook = col_hook(col.record);
@@ -66382,7 +66617,8 @@ var TOPIC_KEY_ORDER = [
66382
66617
  "system_flag",
66383
66618
  "tkey",
66384
66619
  "topic_version",
66385
- "system_topic"
66620
+ "system_topic",
66621
+ "main_topic"
66386
66622
  ];
66387
66623
  var COL_KEY_ORDER = [
66388
66624
  "header",
@@ -66640,7 +66876,8 @@ var TOPIC_FIELDS = [
66640
66876
  "system_flag",
66641
66877
  "tkey",
66642
66878
  "topic_version",
66643
- "system_topic"
66879
+ "system_topic",
66880
+ "main_topic"
66644
66881
  ];
66645
66882
  var COL_FIELDS = [
66646
66883
  "header",
@@ -68257,6 +68494,20 @@ function render_topics(gobj, $body) {
68257
68494
  {},
68258
68495
  ""
68259
68496
  ],
68497
+ topic.main_topic ? [
68498
+ "span",
68499
+ {
68500
+ class: "SCHEMA_TOPIC_MAIN ml-2",
68501
+ style: "color:#d4a017;",
68502
+ title: (0, i18next.t)("main topic"),
68503
+ "data-i18n-title": "main topic"
68504
+ },
68505
+ "★"
68506
+ ] : [
68507
+ "span",
68508
+ {},
68509
+ ""
68510
+ ],
68260
68511
  ambiguous ? [
68261
68512
  "div",
68262
68513
  { class: "SCHEMA_TOPIC_ID is-size-7 yui-text-quiet" },
@@ -69251,6 +69502,26 @@ function open_topic_form(gobj, treedb, topic) {
69251
69502
  ]]
69252
69503
  ]]
69253
69504
  ],
69505
+ [
69506
+ "div",
69507
+ { class: "SCHEMA_TOPIC_FORM_MAIN field mb-3" },
69508
+ [[
69509
+ "label",
69510
+ { class: "checkbox" },
69511
+ [["input", el_attrs({
69512
+ type: "checkbox",
69513
+ "data-name": "main_topic",
69514
+ checked: record.main_topic ? "checked" : void 0
69515
+ })], [
69516
+ "span",
69517
+ {
69518
+ class: "ml-1",
69519
+ i18n: "main topic"
69520
+ },
69521
+ (0, i18next.t)("main topic")
69522
+ ]]
69523
+ ]]
69524
+ ],
69254
69525
  [
69255
69526
  "div",
69256
69527
  {
@@ -70043,6 +70314,7 @@ function topic_record(gobj, treedb, topic, values) {
70043
70314
  record.tkey = values.tkey || "";
70044
70315
  record.topic_version = values.topic_version === "" ? 1 : Number(values.topic_version);
70045
70316
  record.system_topic = !!values.system_topic;
70317
+ record.main_topic = !!values.main_topic;
70046
70318
  return record;
70047
70319
  }
70048
70320
  /***************************