@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.
@@ -906,6 +906,7 @@ var PRIVATE_DATA$21 = {
906
906
  navs: [],
907
907
  item_index: {},
908
908
  section_pos: {},
909
+ route_mru: [],
909
910
  sub_routes: {},
910
911
  event_handlers: {},
911
912
  hash_handler: null,
@@ -1614,6 +1615,7 @@ function navigate_to(gobj, route, depth, no_drain) {
1614
1615
  stage.active_route = matched_route;
1615
1616
  gobj_write_attr(gobj, "current_route", route);
1616
1617
  remember_position(gobj, route);
1618
+ remember_route(gobj, route);
1617
1619
  update_secondary_nav_visibility(gobj, entry);
1618
1620
  close_all_drawers(gobj);
1619
1621
  close_toolbar_dropdown(gobj);
@@ -2640,6 +2642,40 @@ function remembered_position(gobj, route) {
2640
2642
  return last;
2641
2643
  }
2642
2644
  /************************************************************
2645
+ * Every route the shell lands on, newest last, a route once.
2646
+ * Bounded: it answers "where was the reader last under X", and
2647
+ * a page that has been open all day has no business growing it.
2648
+ ************************************************************/
2649
+ var ROUTE_MRU_MAX = 64;
2650
+ function remember_route(gobj, route) {
2651
+ let mru = gobj.priv.route_mru;
2652
+ let i = mru.indexOf(route);
2653
+ if (i >= 0) mru.splice(i, 1);
2654
+ mru.push(route);
2655
+ if (mru.length > ROUTE_MRU_MAX) mru.splice(0, mru.length - ROUTE_MRU_MAX);
2656
+ }
2657
+ /************************************************************
2658
+ * Where the reader last was UNDER `route`: the most recent
2659
+ * visited route that is `route` itself or lies below it, or
2660
+ * `route` when none does. A mirror of the url for the page, like
2661
+ * the section memory -- never stored, never applied on its own:
2662
+ * a control that wants to go back to a view AS IT WAS LEFT asks,
2663
+ * and navigates there itself.
2664
+ *
2665
+ * What it is for: a treedb graph's focus lives in the url
2666
+ * (`<graph>/<topic>`), so a button that opened the bare route
2667
+ * took the focus away every time it was used.
2668
+ ************************************************************/
2669
+ function yui_shell_last_route_under(shell_gobj, route) {
2670
+ let priv = shell_gobj && shell_gobj.priv;
2671
+ if (!priv || !is_array(priv.route_mru) || !route) return route;
2672
+ for (let i = priv.route_mru.length - 1; i >= 0; i--) {
2673
+ let r = priv.route_mru[i];
2674
+ if (r === route || r.indexOf(route + "/") === 0) return r;
2675
+ }
2676
+ return route;
2677
+ }
2678
+ /************************************************************
2643
2679
  * Resolve the shell that governs `gobj`: the nearest
2644
2680
  * C_YUI_SHELL ancestor, else the last shell created on the
2645
2681
  * page (apps have exactly one). Null when no shell exists —
@@ -13070,7 +13106,7 @@ function ac_find_nodes$3(gobj, event, kw, src) {
13070
13106
  /************************************************************
13071
13107
  * EV_EXPAND_ALL
13072
13108
  ************************************************************/
13073
- function ac_expand_all$4(gobj, event, kw, src) {
13109
+ function ac_expand_all$2(gobj, event, kw, src) {
13074
13110
  let priv = gobj.priv;
13075
13111
  if (priv.collapsed.size === 0) return 0;
13076
13112
  priv.collapsed.clear();
@@ -13082,7 +13118,7 @@ function ac_expand_all$4(gobj, event, kw, src) {
13082
13118
  /************************************************************
13083
13119
  * EV_COLLAPSE_ALL — fold every card but the root.
13084
13120
  ************************************************************/
13085
- function ac_collapse_all$4(gobj, event, kw, src) {
13121
+ function ac_collapse_all$2(gobj, event, kw, src) {
13086
13122
  let priv = gobj.priv;
13087
13123
  let json_data = gobj_read_attr(gobj, "json_data");
13088
13124
  if (!json_data) return 0;
@@ -13382,12 +13418,12 @@ function create_gclass$13(gclass_name) {
13382
13418
  ],
13383
13419
  [
13384
13420
  "EV_EXPAND_ALL",
13385
- ac_expand_all$4,
13421
+ ac_expand_all$2,
13386
13422
  null
13387
13423
  ],
13388
13424
  [
13389
13425
  "EV_COLLAPSE_ALL",
13390
- ac_collapse_all$4,
13426
+ ac_collapse_all$2,
13391
13427
  null
13392
13428
  ],
13393
13429
  [
@@ -14459,7 +14495,7 @@ function ac_search$2(gobj, event, kw, src) {
14459
14495
  /************************************************************
14460
14496
  * EV_EXPAND_ALL — expand every already-loaded container
14461
14497
  ************************************************************/
14462
- function ac_expand_all$3(gobj, event, kw, src) {
14498
+ function ac_expand_all$1(gobj, event, kw, src) {
14463
14499
  let priv = gobj.priv;
14464
14500
  if (priv.root === null || priv.root === void 0) return 0;
14465
14501
  let paths = [];
@@ -14477,7 +14513,7 @@ function ac_expand_all$3(gobj, event, kw, src) {
14477
14513
  /************************************************************
14478
14514
  * EV_COLLAPSE_ALL
14479
14515
  ************************************************************/
14480
- function ac_collapse_all$3(gobj, event, kw, src) {
14516
+ function ac_collapse_all$1(gobj, event, kw, src) {
14481
14517
  gobj.priv.expanded.clear();
14482
14518
  render_view$1(gobj);
14483
14519
  return 0;
@@ -14662,12 +14698,12 @@ function create_gclass$12(gclass_name) {
14662
14698
  ],
14663
14699
  [
14664
14700
  "EV_EXPAND_ALL",
14665
- ac_expand_all$3,
14701
+ ac_expand_all$1,
14666
14702
  null
14667
14703
  ],
14668
14704
  [
14669
14705
  "EV_COLLAPSE_ALL",
14670
- ac_collapse_all$3,
14706
+ ac_collapse_all$1,
14671
14707
  null
14672
14708
  ],
14673
14709
  [
@@ -19689,7 +19725,7 @@ function ac_toggle_collapse(gobj, event, kw, src) {
19689
19725
  /************************************************************
19690
19726
  * Force every node visible (overrides threshold).
19691
19727
  ************************************************************/
19692
- function ac_expand_all$2(gobj, event, kw, src) {
19728
+ function ac_expand_all(gobj, event, kw, src) {
19693
19729
  let priv = gobj.priv;
19694
19730
  if (!priv.collapse_state) priv.collapse_state = {};
19695
19731
  if (priv.node_by_id) for (let id of Object.keys(priv.node_by_id)) {
@@ -19702,7 +19738,7 @@ function ac_expand_all$2(gobj, event, kw, src) {
19702
19738
  /************************************************************
19703
19739
  * Collapse every node that has children.
19704
19740
  ************************************************************/
19705
- function ac_collapse_all$2(gobj, event, kw, src) {
19741
+ function ac_collapse_all(gobj, event, kw, src) {
19706
19742
  let priv = gobj.priv;
19707
19743
  if (!priv.collapse_state) priv.collapse_state = {};
19708
19744
  if (priv.node_by_id) for (let id of Object.keys(priv.node_by_id)) {
@@ -20054,12 +20090,12 @@ function create_gclass$9(gclass_name) {
20054
20090
  ],
20055
20091
  [
20056
20092
  "EV_EXPAND_ALL",
20057
- ac_expand_all$2,
20093
+ ac_expand_all,
20058
20094
  null
20059
20095
  ],
20060
20096
  [
20061
20097
  "EV_COLLAPSE_ALL",
20062
- ac_collapse_all$2,
20098
+ ac_collapse_all,
20063
20099
  null
20064
20100
  ],
20065
20101
  [
@@ -21903,15 +21939,12 @@ function cmd_get_topic_data$1(gobj, cmd, kw, src) {
21903
21939
  ************************************************************/
21904
21940
  function build_ui$6(gobj) {
21905
21941
  let source_url = gobj_read_str_attr(gobj, "source_url") || "";
21906
- let routes = gobj_read_attr(gobj, "card_action_routes");
21907
- let graph_tpl = is_object(routes) && typeof routes.graph === "string" ? routes.graph : "";
21908
- let graph_href = /\/\{topic\}$/.test(graph_tpl) ? graph_tpl.replace(/\/\{topic\}$/, "") : "";
21942
+ let graph_href = graph_route_of(gobj);
21909
21943
  let graph_items = [];
21910
21944
  if (graph_href) graph_items.push([
21911
- "a",
21945
+ "button",
21912
21946
  {
21913
21947
  class: "button TREEDB_GRAPH_BTN",
21914
- href: graph_href,
21915
21948
  style: "margin-left:auto;",
21916
21949
  title: t("graph"),
21917
21950
  "aria-label": t("graph"),
@@ -21926,7 +21959,11 @@ function build_ui$6(gobj) {
21926
21959
  "span",
21927
21960
  { i18n: "graph" },
21928
21961
  "graph"
21929
- ]]
21962
+ ]],
21963
+ { click: (evt) => {
21964
+ evt.stopPropagation();
21965
+ gobj_send_event(gobj, "EV_OPEN_GRAPH", {}, gobj);
21966
+ } }
21930
21967
  ]);
21931
21968
  let $container = createElement2([
21932
21969
  "div",
@@ -22087,6 +22124,17 @@ function build_ui$6(gobj) {
22087
22124
  if (gobj_read_bool_attr(gobj, "with_cards_landing")) show_topics_landing(gobj);
22088
22125
  }
22089
22126
  /************************************************************
22127
+ * The graph's route with no topic: the host's card template
22128
+ * `card_action_routes.graph` without its trailing `/{topic}`,
22129
+ * as a ROUTE (no `#`). "" when the host gives no such template.
22130
+ ************************************************************/
22131
+ function graph_route_of(gobj) {
22132
+ let routes = gobj_read_attr(gobj, "card_action_routes");
22133
+ let tpl = is_object(routes) && typeof routes.graph === "string" ? routes.graph : "";
22134
+ if (!/\/\{topic\}$/.test(tpl)) return "";
22135
+ return tpl.replace(/\/\{topic\}$/, "").replace(/^#/, "");
22136
+ }
22137
+ /************************************************************
22090
22138
  * Is the backend session live? The Raw JSON viewer issues a remote
22091
22139
  * print-tranger, so it only makes sense with a session — the remote
22092
22140
  * (C_IEVENT_CLI) is in ST_SESSION exactly while connected.
@@ -23455,6 +23503,26 @@ function ac_open_json$1(gobj, event, kw, src) {
23455
23503
  return 0;
23456
23504
  }
23457
23505
  /********************************************
23506
+ * Open the graph where the reader left it: its last visited
23507
+ * route under the graph's base -- `<graph>/<topic>` when a topic
23508
+ * was focused there -- or the bare base when the graph was never
23509
+ * opened in this page. A user move: pushed, so Back returns.
23510
+ ********************************************/
23511
+ function ac_open_graph(gobj, event, kw, src) {
23512
+ let base = graph_route_of(gobj);
23513
+ if (!base) {
23514
+ log_error(`${gobj_short_name(gobj)}: open graph with no graph route`);
23515
+ return -1;
23516
+ }
23517
+ let shell = yui_shell_of(gobj);
23518
+ if (!shell) {
23519
+ log_error(`${gobj_short_name(gobj)}: open graph with no shell`);
23520
+ return -1;
23521
+ }
23522
+ yui_shell_navigate(shell, yui_shell_last_route_under(shell, base), { push: true });
23523
+ return 0;
23524
+ }
23525
+ /********************************************
23458
23526
  * The viewer asked to load a collapsed subtree (tranger mode only):
23459
23527
  * re-issue print-tranger for that path.
23460
23528
  ********************************************/
@@ -23553,6 +23621,11 @@ function create_gclass$6(gclass_name) {
23553
23621
  ac_open_json$1,
23554
23622
  null
23555
23623
  ],
23624
+ [
23625
+ "EV_OPEN_GRAPH",
23626
+ ac_open_graph,
23627
+ null
23628
+ ],
23556
23629
  [
23557
23630
  "EV_EXPAND_PATH",
23558
23631
  ac_json_expand_path$1,
@@ -23617,6 +23690,7 @@ function create_gclass$6(gclass_name) {
23617
23690
  ["EV_RECORD_WRITTEN", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
23618
23691
  ["EV_REFRESH_TOPIC", 0],
23619
23692
  ["EV_OPEN_JSON", 0],
23693
+ ["EV_OPEN_GRAPH", 0],
23620
23694
  ["EV_EXPAND_PATH", 0],
23621
23695
  ["EV_JSON_CLOSED", 0],
23622
23696
  ["EV_SHOW", 0],
@@ -27660,7 +27734,7 @@ function fold_build_model(descs, records, opts) {
27660
27734
  for (let k of reach_from(nodes, [node.key])) reached.add(k);
27661
27735
  }
27662
27736
  for (let topic_name of topics) if (roots[topic_name].length > 0) groups.set(fold_root_group_key(topic_name), roots[topic_name]);
27663
- return {
27737
+ let model = {
27664
27738
  nodes,
27665
27739
  groups,
27666
27740
  topics,
@@ -27669,8 +27743,52 @@ function fold_build_model(descs, records, opts) {
27669
27743
  main_topic,
27670
27744
  linked,
27671
27745
  loose,
27672
- loose_shown
27746
+ loose_shown,
27747
+ level_of: null,
27748
+ levels: 0
27673
27749
  };
27750
+ count_levels(model);
27751
+ return model;
27752
+ }
27753
+ /************************************************************
27754
+ * The LEVELS of the main tree: the roots of the main topic --
27755
+ * and of the topics hung from it, whose roots are the loose
27756
+ * records the reader asked for -- are level 1, their children
27757
+ * level 2, and so on down every hook of every topic that is not
27758
+ * hidden. A node reached by two paths takes the shallowest.
27759
+ *
27760
+ * With no main topic every topic is a tree of its own and all
27761
+ * of them count. A topic the schema does not tie to the main
27762
+ * one is a separate tree, and has no level: the stepper of the
27763
+ * toolbar speaks for the main tree and nothing else, those
27764
+ * trees are opened by their own pills.
27765
+ ************************************************************/
27766
+ function count_levels(model) {
27767
+ let level_of = /* @__PURE__ */ new Map();
27768
+ let queue = [];
27769
+ let deepest = 0;
27770
+ for (let topic_name of model.topics) {
27771
+ if (model.main_topic && !model.linked.has(topic_name)) continue;
27772
+ let roots = model.groups.get(fold_root_group_key(topic_name)) || [];
27773
+ for (let key of roots) if (!level_of.has(key)) {
27774
+ level_of.set(key, 1);
27775
+ queue.push(key);
27776
+ }
27777
+ }
27778
+ let head = 0;
27779
+ while (head < queue.length) {
27780
+ let key = queue[head++];
27781
+ let lv = level_of.get(key);
27782
+ if (lv > deepest) deepest = lv;
27783
+ let node = model.nodes.get(key);
27784
+ if (!node) continue;
27785
+ for (let hook of Object.keys(node.hooks)) for (let child of node.hooks[hook]) if (!level_of.has(child)) {
27786
+ level_of.set(child, lv + 1);
27787
+ queue.push(child);
27788
+ }
27789
+ }
27790
+ model.level_of = level_of;
27791
+ model.levels = deepest;
27674
27792
  }
27675
27793
  /************************************************************
27676
27794
  * The topic the schema hangs the others from: the one whose hooks
@@ -27796,14 +27914,6 @@ function fold_show_more(model, state, group_key) {
27796
27914
  return n;
27797
27915
  }
27798
27916
  /************************************************************
27799
- * Every group open on ALL its children. This is the reader
27800
- * asking for the whole thing; the pages are for the other way in.
27801
- ************************************************************/
27802
- function fold_expand_all(model, state) {
27803
- state.shown.clear();
27804
- for (let [group_key, list] of model.groups) if (list.length > 0) state.shown.set(group_key, list.length);
27805
- }
27806
- /************************************************************
27807
27917
  * The roots and nothing else -- the first page of each topic's
27808
27918
  * roots, the way the graph looks before anything is opened.
27809
27919
  ************************************************************/
@@ -27815,40 +27925,51 @@ function fold_collapse_all(model, state) {
27815
27925
  }
27816
27926
  }
27817
27927
  /************************************************************
27818
- * Open the tree down to `depth` levels: the roots are level 1,
27819
- * their children level 2. A node reached by two paths takes the
27820
- * shallowest. Each group opens on its first page. `depth` of 1
27821
- * is the roots alone; anything below that is treated as 1.
27928
+ * The toolbar's STEPPER over the levels of the main tree (see
27929
+ * count_levels): level N shows the roots and N-1 levels under
27930
+ * them. A level is a floor of what is open, not a picture that
27931
+ * replaces it:
27932
+ *
27933
+ * opening to N opens, on its first page, every hook of
27934
+ * every node above level N that is folded;
27935
+ * what the reader opened, and every page
27936
+ * already shown, stays
27937
+ * closing to N folds every hook of every node at level N
27938
+ * or below, and touches nothing above it
27939
+ *
27940
+ * A separate tree (a topic not tied to the main one) has no
27941
+ * level, so neither ever touches it.
27822
27942
  ************************************************************/
27823
- function fold_expand_to_depth(model, state, depth) {
27824
- fold_collapse_all(model, state);
27825
- if (!(depth > 1)) return;
27826
- let level = /* @__PURE__ */ new Map();
27827
- let queue = [];
27828
- for (let topic_name of model.topics) {
27829
- let roots = model.groups.get(fold_root_group_key(topic_name)) || [];
27830
- for (let key of roots) if (!level.has(key)) {
27831
- level.set(key, 1);
27832
- queue.push(key);
27833
- }
27834
- }
27835
- while (queue.length) {
27836
- let key = queue.shift();
27837
- let lv = level.get(key);
27838
- if (lv >= depth) continue;
27943
+ function fold_level_clamp(model, level) {
27944
+ let top = model.levels > 0 ? model.levels : 1;
27945
+ level = Math.floor(Number(level)) || 1;
27946
+ if (level < 1) return 1;
27947
+ if (level > top) return top;
27948
+ return level;
27949
+ }
27950
+ function fold_open_levels(model, state, level) {
27951
+ for (let [key, lv] of model.level_of) {
27952
+ if (lv >= level) continue;
27839
27953
  let node = model.nodes.get(key);
27840
27954
  if (!node) continue;
27841
27955
  for (let hook of Object.keys(node.hooks)) {
27842
- let children = node.hooks[hook];
27843
- if (children.length === 0) continue;
27844
- state.shown.set(fold_group_key(key, hook), state.page_size);
27845
- for (let child of children) if (!level.has(child)) {
27846
- level.set(child, lv + 1);
27847
- queue.push(child);
27848
- }
27956
+ let group_key = fold_group_key(key, hook);
27957
+ if (node.hooks[hook].length > 0 && !fold_is_open(state, group_key)) state.shown.set(group_key, state.page_size);
27849
27958
  }
27850
27959
  }
27851
27960
  }
27961
+ function fold_close_levels(model, state, level) {
27962
+ for (let [key, lv] of model.level_of) {
27963
+ if (lv < level) continue;
27964
+ let node = model.nodes.get(key);
27965
+ if (!node) continue;
27966
+ for (let hook of Object.keys(node.hooks)) state.shown.delete(fold_group_key(key, hook));
27967
+ }
27968
+ }
27969
+ function fold_expand_to_level(model, state, level) {
27970
+ fold_collapse_all(model, state);
27971
+ fold_open_levels(model, state, fold_level_clamp(model, level));
27972
+ }
27852
27973
  /************************************************************
27853
27974
  * The set of node keys on screen, from the roots down through
27854
27975
  * every open group -- and only through open groups whose parent
@@ -55091,8 +55212,9 @@ function ensure_brush_select_owned() {
55091
55212
  * The graph opens FOLDED, like a JSON viewer.
55092
55213
  *
55093
55214
  * Every record of every topic is fetched, but only the ones on
55094
- * screen become G6 nodes: the roots, `expand_depth` levels under
55095
- * them, one page (`fold_page_size`) of children per hook. Each
55215
+ * screen become G6 nodes: the roots, and the MAIN tree open to
55216
+ * the reader's level (`fold_level`, `expand_depth` until they
55217
+ * step it), one page (`fold_page_size`) of children per hook. Each
55096
55218
  * card carries a pill per hook that has children -- `▸ devices
55097
55219
  * 142` -- which opens or folds that hook; an open hook with more
55098
55220
  * children than its page ends in a `+N` chip that opens the next
@@ -55270,7 +55392,8 @@ var attrs_table$4 = [
55270
55392
  SDATA(data_type_t.DTP_LIST, "layout_names", sdata_flag_t.SDF_RD, JSON.stringify(Object.keys(_layouts)), "Available layout names (read-only, for parent to query)"),
55271
55393
  SDATA(data_type_t.DTP_STRING, "hook_port_position", 0, "bottom", "Hook port position"),
55272
55394
  SDATA(data_type_t.DTP_STRING, "fkey_port_position", 0, "top", "Fkey port position"),
55273
- SDATA(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"),
55395
+ SDATA(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"),
55396
+ SDATA(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"),
55274
55397
  SDATA(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"),
55275
55398
  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"),
55276
55399
  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"),
@@ -55362,6 +55485,7 @@ var PRIVATE_DATA$4 = {
55362
55485
  _nodes_total: 0,
55363
55486
  _nodes_placed: 0,
55364
55487
  expand_depth: 2,
55488
+ fold_level: 0,
55365
55489
  fold_page_size: 24,
55366
55490
  hidden_topics: null,
55367
55491
  main_topic: "",
@@ -61246,7 +61370,12 @@ function publish_legend_state(gobj, visible) {
61246
61370
  gobj_publish_event(gobj, "EV_LEGEND_STATE", {
61247
61371
  main_topic: model.main_topic,
61248
61372
  main_chosen: !!gobj_read_str_attr(gobj, "main_topic"),
61249
- topics
61373
+ topics,
61374
+ fold: {
61375
+ topic: model.main_topic,
61376
+ level: current_fold_level(gobj),
61377
+ levels: model.levels
61378
+ }
61250
61379
  });
61251
61380
  }
61252
61381
  async function reconcile_fold_apply(gobj, opts, visible) {
@@ -61551,7 +61680,7 @@ function ac_load_data(gobj, event, kw, src) {
61551
61680
  }
61552
61681
  if (all_loaded && priv.graph) {
61553
61682
  rebuild_fold_model(gobj);
61554
- fold_expand_to_depth(priv._fold_model, priv._fold_state, gobj_read_integer_attr(gobj, "expand_depth"));
61683
+ fold_expand_to_level(priv._fold_model, priv._fold_state, current_fold_level(gobj));
61555
61684
  count_placed_records(gobj);
61556
61685
  let auto_laid = false;
61557
61686
  try {
@@ -61955,19 +62084,46 @@ function ac_show_more(gobj, event, kw, src) {
61955
62084
  return 0;
61956
62085
  }
61957
62086
  /************************************************************
61958
- * The whole treedb, every hook open on all its children. This
61959
- * is the reader asking for the pile on purpose; the pages are
61960
- * the other way in.
62087
+ * The level of the main tree the toolbar's stepper stands on:
62088
+ * `fold_level` once the reader has stepped, `expand_depth`
62089
+ * until then, clamped to the levels the tree has.
61961
62090
  ************************************************************/
61962
- function ac_expand_all$1(gobj, event, kw, src) {
62091
+ function current_fold_level(gobj) {
61963
62092
  let priv = gobj.priv;
61964
- if (!priv._fold_model) {
61965
- log_error(`${gobj_short_name(gobj)}: expand all with no tree loaded`);
62093
+ let level = gobj_read_integer_attr(gobj, "fold_level");
62094
+ if (!(level > 0)) level = gobj_read_integer_attr(gobj, "expand_depth");
62095
+ if (!priv._fold_model) return level;
62096
+ return fold_level_clamp(priv._fold_model, level);
62097
+ }
62098
+ /************************************************************
62099
+ * The toolbar's stepper: one level more or one less of the
62100
+ * MAIN tree (the host owns the number and persists it; this
62101
+ * applies it). Deeper opens the hooks above the new level,
62102
+ * shallower folds from it down -- a level is a floor, not a
62103
+ * picture, so what the reader opened by its pill stays (see
62104
+ * treedb_fold_model). Replaces expand all / collapse all
62105
+ * (7.23.141): the whole treedb at once was the pile, and
62106
+ * closing threw away everything the reader had opened.
62107
+ ************************************************************/
62108
+ function ac_set_fold_level(gobj, event, kw, src) {
62109
+ let priv = gobj.priv;
62110
+ let level = Number(kw && kw.level);
62111
+ if (!(level > 0)) {
62112
+ log_error(`${gobj_short_name(gobj)}: fold level '${kw && kw.level}' is not a level`);
61966
62113
  return -1;
61967
62114
  }
62115
+ if (!priv._fold_model) {
62116
+ gobj_write_attr(gobj, "fold_level", level);
62117
+ return 0;
62118
+ }
62119
+ let from = current_fold_level(gobj);
62120
+ level = fold_level_clamp(priv._fold_model, level);
62121
+ gobj_write_attr(gobj, "fold_level", level);
62122
+ if (level === from) return 0;
61968
62123
  let keep = fold_keep_node(gobj);
61969
62124
  let vp = yui_graph_viewport_of(priv.graph, keep);
61970
- fold_expand_all(priv._fold_model, priv._fold_state);
62125
+ if (level > from) fold_open_levels(priv._fold_model, priv._fold_state, level);
62126
+ else fold_close_levels(priv._fold_model, priv._fold_state, level);
61971
62127
  reconcile_fold(gobj, {
61972
62128
  keep,
61973
62129
  keep_vp: vp
@@ -61995,29 +62151,11 @@ function fold_keep_node(gobj) {
61995
62151
  return "";
61996
62152
  }
61997
62153
  /************************************************************
61998
- * The roots alone.
61999
- ************************************************************/
62000
- function ac_collapse_all$1(gobj, event, kw, src) {
62001
- let priv = gobj.priv;
62002
- if (!priv._fold_model) {
62003
- log_error(`${gobj_short_name(gobj)}: collapse all with no tree loaded`);
62004
- return -1;
62005
- }
62006
- let keep = fold_keep_node(gobj);
62007
- let vp = yui_graph_viewport_of(priv.graph, keep);
62008
- fold_collapse_all(priv._fold_model, priv._fold_state);
62009
- reconcile_fold(gobj, {
62010
- keep,
62011
- keep_vp: vp
62012
- });
62013
- return 0;
62014
- }
62015
- /************************************************************
62016
62154
  * The legend's three settings, from the host, which persists
62017
62155
  * them. Each rebuilds the tree; the fold state is kept -- it is
62018
62156
  * keyed by group, and a group that is gone is never read -- except
62019
62157
  * for a new MAIN topic, whose roots are other roots: the tree
62020
- * opens again at `expand_depth`.
62158
+ * opens again at the reader's level, now counted from them.
62021
62159
  ************************************************************/
62022
62160
  function ac_set_hidden_topics(gobj, event, kw, src) {
62023
62161
  let priv = gobj.priv;
@@ -62035,7 +62173,7 @@ function ac_set_main_topic(gobj, event, kw, src) {
62035
62173
  let keep = fold_keep_node(gobj);
62036
62174
  let vp = yui_graph_viewport_of(priv.graph, keep);
62037
62175
  rebuild_fold_model(gobj);
62038
- fold_expand_to_depth(priv._fold_model, priv._fold_state, gobj_read_integer_attr(gobj, "expand_depth"));
62176
+ fold_expand_to_level(priv._fold_model, priv._fold_state, current_fold_level(gobj));
62039
62177
  reconcile_fold(gobj, {
62040
62178
  relayout: true,
62041
62179
  keep,
@@ -62596,13 +62734,8 @@ function create_gclass$4(gclass_name) {
62596
62734
  null
62597
62735
  ],
62598
62736
  [
62599
- "EV_EXPAND_ALL",
62600
- ac_expand_all$1,
62601
- null
62602
- ],
62603
- [
62604
- "EV_COLLAPSE_ALL",
62605
- ac_collapse_all$1,
62737
+ "EV_SET_FOLD_LEVEL",
62738
+ ac_set_fold_level,
62606
62739
  null
62607
62740
  ],
62608
62741
  [
@@ -62745,8 +62878,7 @@ function create_gclass$4(gclass_name) {
62745
62878
  ["EV_CAMERA_CHANGED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
62746
62879
  ["EV_TOGGLE_FOLD", 0],
62747
62880
  ["EV_SHOW_MORE", 0],
62748
- ["EV_EXPAND_ALL", 0],
62749
- ["EV_COLLAPSE_ALL", 0],
62881
+ ["EV_SET_FOLD_LEVEL", 0],
62750
62882
  ["EV_SET_HIDDEN_TOPICS", 0],
62751
62883
  ["EV_SET_MAIN_TOPIC", 0],
62752
62884
  ["EV_SET_LOOSE_TOPICS", 0],
@@ -62830,6 +62962,7 @@ var attrs_table$3 = [
62830
62962
  SDATA(data_type_t.DTP_STRING, "node_mode", 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"),
62831
62963
  SDATA(data_type_t.DTP_BOOLEAN, "node_labels", sdata_flag_t.SDF_PERSIST, true, "A `shape` node says its name under its figure. User preference"),
62832
62964
  SDATA(data_type_t.DTP_DICT, "camera", 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"),
62965
+ SDATA(data_type_t.DTP_INTEGER, "fold_level", 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"),
62833
62966
  SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
62834
62967
  SDATA(data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
62835
62968
  SDATA(data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
@@ -62905,6 +63038,7 @@ function mt_create$3(gobj) {
62905
63038
  hook_port_position: "bottom",
62906
63039
  fkey_port_position: "top",
62907
63040
  expand_depth: gobj_read_integer_attr(gobj, "expand_depth"),
63041
+ fold_level: gobj_read_integer_attr(gobj, "fold_level"),
62908
63042
  fold_page_size: gobj_read_integer_attr(gobj, "fold_page_size"),
62909
63043
  minimap_min_nodes: gobj_read_integer_attr(gobj, "minimap_min_nodes"),
62910
63044
  with_gridline: gobj_read_bool_attr(gobj, "with_gridline"),
@@ -63212,6 +63346,127 @@ function refresh_node_shape_buttons(gobj, $root) {
63212
63346
  $labels.disabled = mode !== "shape";
63213
63347
  }
63214
63348
  }
63349
+ /************************************************************
63350
+ * The fold STEPPER: one level less, where the tree stands, one
63351
+ * level more.
63352
+ *
63353
+ * It replaces the fold pair (7.23.141). `expand all` opened every
63354
+ * hook of the treedb -- the pile, on purpose, which on a treedb
63355
+ * of thousands is a pile nobody reads -- and `collapse all` threw
63356
+ * away everything the reader had opened. The stepper walks the
63357
+ * MAIN tree a level at a time, the way the frontend view opens
63358
+ * one level per press: `+` opens the next level under the main
63359
+ * topic's roots, through every topic that is not hidden; `-`
63360
+ * folds the deepest one. Pages and pills the reader opened stay.
63361
+ *
63362
+ * The readout says what the two buttons act on, which no chevron
63363
+ * can: the main topic -- its swatch and its name, which is DATA
63364
+ * and never translated -- and `level/levels`. On a phone the name
63365
+ * goes and the numbers stay. The level is the reader's, per
63366
+ * treedb: persisted (`fold_level`) and handed to the engine on
63367
+ * creation, so a reload opens where it was left.
63368
+ *
63369
+ * Both buttons are born disabled: nothing can be stepped until
63370
+ * the engine has said how deep the tree goes (EV_LEGEND_STATE).
63371
+ ************************************************************/
63372
+ function fold_level_items(gobj, wide) {
63373
+ let step_button = (cls, icon, key, event_name) => {
63374
+ return [
63375
+ "button",
63376
+ {
63377
+ class: `${cls} button`,
63378
+ type: "button",
63379
+ style: {
63380
+ height: wide,
63381
+ width: "2.5em"
63382
+ },
63383
+ disabled: true,
63384
+ title: t(key),
63385
+ "data-i18n-title": key,
63386
+ "aria-label": t(key),
63387
+ "data-i18n-aria-label": key
63388
+ },
63389
+ yui_toolbar_icon(icon),
63390
+ { click: (evt) => {
63391
+ evt.stopPropagation();
63392
+ gobj_send_event(gobj, event_name, {}, gobj);
63393
+ } }
63394
+ ];
63395
+ };
63396
+ return [[
63397
+ "div",
63398
+ {
63399
+ class: "GRAPH_FOLD_STEPPER buttons has-addons",
63400
+ style: "margin:0; flex:0 0 auto; flex-wrap:nowrap; align-items:stretch;"
63401
+ },
63402
+ [
63403
+ step_button("GRAPH_FOLD_COLLAPSE", "yi-chevron-right", "collapse one level", "EV_COLLAPSE_LEVEL"),
63404
+ [
63405
+ "div",
63406
+ {
63407
+ class: "GRAPH_FOLD_LEVEL button",
63408
+ style: {
63409
+ height: wide,
63410
+ cursor: "default",
63411
+ gap: ".35rem"
63412
+ },
63413
+ role: "status",
63414
+ title: t("fold level"),
63415
+ "data-i18n-title": "fold level"
63416
+ },
63417
+ [
63418
+ ["span", {
63419
+ class: "GRAPH_FOLD_SWATCH",
63420
+ style: "display:none; width:.8rem; height:.8rem; border-radius:3px; flex:0 0 auto; border:1px solid rgba(0,0,0,.25);"
63421
+ }],
63422
+ ["span", {
63423
+ class: "GRAPH_FOLD_TOPIC is-hidden-mobile",
63424
+ style: "max-width:10em; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:600;"
63425
+ }],
63426
+ [
63427
+ "span",
63428
+ {
63429
+ class: "GRAPH_FOLD_WORD",
63430
+ i18n: "level"
63431
+ },
63432
+ "level"
63433
+ ],
63434
+ [
63435
+ "span",
63436
+ {
63437
+ class: "GRAPH_FOLD_COUNT",
63438
+ style: "font-variant-numeric:tabular-nums;"
63439
+ },
63440
+ "–"
63441
+ ]
63442
+ ]
63443
+ ],
63444
+ step_button("GRAPH_FOLD_EXPAND", "yi-chevron-down", "expand one level", "EV_EXPAND_LEVEL")
63445
+ ]
63446
+ ]];
63447
+ }
63448
+ function refresh_fold_level(gobj) {
63449
+ let priv = gobj.priv;
63450
+ let $container = gobj_read_attr(gobj, "$container");
63451
+ if (!$container) return;
63452
+ let $readout = $container.querySelector(".GRAPH_FOLD_LEVEL");
63453
+ if (!$readout) return;
63454
+ let state = priv._legend_state || {};
63455
+ let fold = state.fold || {};
63456
+ let levels = fold.levels > 0 ? fold.levels : 0;
63457
+ let level = fold.level > 0 ? fold.level : 0;
63458
+ let topic = fold.topic || "";
63459
+ let entry = (is_array(state.topics) ? state.topics : []).find((e) => e.topic === topic);
63460
+ let $swatch = $readout.querySelector(".GRAPH_FOLD_SWATCH");
63461
+ $swatch.style.display = topic ? "inline-block" : "none";
63462
+ $swatch.style.background = entry && entry.color || "#94a3b8";
63463
+ $readout.querySelector(".GRAPH_FOLD_TOPIC").textContent = topic;
63464
+ $readout.querySelector(".GRAPH_FOLD_COUNT").textContent = levels ? `${level}/${levels}` : "–";
63465
+ let $collapse = $container.querySelector(".GRAPH_FOLD_COLLAPSE");
63466
+ let $expand = $container.querySelector(".GRAPH_FOLD_EXPAND");
63467
+ if ($collapse) $collapse.disabled = !(levels && level > 1);
63468
+ if ($expand) $expand.disabled = !(levels && level < levels);
63469
+ }
63215
63470
  function make_toolbar(gobj) {
63216
63471
  let priv = gobj.priv;
63217
63472
  let modes = gobj_read_attr(gobj, "operation_modes");
@@ -63278,7 +63533,7 @@ function make_toolbar(gobj) {
63278
63533
  } }
63279
63534
  ]
63280
63535
  ];
63281
- let center_items = [...yui_graph_fold_items(gobj, gobj_read_str_attr(gobj, "wide")), ...node_shape_items(gobj, gobj_read_str_attr(gobj, "wide"))];
63536
+ let center_items = [...fold_level_items(gobj, gobj_read_str_attr(gobj, "wide")), ...node_shape_items(gobj, gobj_read_str_attr(gobj, "wide"))];
63282
63537
  let backend_items = [[
63283
63538
  "button",
63284
63539
  {
@@ -64534,6 +64789,7 @@ function ac_legend_state(gobj, event, kw, src) {
64534
64789
  let priv = gobj.priv;
64535
64790
  priv._legend_state = kw || null;
64536
64791
  refresh_legend(gobj);
64792
+ refresh_fold_level(gobj);
64537
64793
  return 0;
64538
64794
  }
64539
64795
  /************************************************************
@@ -64646,17 +64902,33 @@ function ac_find_nodes(gobj, event, kw, src) {
64646
64902
  return 0;
64647
64903
  }
64648
64904
  /************************************************************
64649
- * The fold pair of the toolbar, forwarded to the G6 child,
64650
- * which owns the tree.
64905
+ * One press of the fold stepper (EV_EXPAND_LEVEL /
64906
+ * EV_COLLAPSE_LEVEL): the level moves by one, is SAVED (the
64907
+ * reader's, per treedb) and reaches the G6 child, which owns
64908
+ * the tree.
64909
+ *
64910
+ * The readout moves at once, not when the engine speaks again:
64911
+ * a reconcile is asynchronous, and a second press that came
64912
+ * before it would step from the level already left -- two
64913
+ * presses, one level.
64651
64914
  ************************************************************/
64652
- function ac_expand_all(gobj, event, kw, src) {
64915
+ function ac_fold_level_step(gobj, event, kw, src) {
64653
64916
  let priv = gobj.priv;
64654
- if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_EXPAND_ALL", {}, gobj);
64655
- return 0;
64656
- }
64657
- function ac_collapse_all(gobj, event, kw, src) {
64658
- let priv = gobj.priv;
64659
- if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_COLLAPSE_ALL", {}, gobj);
64917
+ let fold = priv._legend_state && priv._legend_state.fold;
64918
+ if (!fold || !(fold.levels > 0)) {
64919
+ log_error(`${gobj_short_name(gobj)}: ${event} with no tree to step through`);
64920
+ return -1;
64921
+ }
64922
+ let level = fold.level + (event === "EV_EXPAND_LEVEL" ? 1 : -1);
64923
+ if (level < 1 || level > fold.levels) {
64924
+ log_error(`${gobj_short_name(gobj)}: ${event}: level ${level} out of 1..${fold.levels}`);
64925
+ return -1;
64926
+ }
64927
+ gobj_write_attr(gobj, "fold_level", level);
64928
+ gobj_save_persistent_attrs(gobj, "fold_level");
64929
+ fold.level = level;
64930
+ refresh_fold_level(gobj);
64931
+ if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_SET_FOLD_LEVEL", { level }, gobj);
64660
64932
  return 0;
64661
64933
  }
64662
64934
  /************************************************************
@@ -64893,13 +65165,13 @@ function create_gclass$3(gclass_name) {
64893
65165
  null
64894
65166
  ],
64895
65167
  [
64896
- "EV_EXPAND_ALL",
64897
- ac_expand_all,
65168
+ "EV_EXPAND_LEVEL",
65169
+ ac_fold_level_step,
64898
65170
  null
64899
65171
  ],
64900
65172
  [
64901
- "EV_COLLAPSE_ALL",
64902
- ac_collapse_all,
65173
+ "EV_COLLAPSE_LEVEL",
65174
+ ac_fold_level_step,
64903
65175
  null
64904
65176
  ],
64905
65177
  [
@@ -64984,8 +65256,8 @@ function create_gclass$3(gclass_name) {
64984
65256
  ["EV_SET_OPERATION_MODE", 0],
64985
65257
  ["EV_SET_FOCUS_TOPIC", 0],
64986
65258
  ["EV_FIND_NODES", 0],
64987
- ["EV_EXPAND_ALL", 0],
64988
- ["EV_COLLAPSE_ALL", 0],
65259
+ ["EV_EXPAND_LEVEL", 0],
65260
+ ["EV_COLLAPSE_LEVEL", 0],
64989
65261
  ["EV_SET_NODE_MODE", 0],
64990
65262
  ["EV_TOGGLE_NODE_LABELS", 0],
64991
65263
  ["EV_FIND_RESULT", 0],