@yuneta/gobj-ui 7.23.139 → 7.23.141

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.
@@ -911,6 +911,7 @@ var PRIVATE_DATA$21 = {
911
911
  navs: [],
912
912
  item_index: {},
913
913
  section_pos: {},
914
+ route_mru: [],
914
915
  sub_routes: {},
915
916
  event_handlers: {},
916
917
  hash_handler: null,
@@ -1619,6 +1620,7 @@ function navigate_to(gobj, route, depth, no_drain) {
1619
1620
  stage.active_route = matched_route;
1620
1621
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "current_route", route);
1621
1622
  remember_position(gobj, route);
1623
+ remember_route(gobj, route);
1622
1624
  update_secondary_nav_visibility(gobj, entry);
1623
1625
  close_all_drawers(gobj);
1624
1626
  close_toolbar_dropdown(gobj);
@@ -2645,6 +2647,40 @@ function remembered_position(gobj, route) {
2645
2647
  return last;
2646
2648
  }
2647
2649
  /************************************************************
2650
+ * Every route the shell lands on, newest last, a route once.
2651
+ * Bounded: it answers "where was the reader last under X", and
2652
+ * a page that has been open all day has no business growing it.
2653
+ ************************************************************/
2654
+ var ROUTE_MRU_MAX = 64;
2655
+ function remember_route(gobj, route) {
2656
+ let mru = gobj.priv.route_mru;
2657
+ let i = mru.indexOf(route);
2658
+ if (i >= 0) mru.splice(i, 1);
2659
+ mru.push(route);
2660
+ if (mru.length > ROUTE_MRU_MAX) mru.splice(0, mru.length - ROUTE_MRU_MAX);
2661
+ }
2662
+ /************************************************************
2663
+ * Where the reader last was UNDER `route`: the most recent
2664
+ * visited route that is `route` itself or lies below it, or
2665
+ * `route` when none does. A mirror of the url for the page, like
2666
+ * the section memory -- never stored, never applied on its own:
2667
+ * a control that wants to go back to a view AS IT WAS LEFT asks,
2668
+ * and navigates there itself.
2669
+ *
2670
+ * What it is for: a treedb graph's focus lives in the url
2671
+ * (`<graph>/<topic>`), so a button that opened the bare route
2672
+ * took the focus away every time it was used.
2673
+ ************************************************************/
2674
+ function yui_shell_last_route_under(shell_gobj, route) {
2675
+ let priv = shell_gobj && shell_gobj.priv;
2676
+ if (!priv || !(0, _yuneta_gobj_js.is_array)(priv.route_mru) || !route) return route;
2677
+ for (let i = priv.route_mru.length - 1; i >= 0; i--) {
2678
+ let r = priv.route_mru[i];
2679
+ if (r === route || r.indexOf(route + "/") === 0) return r;
2680
+ }
2681
+ return route;
2682
+ }
2683
+ /************************************************************
2648
2684
  * Resolve the shell that governs `gobj`: the nearest
2649
2685
  * C_YUI_SHELL ancestor, else the last shell created on the
2650
2686
  * page (apps have exactly one). Null when no shell exists —
@@ -13075,7 +13111,7 @@ function ac_find_nodes$3(gobj, event, kw, src) {
13075
13111
  /************************************************************
13076
13112
  * EV_EXPAND_ALL
13077
13113
  ************************************************************/
13078
- function ac_expand_all$4(gobj, event, kw, src) {
13114
+ function ac_expand_all$2(gobj, event, kw, src) {
13079
13115
  let priv = gobj.priv;
13080
13116
  if (priv.collapsed.size === 0) return 0;
13081
13117
  priv.collapsed.clear();
@@ -13087,7 +13123,7 @@ function ac_expand_all$4(gobj, event, kw, src) {
13087
13123
  /************************************************************
13088
13124
  * EV_COLLAPSE_ALL — fold every card but the root.
13089
13125
  ************************************************************/
13090
- function ac_collapse_all$4(gobj, event, kw, src) {
13126
+ function ac_collapse_all$2(gobj, event, kw, src) {
13091
13127
  let priv = gobj.priv;
13092
13128
  let json_data = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "json_data");
13093
13129
  if (!json_data) return 0;
@@ -13387,12 +13423,12 @@ function create_gclass$13(gclass_name) {
13387
13423
  ],
13388
13424
  [
13389
13425
  "EV_EXPAND_ALL",
13390
- ac_expand_all$4,
13426
+ ac_expand_all$2,
13391
13427
  null
13392
13428
  ],
13393
13429
  [
13394
13430
  "EV_COLLAPSE_ALL",
13395
- ac_collapse_all$4,
13431
+ ac_collapse_all$2,
13396
13432
  null
13397
13433
  ],
13398
13434
  [
@@ -14464,7 +14500,7 @@ function ac_search$2(gobj, event, kw, src) {
14464
14500
  /************************************************************
14465
14501
  * EV_EXPAND_ALL — expand every already-loaded container
14466
14502
  ************************************************************/
14467
- function ac_expand_all$3(gobj, event, kw, src) {
14503
+ function ac_expand_all$1(gobj, event, kw, src) {
14468
14504
  let priv = gobj.priv;
14469
14505
  if (priv.root === null || priv.root === void 0) return 0;
14470
14506
  let paths = [];
@@ -14482,7 +14518,7 @@ function ac_expand_all$3(gobj, event, kw, src) {
14482
14518
  /************************************************************
14483
14519
  * EV_COLLAPSE_ALL
14484
14520
  ************************************************************/
14485
- function ac_collapse_all$3(gobj, event, kw, src) {
14521
+ function ac_collapse_all$1(gobj, event, kw, src) {
14486
14522
  gobj.priv.expanded.clear();
14487
14523
  render_view$1(gobj);
14488
14524
  return 0;
@@ -14667,12 +14703,12 @@ function create_gclass$12(gclass_name) {
14667
14703
  ],
14668
14704
  [
14669
14705
  "EV_EXPAND_ALL",
14670
- ac_expand_all$3,
14706
+ ac_expand_all$1,
14671
14707
  null
14672
14708
  ],
14673
14709
  [
14674
14710
  "EV_COLLAPSE_ALL",
14675
- ac_collapse_all$3,
14711
+ ac_collapse_all$1,
14676
14712
  null
14677
14713
  ],
14678
14714
  [
@@ -19694,7 +19730,7 @@ function ac_toggle_collapse(gobj, event, kw, src) {
19694
19730
  /************************************************************
19695
19731
  * Force every node visible (overrides threshold).
19696
19732
  ************************************************************/
19697
- function ac_expand_all$2(gobj, event, kw, src) {
19733
+ function ac_expand_all(gobj, event, kw, src) {
19698
19734
  let priv = gobj.priv;
19699
19735
  if (!priv.collapse_state) priv.collapse_state = {};
19700
19736
  if (priv.node_by_id) for (let id of Object.keys(priv.node_by_id)) {
@@ -19707,7 +19743,7 @@ function ac_expand_all$2(gobj, event, kw, src) {
19707
19743
  /************************************************************
19708
19744
  * Collapse every node that has children.
19709
19745
  ************************************************************/
19710
- function ac_collapse_all$2(gobj, event, kw, src) {
19746
+ function ac_collapse_all(gobj, event, kw, src) {
19711
19747
  let priv = gobj.priv;
19712
19748
  if (!priv.collapse_state) priv.collapse_state = {};
19713
19749
  if (priv.node_by_id) for (let id of Object.keys(priv.node_by_id)) {
@@ -20059,12 +20095,12 @@ function create_gclass$9(gclass_name) {
20059
20095
  ],
20060
20096
  [
20061
20097
  "EV_EXPAND_ALL",
20062
- ac_expand_all$2,
20098
+ ac_expand_all,
20063
20099
  null
20064
20100
  ],
20065
20101
  [
20066
20102
  "EV_COLLAPSE_ALL",
20067
- ac_collapse_all$2,
20103
+ ac_collapse_all,
20068
20104
  null
20069
20105
  ],
20070
20106
  [
@@ -21908,15 +21944,12 @@ function cmd_get_topic_data$1(gobj, cmd, kw, src) {
21908
21944
  ************************************************************/
21909
21945
  function build_ui$6(gobj) {
21910
21946
  let source_url = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "source_url") || "";
21911
- let routes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "card_action_routes");
21912
- let graph_tpl = (0, _yuneta_gobj_js.is_object)(routes) && typeof routes.graph === "string" ? routes.graph : "";
21913
- let graph_href = /\/\{topic\}$/.test(graph_tpl) ? graph_tpl.replace(/\/\{topic\}$/, "") : "";
21947
+ let graph_href = graph_route_of(gobj);
21914
21948
  let graph_items = [];
21915
21949
  if (graph_href) graph_items.push([
21916
- "a",
21950
+ "button",
21917
21951
  {
21918
21952
  class: "button TREEDB_GRAPH_BTN",
21919
- href: graph_href,
21920
21953
  style: "margin-left:auto;",
21921
21954
  title: (0, i18next.t)("graph"),
21922
21955
  "aria-label": (0, i18next.t)("graph"),
@@ -21931,7 +21964,11 @@ function build_ui$6(gobj) {
21931
21964
  "span",
21932
21965
  { i18n: "graph" },
21933
21966
  "graph"
21934
- ]]
21967
+ ]],
21968
+ { click: (evt) => {
21969
+ evt.stopPropagation();
21970
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_OPEN_GRAPH", {}, gobj);
21971
+ } }
21935
21972
  ]);
21936
21973
  let $container = (0, _yuneta_gobj_js.createElement2)([
21937
21974
  "div",
@@ -22092,6 +22129,17 @@ function build_ui$6(gobj) {
22092
22129
  if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_cards_landing")) show_topics_landing(gobj);
22093
22130
  }
22094
22131
  /************************************************************
22132
+ * The graph's route with no topic: the host's card template
22133
+ * `card_action_routes.graph` without its trailing `/{topic}`,
22134
+ * as a ROUTE (no `#`). "" when the host gives no such template.
22135
+ ************************************************************/
22136
+ function graph_route_of(gobj) {
22137
+ let routes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "card_action_routes");
22138
+ let tpl = (0, _yuneta_gobj_js.is_object)(routes) && typeof routes.graph === "string" ? routes.graph : "";
22139
+ if (!/\/\{topic\}$/.test(tpl)) return "";
22140
+ return tpl.replace(/\/\{topic\}$/, "").replace(/^#/, "");
22141
+ }
22142
+ /************************************************************
22095
22143
  * Is the backend session live? The Raw JSON viewer issues a remote
22096
22144
  * print-tranger, so it only makes sense with a session — the remote
22097
22145
  * (C_IEVENT_CLI) is in ST_SESSION exactly while connected.
@@ -23460,6 +23508,26 @@ function ac_open_json$1(gobj, event, kw, src) {
23460
23508
  return 0;
23461
23509
  }
23462
23510
  /********************************************
23511
+ * Open the graph where the reader left it: its last visited
23512
+ * route under the graph's base -- `<graph>/<topic>` when a topic
23513
+ * was focused there -- or the bare base when the graph was never
23514
+ * opened in this page. A user move: pushed, so Back returns.
23515
+ ********************************************/
23516
+ function ac_open_graph(gobj, event, kw, src) {
23517
+ let base = graph_route_of(gobj);
23518
+ if (!base) {
23519
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: open graph with no graph route`);
23520
+ return -1;
23521
+ }
23522
+ let shell = yui_shell_of(gobj);
23523
+ if (!shell) {
23524
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: open graph with no shell`);
23525
+ return -1;
23526
+ }
23527
+ yui_shell_navigate(shell, yui_shell_last_route_under(shell, base), { push: true });
23528
+ return 0;
23529
+ }
23530
+ /********************************************
23463
23531
  * The viewer asked to load a collapsed subtree (tranger mode only):
23464
23532
  * re-issue print-tranger for that path.
23465
23533
  ********************************************/
@@ -23558,6 +23626,11 @@ function create_gclass$6(gclass_name) {
23558
23626
  ac_open_json$1,
23559
23627
  null
23560
23628
  ],
23629
+ [
23630
+ "EV_OPEN_GRAPH",
23631
+ ac_open_graph,
23632
+ null
23633
+ ],
23561
23634
  [
23562
23635
  "EV_EXPAND_PATH",
23563
23636
  ac_json_expand_path$1,
@@ -23622,6 +23695,7 @@ function create_gclass$6(gclass_name) {
23622
23695
  ["EV_RECORD_WRITTEN", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
23623
23696
  ["EV_REFRESH_TOPIC", 0],
23624
23697
  ["EV_OPEN_JSON", 0],
23698
+ ["EV_OPEN_GRAPH", 0],
23625
23699
  ["EV_EXPAND_PATH", 0],
23626
23700
  ["EV_JSON_CLOSED", 0],
23627
23701
  ["EV_SHOW", 0],
@@ -27665,7 +27739,7 @@ function fold_build_model(descs, records, opts) {
27665
27739
  for (let k of reach_from(nodes, [node.key])) reached.add(k);
27666
27740
  }
27667
27741
  for (let topic_name of topics) if (roots[topic_name].length > 0) groups.set(fold_root_group_key(topic_name), roots[topic_name]);
27668
- return {
27742
+ let model = {
27669
27743
  nodes,
27670
27744
  groups,
27671
27745
  topics,
@@ -27674,8 +27748,52 @@ function fold_build_model(descs, records, opts) {
27674
27748
  main_topic,
27675
27749
  linked,
27676
27750
  loose,
27677
- loose_shown
27751
+ loose_shown,
27752
+ level_of: null,
27753
+ levels: 0
27678
27754
  };
27755
+ count_levels(model);
27756
+ return model;
27757
+ }
27758
+ /************************************************************
27759
+ * The LEVELS of the main tree: the roots of the main topic --
27760
+ * and of the topics hung from it, whose roots are the loose
27761
+ * records the reader asked for -- are level 1, their children
27762
+ * level 2, and so on down every hook of every topic that is not
27763
+ * hidden. A node reached by two paths takes the shallowest.
27764
+ *
27765
+ * With no main topic every topic is a tree of its own and all
27766
+ * of them count. A topic the schema does not tie to the main
27767
+ * one is a separate tree, and has no level: the stepper of the
27768
+ * toolbar speaks for the main tree and nothing else, those
27769
+ * trees are opened by their own pills.
27770
+ ************************************************************/
27771
+ function count_levels(model) {
27772
+ let level_of = /* @__PURE__ */ new Map();
27773
+ let queue = [];
27774
+ let deepest = 0;
27775
+ for (let topic_name of model.topics) {
27776
+ if (model.main_topic && !model.linked.has(topic_name)) continue;
27777
+ let roots = model.groups.get(fold_root_group_key(topic_name)) || [];
27778
+ for (let key of roots) if (!level_of.has(key)) {
27779
+ level_of.set(key, 1);
27780
+ queue.push(key);
27781
+ }
27782
+ }
27783
+ let head = 0;
27784
+ while (head < queue.length) {
27785
+ let key = queue[head++];
27786
+ let lv = level_of.get(key);
27787
+ if (lv > deepest) deepest = lv;
27788
+ let node = model.nodes.get(key);
27789
+ if (!node) continue;
27790
+ for (let hook of Object.keys(node.hooks)) for (let child of node.hooks[hook]) if (!level_of.has(child)) {
27791
+ level_of.set(child, lv + 1);
27792
+ queue.push(child);
27793
+ }
27794
+ }
27795
+ model.level_of = level_of;
27796
+ model.levels = deepest;
27679
27797
  }
27680
27798
  /************************************************************
27681
27799
  * The topic the schema hangs the others from: the one whose hooks
@@ -27801,14 +27919,6 @@ function fold_show_more(model, state, group_key) {
27801
27919
  return n;
27802
27920
  }
27803
27921
  /************************************************************
27804
- * Every group open on ALL its children. This is the reader
27805
- * asking for the whole thing; the pages are for the other way in.
27806
- ************************************************************/
27807
- function fold_expand_all(model, state) {
27808
- state.shown.clear();
27809
- for (let [group_key, list] of model.groups) if (list.length > 0) state.shown.set(group_key, list.length);
27810
- }
27811
- /************************************************************
27812
27922
  * The roots and nothing else -- the first page of each topic's
27813
27923
  * roots, the way the graph looks before anything is opened.
27814
27924
  ************************************************************/
@@ -27820,40 +27930,51 @@ function fold_collapse_all(model, state) {
27820
27930
  }
27821
27931
  }
27822
27932
  /************************************************************
27823
- * Open the tree down to `depth` levels: the roots are level 1,
27824
- * their children level 2. A node reached by two paths takes the
27825
- * shallowest. Each group opens on its first page. `depth` of 1
27826
- * is the roots alone; anything below that is treated as 1.
27933
+ * The toolbar's STEPPER over the levels of the main tree (see
27934
+ * count_levels): level N shows the roots and N-1 levels under
27935
+ * them. A level is a floor of what is open, not a picture that
27936
+ * replaces it:
27937
+ *
27938
+ * opening to N opens, on its first page, every hook of
27939
+ * every node above level N that is folded;
27940
+ * what the reader opened, and every page
27941
+ * already shown, stays
27942
+ * closing to N folds every hook of every node at level N
27943
+ * or below, and touches nothing above it
27944
+ *
27945
+ * A separate tree (a topic not tied to the main one) has no
27946
+ * level, so neither ever touches it.
27827
27947
  ************************************************************/
27828
- function fold_expand_to_depth(model, state, depth) {
27829
- fold_collapse_all(model, state);
27830
- if (!(depth > 1)) return;
27831
- let level = /* @__PURE__ */ new Map();
27832
- let queue = [];
27833
- for (let topic_name of model.topics) {
27834
- let roots = model.groups.get(fold_root_group_key(topic_name)) || [];
27835
- for (let key of roots) if (!level.has(key)) {
27836
- level.set(key, 1);
27837
- queue.push(key);
27838
- }
27839
- }
27840
- while (queue.length) {
27841
- let key = queue.shift();
27842
- let lv = level.get(key);
27843
- if (lv >= depth) continue;
27948
+ function fold_level_clamp(model, level) {
27949
+ let top = model.levels > 0 ? model.levels : 1;
27950
+ level = Math.floor(Number(level)) || 1;
27951
+ if (level < 1) return 1;
27952
+ if (level > top) return top;
27953
+ return level;
27954
+ }
27955
+ function fold_open_levels(model, state, level) {
27956
+ for (let [key, lv] of model.level_of) {
27957
+ if (lv >= level) continue;
27844
27958
  let node = model.nodes.get(key);
27845
27959
  if (!node) continue;
27846
27960
  for (let hook of Object.keys(node.hooks)) {
27847
- let children = node.hooks[hook];
27848
- if (children.length === 0) continue;
27849
- state.shown.set(fold_group_key(key, hook), state.page_size);
27850
- for (let child of children) if (!level.has(child)) {
27851
- level.set(child, lv + 1);
27852
- queue.push(child);
27853
- }
27961
+ let group_key = fold_group_key(key, hook);
27962
+ if (node.hooks[hook].length > 0 && !fold_is_open(state, group_key)) state.shown.set(group_key, state.page_size);
27854
27963
  }
27855
27964
  }
27856
27965
  }
27966
+ function fold_close_levels(model, state, level) {
27967
+ for (let [key, lv] of model.level_of) {
27968
+ if (lv < level) continue;
27969
+ let node = model.nodes.get(key);
27970
+ if (!node) continue;
27971
+ for (let hook of Object.keys(node.hooks)) state.shown.delete(fold_group_key(key, hook));
27972
+ }
27973
+ }
27974
+ function fold_expand_to_level(model, state, level) {
27975
+ fold_collapse_all(model, state);
27976
+ fold_open_levels(model, state, fold_level_clamp(model, level));
27977
+ }
27857
27978
  /************************************************************
27858
27979
  * The set of node keys on screen, from the roots down through
27859
27980
  * every open group -- and only through open groups whose parent
@@ -55096,8 +55217,9 @@ function ensure_brush_select_owned() {
55096
55217
  * The graph opens FOLDED, like a JSON viewer.
55097
55218
  *
55098
55219
  * Every record of every topic is fetched, but only the ones on
55099
- * screen become G6 nodes: the roots, `expand_depth` levels under
55100
- * them, one page (`fold_page_size`) of children per hook. Each
55220
+ * screen become G6 nodes: the roots, and the MAIN tree open to
55221
+ * the reader's level (`fold_level`, `expand_depth` until they
55222
+ * step it), one page (`fold_page_size`) of children per hook. Each
55101
55223
  * card carries a pill per hook that has children -- `▸ devices
55102
55224
  * 142` -- which opens or folds that hook; an open hook with more
55103
55225
  * children than its page ends in a `+N` chip that opens the next
@@ -55275,7 +55397,8 @@ var attrs_table$4 = [
55275
55397
  (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)"),
55276
55398
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "hook_port_position", 0, "bottom", "Hook port position"),
55277
55399
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "fkey_port_position", 0, "top", "Fkey port position"),
55278
- (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"),
55400
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "expand_depth", 0, 2, "Levels open when a treedb loads, until the reader steps (`fold_level`): 1 = the roots alone, 2 = the roots and their children. Every open hook shows its first page"),
55401
+ (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"),
55279
55402
  (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"),
55280
55403
  (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"),
55281
55404
  (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"),
@@ -55367,6 +55490,7 @@ var PRIVATE_DATA$4 = {
55367
55490
  _nodes_total: 0,
55368
55491
  _nodes_placed: 0,
55369
55492
  expand_depth: 2,
55493
+ fold_level: 0,
55370
55494
  fold_page_size: 24,
55371
55495
  hidden_topics: null,
55372
55496
  main_topic: "",
@@ -61251,7 +61375,12 @@ function publish_legend_state(gobj, visible) {
61251
61375
  (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_LEGEND_STATE", {
61252
61376
  main_topic: model.main_topic,
61253
61377
  main_chosen: !!(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "main_topic"),
61254
- topics
61378
+ topics,
61379
+ fold: {
61380
+ topic: model.main_topic,
61381
+ level: current_fold_level(gobj),
61382
+ levels: model.levels
61383
+ }
61255
61384
  });
61256
61385
  }
61257
61386
  async function reconcile_fold_apply(gobj, opts, visible) {
@@ -61556,7 +61685,7 @@ function ac_load_data(gobj, event, kw, src) {
61556
61685
  }
61557
61686
  if (all_loaded && priv.graph) {
61558
61687
  rebuild_fold_model(gobj);
61559
- fold_expand_to_depth(priv._fold_model, priv._fold_state, (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "expand_depth"));
61688
+ fold_expand_to_level(priv._fold_model, priv._fold_state, current_fold_level(gobj));
61560
61689
  count_placed_records(gobj);
61561
61690
  let auto_laid = false;
61562
61691
  try {
@@ -61960,19 +62089,46 @@ function ac_show_more(gobj, event, kw, src) {
61960
62089
  return 0;
61961
62090
  }
61962
62091
  /************************************************************
61963
- * The whole treedb, every hook open on all its children. This
61964
- * is the reader asking for the pile on purpose; the pages are
61965
- * the other way in.
62092
+ * The level of the main tree the toolbar's stepper stands on:
62093
+ * `fold_level` once the reader has stepped, `expand_depth`
62094
+ * until then, clamped to the levels the tree has.
61966
62095
  ************************************************************/
61967
- function ac_expand_all$1(gobj, event, kw, src) {
62096
+ function current_fold_level(gobj) {
61968
62097
  let priv = gobj.priv;
61969
- if (!priv._fold_model) {
61970
- (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: expand all with no tree loaded`);
62098
+ let level = (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "fold_level");
62099
+ if (!(level > 0)) level = (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "expand_depth");
62100
+ if (!priv._fold_model) return level;
62101
+ return fold_level_clamp(priv._fold_model, level);
62102
+ }
62103
+ /************************************************************
62104
+ * The toolbar's stepper: one level more or one less of the
62105
+ * MAIN tree (the host owns the number and persists it; this
62106
+ * applies it). Deeper opens the hooks above the new level,
62107
+ * shallower folds from it down -- a level is a floor, not a
62108
+ * picture, so what the reader opened by its pill stays (see
62109
+ * treedb_fold_model). Replaces expand all / collapse all
62110
+ * (7.23.141): the whole treedb at once was the pile, and
62111
+ * closing threw away everything the reader had opened.
62112
+ ************************************************************/
62113
+ function ac_set_fold_level(gobj, event, kw, src) {
62114
+ let priv = gobj.priv;
62115
+ let level = Number(kw && kw.level);
62116
+ if (!(level > 0)) {
62117
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: fold level '${kw && kw.level}' is not a level`);
61971
62118
  return -1;
61972
62119
  }
62120
+ if (!priv._fold_model) {
62121
+ (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "fold_level", level);
62122
+ return 0;
62123
+ }
62124
+ let from = current_fold_level(gobj);
62125
+ level = fold_level_clamp(priv._fold_model, level);
62126
+ (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "fold_level", level);
62127
+ if (level === from) return 0;
61973
62128
  let keep = fold_keep_node(gobj);
61974
62129
  let vp = yui_graph_viewport_of(priv.graph, keep);
61975
- fold_expand_all(priv._fold_model, priv._fold_state);
62130
+ if (level > from) fold_open_levels(priv._fold_model, priv._fold_state, level);
62131
+ else fold_close_levels(priv._fold_model, priv._fold_state, level);
61976
62132
  reconcile_fold(gobj, {
61977
62133
  keep,
61978
62134
  keep_vp: vp
@@ -62000,29 +62156,11 @@ function fold_keep_node(gobj) {
62000
62156
  return "";
62001
62157
  }
62002
62158
  /************************************************************
62003
- * The roots alone.
62004
- ************************************************************/
62005
- function ac_collapse_all$1(gobj, event, kw, src) {
62006
- let priv = gobj.priv;
62007
- if (!priv._fold_model) {
62008
- (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: collapse all with no tree loaded`);
62009
- return -1;
62010
- }
62011
- let keep = fold_keep_node(gobj);
62012
- let vp = yui_graph_viewport_of(priv.graph, keep);
62013
- fold_collapse_all(priv._fold_model, priv._fold_state);
62014
- reconcile_fold(gobj, {
62015
- keep,
62016
- keep_vp: vp
62017
- });
62018
- return 0;
62019
- }
62020
- /************************************************************
62021
62159
  * The legend's three settings, from the host, which persists
62022
62160
  * them. Each rebuilds the tree; the fold state is kept -- it is
62023
62161
  * keyed by group, and a group that is gone is never read -- except
62024
62162
  * for a new MAIN topic, whose roots are other roots: the tree
62025
- * opens again at `expand_depth`.
62163
+ * opens again at the reader's level, now counted from them.
62026
62164
  ************************************************************/
62027
62165
  function ac_set_hidden_topics(gobj, event, kw, src) {
62028
62166
  let priv = gobj.priv;
@@ -62040,7 +62178,7 @@ function ac_set_main_topic(gobj, event, kw, src) {
62040
62178
  let keep = fold_keep_node(gobj);
62041
62179
  let vp = yui_graph_viewport_of(priv.graph, keep);
62042
62180
  rebuild_fold_model(gobj);
62043
- fold_expand_to_depth(priv._fold_model, priv._fold_state, (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "expand_depth"));
62181
+ fold_expand_to_level(priv._fold_model, priv._fold_state, current_fold_level(gobj));
62044
62182
  reconcile_fold(gobj, {
62045
62183
  relayout: true,
62046
62184
  keep,
@@ -62601,13 +62739,8 @@ function create_gclass$4(gclass_name) {
62601
62739
  null
62602
62740
  ],
62603
62741
  [
62604
- "EV_EXPAND_ALL",
62605
- ac_expand_all$1,
62606
- null
62607
- ],
62608
- [
62609
- "EV_COLLAPSE_ALL",
62610
- ac_collapse_all$1,
62742
+ "EV_SET_FOLD_LEVEL",
62743
+ ac_set_fold_level,
62611
62744
  null
62612
62745
  ],
62613
62746
  [
@@ -62750,8 +62883,7 @@ function create_gclass$4(gclass_name) {
62750
62883
  ["EV_CAMERA_CHANGED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
62751
62884
  ["EV_TOGGLE_FOLD", 0],
62752
62885
  ["EV_SHOW_MORE", 0],
62753
- ["EV_EXPAND_ALL", 0],
62754
- ["EV_COLLAPSE_ALL", 0],
62886
+ ["EV_SET_FOLD_LEVEL", 0],
62755
62887
  ["EV_SET_HIDDEN_TOPICS", 0],
62756
62888
  ["EV_SET_MAIN_TOPIC", 0],
62757
62889
  ["EV_SET_LOOSE_TOPICS", 0],
@@ -62835,6 +62967,7 @@ var attrs_table$3 = [
62835
62967
  (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"),
62836
62968
  (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"),
62837
62969
  (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"),
62970
+ (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"),
62838
62971
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
62839
62972
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
62840
62973
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
@@ -62910,6 +63043,7 @@ function mt_create$3(gobj) {
62910
63043
  hook_port_position: "bottom",
62911
63044
  fkey_port_position: "top",
62912
63045
  expand_depth: (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "expand_depth"),
63046
+ fold_level: (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "fold_level"),
62913
63047
  fold_page_size: (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "fold_page_size"),
62914
63048
  minimap_min_nodes: (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "minimap_min_nodes"),
62915
63049
  with_gridline: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_gridline"),
@@ -63217,6 +63351,127 @@ function refresh_node_shape_buttons(gobj, $root) {
63217
63351
  $labels.disabled = mode !== "shape";
63218
63352
  }
63219
63353
  }
63354
+ /************************************************************
63355
+ * The fold STEPPER: one level less, where the tree stands, one
63356
+ * level more.
63357
+ *
63358
+ * It replaces the fold pair (7.23.141). `expand all` opened every
63359
+ * hook of the treedb -- the pile, on purpose, which on a treedb
63360
+ * of thousands is a pile nobody reads -- and `collapse all` threw
63361
+ * away everything the reader had opened. The stepper walks the
63362
+ * MAIN tree a level at a time, the way the frontend view opens
63363
+ * one level per press: `+` opens the next level under the main
63364
+ * topic's roots, through every topic that is not hidden; `-`
63365
+ * folds the deepest one. Pages and pills the reader opened stay.
63366
+ *
63367
+ * The readout says what the two buttons act on, which no chevron
63368
+ * can: the main topic -- its swatch and its name, which is DATA
63369
+ * and never translated -- and `level/levels`. On a phone the name
63370
+ * goes and the numbers stay. The level is the reader's, per
63371
+ * treedb: persisted (`fold_level`) and handed to the engine on
63372
+ * creation, so a reload opens where it was left.
63373
+ *
63374
+ * Both buttons are born disabled: nothing can be stepped until
63375
+ * the engine has said how deep the tree goes (EV_LEGEND_STATE).
63376
+ ************************************************************/
63377
+ function fold_level_items(gobj, wide) {
63378
+ let step_button = (cls, icon, key, event_name) => {
63379
+ return [
63380
+ "button",
63381
+ {
63382
+ class: `${cls} button`,
63383
+ type: "button",
63384
+ style: {
63385
+ height: wide,
63386
+ width: "2.5em"
63387
+ },
63388
+ disabled: true,
63389
+ title: (0, i18next.t)(key),
63390
+ "data-i18n-title": key,
63391
+ "aria-label": (0, i18next.t)(key),
63392
+ "data-i18n-aria-label": key
63393
+ },
63394
+ yui_toolbar_icon(icon),
63395
+ { click: (evt) => {
63396
+ evt.stopPropagation();
63397
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, event_name, {}, gobj);
63398
+ } }
63399
+ ];
63400
+ };
63401
+ return [[
63402
+ "div",
63403
+ {
63404
+ class: "GRAPH_FOLD_STEPPER buttons has-addons",
63405
+ style: "margin:0; flex:0 0 auto; flex-wrap:nowrap; align-items:stretch;"
63406
+ },
63407
+ [
63408
+ step_button("GRAPH_FOLD_COLLAPSE", "yi-chevron-right", "collapse one level", "EV_COLLAPSE_LEVEL"),
63409
+ [
63410
+ "div",
63411
+ {
63412
+ class: "GRAPH_FOLD_LEVEL button",
63413
+ style: {
63414
+ height: wide,
63415
+ cursor: "default",
63416
+ gap: ".35rem"
63417
+ },
63418
+ role: "status",
63419
+ title: (0, i18next.t)("fold level"),
63420
+ "data-i18n-title": "fold level"
63421
+ },
63422
+ [
63423
+ ["span", {
63424
+ class: "GRAPH_FOLD_SWATCH",
63425
+ style: "display:none; width:.8rem; height:.8rem; border-radius:3px; flex:0 0 auto; border:1px solid rgba(0,0,0,.25);"
63426
+ }],
63427
+ ["span", {
63428
+ class: "GRAPH_FOLD_TOPIC is-hidden-mobile",
63429
+ style: "max-width:10em; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:600;"
63430
+ }],
63431
+ [
63432
+ "span",
63433
+ {
63434
+ class: "GRAPH_FOLD_WORD",
63435
+ i18n: "level"
63436
+ },
63437
+ "level"
63438
+ ],
63439
+ [
63440
+ "span",
63441
+ {
63442
+ class: "GRAPH_FOLD_COUNT",
63443
+ style: "font-variant-numeric:tabular-nums;"
63444
+ },
63445
+ "–"
63446
+ ]
63447
+ ]
63448
+ ],
63449
+ step_button("GRAPH_FOLD_EXPAND", "yi-chevron-down", "expand one level", "EV_EXPAND_LEVEL")
63450
+ ]
63451
+ ]];
63452
+ }
63453
+ function refresh_fold_level(gobj) {
63454
+ let priv = gobj.priv;
63455
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
63456
+ if (!$container) return;
63457
+ let $readout = $container.querySelector(".GRAPH_FOLD_LEVEL");
63458
+ if (!$readout) return;
63459
+ let state = priv._legend_state || {};
63460
+ let fold = state.fold || {};
63461
+ let levels = fold.levels > 0 ? fold.levels : 0;
63462
+ let level = fold.level > 0 ? fold.level : 0;
63463
+ let topic = fold.topic || "";
63464
+ let entry = ((0, _yuneta_gobj_js.is_array)(state.topics) ? state.topics : []).find((e) => e.topic === topic);
63465
+ let $swatch = $readout.querySelector(".GRAPH_FOLD_SWATCH");
63466
+ $swatch.style.display = topic ? "inline-block" : "none";
63467
+ $swatch.style.background = entry && entry.color || "#94a3b8";
63468
+ $readout.querySelector(".GRAPH_FOLD_TOPIC").textContent = topic;
63469
+ $readout.querySelector(".GRAPH_FOLD_COUNT").textContent = levels ? `${level}/${levels}` : "–";
63470
+ let $collapse = $container.querySelector(".GRAPH_FOLD_COLLAPSE");
63471
+ let $expand = $container.querySelector(".GRAPH_FOLD_EXPAND");
63472
+ if ($collapse) $collapse.disabled = !(levels && level > 1);
63473
+ if ($expand) $expand.disabled = !(levels && level < levels);
63474
+ }
63220
63475
  function make_toolbar(gobj) {
63221
63476
  let priv = gobj.priv;
63222
63477
  let modes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "operation_modes");
@@ -63283,7 +63538,7 @@ function make_toolbar(gobj) {
63283
63538
  } }
63284
63539
  ]
63285
63540
  ];
63286
- 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"))];
63541
+ 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"))];
63287
63542
  let backend_items = [[
63288
63543
  "button",
63289
63544
  {
@@ -64539,6 +64794,7 @@ function ac_legend_state(gobj, event, kw, src) {
64539
64794
  let priv = gobj.priv;
64540
64795
  priv._legend_state = kw || null;
64541
64796
  refresh_legend(gobj);
64797
+ refresh_fold_level(gobj);
64542
64798
  return 0;
64543
64799
  }
64544
64800
  /************************************************************
@@ -64651,17 +64907,33 @@ function ac_find_nodes(gobj, event, kw, src) {
64651
64907
  return 0;
64652
64908
  }
64653
64909
  /************************************************************
64654
- * The fold pair of the toolbar, forwarded to the G6 child,
64655
- * which owns the tree.
64910
+ * One press of the fold stepper (EV_EXPAND_LEVEL /
64911
+ * EV_COLLAPSE_LEVEL): the level moves by one, is SAVED (the
64912
+ * reader's, per treedb) and reaches the G6 child, which owns
64913
+ * the tree.
64914
+ *
64915
+ * The readout moves at once, not when the engine speaks again:
64916
+ * a reconcile is asynchronous, and a second press that came
64917
+ * before it would step from the level already left -- two
64918
+ * presses, one level.
64656
64919
  ************************************************************/
64657
- function ac_expand_all(gobj, event, kw, src) {
64920
+ function ac_fold_level_step(gobj, event, kw, src) {
64658
64921
  let priv = gobj.priv;
64659
- if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_EXPAND_ALL", {}, gobj);
64660
- return 0;
64661
- }
64662
- function ac_collapse_all(gobj, event, kw, src) {
64663
- let priv = gobj.priv;
64664
- if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_COLLAPSE_ALL", {}, gobj);
64922
+ let fold = priv._legend_state && priv._legend_state.fold;
64923
+ if (!fold || !(fold.levels > 0)) {
64924
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} with no tree to step through`);
64925
+ return -1;
64926
+ }
64927
+ let level = fold.level + (event === "EV_EXPAND_LEVEL" ? 1 : -1);
64928
+ if (level < 1 || level > fold.levels) {
64929
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event}: level ${level} out of 1..${fold.levels}`);
64930
+ return -1;
64931
+ }
64932
+ (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "fold_level", level);
64933
+ (0, _yuneta_gobj_js.gobj_save_persistent_attrs)(gobj, "fold_level");
64934
+ fold.level = level;
64935
+ refresh_fold_level(gobj);
64936
+ if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_SET_FOLD_LEVEL", { level }, gobj);
64665
64937
  return 0;
64666
64938
  }
64667
64939
  /************************************************************
@@ -64898,13 +65170,13 @@ function create_gclass$3(gclass_name) {
64898
65170
  null
64899
65171
  ],
64900
65172
  [
64901
- "EV_EXPAND_ALL",
64902
- ac_expand_all,
65173
+ "EV_EXPAND_LEVEL",
65174
+ ac_fold_level_step,
64903
65175
  null
64904
65176
  ],
64905
65177
  [
64906
- "EV_COLLAPSE_ALL",
64907
- ac_collapse_all,
65178
+ "EV_COLLAPSE_LEVEL",
65179
+ ac_fold_level_step,
64908
65180
  null
64909
65181
  ],
64910
65182
  [
@@ -64989,8 +65261,8 @@ function create_gclass$3(gclass_name) {
64989
65261
  ["EV_SET_OPERATION_MODE", 0],
64990
65262
  ["EV_SET_FOCUS_TOPIC", 0],
64991
65263
  ["EV_FIND_NODES", 0],
64992
- ["EV_EXPAND_ALL", 0],
64993
- ["EV_COLLAPSE_ALL", 0],
65264
+ ["EV_EXPAND_LEVEL", 0],
65265
+ ["EV_COLLAPSE_LEVEL", 0],
64994
65266
  ["EV_SET_NODE_MODE", 0],
64995
65267
  ["EV_TOGGLE_NODE_LABELS", 0],
64996
65268
  ["EV_FIND_RESULT", 0],