@yuneta/gobj-ui 7.23.67 → 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.67",
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
 
@@ -421,6 +424,7 @@ let PRIVATE_DATA = {
421
424
  // it exists to diff the repaint)
422
425
  _pending_focus_topic: null, // focus requested before data was loaded
423
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
424
428
  _pending_find: null, // find requested before data was loaded
425
429
  _layout_asked: "", // layout the host asked for at create (see mt_create)
426
430
  _nodes_total: 0, // records of the treedb (auto_layout)
@@ -429,6 +433,9 @@ let PRIVATE_DATA = {
429
433
  /*---------------- folding ----------------*/
430
434
  expand_depth: 2,
431
435
  fold_page_size: 24,
436
+ hidden_topics: null,
437
+ main_topic: "",
438
+ loose_topics: null,
432
439
  _fold_model: null, // treedb_fold_model: the tree of the records
433
440
  _fold_state: null, // ...and which groups of it are open
434
441
  _fold_listener: null, // delegated click on the card pills
@@ -2992,27 +2999,40 @@ function graph_find_nodes(gobj, term)
2992
2999
  * folded away, and a find that only read what was on screen would
2993
3000
  * answer "nothing" for a device three levels down. */
2994
3001
  let keys = [];
3002
+ let hidden_matches = 0;
2995
3003
  if(!empty_string(term)) {
2996
3004
  let needle = String(term).toLowerCase();
2997
- for(let node of model.nodes.values()) {
2998
- let record = node.record || {};
3005
+ let matches = (topic_name, record) => {
2999
3006
  let label = "";
3000
3007
  try {
3001
- label = node_label(priv.descs[node.topic_name] || {}, record) || "";
3008
+ label = node_label(priv.descs[topic_name] || {}, record) || "";
3002
3009
  } catch(e) {
3003
3010
  label = "";
3004
3011
  }
3005
3012
  let haystack = [
3006
3013
  label,
3007
3014
  record.id,
3008
- node.topic_name
3015
+ topic_name
3009
3016
  ].filter((v) => typeof v === "string").join(" ").toLowerCase();
3010
-
3011
- 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 || {})) {
3012
3021
  keys.push(node.key);
3013
3022
  }
3014
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
+ }
3015
3034
  }
3035
+ priv._find_hidden_matches = hidden_matches;
3016
3036
 
3017
3037
  /* Hidden matches are opened up to, one page of them at most: a
3018
3038
  * single letter matches half the treedb, and unfolding half the
@@ -7536,7 +7556,11 @@ function rebuild_fold_model(gobj)
7536
7556
  {
7537
7557
  let priv = gobj.priv;
7538
7558
 
7539
- 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
+ });
7540
7564
  if(!priv._fold_state) {
7541
7565
  priv._fold_state = fold_new_state(gobj_read_integer_attr(gobj, "fold_page_size"));
7542
7566
  }
@@ -7622,9 +7646,60 @@ async function reconcile_fold_now(gobj, opts)
7622
7646
  } finally {
7623
7647
  history_resume(gobj);
7624
7648
  }
7649
+ publish_legend_state(gobj, visible);
7625
7650
  return changed;
7626
7651
  }
7627
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
+
7628
7703
  async function reconcile_fold_apply(gobj, opts, visible)
7629
7704
  {
7630
7705
  let priv = gobj.priv;
@@ -8627,6 +8702,58 @@ function ac_collapse_all(gobj, event, kw, src)
8627
8702
  return 0;
8628
8703
  }
8629
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
+
8630
8757
  /************************************************************
8631
8758
  * Tell whoever asked how many nodes the term matched. The count is
8632
8759
  * the answer to the question the box asks; without it "nothing moved"
@@ -8634,7 +8761,11 @@ function ac_collapse_all(gobj, event, kw, src)
8634
8761
  ************************************************************/
8635
8762
  function publish_find_result(gobj, term, matches)
8636
8763
  {
8637
- 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
+ });
8638
8769
  }
8639
8770
 
8640
8771
  /************************************************************
@@ -9184,6 +9315,9 @@ function create_gclass(gclass_name)
9184
9315
  ["EV_SHOW_MORE", ac_show_more, null],
9185
9316
  ["EV_EXPAND_ALL", ac_expand_all, null],
9186
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],
9187
9321
  ["EV_CENTER", ac_center, null],
9188
9322
  ["EV_FULLSCREEN", ac_fullscreen, null],
9189
9323
  ["EV_SET_LAYOUT", ac_set_layout, null],
@@ -9241,6 +9375,10 @@ function create_gclass(gclass_name)
9241
9375
  ["EV_SHOW_MORE", 0],
9242
9376
  ["EV_EXPAND_ALL", 0],
9243
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],
9244
9382
  ["EV_CENTER", 0],
9245
9383
  ["EV_FULLSCREEN", 0],
9246
9384
  ["EV_SET_LAYOUT", 0],