@yuneta/gobj-ui 7.23.67 → 7.23.69

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.
@@ -1,5 +1,5 @@
1
1
  import * as gobj_js from "@yuneta/gobj-js";
2
- import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, duplicate_objects, empty_string, escapeHtml, event_flag_t, gclass_create, gclass_find_by_name, gclass_flag_t, getPositionRelativeToBody, gobj_bottom_gobj, gobj_change_state, gobj_command, gobj_create, gobj_create_pure_child, gobj_create_service, gobj_current_state, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_post_event, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_start_tree, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, gobj_yuno_name, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_string, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_get_ids, kwid_new_dict, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_console_log_enabled, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
2
+ import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, delete_from_list, duplicate_objects, empty_string, escapeHtml, event_flag_t, gclass_create, gclass_find_by_name, gclass_flag_t, getPositionRelativeToBody, gobj_bottom_gobj, gobj_change_state, gobj_command, gobj_create, gobj_create_pure_child, gobj_create_service, gobj_current_state, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_post_event, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_start_tree, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, gobj_yuno_name, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_string, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_get_ids, kwid_new_dict, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_console_log_enabled, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
3
3
  import i18next, { t } from "i18next";
4
4
  import TomSelect from "tom-select";
5
5
  import { createJSONEditor } from "vanilla-jsoneditor";
@@ -26909,17 +26909,26 @@ function parents_of(descs, desc, record) {
26909
26909
  * topics: [topic_name, ...] topics with records, in desc order
26910
26910
  * }
26911
26911
  ************************************************************/
26912
- function fold_build_model(descs, records) {
26912
+ function fold_build_model(descs, records, opts) {
26913
26913
  let nodes = /* @__PURE__ */ new Map();
26914
26914
  let groups = /* @__PURE__ */ new Map();
26915
26915
  let topics = [];
26916
26916
  descs = is_object(descs) ? descs : {};
26917
26917
  records = is_object(records) ? records : {};
26918
+ opts = is_object(opts) ? opts : {};
26919
+ let hidden = new Set(is_array(opts.hidden_topics) ? opts.hidden_topics : []);
26920
+ let loose_shown = new Set(is_array(opts.loose_topics) ? opts.loose_topics : []);
26921
+ let main_topic = opts.main_topic || fold_main_topic(descs);
26922
+ if (main_topic && (!descs[main_topic] || hidden.has(main_topic))) main_topic = "";
26923
+ let linked = fold_linked_topics(descs, main_topic);
26924
+ let totals = {};
26918
26925
  for (let topic_name of Object.keys(descs)) {
26919
26926
  if (topic_name.substring(0, 2) === "__") continue;
26920
26927
  descs[topic_name];
26921
26928
  let list = records[topic_name];
26922
26929
  if (!is_array(list)) continue;
26930
+ totals[topic_name] = list.length;
26931
+ if (hidden.has(topic_name)) continue;
26923
26932
  topics.push(topic_name);
26924
26933
  for (let i = 0; i < list.length; i++) {
26925
26934
  let record = list[i];
@@ -26948,21 +26957,100 @@ function fold_build_model(descs, records) {
26948
26957
  }
26949
26958
  for (let node of nodes.values()) for (let hook of Object.keys(node.hooks)) groups.set(fold_group_key(node.key, hook), node.hooks[hook]);
26950
26959
  let roots = {};
26951
- for (let topic_name of topics) roots[topic_name] = [];
26952
- for (let node of nodes.values()) if (node.parents.length === 0) roots[node.topic_name].push(node.key);
26960
+ let loose = {};
26961
+ for (let topic_name of topics) {
26962
+ roots[topic_name] = [];
26963
+ loose[topic_name] = [];
26964
+ }
26965
+ for (let node of nodes.values()) {
26966
+ if (node.parents.length > 0) continue;
26967
+ let t = node.topic_name;
26968
+ if (main_topic && t !== main_topic && linked.has(t)) {
26969
+ loose[t].push(node.key);
26970
+ if (loose_shown.has(t)) roots[t].push(node.key);
26971
+ } else roots[t].push(node.key);
26972
+ }
26953
26973
  let reached = reach_from(nodes, [].concat(...Object.values(roots)));
26954
- for (let node of nodes.values()) if (!reached.has(node.key)) {
26955
- roots[node.topic_name].push(node.key);
26974
+ for (let node of nodes.values()) {
26975
+ if (reached.has(node.key)) continue;
26976
+ let t = node.topic_name;
26977
+ if (main_topic && t !== main_topic && linked.has(t)) {
26978
+ if (node.parents.length > 0) loose[t].push(node.key);
26979
+ if (!loose_shown.has(t)) continue;
26980
+ }
26981
+ roots[t].push(node.key);
26956
26982
  for (let k of reach_from(nodes, [node.key])) reached.add(k);
26957
26983
  }
26958
26984
  for (let topic_name of topics) if (roots[topic_name].length > 0) groups.set(fold_root_group_key(topic_name), roots[topic_name]);
26959
26985
  return {
26960
26986
  nodes,
26961
26987
  groups,
26962
- topics
26988
+ topics,
26989
+ totals,
26990
+ hidden,
26991
+ main_topic,
26992
+ linked,
26993
+ loose,
26994
+ loose_shown
26963
26995
  };
26964
26996
  }
26965
26997
  /************************************************************
26998
+ * The topic the schema hangs the others from: the one whose hooks
26999
+ * reach the most OTHER topics, a self-referent hook breaking a tie
27000
+ * (a tree of places over a flat list of groups), schema order
27001
+ * breaking the rest. "" when no topic reaches another -- a treedb of
27002
+ * unrelated lists has no trunk, and every topic is a tree of its own.
27003
+ ************************************************************/
27004
+ function fold_main_topic(descs) {
27005
+ descs = is_object(descs) ? descs : {};
27006
+ let best = "";
27007
+ let best_score = 0;
27008
+ let best_self = false;
27009
+ for (let topic_name of Object.keys(descs)) {
27010
+ if (topic_name.substring(0, 2) === "__") continue;
27011
+ let reach = hook_targets(descs, topic_name);
27012
+ let self = reach.has(topic_name);
27013
+ reach.delete(topic_name);
27014
+ let score = reach.size;
27015
+ if (score === 0) continue;
27016
+ if (score > best_score || score === best_score && self && !best_self) {
27017
+ best = topic_name;
27018
+ best_score = score;
27019
+ best_self = self;
27020
+ }
27021
+ }
27022
+ return best;
27023
+ }
27024
+ function hook_targets(descs, topic_name) {
27025
+ let out = /* @__PURE__ */ new Set();
27026
+ let desc = descs[topic_name];
27027
+ if (!desc || desc.node_treedb_type === "extended") return out;
27028
+ let cols = is_array(desc.cols) ? desc.cols : [];
27029
+ for (let col of cols) {
27030
+ if (!is_object(col.hook) || !is_array(col.flag) || col.flag.indexOf("hook") < 0) continue;
27031
+ for (let child of Object.keys(col.hook)) if (child.substring(0, 2) !== "__" && descs[child]) out.add(child);
27032
+ }
27033
+ return out;
27034
+ }
27035
+ /************************************************************
27036
+ * The topics the schema hangs from `main_topic`, main included:
27037
+ * reached by following hooks from it. A record of one of these
27038
+ * with no parent is LOOSE; a topic outside the set is its own tree.
27039
+ * Empty when there is no main topic.
27040
+ ************************************************************/
27041
+ function fold_linked_topics(descs, main_topic) {
27042
+ let linked = /* @__PURE__ */ new Set();
27043
+ if (!main_topic || !is_object(descs) || !descs[main_topic]) return linked;
27044
+ let queue = [main_topic];
27045
+ while (queue.length) {
27046
+ let t = queue.shift();
27047
+ if (linked.has(t)) continue;
27048
+ linked.add(t);
27049
+ for (let child of hook_targets(descs, t)) if (!linked.has(child)) queue.push(child);
27050
+ }
27051
+ return linked;
27052
+ }
27053
+ /************************************************************
26966
27054
  * Everything reachable downwards from `start`, cycles included.
26967
27055
  ************************************************************/
26968
27056
  function reach_from(nodes, start) {
@@ -26993,7 +27081,10 @@ function fold_group_total(model, group_key) {
26993
27081
  }
26994
27082
  function fold_group_shown(model, state, group_key) {
26995
27083
  let total = fold_group_total(model, group_key);
26996
- let n = state.shown.get(group_key) || 0;
27084
+ let n;
27085
+ if (state.shown.has(group_key)) n = state.shown.get(group_key);
27086
+ else if (group_key.charAt(0) === "|") n = state.page_size;
27087
+ else n = 0;
26997
27088
  return Math.min(n, total);
26998
27089
  }
26999
27090
  function fold_is_open(state, group_key) {
@@ -54099,6 +54190,9 @@ var attrs_table$4 = [
54099
54190
  SDATA(data_type_t.DTP_STRING, "fkey_port_position", 0, "top", "Fkey port position"),
54100
54191
  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"),
54101
54192
  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"),
54193
+ 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"),
54194
+ 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"),
54195
+ 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"),
54102
54196
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
54103
54197
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
54104
54198
  SDATA(data_type_t.DTP_STRING, "wide", 0, "40px", "Height of header"),
@@ -54168,12 +54262,16 @@ var PRIVATE_DATA$4 = {
54168
54262
  _selected_paint_ids: [],
54169
54263
  _pending_focus_topic: null,
54170
54264
  _pending_focus_all: false,
54265
+ _find_hidden_matches: 0,
54171
54266
  _pending_find: null,
54172
54267
  _layout_asked: "",
54173
54268
  _nodes_total: 0,
54174
54269
  _nodes_placed: 0,
54175
54270
  expand_depth: 2,
54176
54271
  fold_page_size: 24,
54272
+ hidden_topics: null,
54273
+ main_topic: "",
54274
+ loose_topics: null,
54177
54275
  _fold_model: null,
54178
54276
  _fold_state: null,
54179
54277
  _fold_listener: null,
@@ -55749,23 +55847,26 @@ function graph_find_nodes(gobj, term) {
55749
55847
  let model = priv._fold_model;
55750
55848
  let state = priv._fold_state;
55751
55849
  let keys = [];
55850
+ let hidden_matches = 0;
55752
55851
  if (!empty_string(term)) {
55753
55852
  let needle = String(term).toLowerCase();
55754
- for (let node of model.nodes.values()) {
55755
- let record = node.record || {};
55853
+ let matches = (topic_name, record) => {
55756
55854
  let label = "";
55757
55855
  try {
55758
- label = node_label(priv.descs[node.topic_name] || {}, record) || "";
55856
+ label = node_label(priv.descs[topic_name] || {}, record) || "";
55759
55857
  } catch (e) {
55760
55858
  label = "";
55761
55859
  }
55762
- if ([
55860
+ return [
55763
55861
  label,
55764
55862
  record.id,
55765
- node.topic_name
55766
- ].filter((v) => typeof v === "string").join(" ").toLowerCase().includes(needle)) keys.push(node.key);
55767
- }
55863
+ topic_name
55864
+ ].filter((v) => typeof v === "string").join(" ").toLowerCase().includes(needle);
55865
+ };
55866
+ for (let node of model.nodes.values()) if (matches(node.topic_name, node.record || {})) keys.push(node.key);
55867
+ for (let topic_name of model.hidden) for (let record of priv.records[topic_name] || []) if (is_object(record) && matches(topic_name, record)) hidden_matches++;
55768
55868
  }
55869
+ priv._find_hidden_matches = hidden_matches;
55769
55870
  let visible = fold_visible_set(model, state);
55770
55871
  let revealed = 0;
55771
55872
  for (let key of keys) {
@@ -58696,7 +58797,11 @@ function uninstall_fold_listener(gobj) {
58696
58797
  ************************************************************/
58697
58798
  function rebuild_fold_model(gobj) {
58698
58799
  let priv = gobj.priv;
58699
- priv._fold_model = fold_build_model(priv.descs || {}, priv.records || {});
58800
+ priv._fold_model = fold_build_model(priv.descs || {}, priv.records || {}, {
58801
+ hidden_topics: gobj_read_attr(gobj, "hidden_topics") || [],
58802
+ main_topic: gobj_read_str_attr(gobj, "main_topic") || "",
58803
+ loose_topics: gobj_read_attr(gobj, "loose_topics") || []
58804
+ });
58700
58805
  if (!priv._fold_state) priv._fold_state = fold_new_state(gobj_read_integer_attr(gobj, "fold_page_size"));
58701
58806
  if (!priv._pill_sig) priv._pill_sig = /* @__PURE__ */ new Map();
58702
58807
  }
@@ -58758,8 +58863,47 @@ async function reconcile_fold_now(gobj, opts) {
58758
58863
  } finally {
58759
58864
  history_resume(gobj);
58760
58865
  }
58866
+ publish_legend_state(gobj, visible);
58761
58867
  return changed;
58762
58868
  }
58869
+ /************************************************************
58870
+ * What the legend has to say, published after every reconcile:
58871
+ * one entry per topic of the treedb, hidden ones included, with
58872
+ * its colour, how many records it has, how many are on screen,
58873
+ * how many are LOOSE, and the flags -- plus which topic is the
58874
+ * main one and whether it was deduced or chosen. The host draws
58875
+ * the strip from this and nothing else; the engine is the truth.
58876
+ ************************************************************/
58877
+ function publish_legend_state(gobj, visible) {
58878
+ let priv = gobj.priv;
58879
+ let model = priv._fold_model;
58880
+ if (!model || !priv.descs) return;
58881
+ let on_screen = {};
58882
+ for (let key of visible) {
58883
+ let node = model.nodes.get(key);
58884
+ if (node) on_screen[node.topic_name] = (on_screen[node.topic_name] || 0) + 1;
58885
+ }
58886
+ let topics = [];
58887
+ for (const [topic_name, desc] of Object.entries(priv.descs)) {
58888
+ if (topic_name.substring(0, 2) === "__" || !desc) continue;
58889
+ let loose = model.loose[topic_name] || [];
58890
+ topics.push({
58891
+ topic: topic_name,
58892
+ color: desc.color || "",
58893
+ total: model.totals[topic_name] || 0,
58894
+ visible: on_screen[topic_name] || 0,
58895
+ loose: loose.length,
58896
+ loose_shown: model.loose_shown.has(topic_name),
58897
+ hidden: model.hidden.has(topic_name),
58898
+ linked: model.linked.has(topic_name)
58899
+ });
58900
+ }
58901
+ gobj_publish_event(gobj, "EV_LEGEND_STATE", {
58902
+ main_topic: model.main_topic,
58903
+ main_chosen: !!gobj_read_str_attr(gobj, "main_topic"),
58904
+ topics
58905
+ });
58906
+ }
58763
58907
  async function reconcile_fold_apply(gobj, opts, visible) {
58764
58908
  let priv = gobj.priv;
58765
58909
  let graph = priv.graph;
@@ -59435,6 +59579,43 @@ function ac_collapse_all$1(gobj, event, kw, src) {
59435
59579
  return 0;
59436
59580
  }
59437
59581
  /************************************************************
59582
+ * The legend's three settings, from the host, which persists
59583
+ * them. Each rebuilds the tree; the fold state is kept -- it is
59584
+ * keyed by group, and a group that is gone is never read -- except
59585
+ * for a new MAIN topic, whose roots are other roots: the tree
59586
+ * opens again at `expand_depth`.
59587
+ ************************************************************/
59588
+ function ac_set_hidden_topics(gobj, event, kw, src) {
59589
+ let priv = gobj.priv;
59590
+ let topics = kw && is_array(kw.topics) ? kw.topics : [];
59591
+ gobj_write_attr(gobj, "hidden_topics", topics);
59592
+ if (!priv._fold_model) return 0;
59593
+ rebuild_fold_model(gobj);
59594
+ reconcile_fold(gobj, {});
59595
+ return 0;
59596
+ }
59597
+ function ac_set_main_topic(gobj, event, kw, src) {
59598
+ let priv = gobj.priv;
59599
+ gobj_write_str_attr(gobj, "main_topic", kw && kw.topic || "");
59600
+ if (!priv._fold_model) return 0;
59601
+ rebuild_fold_model(gobj);
59602
+ fold_expand_to_depth(priv._fold_model, priv._fold_state, gobj_read_integer_attr(gobj, "expand_depth"));
59603
+ reconcile_fold(gobj, {
59604
+ relayout: true,
59605
+ fit: true
59606
+ });
59607
+ return 0;
59608
+ }
59609
+ function ac_set_loose_topics(gobj, event, kw, src) {
59610
+ let priv = gobj.priv;
59611
+ let topics = kw && is_array(kw.topics) ? kw.topics : [];
59612
+ gobj_write_attr(gobj, "loose_topics", topics);
59613
+ if (!priv._fold_model) return 0;
59614
+ rebuild_fold_model(gobj);
59615
+ reconcile_fold(gobj, {});
59616
+ return 0;
59617
+ }
59618
+ /************************************************************
59438
59619
  * Tell whoever asked how many nodes the term matched. The count is
59439
59620
  * the answer to the question the box asks; without it "nothing moved"
59440
59621
  * and "nothing matched" look the same.
@@ -59442,7 +59623,8 @@ function ac_collapse_all$1(gobj, event, kw, src) {
59442
59623
  function publish_find_result(gobj, term, matches) {
59443
59624
  gobj_publish_event(gobj, "EV_FIND_RESULT", {
59444
59625
  term,
59445
- matches
59626
+ matches,
59627
+ hidden_matches: gobj.priv._find_hidden_matches || 0
59446
59628
  });
59447
59629
  }
59448
59630
  /************************************************************
@@ -59908,6 +60090,21 @@ function create_gclass$4(gclass_name) {
59908
60090
  ac_collapse_all$1,
59909
60091
  null
59910
60092
  ],
60093
+ [
60094
+ "EV_SET_HIDDEN_TOPICS",
60095
+ ac_set_hidden_topics,
60096
+ null
60097
+ ],
60098
+ [
60099
+ "EV_SET_MAIN_TOPIC",
60100
+ ac_set_main_topic,
60101
+ null
60102
+ ],
60103
+ [
60104
+ "EV_SET_LOOSE_TOPICS",
60105
+ ac_set_loose_topics,
60106
+ null
60107
+ ],
59911
60108
  [
59912
60109
  "EV_CENTER",
59913
60110
  ac_center,
@@ -60009,6 +60206,10 @@ function create_gclass$4(gclass_name) {
60009
60206
  ["EV_SHOW_MORE", 0],
60010
60207
  ["EV_EXPAND_ALL", 0],
60011
60208
  ["EV_COLLAPSE_ALL", 0],
60209
+ ["EV_SET_HIDDEN_TOPICS", 0],
60210
+ ["EV_SET_MAIN_TOPIC", 0],
60211
+ ["EV_SET_LOOSE_TOPICS", 0],
60212
+ ["EV_LEGEND_STATE", event_flag_t.EVF_OUTPUT_EVENT],
60012
60213
  ["EV_CENTER", 0],
60013
60214
  ["EV_FULLSCREEN", 0],
60014
60215
  ["EV_SET_LAYOUT", 0],
@@ -60070,6 +60271,9 @@ var attrs_table$3 = [
60070
60271
  SDATA(data_type_t.DTP_INTEGER, "fold_page_size", 0, 24, "Children of one hook shown per page in the graph; a `+N` chip opens the next page. Forwarded to the G6 child"),
60071
60272
  SDATA(data_type_t.DTP_STRING, "operation_mode", sdata_flag_t.SDF_PERSIST, "reading", "Current operation mode (internal behaviour or role). Changed by the user trough the gui."),
60072
60273
  SDATA(data_type_t.DTP_STRING, "layout", sdata_flag_t.SDF_PERSIST, "", "Current graph layout. User preference. Changed by the user through the gui."),
60274
+ SDATA(data_type_t.DTP_LIST, "hidden_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics hidden from the graph (legend). User preference, per treedb"),
60275
+ SDATA(data_type_t.DTP_STRING, "main_topic", sdata_flag_t.SDF_PERSIST, "", "The topic the tree hangs from; empty = deduced by the graph. User preference, per treedb"),
60276
+ SDATA(data_type_t.DTP_LIST, "loose_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics whose LOOSE records (no parent) are shown. User preference, per treedb"),
60073
60277
  SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
60074
60278
  SDATA(data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
60075
60279
  SDATA(data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
@@ -60111,6 +60315,7 @@ var PRIVATE_DATA$3 = {
60111
60315
  operation_mode: null,
60112
60316
  layout: null,
60113
60317
  _topics_subscribed: {},
60318
+ _legend_state: null,
60114
60319
  _links_subscribed: false,
60115
60320
  is_pinhold_window: false
60116
60321
  };
@@ -60143,7 +60348,10 @@ function mt_create$3(gobj) {
60143
60348
  hook_port_position: "bottom",
60144
60349
  fkey_port_position: "top",
60145
60350
  expand_depth: gobj_read_integer_attr(gobj, "expand_depth"),
60146
- fold_page_size: gobj_read_integer_attr(gobj, "fold_page_size")
60351
+ fold_page_size: gobj_read_integer_attr(gobj, "fold_page_size"),
60352
+ hidden_topics: gobj_read_attr(gobj, "hidden_topics") || [],
60353
+ main_topic: gobj_read_str_attr(gobj, "main_topic") || "",
60354
+ loose_topics: gobj_read_attr(gobj, "loose_topics") || []
60147
60355
  }, gobj);
60148
60356
  populate_nodes_tree_options(gobj);
60149
60357
  if (priv.with_treedb_tables) {}
@@ -60210,8 +60418,8 @@ function build_ui$3(gobj) {
60210
60418
  [
60211
60419
  "div",
60212
60420
  {
60213
- class: "GRAPH_LEGEND is-hidden is-flex-grow-0",
60214
- style: "display:flex; flex-wrap:wrap; align-items:center; gap:.4rem; padding:.35rem .5rem;"
60421
+ class: "GRAPH_LEGEND is-flex-grow-0",
60422
+ style: "display:flex; flex-wrap:nowrap; align-items:center; gap:.35rem; padding:.3rem .5rem; overflow-x:auto; min-height:2.2rem;"
60215
60423
  },
60216
60424
  []
60217
60425
  ],
@@ -60402,29 +60610,6 @@ function make_toolbar(gobj) {
60402
60610
  } }
60403
60611
  ],
60404
60612
  ...yui_graph_fold_items(gobj, gobj_read_str_attr(gobj, "wide")),
60405
- [
60406
- "button",
60407
- {
60408
- class: "GRAPH_LEGEND_BTN button ml-2",
60409
- title: t("legend"),
60410
- "aria-label": t("legend"),
60411
- "data-i18n-title": "legend",
60412
- "data-i18n-aria-label": "legend"
60413
- },
60414
- [["i", { class: "yi-square" }], [
60415
- "span",
60416
- {
60417
- class: "is-hidden-mobile",
60418
- style: "padding-left:5px;",
60419
- i18n: "legend"
60420
- },
60421
- "legend"
60422
- ]],
60423
- { click: (evt) => {
60424
- evt.stopPropagation();
60425
- gobj_send_event(gobj, "EV_TOGGLE_LEGEND", {}, gobj);
60426
- } }
60427
- ],
60428
60613
  [
60429
60614
  "button",
60430
60615
  {
@@ -61448,61 +61633,207 @@ function ac_set_operation_mode(gobj, event, kw, src) {
61448
61633
  * card's graph icon travels — as EV_TOPIC_SELECTED, which the host turns
61449
61634
  * into the URL, so what you are looking at stays linkable.
61450
61635
  ************************************************************/
61636
+ var LEGEND_GLYPH_STYLE = "font-size:1.5em; line-height:1; color:inherit;";
61451
61637
  function refresh_legend(gobj) {
61452
61638
  let priv = gobj.priv;
61453
61639
  let $container = gobj_read_attr(gobj, "$container");
61454
61640
  if (!$container) return;
61455
61641
  let $legend = $container.querySelector(".GRAPH_LEGEND");
61456
61642
  if (!$legend) return;
61457
- let descs = priv.gobj_nodes_tree ? gobj_read_attr(priv.gobj_nodes_tree, "descs") : null;
61643
+ let state = priv._legend_state;
61458
61644
  $legend.textContent = "";
61459
- if (!descs) return;
61460
- for (const [topic_name, desc] of Object.entries(descs)) {
61461
- if (topic_name.substring(0, 2) === "__") continue;
61462
- if (!desc || !desc.color) continue;
61645
+ if (!state || !is_array(state.topics)) return;
61646
+ let topics = state.topics.slice().sort((a, b) => {
61647
+ return (a.topic === state.main_topic ? 0 : 1) - (b.topic === state.main_topic ? 0 : 1);
61648
+ });
61649
+ for (let entry of topics) {
61650
+ let topic_name = entry.topic;
61651
+ let is_main = topic_name === state.main_topic;
61463
61652
  let focused = priv.focus_topic === topic_name;
61464
- let $item = createElement2([
61653
+ let hidden = !!entry.hidden;
61654
+ let controls = [];
61655
+ controls.push(["span", {
61656
+ class: "GRAPH_LEGEND_SWATCH",
61657
+ style: `display:inline-block; width:.8rem; height:.8rem; border-radius:3px; margin-right:.35rem; flex:0 0 auto; background:${entry.color || "#94a3b8"}; border:1px solid rgba(0,0,0,.25);` + (hidden ? "opacity:.35;" : "")
61658
+ }], [
61659
+ "span",
61660
+ {
61661
+ class: "GRAPH_LEGEND_NAME",
61662
+ style: hidden ? "text-decoration:line-through; opacity:.6;" : ""
61663
+ },
61664
+ topic_name
61665
+ ], [
61666
+ "span",
61667
+ {
61668
+ class: "GRAPH_LEGEND_COUNT has-text-grey",
61669
+ style: "margin-left:.35rem; font-size:.8em;"
61670
+ },
61671
+ hidden ? `${entry.total}` : `${entry.visible}/${entry.total}`
61672
+ ]);
61673
+ let body_attrs = {
61674
+ class: "GRAPH_LEGEND_ITEM button is-small" + (focused ? " is-primary is-light" : "") + (is_main ? " GRAPH_LEGEND_MAIN" : ""),
61675
+ type: "button",
61676
+ style: "gap:0; flex:0 0 auto;",
61677
+ title: is_main ? t("main topic") : hidden ? t("show topic") : t("hide topic"),
61678
+ "aria-label": topic_name,
61679
+ "aria-pressed": hidden ? "false" : "true"
61680
+ };
61681
+ if (is_main) body_attrs.disabled = "disabled";
61682
+ let $body = createElement2([
61683
+ "button",
61684
+ body_attrs,
61685
+ controls,
61686
+ { click: (evt) => {
61687
+ evt.stopPropagation();
61688
+ gobj_send_event(gobj, "EV_LEGEND_TOPIC", {
61689
+ topic: topic_name,
61690
+ action: "toggle"
61691
+ }, gobj);
61692
+ } }
61693
+ ]);
61694
+ let extras = [];
61695
+ if (is_main && state.main_chosen) extras.push([
61465
61696
  "button",
61466
61697
  {
61467
- class: "GRAPH_LEGEND_ITEM button is-small" + (focused ? " is-primary is-light" : ""),
61468
- title: topic_name,
61469
- "aria-label": topic_name,
61470
- "aria-pressed": focused ? "true" : "false"
61698
+ class: "GRAPH_LEGEND_STAR button is-small is-warning is-light",
61699
+ type: "button",
61700
+ style: "padding:0 .4rem;",
61701
+ title: t("main topic"),
61702
+ "data-i18n-title": "main topic",
61703
+ "aria-label": t("main topic"),
61704
+ "aria-pressed": "true"
61471
61705
  },
61472
- [["span", {
61473
- class: "GRAPH_LEGEND_SWATCH",
61474
- style: `display:inline-block; width:.8rem; height:.8rem; border-radius:3px; margin-right:.4rem; background:${desc.color}; border:1px solid rgba(0,0,0,.25);`
61475
- }], [
61706
+ [[
61476
61707
  "span",
61477
- {},
61478
- topic_name
61708
+ { style: LEGEND_GLYPH_STYLE },
61709
+ "★"
61479
61710
  ]],
61480
61711
  { click: (evt) => {
61481
61712
  evt.stopPropagation();
61482
- gobj_send_event(gobj, "EV_LEGEND_TOPIC", { topic: topic_name }, gobj);
61713
+ gobj_send_event(gobj, "EV_LEGEND_TOPIC", {
61714
+ topic: "",
61715
+ action: "main"
61716
+ }, gobj);
61717
+ } }
61718
+ ]);
61719
+ else if (is_main) extras.push([
61720
+ "span",
61721
+ {
61722
+ class: "GRAPH_LEGEND_STAR button is-small is-static",
61723
+ style: "padding:0 .4rem;",
61724
+ title: t("main topic"),
61725
+ "data-i18n-title": "main topic"
61726
+ },
61727
+ [[
61728
+ "span",
61729
+ { style: LEGEND_GLYPH_STYLE },
61730
+ "★"
61731
+ ]]
61732
+ ]);
61733
+ else if (!hidden) extras.push([
61734
+ "button",
61735
+ {
61736
+ class: "GRAPH_LEGEND_STAR button is-small",
61737
+ type: "button",
61738
+ style: "padding:0 .4rem;",
61739
+ title: t("main topic"),
61740
+ "data-i18n-title": "main topic",
61741
+ "aria-label": t("main topic")
61742
+ },
61743
+ [[
61744
+ "span",
61745
+ { style: LEGEND_GLYPH_STYLE },
61746
+ "☆"
61747
+ ]],
61748
+ { click: (evt) => {
61749
+ evt.stopPropagation();
61750
+ gobj_send_event(gobj, "EV_LEGEND_TOPIC", {
61751
+ topic: topic_name,
61752
+ action: "main"
61753
+ }, gobj);
61754
+ } }
61755
+ ]);
61756
+ if (!hidden && entry.linked && entry.loose > 0) extras.push([
61757
+ "button",
61758
+ {
61759
+ class: "GRAPH_LEGEND_LOOSE button is-small" + (entry.loose_shown ? " is-warning is-light" : ""),
61760
+ type: "button",
61761
+ style: "padding:0 .45rem;",
61762
+ title: t("loose records"),
61763
+ "data-i18n-title": "loose records",
61764
+ "aria-label": t("loose records"),
61765
+ "aria-pressed": entry.loose_shown ? "true" : "false"
61766
+ },
61767
+ `+${entry.loose}`,
61768
+ { click: (evt) => {
61769
+ evt.stopPropagation();
61770
+ gobj_send_event(gobj, "EV_LEGEND_TOPIC", {
61771
+ topic: topic_name,
61772
+ action: "loose"
61773
+ }, gobj);
61774
+ } }
61775
+ ]);
61776
+ if (!hidden) extras.push([
61777
+ "button",
61778
+ {
61779
+ class: "GRAPH_LEGEND_FOCUS button is-small" + (focused ? " is-primary is-light" : ""),
61780
+ type: "button",
61781
+ style: "padding:0 .4rem;",
61782
+ title: t("highlight topic"),
61783
+ "data-i18n-title": "highlight topic",
61784
+ "aria-label": t("highlight topic"),
61785
+ "aria-pressed": focused ? "true" : "false"
61786
+ },
61787
+ [["i", {
61788
+ class: "yi-location-crosshairs",
61789
+ style: LEGEND_GLYPH_STYLE
61790
+ }]],
61791
+ { click: (evt) => {
61792
+ evt.stopPropagation();
61793
+ gobj_send_event(gobj, "EV_LEGEND_TOPIC", {
61794
+ topic: topic_name,
61795
+ action: "focus"
61796
+ }, gobj);
61483
61797
  } }
61484
61798
  ]);
61485
- $legend.appendChild($item);
61799
+ let $chip = createElement2([
61800
+ "div",
61801
+ {
61802
+ class: "GRAPH_LEGEND_CHIP buttons has-addons",
61803
+ style: "margin:0; flex:0 0 auto; flex-wrap:nowrap;"
61804
+ },
61805
+ [$body].concat(extras.map((spec) => createElement2(spec)))
61806
+ ]);
61807
+ $legend.appendChild($chip);
61486
61808
  }
61487
61809
  }
61488
61810
  /************************************************************
61489
- *
61811
+ * The engine said what is drawn: repaint the strip.
61490
61812
  ************************************************************/
61491
- function ac_toggle_legend(gobj, event, kw, src) {
61492
- let $container = gobj_read_attr(gobj, "$container");
61493
- if (!$container) return 0;
61494
- let $legend = $container.querySelector(".GRAPH_LEGEND");
61495
- if (!$legend) {
61496
- log_error(`${gobj_short_name(gobj)}: no legend strip to toggle`);
61497
- return -1;
61498
- }
61499
- if ($legend.classList.contains("is-hidden")) {
61500
- refresh_legend(gobj);
61501
- $legend.classList.remove("is-hidden");
61502
- } else $legend.classList.add("is-hidden");
61813
+ function ac_legend_state(gobj, event, kw, src) {
61814
+ let priv = gobj.priv;
61815
+ priv._legend_state = kw || null;
61816
+ refresh_legend(gobj);
61503
61817
  return 0;
61504
61818
  }
61505
61819
  /************************************************************
61820
+ *
61821
+ ************************************************************/
61822
+ /************************************************************
61823
+ * One of the legend's settings changed: write it, SAVE it (they
61824
+ * are the reader's, per treedb) and tell the engine.
61825
+ ************************************************************/
61826
+ function set_legend_list_attr(gobj, name, event_name, topics) {
61827
+ let priv = gobj.priv;
61828
+ gobj_write_attr(gobj, name, topics);
61829
+ gobj_save_persistent_attrs(gobj, name);
61830
+ if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, event_name, { topics }, gobj);
61831
+ }
61832
+ function legend_list_attr(gobj, name) {
61833
+ let list = gobj_read_attr(gobj, name);
61834
+ return is_array(list) ? list.slice() : [];
61835
+ }
61836
+ /************************************************************
61506
61837
  * A legend entry was clicked: focus that topic, or clear the focus if
61507
61838
  * it was already the focused one. Published UP rather than applied
61508
61839
  * here, so the URL follows — the host owns the route.
@@ -61510,10 +61841,41 @@ function ac_toggle_legend(gobj, event, kw, src) {
61510
61841
  function ac_legend_topic(gobj, event, kw, src) {
61511
61842
  let priv = gobj.priv;
61512
61843
  let topic = kw && kw.topic || "";
61513
- if (!topic) {
61844
+ let action = kw && kw.action || "focus";
61845
+ if (!topic && action !== "main") {
61514
61846
  log_error(`${gobj_short_name(gobj)}: legend click with no topic`);
61515
61847
  return -1;
61516
61848
  }
61849
+ let state = priv._legend_state || {};
61850
+ switch (action) {
61851
+ case "toggle": {
61852
+ if (topic === state.main_topic) {
61853
+ log_error(`${gobj_short_name(gobj)}: the main topic '${topic}' cannot be hidden`);
61854
+ return -1;
61855
+ }
61856
+ let hidden = legend_list_attr(gobj, "hidden_topics");
61857
+ if (str_in_list(hidden, topic)) delete_from_list(hidden, topic);
61858
+ else hidden.push(topic);
61859
+ set_legend_list_attr(gobj, "hidden_topics", "EV_SET_HIDDEN_TOPICS", hidden);
61860
+ return 0;
61861
+ }
61862
+ case "loose": {
61863
+ let loose = legend_list_attr(gobj, "loose_topics");
61864
+ if (str_in_list(loose, topic)) delete_from_list(loose, topic);
61865
+ else loose.push(topic);
61866
+ set_legend_list_attr(gobj, "loose_topics", "EV_SET_LOOSE_TOPICS", loose);
61867
+ return 0;
61868
+ }
61869
+ case "main":
61870
+ gobj_write_str_attr(gobj, "main_topic", topic);
61871
+ gobj_save_persistent_attrs(gobj, "main_topic");
61872
+ if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_SET_MAIN_TOPIC", { topic }, gobj);
61873
+ return 0;
61874
+ case "focus": break;
61875
+ default:
61876
+ log_error(`${gobj_short_name(gobj)}: legend action unknown: ${action}`);
61877
+ return -1;
61878
+ }
61517
61879
  let next = priv.focus_topic === topic ? "" : topic;
61518
61880
  gobj_send_event(gobj, "EV_SET_FOCUS_TOPIC", {
61519
61881
  topic: next,
@@ -61574,20 +61936,28 @@ function ac_find_result(gobj, event, kw, src) {
61574
61936
  $count.textContent = "";
61575
61937
  return 0;
61576
61938
  }
61577
- $count.textContent = t("matches", { count: kw && kw.matches || 0 });
61939
+ let text = t("matches", { count: kw && kw.matches || 0 });
61940
+ let hidden = kw && kw.hidden_matches || 0;
61941
+ if (hidden > 0) text += ` (+${hidden} ${t("hidden topics")})`;
61942
+ $count.textContent = text;
61578
61943
  $result.classList.remove("is-hidden");
61579
61944
  return 0;
61580
61945
  }
61581
61946
  function ac_set_focus_topic(gobj, event, kw, src) {
61582
61947
  let priv = gobj.priv;
61583
61948
  priv.focus_topic = kw && kw.topic || "";
61949
+ if (priv.focus_topic) {
61950
+ let hidden = legend_list_attr(gobj, "hidden_topics");
61951
+ if (str_in_list(hidden, priv.focus_topic)) {
61952
+ delete_from_list(hidden, priv.focus_topic);
61953
+ set_legend_list_attr(gobj, "hidden_topics", "EV_SET_HIDDEN_TOPICS", hidden);
61954
+ }
61955
+ }
61584
61956
  if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_FOCUS_TOPIC", {
61585
61957
  topic: priv.focus_topic,
61586
61958
  reveal: kw && kw.reveal || "page"
61587
61959
  }, gobj);
61588
- let $container = gobj_read_attr(gobj, "$container");
61589
- let $legend = $container ? $container.querySelector(".GRAPH_LEGEND") : null;
61590
- if ($legend && !$legend.classList.contains("is-hidden")) refresh_legend(gobj);
61960
+ refresh_legend(gobj);
61591
61961
  return 0;
61592
61962
  }
61593
61963
  /************************************************************
@@ -61771,13 +62141,13 @@ function create_gclass$3(gclass_name) {
61771
62141
  null
61772
62142
  ],
61773
62143
  [
61774
- "EV_TOGGLE_LEGEND",
61775
- ac_toggle_legend,
62144
+ "EV_LEGEND_TOPIC",
62145
+ ac_legend_topic,
61776
62146
  null
61777
62147
  ],
61778
62148
  [
61779
- "EV_LEGEND_TOPIC",
61780
- ac_legend_topic,
62149
+ "EV_LEGEND_STATE",
62150
+ ac_legend_state,
61781
62151
  null
61782
62152
  ],
61783
62153
  [
@@ -61831,8 +62201,8 @@ function create_gclass$3(gclass_name) {
61831
62201
  ["EV_COLLAPSE_ALL", 0],
61832
62202
  ["EV_FIND_RESULT", 0],
61833
62203
  ["EV_LAYOUT_AUTOSET", 0],
61834
- ["EV_TOGGLE_LEGEND", 0],
61835
62204
  ["EV_LEGEND_TOPIC", 0],
62205
+ ["EV_LEGEND_STATE", 0],
61836
62206
  ["EV_TOPIC_SELECTED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
61837
62207
  ["EV_OPERATION_MODE_CHANGED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
61838
62208
  ["EV_RECORD_WRITTEN", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],