@yuneta/gobj-ui 7.23.66 → 7.23.68

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.66",
3
+ "version": "7.23.68",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -341,6 +341,9 @@ SDATA(data_type_t.DTP_STRING, "fkey_port_position", 0, "top", "Fkey po
341
341
  /*---------------- Folding ----------------*/
342
342
  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"),
343
343
  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"),
344
+ 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"),
345
+ 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"),
346
+ SDATA(data_type_t.DTP_LIST, "loose_topics", 0, "[]", "Topics whose loose records (no parent, in a topic the schema hangs from the main one) are shown as roots"),
344
347
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
345
348
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
346
349
 
@@ -420,6 +423,8 @@ let PRIVATE_DATA = {
420
423
  // itself; this is what is on screen, and
421
424
  // it exists to diff the repaint)
422
425
  _pending_focus_topic: null, // focus requested before data was loaded
426
+ _pending_focus_all: false, // ...and whether it reveals the whole topic
427
+ _find_hidden_matches: 0, // matches of the last find in HIDDEN topics
423
428
  _pending_find: null, // find requested before data was loaded
424
429
  _layout_asked: "", // layout the host asked for at create (see mt_create)
425
430
  _nodes_total: 0, // records of the treedb (auto_layout)
@@ -428,6 +433,9 @@ let PRIVATE_DATA = {
428
433
  /*---------------- folding ----------------*/
429
434
  expand_depth: 2,
430
435
  fold_page_size: 24,
436
+ hidden_topics: null,
437
+ main_topic: "",
438
+ loose_topics: null,
431
439
  _fold_model: null, // treedb_fold_model: the tree of the records
432
440
  _fold_state: null, // ...and which groups of it are open
433
441
  _fold_listener: null, // delegated click on the card pills
@@ -2850,12 +2858,13 @@ async function graph_fit_selection(gobj, ids)
2850
2858
  * once the data loads (see ac_load_data). Guarded end-to-end — a
2851
2859
  * missing G6 API or an unknown topic logs and no-ops, never throws.
2852
2860
  ************************************************************/
2853
- function graph_focus_topic(gobj, topic)
2861
+ function graph_focus_topic(gobj, topic, reveal_all)
2854
2862
  {
2855
2863
  let priv = gobj.priv;
2856
2864
  let graph = priv.graph;
2857
2865
  if(!graph || !priv.graph_rendered || !priv._fold_model) {
2858
2866
  priv._pending_focus_topic = topic;
2867
+ priv._pending_focus_all = !!reveal_all;
2859
2868
  return;
2860
2869
  }
2861
2870
 
@@ -2863,11 +2872,13 @@ function graph_focus_topic(gobj, topic)
2863
2872
  let state = priv._fold_state;
2864
2873
 
2865
2874
  /* "Show me these": the ones on screen are highlighted, and the
2866
- * hidden ones are opened up to -- ONE PAGE of them, the same cap
2867
- * the find has. The graph's per-topic route lands here on every
2868
- * load (`.../graph/devices` is a focus on `devices`), and
2875
+ * hidden ones are opened up to. How many depends on who asks.
2876
+ * A click on the LEGEND (`reveal_all`) means every record of the
2877
+ * topic, however deep it is folded away. The graph's per-topic
2878
+ * ROUTE lands here on every load (`.../graph/devices` is a focus
2879
+ * on `devices`) and gets ONE PAGE, the same cap the find has:
2869
2880
  * revealing a whole topic on landing opened 563 groups of a
2870
- * 6400-record treedb: the pile, back, before anybody touched
2881
+ * 6400-record treedb -- the pile, back, before anybody touched
2871
2882
  * anything. */
2872
2883
  let keys = [];
2873
2884
  if(!empty_string(topic)) {
@@ -2885,7 +2896,7 @@ function graph_focus_topic(gobj, topic)
2885
2896
  if(visible.has(key)) {
2886
2897
  continue;
2887
2898
  }
2888
- if(revealed >= state.page_size) {
2899
+ if(!reveal_all && revealed >= state.page_size) {
2889
2900
  break;
2890
2901
  }
2891
2902
  fold_reveal(model, state, key);
@@ -2988,27 +2999,40 @@ function graph_find_nodes(gobj, term)
2988
2999
  * folded away, and a find that only read what was on screen would
2989
3000
  * answer "nothing" for a device three levels down. */
2990
3001
  let keys = [];
3002
+ let hidden_matches = 0;
2991
3003
  if(!empty_string(term)) {
2992
3004
  let needle = String(term).toLowerCase();
2993
- for(let node of model.nodes.values()) {
2994
- let record = node.record || {};
3005
+ let matches = (topic_name, record) => {
2995
3006
  let label = "";
2996
3007
  try {
2997
- label = node_label(priv.descs[node.topic_name] || {}, record) || "";
3008
+ label = node_label(priv.descs[topic_name] || {}, record) || "";
2998
3009
  } catch(e) {
2999
3010
  label = "";
3000
3011
  }
3001
3012
  let haystack = [
3002
3013
  label,
3003
3014
  record.id,
3004
- node.topic_name
3015
+ topic_name
3005
3016
  ].filter((v) => typeof v === "string").join(" ").toLowerCase();
3006
-
3007
- if(haystack.includes(needle)) {
3017
+ return haystack.includes(needle);
3018
+ };
3019
+ for(let node of model.nodes.values()) {
3020
+ if(matches(node.topic_name, node.record || {})) {
3008
3021
  keys.push(node.key);
3009
3022
  }
3010
3023
  }
3024
+ /* A hidden topic is searched too, and COUNTED apart: a term
3025
+ * that only matches there would otherwise answer "0", which
3026
+ * reads as "does not exist" when it means "is hidden". */
3027
+ for(let topic_name of model.hidden) {
3028
+ for(let record of (priv.records[topic_name] || [])) {
3029
+ if(is_object(record) && matches(topic_name, record)) {
3030
+ hidden_matches++;
3031
+ }
3032
+ }
3033
+ }
3011
3034
  }
3035
+ priv._find_hidden_matches = hidden_matches;
3012
3036
 
3013
3037
  /* Hidden matches are opened up to, one page of them at most: a
3014
3038
  * single letter matches half the treedb, and unfolding half the
@@ -7532,7 +7556,11 @@ function rebuild_fold_model(gobj)
7532
7556
  {
7533
7557
  let priv = gobj.priv;
7534
7558
 
7535
- priv._fold_model = fold_build_model(priv.descs || {}, priv.records || {});
7559
+ priv._fold_model = fold_build_model(priv.descs || {}, priv.records || {}, {
7560
+ hidden_topics: gobj_read_attr(gobj, "hidden_topics") || [],
7561
+ main_topic: gobj_read_str_attr(gobj, "main_topic") || "",
7562
+ loose_topics: gobj_read_attr(gobj, "loose_topics") || [],
7563
+ });
7536
7564
  if(!priv._fold_state) {
7537
7565
  priv._fold_state = fold_new_state(gobj_read_integer_attr(gobj, "fold_page_size"));
7538
7566
  }
@@ -7618,9 +7646,60 @@ async function reconcile_fold_now(gobj, opts)
7618
7646
  } finally {
7619
7647
  history_resume(gobj);
7620
7648
  }
7649
+ publish_legend_state(gobj, visible);
7621
7650
  return changed;
7622
7651
  }
7623
7652
 
7653
+ /************************************************************
7654
+ * What the legend has to say, published after every reconcile:
7655
+ * one entry per topic of the treedb, hidden ones included, with
7656
+ * its colour, how many records it has, how many are on screen,
7657
+ * how many are LOOSE, and the flags -- plus which topic is the
7658
+ * main one and whether it was deduced or chosen. The host draws
7659
+ * the strip from this and nothing else; the engine is the truth.
7660
+ ************************************************************/
7661
+ function publish_legend_state(gobj, visible)
7662
+ {
7663
+ let priv = gobj.priv;
7664
+ let model = priv._fold_model;
7665
+
7666
+ if(!model || !priv.descs) {
7667
+ return;
7668
+ }
7669
+
7670
+ let on_screen = {};
7671
+ for(let key of visible) {
7672
+ let node = model.nodes.get(key);
7673
+ if(node) {
7674
+ on_screen[node.topic_name] = (on_screen[node.topic_name] || 0) + 1;
7675
+ }
7676
+ }
7677
+
7678
+ let topics = [];
7679
+ for(const [topic_name, desc] of Object.entries(priv.descs)) {
7680
+ if(topic_name.substring(0, 2) === "__" || !desc) {
7681
+ continue;
7682
+ }
7683
+ let loose = model.loose[topic_name] || [];
7684
+ topics.push({
7685
+ topic: topic_name,
7686
+ color: desc.color || "",
7687
+ total: model.totals[topic_name] || 0,
7688
+ visible: on_screen[topic_name] || 0,
7689
+ loose: loose.length,
7690
+ loose_shown: model.loose_shown.has(topic_name),
7691
+ hidden: model.hidden.has(topic_name),
7692
+ linked: model.linked.has(topic_name),
7693
+ });
7694
+ }
7695
+
7696
+ gobj_publish_event(gobj, "EV_LEGEND_STATE", {
7697
+ main_topic: model.main_topic,
7698
+ main_chosen: !!gobj_read_str_attr(gobj, "main_topic"),
7699
+ topics: topics,
7700
+ });
7701
+ }
7702
+
7624
7703
  async function reconcile_fold_apply(gobj, opts, visible)
7625
7704
  {
7626
7705
  let priv = gobj.priv;
@@ -8025,8 +8104,10 @@ function ac_load_data(gobj, event, kw, src)
8025
8104
  * requested before the data was ready (deep link). */
8026
8105
  if(priv._pending_focus_topic !== null) {
8027
8106
  let ft = priv._pending_focus_topic;
8107
+ let all = priv._pending_focus_all;
8028
8108
  priv._pending_focus_topic = null;
8029
- graph_focus_topic(gobj, ft);
8109
+ priv._pending_focus_all = false;
8110
+ graph_focus_topic(gobj, ft, all);
8030
8111
  }
8031
8112
  if(priv._pending_find !== null) {
8032
8113
  let term = priv._pending_find;
@@ -8526,7 +8607,7 @@ function ac_zoom_selection(gobj, event, kw, src)
8526
8607
 
8527
8608
  function ac_focus_topic(gobj, event, kw, src)
8528
8609
  {
8529
- graph_focus_topic(gobj, kw && kw.topic);
8610
+ graph_focus_topic(gobj, kw && kw.topic, !!(kw && kw.reveal === "all"));
8530
8611
  return 0;
8531
8612
  }
8532
8613
 
@@ -8621,6 +8702,58 @@ function ac_collapse_all(gobj, event, kw, src)
8621
8702
  return 0;
8622
8703
  }
8623
8704
 
8705
+ /************************************************************
8706
+ * The legend's three settings, from the host, which persists
8707
+ * them. Each rebuilds the tree; the fold state is kept -- it is
8708
+ * keyed by group, and a group that is gone is never read -- except
8709
+ * for a new MAIN topic, whose roots are other roots: the tree
8710
+ * opens again at `expand_depth`.
8711
+ ************************************************************/
8712
+ function ac_set_hidden_topics(gobj, event, kw, src)
8713
+ {
8714
+ let priv = gobj.priv;
8715
+ let topics = (kw && is_array(kw.topics))? kw.topics : [];
8716
+
8717
+ gobj_write_attr(gobj, "hidden_topics", topics);
8718
+ if(!priv._fold_model) {
8719
+ return 0; /* applied when the records arrive */
8720
+ }
8721
+ rebuild_fold_model(gobj);
8722
+ reconcile_fold(gobj, {});
8723
+ return 0;
8724
+ }
8725
+
8726
+ function ac_set_main_topic(gobj, event, kw, src)
8727
+ {
8728
+ let priv = gobj.priv;
8729
+
8730
+ gobj_write_str_attr(gobj, "main_topic", (kw && kw.topic) || "");
8731
+ if(!priv._fold_model) {
8732
+ return 0;
8733
+ }
8734
+ rebuild_fold_model(gobj);
8735
+ fold_expand_to_depth(
8736
+ priv._fold_model, priv._fold_state,
8737
+ gobj_read_integer_attr(gobj, "expand_depth")
8738
+ );
8739
+ reconcile_fold(gobj, {relayout: true, fit: true});
8740
+ return 0;
8741
+ }
8742
+
8743
+ function ac_set_loose_topics(gobj, event, kw, src)
8744
+ {
8745
+ let priv = gobj.priv;
8746
+ let topics = (kw && is_array(kw.topics))? kw.topics : [];
8747
+
8748
+ gobj_write_attr(gobj, "loose_topics", topics);
8749
+ if(!priv._fold_model) {
8750
+ return 0;
8751
+ }
8752
+ rebuild_fold_model(gobj);
8753
+ reconcile_fold(gobj, {});
8754
+ return 0;
8755
+ }
8756
+
8624
8757
  /************************************************************
8625
8758
  * Tell whoever asked how many nodes the term matched. The count is
8626
8759
  * the answer to the question the box asks; without it "nothing moved"
@@ -8628,7 +8761,11 @@ function ac_collapse_all(gobj, event, kw, src)
8628
8761
  ************************************************************/
8629
8762
  function publish_find_result(gobj, term, matches)
8630
8763
  {
8631
- gobj_publish_event(gobj, "EV_FIND_RESULT", {term: term, matches: matches});
8764
+ gobj_publish_event(gobj, "EV_FIND_RESULT", {
8765
+ term: term,
8766
+ matches: matches,
8767
+ hidden_matches: gobj.priv._find_hidden_matches || 0,
8768
+ });
8632
8769
  }
8633
8770
 
8634
8771
  /************************************************************
@@ -9178,6 +9315,9 @@ function create_gclass(gclass_name)
9178
9315
  ["EV_SHOW_MORE", ac_show_more, null],
9179
9316
  ["EV_EXPAND_ALL", ac_expand_all, null],
9180
9317
  ["EV_COLLAPSE_ALL", ac_collapse_all, null],
9318
+ ["EV_SET_HIDDEN_TOPICS", ac_set_hidden_topics, null],
9319
+ ["EV_SET_MAIN_TOPIC", ac_set_main_topic, null],
9320
+ ["EV_SET_LOOSE_TOPICS", ac_set_loose_topics, null],
9181
9321
  ["EV_CENTER", ac_center, null],
9182
9322
  ["EV_FULLSCREEN", ac_fullscreen, null],
9183
9323
  ["EV_SET_LAYOUT", ac_set_layout, null],
@@ -9235,6 +9375,10 @@ function create_gclass(gclass_name)
9235
9375
  ["EV_SHOW_MORE", 0],
9236
9376
  ["EV_EXPAND_ALL", 0],
9237
9377
  ["EV_COLLAPSE_ALL", 0],
9378
+ ["EV_SET_HIDDEN_TOPICS", 0],
9379
+ ["EV_SET_MAIN_TOPIC", 0],
9380
+ ["EV_SET_LOOSE_TOPICS", 0],
9381
+ ["EV_LEGEND_STATE", event_flag_t.EVF_OUTPUT_EVENT],
9238
9382
  ["EV_CENTER", 0],
9239
9383
  ["EV_FULLSCREEN", 0],
9240
9384
  ["EV_SET_LAYOUT", 0],