@yuneta/gobj-ui 7.23.140 → 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.
- package/README.md +99 -26
- package/dist/gobj-ui.cjs.js +304 -106
- package/dist/gobj-ui.es.js +304 -106
- package/package.json +2 -2
- package/src/c_g6_nodes_tree.js +75 -46
- package/src/c_yui_treedb_graph.js +146 -21
- package/src/treedb_fold_model.js +133 -1
- package/src/treedb_fold_model.test.js +80 -0
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* The graph opens FOLDED, like a JSON viewer.
|
|
14
14
|
*
|
|
15
15
|
* Every record of every topic is fetched, but only the ones on
|
|
16
|
-
* screen become G6 nodes: the roots,
|
|
17
|
-
*
|
|
16
|
+
* screen become G6 nodes: the roots, and the MAIN tree open to
|
|
17
|
+
* the reader's level (`fold_level`, `expand_depth` until they
|
|
18
|
+
* step it), one page (`fold_page_size`) of children per hook. Each
|
|
18
19
|
* card carries a pill per hook that has children -- `▸ devices
|
|
19
20
|
* 142` -- which opens or folds that hook; an open hook with more
|
|
20
21
|
* children than its page ends in a `+N` chip that opens the next
|
|
@@ -117,9 +118,10 @@ import {
|
|
|
117
118
|
fold_build_model,
|
|
118
119
|
fold_new_state,
|
|
119
120
|
fold_visible_set,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
fold_expand_to_level,
|
|
122
|
+
fold_open_levels,
|
|
123
|
+
fold_close_levels,
|
|
124
|
+
fold_level_clamp,
|
|
123
125
|
fold_toggle,
|
|
124
126
|
fold_show_more,
|
|
125
127
|
fold_reveal,
|
|
@@ -434,7 +436,8 @@ SDATA(data_type_t.DTP_STRING, "hook_port_position", 0, "bottom", "Hook po
|
|
|
434
436
|
SDATA(data_type_t.DTP_STRING, "fkey_port_position", 0, "top", "Fkey port position"),
|
|
435
437
|
|
|
436
438
|
/*---------------- Folding ----------------*/
|
|
437
|
-
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"),
|
|
439
|
+
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"),
|
|
440
|
+
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"),
|
|
438
441
|
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"),
|
|
439
442
|
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"),
|
|
440
443
|
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"),
|
|
@@ -543,6 +546,7 @@ let PRIVATE_DATA = {
|
|
|
543
546
|
|
|
544
547
|
/*---------------- folding ----------------*/
|
|
545
548
|
expand_depth: 2,
|
|
549
|
+
fold_level: 0,
|
|
546
550
|
fold_page_size: 24,
|
|
547
551
|
hidden_topics: null,
|
|
548
552
|
main_topic: "",
|
|
@@ -9541,6 +9545,13 @@ function publish_legend_state(gobj, visible)
|
|
|
9541
9545
|
main_topic: model.main_topic,
|
|
9542
9546
|
main_chosen: !!gobj_read_str_attr(gobj, "main_topic"),
|
|
9543
9547
|
topics: topics,
|
|
9548
|
+
/* The toolbar's stepper: the tree it walks, where it stands,
|
|
9549
|
+
* and how deep it goes. `levels` 0 = nothing to step through. */
|
|
9550
|
+
fold: {
|
|
9551
|
+
topic: model.main_topic,
|
|
9552
|
+
level: current_fold_level(gobj),
|
|
9553
|
+
levels: model.levels,
|
|
9554
|
+
},
|
|
9544
9555
|
});
|
|
9545
9556
|
}
|
|
9546
9557
|
|
|
@@ -10003,14 +10014,13 @@ function ac_load_data(gobj, event, kw, src)
|
|
|
10003
10014
|
}
|
|
10004
10015
|
|
|
10005
10016
|
if(all_loaded && priv.graph) {
|
|
10006
|
-
/* The tree of the records, opened
|
|
10007
|
-
* roots and their children by default. Only that
|
|
10008
|
-
* G6 nodes -- a treedb of a thousand records opens
|
|
10009
|
-
* handful of cards with a count on each cut. */
|
|
10017
|
+
/* The tree of the records, the main one opened to the reader's
|
|
10018
|
+
* level: the roots and their children by default. Only that
|
|
10019
|
+
* much becomes G6 nodes -- a treedb of a thousand records opens
|
|
10020
|
+
* as a handful of cards with a count on each cut. */
|
|
10010
10021
|
rebuild_fold_model(gobj);
|
|
10011
|
-
|
|
10012
|
-
priv._fold_model, priv._fold_state,
|
|
10013
|
-
gobj_read_integer_attr(gobj, "expand_depth")
|
|
10022
|
+
fold_expand_to_level(
|
|
10023
|
+
priv._fold_model, priv._fold_state, current_fold_level(gobj)
|
|
10014
10024
|
);
|
|
10015
10025
|
|
|
10016
10026
|
/* Before the layout runs, and only now: whether anybody has
|
|
@@ -10622,21 +10632,62 @@ function ac_show_more(gobj, event, kw, src)
|
|
|
10622
10632
|
}
|
|
10623
10633
|
|
|
10624
10634
|
/************************************************************
|
|
10625
|
-
* The
|
|
10626
|
-
*
|
|
10627
|
-
* the
|
|
10635
|
+
* The level of the main tree the toolbar's stepper stands on:
|
|
10636
|
+
* `fold_level` once the reader has stepped, `expand_depth`
|
|
10637
|
+
* until then, clamped to the levels the tree has.
|
|
10628
10638
|
************************************************************/
|
|
10629
|
-
function
|
|
10639
|
+
function current_fold_level(gobj)
|
|
10630
10640
|
{
|
|
10631
10641
|
let priv = gobj.priv;
|
|
10642
|
+
let level = gobj_read_integer_attr(gobj, "fold_level");
|
|
10632
10643
|
|
|
10644
|
+
if(!(level > 0)) {
|
|
10645
|
+
level = gobj_read_integer_attr(gobj, "expand_depth");
|
|
10646
|
+
}
|
|
10633
10647
|
if(!priv._fold_model) {
|
|
10634
|
-
|
|
10648
|
+
return level;
|
|
10649
|
+
}
|
|
10650
|
+
return fold_level_clamp(priv._fold_model, level);
|
|
10651
|
+
}
|
|
10652
|
+
|
|
10653
|
+
/************************************************************
|
|
10654
|
+
* The toolbar's stepper: one level more or one less of the
|
|
10655
|
+
* MAIN tree (the host owns the number and persists it; this
|
|
10656
|
+
* applies it). Deeper opens the hooks above the new level,
|
|
10657
|
+
* shallower folds from it down -- a level is a floor, not a
|
|
10658
|
+
* picture, so what the reader opened by its pill stays (see
|
|
10659
|
+
* treedb_fold_model). Replaces expand all / collapse all
|
|
10660
|
+
* (7.23.141): the whole treedb at once was the pile, and
|
|
10661
|
+
* closing threw away everything the reader had opened.
|
|
10662
|
+
************************************************************/
|
|
10663
|
+
function ac_set_fold_level(gobj, event, kw, src)
|
|
10664
|
+
{
|
|
10665
|
+
let priv = gobj.priv;
|
|
10666
|
+
let level = Number(kw && kw.level);
|
|
10667
|
+
|
|
10668
|
+
if(!(level > 0)) {
|
|
10669
|
+
log_error(`${gobj_short_name(gobj)}: fold level '${kw && kw.level}' is not a level`);
|
|
10635
10670
|
return -1;
|
|
10636
10671
|
}
|
|
10672
|
+
if(!priv._fold_model) {
|
|
10673
|
+
gobj_write_attr(gobj, "fold_level", level);
|
|
10674
|
+
return 0; /* applied when the records arrive */
|
|
10675
|
+
}
|
|
10676
|
+
|
|
10677
|
+
let from = current_fold_level(gobj);
|
|
10678
|
+
level = fold_level_clamp(priv._fold_model, level);
|
|
10679
|
+
gobj_write_attr(gobj, "fold_level", level);
|
|
10680
|
+
if(level === from) {
|
|
10681
|
+
return 0;
|
|
10682
|
+
}
|
|
10683
|
+
|
|
10637
10684
|
let keep = fold_keep_node(gobj);
|
|
10638
10685
|
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
10639
|
-
|
|
10686
|
+
if(level > from) {
|
|
10687
|
+
fold_open_levels(priv._fold_model, priv._fold_state, level);
|
|
10688
|
+
} else {
|
|
10689
|
+
fold_close_levels(priv._fold_model, priv._fold_state, level);
|
|
10690
|
+
}
|
|
10640
10691
|
reconcile_fold(gobj, {keep: keep, keep_vp: vp});
|
|
10641
10692
|
|
|
10642
10693
|
return 0;
|
|
@@ -10669,31 +10720,12 @@ function fold_keep_node(gobj)
|
|
|
10669
10720
|
return "";
|
|
10670
10721
|
}
|
|
10671
10722
|
|
|
10672
|
-
/************************************************************
|
|
10673
|
-
* The roots alone.
|
|
10674
|
-
************************************************************/
|
|
10675
|
-
function ac_collapse_all(gobj, event, kw, src)
|
|
10676
|
-
{
|
|
10677
|
-
let priv = gobj.priv;
|
|
10678
|
-
|
|
10679
|
-
if(!priv._fold_model) {
|
|
10680
|
-
log_error(`${gobj_short_name(gobj)}: collapse all with no tree loaded`);
|
|
10681
|
-
return -1;
|
|
10682
|
-
}
|
|
10683
|
-
let keep = fold_keep_node(gobj);
|
|
10684
|
-
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
10685
|
-
fold_collapse_all(priv._fold_model, priv._fold_state);
|
|
10686
|
-
reconcile_fold(gobj, {keep: keep, keep_vp: vp});
|
|
10687
|
-
|
|
10688
|
-
return 0;
|
|
10689
|
-
}
|
|
10690
|
-
|
|
10691
10723
|
/************************************************************
|
|
10692
10724
|
* The legend's three settings, from the host, which persists
|
|
10693
10725
|
* them. Each rebuilds the tree; the fold state is kept -- it is
|
|
10694
10726
|
* keyed by group, and a group that is gone is never read -- except
|
|
10695
10727
|
* for a new MAIN topic, whose roots are other roots: the tree
|
|
10696
|
-
* opens again at
|
|
10728
|
+
* opens again at the reader's level, now counted from them.
|
|
10697
10729
|
************************************************************/
|
|
10698
10730
|
function ac_set_hidden_topics(gobj, event, kw, src)
|
|
10699
10731
|
{
|
|
@@ -10724,9 +10756,8 @@ function ac_set_main_topic(gobj, event, kw, src)
|
|
|
10724
10756
|
let keep = fold_keep_node(gobj);
|
|
10725
10757
|
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
10726
10758
|
rebuild_fold_model(gobj);
|
|
10727
|
-
|
|
10728
|
-
priv._fold_model, priv._fold_state,
|
|
10729
|
-
gobj_read_integer_attr(gobj, "expand_depth")
|
|
10759
|
+
fold_expand_to_level(
|
|
10760
|
+
priv._fold_model, priv._fold_state, current_fold_level(gobj)
|
|
10730
10761
|
);
|
|
10731
10762
|
reconcile_fold(gobj, {relayout: true, keep: keep, keep_vp: vp});
|
|
10732
10763
|
return 0;
|
|
@@ -11404,8 +11435,7 @@ function create_gclass(gclass_name)
|
|
|
11404
11435
|
["EV_FIND_NODES", ac_find_nodes, null],
|
|
11405
11436
|
["EV_TOGGLE_FOLD", ac_toggle_fold, null],
|
|
11406
11437
|
["EV_SHOW_MORE", ac_show_more, null],
|
|
11407
|
-
["
|
|
11408
|
-
["EV_COLLAPSE_ALL", ac_collapse_all, null],
|
|
11438
|
+
["EV_SET_FOLD_LEVEL", ac_set_fold_level, null],
|
|
11409
11439
|
["EV_SET_HIDDEN_TOPICS", ac_set_hidden_topics, null],
|
|
11410
11440
|
["EV_SET_MAIN_TOPIC", ac_set_main_topic, null],
|
|
11411
11441
|
["EV_SET_LOOSE_TOPICS", ac_set_loose_topics, null],
|
|
@@ -11472,8 +11502,7 @@ function create_gclass(gclass_name)
|
|
|
11472
11502
|
["EV_CAMERA_CHANGED", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_NO_WARN_SUBS],
|
|
11473
11503
|
["EV_TOGGLE_FOLD", 0],
|
|
11474
11504
|
["EV_SHOW_MORE", 0],
|
|
11475
|
-
["
|
|
11476
|
-
["EV_COLLAPSE_ALL", 0],
|
|
11505
|
+
["EV_SET_FOLD_LEVEL", 0],
|
|
11477
11506
|
["EV_SET_HIDDEN_TOPICS", 0],
|
|
11478
11507
|
["EV_SET_MAIN_TOPIC", 0],
|
|
11479
11508
|
["EV_SET_LOOSE_TOPICS", 0],
|
|
@@ -82,7 +82,6 @@ import {
|
|
|
82
82
|
|
|
83
83
|
import {attach_clear} from "./yui_inputs.js";
|
|
84
84
|
import {register_c_g6_nodes_tree} from "./c_g6_nodes_tree.js";
|
|
85
|
-
import {yui_graph_fold_items} from "./yui_graph_camera.js";
|
|
86
85
|
import {yui_toolbar, yui_toolbar_icon, YUI_TOOLBAR_ICON_SIZE} from "./yui_toolbar.js";
|
|
87
86
|
import {
|
|
88
87
|
removeChildElements,
|
|
@@ -137,6 +136,7 @@ SDATA(data_type_t.DTP_LIST, "loose_topics", sdata_flag_t.SDF_PERSIST, "[
|
|
|
137
136
|
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"),
|
|
138
137
|
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"),
|
|
139
138
|
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"),
|
|
139
|
+
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"),
|
|
140
140
|
|
|
141
141
|
/*---------------- Remote Connection ----------------*/
|
|
142
142
|
SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
|
|
@@ -269,6 +269,7 @@ function mt_create(gobj)
|
|
|
269
269
|
hook_port_position: "bottom",
|
|
270
270
|
fkey_port_position: "top",
|
|
271
271
|
expand_depth: gobj_read_integer_attr(gobj, "expand_depth"),
|
|
272
|
+
fold_level: gobj_read_integer_attr(gobj, "fold_level"),
|
|
272
273
|
fold_page_size: gobj_read_integer_attr(gobj, "fold_page_size"),
|
|
273
274
|
minimap_min_nodes: gobj_read_integer_attr(gobj, "minimap_min_nodes"),
|
|
274
275
|
with_gridline: gobj_read_bool_attr(gobj, "with_gridline"),
|
|
@@ -671,6 +672,117 @@ function refresh_node_shape_buttons(gobj, $root)
|
|
|
671
672
|
}
|
|
672
673
|
}
|
|
673
674
|
|
|
675
|
+
/************************************************************
|
|
676
|
+
* The fold STEPPER: one level less, where the tree stands, one
|
|
677
|
+
* level more.
|
|
678
|
+
*
|
|
679
|
+
* It replaces the fold pair (7.23.141). `expand all` opened every
|
|
680
|
+
* hook of the treedb -- the pile, on purpose, which on a treedb
|
|
681
|
+
* of thousands is a pile nobody reads -- and `collapse all` threw
|
|
682
|
+
* away everything the reader had opened. The stepper walks the
|
|
683
|
+
* MAIN tree a level at a time, the way the frontend view opens
|
|
684
|
+
* one level per press: `+` opens the next level under the main
|
|
685
|
+
* topic's roots, through every topic that is not hidden; `-`
|
|
686
|
+
* folds the deepest one. Pages and pills the reader opened stay.
|
|
687
|
+
*
|
|
688
|
+
* The readout says what the two buttons act on, which no chevron
|
|
689
|
+
* can: the main topic -- its swatch and its name, which is DATA
|
|
690
|
+
* and never translated -- and `level/levels`. On a phone the name
|
|
691
|
+
* goes and the numbers stay. The level is the reader's, per
|
|
692
|
+
* treedb: persisted (`fold_level`) and handed to the engine on
|
|
693
|
+
* creation, so a reload opens where it was left.
|
|
694
|
+
*
|
|
695
|
+
* Both buttons are born disabled: nothing can be stepped until
|
|
696
|
+
* the engine has said how deep the tree goes (EV_LEGEND_STATE).
|
|
697
|
+
************************************************************/
|
|
698
|
+
function fold_level_items(gobj, wide)
|
|
699
|
+
{
|
|
700
|
+
let step_button = (cls, icon, key, event_name) => {
|
|
701
|
+
return ['button', {class: `${cls} button`, type: 'button',
|
|
702
|
+
style: {height: wide, width: '2.5em'},
|
|
703
|
+
disabled: true,
|
|
704
|
+
title: t(key), 'data-i18n-title': key,
|
|
705
|
+
'aria-label': t(key), 'data-i18n-aria-label': key},
|
|
706
|
+
yui_toolbar_icon(icon),
|
|
707
|
+
{
|
|
708
|
+
click: (evt) => {
|
|
709
|
+
evt.stopPropagation();
|
|
710
|
+
gobj_send_event(gobj, event_name, {}, gobj);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
];
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
return [
|
|
717
|
+
['div', {class: 'GRAPH_FOLD_STEPPER buttons has-addons',
|
|
718
|
+
style: 'margin:0; flex:0 0 auto; flex-wrap:nowrap; align-items:stretch;'}, [
|
|
719
|
+
/* The same two chevrons the pills speak: `▸` folded, `▾` open. */
|
|
720
|
+
step_button('GRAPH_FOLD_COLLAPSE', 'yi-chevron-right',
|
|
721
|
+
'collapse one level', 'EV_COLLAPSE_LEVEL'),
|
|
722
|
+
/* A `div` with the button skin, like the legend's main
|
|
723
|
+
* chip: a readout between two buttons of one group, not
|
|
724
|
+
* a third button, and at full strength -- `is-static`
|
|
725
|
+
* is Bulma's grey of something switched off. */
|
|
726
|
+
['div', {class: 'GRAPH_FOLD_LEVEL button',
|
|
727
|
+
style: {height: wide, cursor: 'default', gap: '.35rem'},
|
|
728
|
+
role: 'status',
|
|
729
|
+
title: t('fold level'), 'data-i18n-title': 'fold level'}, [
|
|
730
|
+
['span', {class: 'GRAPH_FOLD_SWATCH',
|
|
731
|
+
style: 'display:none; width:.8rem; height:.8rem; border-radius:3px; ' +
|
|
732
|
+
'flex:0 0 auto; border:1px solid rgba(0,0,0,.25);'}],
|
|
733
|
+
['span', {class: 'GRAPH_FOLD_TOPIC is-hidden-mobile',
|
|
734
|
+
style: 'max-width:10em; overflow:hidden; text-overflow:ellipsis; ' +
|
|
735
|
+
'white-space:nowrap; font-weight:600;'}],
|
|
736
|
+
/* The WORD, not just the numbers: the legend chip under
|
|
737
|
+
* it says `departments 2/3` too, and there it means
|
|
738
|
+
* records on screen. */
|
|
739
|
+
['span', {class: 'GRAPH_FOLD_WORD', i18n: 'level'}, 'level'],
|
|
740
|
+
['span', {class: 'GRAPH_FOLD_COUNT',
|
|
741
|
+
style: 'font-variant-numeric:tabular-nums;'}, '–'],
|
|
742
|
+
]],
|
|
743
|
+
step_button('GRAPH_FOLD_EXPAND', 'yi-chevron-down',
|
|
744
|
+
'expand one level', 'EV_EXPAND_LEVEL'),
|
|
745
|
+
]],
|
|
746
|
+
];
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/* Paint the stepper from the engine's last word (EV_LEGEND_STATE). */
|
|
750
|
+
function refresh_fold_level(gobj)
|
|
751
|
+
{
|
|
752
|
+
let priv = gobj.priv;
|
|
753
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
754
|
+
if(!$container) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
let $readout = $container.querySelector('.GRAPH_FOLD_LEVEL');
|
|
758
|
+
if(!$readout) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
let state = priv._legend_state || {};
|
|
763
|
+
let fold = state.fold || {};
|
|
764
|
+
let levels = (fold.levels > 0)? fold.levels : 0;
|
|
765
|
+
let level = (fold.level > 0)? fold.level : 0;
|
|
766
|
+
let topic = fold.topic || "";
|
|
767
|
+
let entry = (is_array(state.topics)? state.topics : []).find((e) => e.topic === topic);
|
|
768
|
+
|
|
769
|
+
let $swatch = $readout.querySelector('.GRAPH_FOLD_SWATCH');
|
|
770
|
+
$swatch.style.display = topic? 'inline-block' : 'none';
|
|
771
|
+
$swatch.style.background = (entry && entry.color) || '#94a3b8';
|
|
772
|
+
$readout.querySelector('.GRAPH_FOLD_TOPIC').textContent = topic;
|
|
773
|
+
$readout.querySelector('.GRAPH_FOLD_COUNT').textContent =
|
|
774
|
+
levels? `${level}/${levels}` : '–';
|
|
775
|
+
|
|
776
|
+
let $collapse = $container.querySelector('.GRAPH_FOLD_COLLAPSE');
|
|
777
|
+
let $expand = $container.querySelector('.GRAPH_FOLD_EXPAND');
|
|
778
|
+
if($collapse) {
|
|
779
|
+
$collapse.disabled = !(levels && level > 1);
|
|
780
|
+
}
|
|
781
|
+
if($expand) {
|
|
782
|
+
$expand.disabled = !(levels && level < levels);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
674
786
|
function make_toolbar(gobj)
|
|
675
787
|
{
|
|
676
788
|
let priv = gobj.priv;
|
|
@@ -741,11 +853,9 @@ function make_toolbar(gobj)
|
|
|
741
853
|
* Center: how the graph is SHOWN.
|
|
742
854
|
*/
|
|
743
855
|
let center_items = [
|
|
744
|
-
/* The fold
|
|
745
|
-
*
|
|
746
|
-
|
|
747
|
-
* way back to the default depth. */
|
|
748
|
-
...yui_graph_fold_items(gobj, gobj_read_str_attr(gobj, "wide")),
|
|
856
|
+
/* The fold stepper: one level less or more of the main tree,
|
|
857
|
+
* and between the two, which tree and which level. */
|
|
858
|
+
...fold_level_items(gobj, gobj_read_str_attr(gobj, "wide")),
|
|
749
859
|
|
|
750
860
|
/* The view of the nodes -- full cards, one-line pills or
|
|
751
861
|
* figures -- and the name under a figure. */
|
|
@@ -2795,6 +2905,7 @@ function ac_legend_state(gobj, event, kw, src)
|
|
|
2795
2905
|
let priv = gobj.priv;
|
|
2796
2906
|
priv._legend_state = kw || null;
|
|
2797
2907
|
refresh_legend(gobj);
|
|
2908
|
+
refresh_fold_level(gobj);
|
|
2798
2909
|
return 0;
|
|
2799
2910
|
}
|
|
2800
2911
|
|
|
@@ -2964,23 +3075,37 @@ function ac_find_nodes(gobj, event, kw, src)
|
|
|
2964
3075
|
}
|
|
2965
3076
|
|
|
2966
3077
|
/************************************************************
|
|
2967
|
-
*
|
|
2968
|
-
*
|
|
3078
|
+
* One press of the fold stepper (EV_EXPAND_LEVEL /
|
|
3079
|
+
* EV_COLLAPSE_LEVEL): the level moves by one, is SAVED (the
|
|
3080
|
+
* reader's, per treedb) and reaches the G6 child, which owns
|
|
3081
|
+
* the tree.
|
|
3082
|
+
*
|
|
3083
|
+
* The readout moves at once, not when the engine speaks again:
|
|
3084
|
+
* a reconcile is asynchronous, and a second press that came
|
|
3085
|
+
* before it would step from the level already left -- two
|
|
3086
|
+
* presses, one level.
|
|
2969
3087
|
************************************************************/
|
|
2970
|
-
function
|
|
3088
|
+
function ac_fold_level_step(gobj, event, kw, src)
|
|
2971
3089
|
{
|
|
2972
3090
|
let priv = gobj.priv;
|
|
2973
|
-
|
|
2974
|
-
|
|
3091
|
+
let fold = priv._legend_state && priv._legend_state.fold;
|
|
3092
|
+
|
|
3093
|
+
if(!fold || !(fold.levels > 0)) {
|
|
3094
|
+
log_error(`${gobj_short_name(gobj)}: ${event} with no tree to step through`);
|
|
3095
|
+
return -1;
|
|
3096
|
+
}
|
|
3097
|
+
let level = fold.level + ((event === "EV_EXPAND_LEVEL")? 1 : -1);
|
|
3098
|
+
if(level < 1 || level > fold.levels) {
|
|
3099
|
+
log_error(`${gobj_short_name(gobj)}: ${event}: level ${level} out of 1..${fold.levels}`);
|
|
3100
|
+
return -1;
|
|
2975
3101
|
}
|
|
2976
|
-
return 0;
|
|
2977
|
-
}
|
|
2978
3102
|
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3103
|
+
gobj_write_attr(gobj, "fold_level", level);
|
|
3104
|
+
gobj_save_persistent_attrs(gobj, "fold_level");
|
|
3105
|
+
fold.level = level;
|
|
3106
|
+
refresh_fold_level(gobj);
|
|
2982
3107
|
if(priv.gobj_nodes_tree) {
|
|
2983
|
-
gobj_send_event(priv.gobj_nodes_tree, "
|
|
3108
|
+
gobj_send_event(priv.gobj_nodes_tree, "EV_SET_FOLD_LEVEL", {level: level}, gobj);
|
|
2984
3109
|
}
|
|
2985
3110
|
return 0;
|
|
2986
3111
|
}
|
|
@@ -3181,8 +3306,8 @@ function create_gclass(gclass_name)
|
|
|
3181
3306
|
["EV_SET_OPERATION_MODE", ac_set_operation_mode, null],
|
|
3182
3307
|
["EV_SET_FOCUS_TOPIC", ac_set_focus_topic, null],
|
|
3183
3308
|
["EV_FIND_NODES", ac_find_nodes, null],
|
|
3184
|
-
["
|
|
3185
|
-
["
|
|
3309
|
+
["EV_EXPAND_LEVEL", ac_fold_level_step, null],
|
|
3310
|
+
["EV_COLLAPSE_LEVEL", ac_fold_level_step, null],
|
|
3186
3311
|
["EV_SET_NODE_MODE", ac_set_node_mode, null],
|
|
3187
3312
|
["EV_TOGGLE_NODE_LABELS", ac_toggle_node_labels, null],
|
|
3188
3313
|
["EV_LAYOUT_AUTOSET", ac_layout_autoset, null],
|
|
@@ -3226,8 +3351,8 @@ function create_gclass(gclass_name)
|
|
|
3226
3351
|
["EV_SET_OPERATION_MODE", 0],
|
|
3227
3352
|
["EV_SET_FOCUS_TOPIC", 0],
|
|
3228
3353
|
["EV_FIND_NODES", 0],
|
|
3229
|
-
["
|
|
3230
|
-
["
|
|
3354
|
+
["EV_EXPAND_LEVEL", 0],
|
|
3355
|
+
["EV_COLLAPSE_LEVEL", 0],
|
|
3231
3356
|
["EV_SET_NODE_MODE", 0],
|
|
3232
3357
|
["EV_TOGGLE_NODE_LABELS", 0],
|
|
3233
3358
|
["EV_FIND_RESULT", 0],
|
package/src/treedb_fold_model.js
CHANGED
|
@@ -281,7 +281,7 @@ export function fold_build_model(descs, records, opts)
|
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
|
|
284
|
+
let model = {
|
|
285
285
|
nodes: nodes,
|
|
286
286
|
groups: groups,
|
|
287
287
|
topics: topics,
|
|
@@ -291,7 +291,67 @@ export function fold_build_model(descs, records, opts)
|
|
|
291
291
|
linked: linked, /* Set of topics the schema hangs from main */
|
|
292
292
|
loose: loose, /* {topic: [key, ...]} records that should hang and do not */
|
|
293
293
|
loose_shown: loose_shown,
|
|
294
|
+
level_of: null, /* Map<node_key, level> of the MAIN tree, see below */
|
|
295
|
+
levels: 0, /* its deepest level (0 = no tree to step through) */
|
|
294
296
|
};
|
|
297
|
+
count_levels(model);
|
|
298
|
+
return model;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/************************************************************
|
|
302
|
+
* The LEVELS of the main tree: the roots of the main topic --
|
|
303
|
+
* and of the topics hung from it, whose roots are the loose
|
|
304
|
+
* records the reader asked for -- are level 1, their children
|
|
305
|
+
* level 2, and so on down every hook of every topic that is not
|
|
306
|
+
* hidden. A node reached by two paths takes the shallowest.
|
|
307
|
+
*
|
|
308
|
+
* With no main topic every topic is a tree of its own and all
|
|
309
|
+
* of them count. A topic the schema does not tie to the main
|
|
310
|
+
* one is a separate tree, and has no level: the stepper of the
|
|
311
|
+
* toolbar speaks for the main tree and nothing else, those
|
|
312
|
+
* trees are opened by their own pills.
|
|
313
|
+
************************************************************/
|
|
314
|
+
function count_levels(model)
|
|
315
|
+
{
|
|
316
|
+
let level_of = new Map();
|
|
317
|
+
let queue = [];
|
|
318
|
+
let deepest = 0;
|
|
319
|
+
|
|
320
|
+
for(let topic_name of model.topics) {
|
|
321
|
+
if(model.main_topic && !model.linked.has(topic_name)) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
let roots = model.groups.get(fold_root_group_key(topic_name)) || [];
|
|
325
|
+
for(let key of roots) {
|
|
326
|
+
if(!level_of.has(key)) {
|
|
327
|
+
level_of.set(key, 1);
|
|
328
|
+
queue.push(key);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
let head = 0;
|
|
333
|
+
while(head < queue.length) {
|
|
334
|
+
let key = queue[head++];
|
|
335
|
+
let lv = level_of.get(key);
|
|
336
|
+
if(lv > deepest) {
|
|
337
|
+
deepest = lv;
|
|
338
|
+
}
|
|
339
|
+
let node = model.nodes.get(key);
|
|
340
|
+
if(!node) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
for(let hook of Object.keys(node.hooks)) {
|
|
344
|
+
for(let child of node.hooks[hook]) {
|
|
345
|
+
if(!level_of.has(child)) {
|
|
346
|
+
level_of.set(child, lv + 1);
|
|
347
|
+
queue.push(child);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
model.level_of = level_of;
|
|
354
|
+
model.levels = deepest;
|
|
295
355
|
}
|
|
296
356
|
|
|
297
357
|
/************************************************************
|
|
@@ -565,6 +625,78 @@ export function fold_expand_to_depth(model, state, depth)
|
|
|
565
625
|
}
|
|
566
626
|
}
|
|
567
627
|
|
|
628
|
+
/************************************************************
|
|
629
|
+
* The toolbar's STEPPER over the levels of the main tree (see
|
|
630
|
+
* count_levels): level N shows the roots and N-1 levels under
|
|
631
|
+
* them. A level is a floor of what is open, not a picture that
|
|
632
|
+
* replaces it:
|
|
633
|
+
*
|
|
634
|
+
* opening to N opens, on its first page, every hook of
|
|
635
|
+
* every node above level N that is folded;
|
|
636
|
+
* what the reader opened, and every page
|
|
637
|
+
* already shown, stays
|
|
638
|
+
* closing to N folds every hook of every node at level N
|
|
639
|
+
* or below, and touches nothing above it
|
|
640
|
+
*
|
|
641
|
+
* A separate tree (a topic not tied to the main one) has no
|
|
642
|
+
* level, so neither ever touches it.
|
|
643
|
+
************************************************************/
|
|
644
|
+
export function fold_level_clamp(model, level)
|
|
645
|
+
{
|
|
646
|
+
let top = (model.levels > 0)? model.levels : 1;
|
|
647
|
+
level = Math.floor(Number(level)) || 1;
|
|
648
|
+
if(level < 1) {
|
|
649
|
+
return 1;
|
|
650
|
+
}
|
|
651
|
+
if(level > top) {
|
|
652
|
+
return top;
|
|
653
|
+
}
|
|
654
|
+
return level;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export function fold_open_levels(model, state, level)
|
|
658
|
+
{
|
|
659
|
+
for(let [key, lv] of model.level_of) {
|
|
660
|
+
if(lv >= level) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
let node = model.nodes.get(key);
|
|
664
|
+
if(!node) {
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
for(let hook of Object.keys(node.hooks)) {
|
|
668
|
+
let group_key = fold_group_key(key, hook);
|
|
669
|
+
if(node.hooks[hook].length > 0 && !fold_is_open(state, group_key)) {
|
|
670
|
+
state.shown.set(group_key, state.page_size);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export function fold_close_levels(model, state, level)
|
|
677
|
+
{
|
|
678
|
+
for(let [key, lv] of model.level_of) {
|
|
679
|
+
if(lv < level) {
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
let node = model.nodes.get(key);
|
|
683
|
+
if(!node) {
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
for(let hook of Object.keys(node.hooks)) {
|
|
687
|
+
state.shown.delete(fold_group_key(key, hook));
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/* From scratch: the roots of every tree on their first page, and
|
|
693
|
+
* the main tree open down to `level`. What a load opens. */
|
|
694
|
+
export function fold_expand_to_level(model, state, level)
|
|
695
|
+
{
|
|
696
|
+
fold_collapse_all(model, state);
|
|
697
|
+
fold_open_levels(model, state, fold_level_clamp(model, level));
|
|
698
|
+
}
|
|
699
|
+
|
|
568
700
|
/************************************************************
|
|
569
701
|
* The set of node keys on screen, from the roots down through
|
|
570
702
|
* every open group -- and only through open groups whose parent
|