@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/README.md +66 -6
- package/dist/gobj-ui.cjs.js +454 -90
- package/dist/gobj-ui.es.js +455 -91
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +160 -16
- package/src/c_yui_treedb_graph.js +268 -82
- package/src/treedb_fold_model.js +171 -15
- package/src/treedb_fold_model.test.js +120 -2
package/package.json
CHANGED
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -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
|
|
2867
|
-
*
|
|
2868
|
-
*
|
|
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
|
|
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
|
-
|
|
2994
|
-
let record = node.record || {};
|
|
3005
|
+
let matches = (topic_name, record) => {
|
|
2995
3006
|
let label = "";
|
|
2996
3007
|
try {
|
|
2997
|
-
label = node_label(priv.descs[
|
|
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
|
-
|
|
3015
|
+
topic_name
|
|
3005
3016
|
].filter((v) => typeof v === "string").join(" ").toLowerCase();
|
|
3006
|
-
|
|
3007
|
-
|
|
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
|
-
|
|
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", {
|
|
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],
|