@yuneta/gobj-ui 7.23.156 → 7.23.158

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.156",
3
+ "version": "7.23.158",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -132,11 +132,8 @@ import {
132
132
  fold_split_group_key,
133
133
  fold_root_group_key,
134
134
  } from "./treedb_fold_model.js";
135
- import {layout_tree, layout_radial, layout_compact} from "./treedb_layout.js";
136
- import {
137
- elbow_lane, elbow_rank, elbow_route, elbow_combs, curve_hits, rounded_path, CURVE_RADIUS,
138
- ELBOW_STEP,
139
- } from "./treedb_elbow.js";
135
+ import {layout_tree, layout_radial} from "./treedb_layout.js";
136
+ import {elbow_lane, elbow_route} from "./treedb_elbow.js";
140
137
 
141
138
  import {
142
139
  BaseLayout,
@@ -151,8 +148,6 @@ import {
151
148
  Toolbar,
152
149
  register,
153
150
  Polyline,
154
- Cubic,
155
- CubicHorizontal,
156
151
  } from '@antv/g6';
157
152
 
158
153
  import {Circle as CircleGeometry, Rect as RectGeometry} from '@antv/g';
@@ -357,16 +352,6 @@ const _layouts = {
357
352
  nodesep: 18,
358
353
  ranksep: 90,
359
354
  },
360
- /* The same tree packed by CONTOUR (Moen, as mxGraph drew it): a
361
- * closed branch sits beside an open one at the depth they share,
362
- * instead of beside the whole block of the open one. Same rows
363
- * and order as `treedb-tree` (treedb_layout.js). */
364
- "compact-tree": {
365
- type: 'treedb-compact',
366
- direction: 'TB',
367
- nodesep: 18,
368
- ranksep: 90,
369
- },
370
355
  "dagre": {
371
356
  type: 'dagre',
372
357
  rankdir: 'LR',
@@ -558,8 +543,9 @@ let PRIVATE_DATA = {
558
543
  // it exists to diff the repaint)
559
544
  _pending_focus_topic: null, // focus requested before data was loaded
560
545
  _pending_focus_all: false, // ...and whether it reveals the whole topic
561
- _find_hidden_matches: 0, // matches of the last find in HIDDEN topics
562
- _pending_find: null, // find requested before data was loaded
546
+ _find_term: "", // the find box's term, repainted after every reconcile
547
+ _find_current: "", // node id the last Enter of the find centred on
548
+ _find_found: null, // ...and its last count (see find_matches)
563
549
  _layout_asked: "", // layout the host asked for at create (see mt_create)
564
550
  _nodes_total: 0, // records of the treedb (auto_layout)
565
551
  _nodes_placed: 0, // ...of which carry a saved position
@@ -944,11 +930,8 @@ function register_layouts(gobj)
944
930
  _g6_extensions_registered = true;
945
931
  register(ExtensionCategory.LAYOUT, 'manual', ManualLayout);
946
932
  register(ExtensionCategory.LAYOUT, 'treedb-tree', TreedbTreeLayout);
947
- register(ExtensionCategory.LAYOUT, 'treedb-compact', TreedbCompactLayout);
948
933
  register(ExtensionCategory.EDGE, 'treedb-elbow-v', TreedbElbowV);
949
934
  register(ExtensionCategory.EDGE, 'treedb-elbow-h', TreedbElbowH);
950
- register(ExtensionCategory.EDGE, 'treedb-curve-v', TreedbCurveV);
951
- register(ExtensionCategory.EDGE, 'treedb-curve-h', TreedbCurveH);
952
935
  register(ExtensionCategory.LAYOUT, 'treedb-radial', TreedbRadialLayout);
953
936
  register(ExtensionCategory.NODE, 'light', LightNode);
954
937
  register(ExtensionCategory.NODE, 'treedb-card', TreedbCard);
@@ -2049,20 +2032,16 @@ function layout_direction(gobj)
2049
2032
  return str_in_list(LR_LAYOUTS, gobj.priv.layout)? "LR" : "TB";
2050
2033
  }
2051
2034
 
2052
- /* An elbow when the reader asked for one, a curve otherwise -- ours
2053
- * where the layout has rows, which keeps off the cards; G6's plain
2054
- * cubic on a layout with no rows to go round by. */
2035
+ /* A curve, or an elbow when the reader asked for one and the layout
2036
+ * has rows to run it between. */
2055
2037
  function edge_type_for(gobj)
2056
2038
  {
2057
2039
  let lr = (layout_direction(gobj) === "LR");
2058
2040
  let rowless = gobj_read_attr(gobj, "rowless_layouts") || [];
2059
- let rows = !str_in_list(rowless, gobj.priv.layout);
2060
- if(rows && gobj_read_str_attr(gobj, "edge_shape") === "elbow") {
2041
+ if(gobj_read_str_attr(gobj, "edge_shape") === "elbow" &&
2042
+ !str_in_list(rowless, gobj.priv.layout)) {
2061
2043
  return lr? "treedb-elbow-h" : "treedb-elbow-v";
2062
2044
  }
2063
- if(rows) {
2064
- return lr? "treedb-curve-h" : "treedb-curve-v";
2065
- }
2066
2045
  return lr? "cubic-horizontal" : "cubic";
2067
2046
  }
2068
2047
 
@@ -3676,6 +3655,18 @@ function graph_focus_topic(gobj, topic, reveal_all)
3676
3655
  let model = priv._fold_model;
3677
3656
  let state = priv._fold_state;
3678
3657
 
3658
+ /* The two share the highlight slot. A topic takes it from the
3659
+ * find; an EMPTY topic (nothing focused) leaves a live find
3660
+ * alone, or clearing the legend's mark would wipe the find. */
3661
+ if(empty_string(topic) && priv._find_term) {
3662
+ priv._focus_topic = null;
3663
+ return;
3664
+ }
3665
+ if(!empty_string(topic)) {
3666
+ priv._find_term = "";
3667
+ priv._find_current = "";
3668
+ }
3669
+
3679
3670
  /* "Show me these": the ones on screen are highlighted, and the
3680
3671
  * hidden ones are opened up to. How many depends on who asks.
3681
3672
  * A click on the LEGEND (`reveal_all`) means every record of the
@@ -3711,18 +3702,18 @@ function graph_focus_topic(gobj, topic, reveal_all)
3711
3702
  priv._focus_topic = topic || null;
3712
3703
 
3713
3704
  reconcile_fold(gobj, {}).then(() => {
3714
- apply_focus_ids(gobj, keys, "focus topic");
3705
+ apply_focus_ids(gobj, keys, "focus topic", true);
3715
3706
  });
3716
3707
  }
3717
3708
 
3718
3709
  /************************************************************
3719
- * Paint the highlight on these records (by model key) and put
3720
- * the first of them in the middle. Shared by the find and the
3721
- * topic focus, after the reconcile that brought them on screen.
3722
- * Keys that are still off screen are skipped: a match beyond the
3723
- * page a reveal opened is counted, not painted.
3710
+ * Paint the highlight on these records (by model key) and, with
3711
+ * `center`, put the first of them in the middle. Shared by the
3712
+ * find and the topic focus. Keys that are not on screen are
3713
+ * skipped: a match beyond the page a reveal opened is counted,
3714
+ * not painted.
3724
3715
  ************************************************************/
3725
- function apply_focus_ids(gobj, keys, what)
3716
+ function apply_focus_ids(gobj, keys, what, center)
3726
3717
  {
3727
3718
  let priv = gobj.priv;
3728
3719
  let graph = priv.graph;
@@ -3756,7 +3747,7 @@ function apply_focus_ids(gobj, keys, what)
3756
3747
  graph.setElementState(states);
3757
3748
  }
3758
3749
  apply_node_highlight(gobj, prev_ids, ids);
3759
- if(ids.length) {
3750
+ if(center && ids.length) {
3760
3751
  yui_graph_center_on(graph, ids[0]);
3761
3752
  }
3762
3753
  } catch(e) {
@@ -3775,94 +3766,186 @@ function node_id_of_key(gobj, key)
3775
3766
  }
3776
3767
 
3777
3768
  /************************************************************
3778
- * Find the nodes whose name, id or topic contains `term`, highlight
3779
- * them with the same amber 'active' state the topic focus uses, and
3780
- * centre the viewport on them. Returns how many matched, which is the
3781
- * half the toolbar needs: a search that finds nothing must SAY so,
3782
- * because a graph that did not move is also what a graph looks like
3783
- * when the match is off-screen.
3769
+ * The find box: highlight the cards ON SCREEN whose name, id or
3770
+ * topic contains `term`, and change nothing else -- no group is
3771
+ * opened, no layout runs, the camera does not move. The graph the
3772
+ * reader arranged is the graph they are searching; emptying the
3773
+ * box takes the amber off and leaves it exactly as it was.
3774
+ *
3775
+ * It used to search the whole treedb and unfold a page of what it
3776
+ * found, which re-laid the graph out on every keystroke and left
3777
+ * the unfolded groups behind when the box was cleared. Opening up
3778
+ * to a topic is the legend's focus (graph_focus_topic); this only
3779
+ * LOOKS.
3780
+ *
3781
+ * The term is kept and repainted after every reconcile (see
3782
+ * reconcile_fold_now): a group opened by hand shows its matches
3783
+ * already lit, and the count follows what is on screen.
3784
3784
  *
3785
3785
  * It shares the highlight slot with graph_focus_topic on purpose —
3786
- * two amber sets at once would say nothing about either. An empty
3787
- * term clears it. Matching reads the node's LABEL and not only its
3788
- * id: on a topic keyed by rowid or by a qualified path, the id is a
3789
- * counter and the name a human knows is elsewhere (see node_label).
3786
+ * two amber sets at once would say nothing about either. Matching
3787
+ * reads the node's LABEL and not only its id: on a topic keyed by
3788
+ * rowid or by a qualified path, the id is a counter and the name a
3789
+ * human knows is elsewhere (see node_label).
3790
3790
  ************************************************************/
3791
3791
  function graph_find_nodes(gobj, term)
3792
3792
  {
3793
3793
  let priv = gobj.priv;
3794
- let graph = priv.graph;
3795
- if(!graph || !priv.graph_rendered || !priv._fold_model) {
3796
- priv._pending_find = term;
3797
- return 0;
3794
+ let next = empty_string(term)? "" : String(term);
3795
+ let was = priv._find_term;
3796
+
3797
+ priv._find_term = next;
3798
+ priv._find_current = "";
3799
+ priv._find_found = null;
3800
+
3801
+ let ready = priv.graph && priv.graph_rendered && priv._fold_model;
3802
+ if(!next) {
3803
+ /* Only a find that HELD the slot is taken off: an empty box
3804
+ * that was already empty must not wipe a topic focus. */
3805
+ if(was && ready && !priv._focus_topic) {
3806
+ apply_focus_ids(gobj, [], "find", false);
3807
+ }
3808
+ publish_find_result(gobj);
3809
+ return;
3810
+ }
3811
+
3812
+ priv._focus_topic = null; /* a find takes over the highlight */
3813
+ if(!ready) {
3814
+ /* Painted by the first reconcile, when the data arrives. */
3815
+ return;
3816
+ }
3817
+ refresh_find(gobj, fold_visible_set(priv._fold_model, priv._fold_state));
3818
+ }
3819
+
3820
+ /************************************************************
3821
+ * Repaint the find's highlight over the `visible` set and publish
3822
+ * the count. A no-op while no term is live, so the topic focus
3823
+ * keeps the slot.
3824
+ ************************************************************/
3825
+ function refresh_find(gobj, visible)
3826
+ {
3827
+ let priv = gobj.priv;
3828
+ if(!priv._find_term) {
3829
+ return;
3798
3830
  }
3799
3831
 
3832
+ let found = find_matches(gobj, priv._find_term, visible);
3833
+ apply_focus_ids(gobj, found.keys, "find", false);
3834
+ if(priv._find_current && (priv._focus_ids || []).indexOf(priv._find_current) < 0) {
3835
+ priv._find_current = "";
3836
+ }
3837
+ priv._find_found = found;
3838
+ publish_find_result(gobj);
3839
+ }
3840
+
3841
+ /************************************************************
3842
+ * The records matching `term`: the keys ON SCREEN, and how many
3843
+ * more match that are NOT -- folded away in the fold model, or in
3844
+ * a hidden topic. Those two are only counted: "0" alone would read
3845
+ * as "does not exist" when it means "is not on screen".
3846
+ ************************************************************/
3847
+ function find_matches(gobj, term, visible)
3848
+ {
3849
+ let priv = gobj.priv;
3800
3850
  let model = priv._fold_model;
3801
- let state = priv._fold_state;
3851
+ let found = {keys: [], folded: 0, hidden: 0};
3852
+ if(!model || empty_string(term)) {
3853
+ return found;
3854
+ }
3802
3855
 
3803
- /* Searched over the RECORDS, not the cards: most of the treedb is
3804
- * folded away, and a find that only read what was on screen would
3805
- * answer "nothing" for a device three levels down. */
3806
- let keys = [];
3807
- let hidden_matches = 0;
3808
- if(!empty_string(term)) {
3809
- let needle = String(term).toLowerCase();
3810
- let matches = (topic_name, record) => {
3811
- let label = "";
3812
- try {
3813
- label = node_label(priv.descs[topic_name] || {}, record) || "";
3814
- } catch(e) {
3815
- label = "";
3816
- }
3817
- let haystack = [
3818
- label,
3819
- record.id,
3820
- topic_name
3821
- ].filter((v) => typeof v === "string").join(" ").toLowerCase();
3822
- return haystack.includes(needle);
3823
- };
3824
- for(let node of model.nodes.values()) {
3825
- if(matches(node.topic_name, node.record || {})) {
3826
- keys.push(node.key);
3856
+ let needle = String(term).toLowerCase();
3857
+ let matches = (topic_name, record) => {
3858
+ let label = "";
3859
+ try {
3860
+ label = node_label(priv.descs[topic_name] || {}, record) || "";
3861
+ } catch(e) {
3862
+ label = "";
3863
+ }
3864
+ let haystack = [
3865
+ label,
3866
+ record.id,
3867
+ topic_name
3868
+ ].filter((v) => typeof v === "string").join(" ").toLowerCase();
3869
+ return haystack.includes(needle);
3870
+ };
3871
+
3872
+ for(let node of model.nodes.values()) {
3873
+ if(matches(node.topic_name, node.record || {})) {
3874
+ if(visible.has(node.key)) {
3875
+ found.keys.push(node.key);
3876
+ } else {
3877
+ found.folded++;
3827
3878
  }
3828
3879
  }
3829
- /* A hidden topic is searched too, and COUNTED apart: a term
3830
- * that only matches there would otherwise answer "0", which
3831
- * reads as "does not exist" when it means "is hidden". */
3832
- for(let topic_name of model.hidden) {
3833
- for(let record of (priv.records[topic_name] || [])) {
3834
- if(is_object(record) && matches(topic_name, record)) {
3835
- hidden_matches++;
3836
- }
3880
+ }
3881
+ for(let topic_name of model.hidden) {
3882
+ for(let record of (priv.records[topic_name] || [])) {
3883
+ if(is_object(record) && matches(topic_name, record)) {
3884
+ found.hidden++;
3837
3885
  }
3838
3886
  }
3839
3887
  }
3840
- priv._find_hidden_matches = hidden_matches;
3888
+ return found;
3889
+ }
3841
3890
 
3842
- /* Hidden matches are opened up to, one page of them at most: a
3843
- * single letter matches half the treedb, and unfolding half the
3844
- * treedb on a keystroke is the pile this whole thing exists to
3845
- * avoid. The count reported is of all the matches. */
3846
- let visible = fold_visible_set(model, state);
3847
- let revealed = 0;
3848
- for(let key of keys) {
3849
- if(visible.has(key)) {
3850
- continue;
3851
- }
3852
- if(revealed >= state.page_size) {
3853
- break;
3854
- }
3855
- fold_reveal(model, state, key);
3856
- revealed++;
3891
+ /************************************************************
3892
+ * The lit cards in reading order: top to bottom, then left to
3893
+ * right. The order Enter walks, and the one the "k/N" counts in.
3894
+ ************************************************************/
3895
+ function find_order(gobj)
3896
+ {
3897
+ let priv = gobj.priv;
3898
+ let graph = priv.graph;
3899
+ let ids = (priv._focus_ids || []).slice();
3900
+ if(!graph) {
3901
+ return ids;
3857
3902
  }
3858
3903
 
3859
- priv._focus_topic = null; /* a find takes over the highlight */
3904
+ let pos = {};
3905
+ for(let id of ids) {
3906
+ let p = null;
3907
+ try {
3908
+ p = graph.getElementPosition(id);
3909
+ } catch(e) {
3910
+ p = null;
3911
+ }
3912
+ pos[id] = p || [0, 0];
3913
+ }
3914
+ ids.sort((a, b) => (pos[a][1] - pos[b][1]) || (pos[a][0] - pos[b][0]));
3915
+ return ids;
3916
+ }
3860
3917
 
3861
- reconcile_fold(gobj, {}).then(() => {
3862
- apply_focus_ids(gobj, keys, "find");
3863
- });
3918
+ /************************************************************
3919
+ * Enter in the find box: centre the next lit card (`back`: the
3920
+ * previous one). The one camera move of the find, and the reader
3921
+ * asks for it: the highlight alone cannot take anybody to a match
3922
+ * that is outside the frame.
3923
+ ************************************************************/
3924
+ function graph_find_step(gobj, back)
3925
+ {
3926
+ let priv = gobj.priv;
3927
+ let graph = priv.graph;
3928
+ if(!graph || !priv._find_term) {
3929
+ return;
3930
+ }
3931
+ let ids = find_order(gobj);
3932
+ if(ids.length === 0) {
3933
+ return;
3934
+ }
3864
3935
 
3865
- return keys.length;
3936
+ let i = ids.indexOf(priv._find_current);
3937
+ if(i < 0) {
3938
+ i = back? ids.length - 1 : 0;
3939
+ } else {
3940
+ i = (i + (back? ids.length - 1 : 1)) % ids.length;
3941
+ }
3942
+ priv._find_current = ids[i];
3943
+ try {
3944
+ yui_graph_center_on(graph, ids[i]);
3945
+ } catch(e) {
3946
+ log_error(`${gobj_short_name(gobj)}: find step failed: ${e}`);
3947
+ }
3948
+ publish_find_result(gobj);
3866
3949
  }
3867
3950
 
3868
3951
  async function graph_zoom_in(gobj)
@@ -4235,9 +4318,8 @@ class TreedbTreeLayout extends BaseLayout
4235
4318
  * dragged card, a fold or a layout needs nothing else. `-v` runs
4236
4319
  * between rows (top to bottom), `-h` between columns. An edge that
4237
4320
  * does not run forward goes round the two cards, the edges joining
4238
- * the same two cards take lanes, the edges sharing an end turn at
4239
- * staggered heights, and a line that would cross any other card is
4240
- * routed round it (treedb_elbow.js).
4321
+ * the same two cards take lanes, and a line that would cross any
4322
+ * other card is routed round it (treedb_elbow.js).
4241
4323
  ************************************************************/
4242
4324
  const ELBOW_RADIUS = 6;
4243
4325
 
@@ -4309,73 +4391,12 @@ function elbow_lane_of(edge)
4309
4391
  }
4310
4392
  }
4311
4393
 
4312
- /* Where `edge` stands among the forward edges sharing one of its
4313
- * ends, for the stagger (treedb_elbow.js): the ones leaving its
4314
- * source card, and the ones reaching its target card. How far each
4315
- * runs ACROSS is measured from the real port it leaves by -- the
4316
- * hooks of one card sit at different places along its edge. */
4317
- function elbow_stagger_of(edge, vertical)
4318
- {
4319
- let graph = edge.context && edge.context.graph;
4320
- let element = edge.context && edge.context.element;
4321
- let s = edge.sourceNode && edge.sourceNode.id;
4322
- let t = edge.targetNode && edge.targetNode.id;
4323
- if(!graph || !element || s === undefined || t === undefined) {
4324
- return null;
4325
- }
4326
- let across = vertical? 0 : 1;
4327
- let along = vertical? 1 : 0;
4328
- let pos = (id) => graph.getElementPosition(id);
4329
- let port_of = (node_id, key) => {
4330
- let node = element.getElement(node_id);
4331
- let ports = (node && typeof node.getPorts === 'function')? node.getPorts() : {};
4332
- let port = key? ports[key] : null;
4333
- return port? port.getPosition() : pos(node_id);
4334
- };
4335
- try {
4336
- let ps = pos(s);
4337
- let pt = pos(t);
4338
- let leaving = graph.getRelatedEdgesData(s, 'out')
4339
- .filter((d) => d.source === s && pos(d.target)[along] > ps[along]);
4340
- /* The cards they reach, for the combs of a stack; a comb's
4341
- * run ends at its lane, so that is what its span measures. */
4342
- let combs = elbow_combs(leaving.map((d) => ({
4343
- id: d.id,
4344
- box: elbow_box(element.getElement(d.target)),
4345
- })), vertical);
4346
- let dep = leaving.map((d) => {
4347
- let comb = combs.get(d.id);
4348
- let far = comb? comb.lane : pos(d.target)[across];
4349
- return {
4350
- id: d.id,
4351
- span: Math.abs(far - port_of(s, d.style && d.style.sourcePort)[across]),
4352
- };
4353
- });
4354
- let arr = graph.getRelatedEdgesData(t, 'in')
4355
- .filter((d) => d.target === t && pos(d.source)[along] < pt[along])
4356
- .map((d) => ({
4357
- id: d.id,
4358
- span: Math.abs(port_of(d.source, d.style && d.style.sourcePort)[across] -
4359
- pt[across]),
4360
- }));
4361
- return {
4362
- dep: elbow_rank(dep, edge.id),
4363
- arr: elbow_rank(arr, edge.id),
4364
- comb: combs.get(edge.id) || null,
4365
- };
4366
- } catch(e) {
4367
- log_error(`elbow edge ${edge.id}: cannot read the edges sharing its ends: ${e}`);
4368
- return null;
4369
- }
4370
- }
4371
-
4372
4394
  class TreedbElbowV extends Polyline
4373
4395
  {
4374
4396
  getControlPoints(attributes) {
4375
4397
  let [s, t] = this.getEndpoints(attributes, false);
4376
4398
  return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode),
4377
- elbow_lane_of(this), true, elbow_obstacles(this),
4378
- elbow_stagger_of(this, true));
4399
+ elbow_lane_of(this), true, elbow_obstacles(this));
4379
4400
  }
4380
4401
 
4381
4402
  getKeyPath(attributes) {
@@ -4389,8 +4410,7 @@ class TreedbElbowH extends Polyline
4389
4410
  getControlPoints(attributes) {
4390
4411
  let [s, t] = this.getEndpoints(attributes, false);
4391
4412
  return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode),
4392
- elbow_lane_of(this), false, elbow_obstacles(this),
4393
- elbow_stagger_of(this, false));
4413
+ elbow_lane_of(this), false, elbow_obstacles(this));
4394
4414
  }
4395
4415
 
4396
4416
  getKeyPath(attributes) {
@@ -4399,59 +4419,6 @@ class TreedbElbowH extends Polyline
4399
4419
  }
4400
4420
  }
4401
4421
 
4402
- /************************************************************
4403
- * A CURVED edge that keeps off the cards. G6's cubic, as before,
4404
- * unless that very curve -- sampled, not guessed from the
4405
- * rectangle round it -- would cross a card other than its own two,
4406
- * or when it does not run forward and so would cross its own:
4407
- * the card above a lower row of a stack, a row between a parent
4408
- * and a far child. Then it goes the way an elbow would (the comb,
4409
- * the detour, the route: the same lanes and staggered turns) with
4410
- * round corners, so it still reads as a curve. `-v` for rows
4411
- * stacked top to bottom, `-h` for columns.
4412
- ************************************************************/
4413
- function curve_key_path(edge, attributes, vertical, plain)
4414
- {
4415
- let [s, t] = edge.getEndpoints(attributes, false);
4416
- let along = vertical? 1 : 0;
4417
- let box_s = elbow_box(edge.sourceNode);
4418
- let box_t = elbow_box(edge.targetNode);
4419
- let boxes = elbow_obstacles(edge);
4420
- /* An edge that does not run forward -- one of every reciprocal
4421
- * pair, an edge within one row -- runs through its OWN two cards
4422
- * as a cubic, which curve_hits does not look at: it goes round
4423
- * them, as its elbow does. */
4424
- let forward = (t[along] - s[along] > 2 * ELBOW_STEP);
4425
- if(forward && !curve_hits(plain, boxes, box_s, box_t)) {
4426
- return plain;
4427
- }
4428
- let pts = elbow_route(s, t, box_s, box_t, elbow_lane_of(edge), vertical, boxes,
4429
- elbow_stagger_of(edge, vertical));
4430
- return rounded_path([s, ...pts, t], CURVE_RADIUS);
4431
- }
4432
-
4433
- class TreedbCurveV extends Cubic
4434
- {
4435
- getKeyPath(attributes) {
4436
- return curve_key_path(this, attributes, true, super.getKeyPath(attributes));
4437
- }
4438
- }
4439
-
4440
- class TreedbCurveH extends CubicHorizontal
4441
- {
4442
- getKeyPath(attributes) {
4443
- return curve_key_path(this, attributes, false, super.getKeyPath(attributes));
4444
- }
4445
- }
4446
-
4447
- class TreedbCompactLayout extends BaseLayout
4448
- {
4449
- async execute(data, options) {
4450
- let input = treedb_layout_input(data);
4451
- return treedb_layout_output(layout_compact(input.nodes, input.edges, options));
4452
- }
4453
- }
4454
-
4455
4422
  class TreedbRadialLayout extends BaseLayout
4456
4423
  {
4457
4424
  async execute(data, options) {
@@ -9786,6 +9753,7 @@ async function reconcile_fold_now(gobj, opts)
9786
9753
  history_resume(gobj);
9787
9754
  }
9788
9755
  publish_legend_state(gobj, visible);
9756
+ refresh_find(gobj, visible);
9789
9757
  return changed;
9790
9758
  }
9791
9759
 
@@ -10361,11 +10329,6 @@ function ac_load_data(gobj, event, kw, src)
10361
10329
  priv._pending_focus_all = false;
10362
10330
  graph_focus_topic(gobj, ft, all);
10363
10331
  }
10364
- if(priv._pending_find !== null) {
10365
- let term = priv._pending_find;
10366
- priv._pending_find = null;
10367
- publish_find_result(gobj, term, graph_find_nodes(gobj, term));
10368
- }
10369
10332
  });
10370
10333
  }
10371
10334
 
@@ -11192,16 +11155,30 @@ function ac_node_dblclick(gobj, event, kw, src)
11192
11155
  }
11193
11156
 
11194
11157
  /************************************************************
11195
- * Tell whoever asked how many nodes the term matched. The count is
11196
- * the answer to the question the box asks; without it "nothing moved"
11197
- * and "nothing matched" look the same.
11158
+ * Tell whoever asked how many cards the term lit. The count is the
11159
+ * answer to the question the box asks; without it "nothing lit" and
11160
+ * "nothing matched" look the same.
11161
+ *
11162
+ * matches lit cards (on screen)
11163
+ * folded_matches matches folded away, not on screen
11164
+ * hidden_matches matches in hidden topics
11165
+ * current 1-based place of the card Enter centred on
11166
+ * among the lit ones (reading order), 0 = none
11198
11167
  ************************************************************/
11199
- function publish_find_result(gobj, term, matches)
11168
+ function publish_find_result(gobj)
11200
11169
  {
11170
+ let priv = gobj.priv;
11171
+ let found = priv._find_found || {keys: [], folded: 0, hidden: 0};
11172
+ let current = 0;
11173
+ if(priv._find_term && priv._find_current) {
11174
+ current = find_order(gobj).indexOf(priv._find_current) + 1;
11175
+ }
11201
11176
  gobj_publish_event(gobj, "EV_FIND_RESULT", {
11202
- term: term,
11203
- matches: matches,
11204
- hidden_matches: gobj.priv._find_hidden_matches || 0,
11177
+ term: priv._find_term,
11178
+ matches: priv._find_term? (priv._focus_ids || []).length : 0,
11179
+ folded_matches: found.folded,
11180
+ hidden_matches: found.hidden,
11181
+ current: current,
11205
11182
  });
11206
11183
  }
11207
11184
 
@@ -11210,9 +11187,16 @@ function publish_find_result(gobj, term, matches)
11210
11187
  ************************************************************/
11211
11188
  function ac_find_nodes(gobj, event, kw, src)
11212
11189
  {
11213
- let term = (kw && kw.text) || "";
11214
- let matches = graph_find_nodes(gobj, term);
11215
- publish_find_result(gobj, term, matches);
11190
+ graph_find_nodes(gobj, (kw && kw.text) || "");
11191
+ return 0;
11192
+ }
11193
+
11194
+ /************************************************************
11195
+ * Enter in the find box: {back: true} for Shift+Enter.
11196
+ ************************************************************/
11197
+ function ac_find_next(gobj, event, kw, src)
11198
+ {
11199
+ graph_find_step(gobj, !!(kw && kw.back));
11216
11200
  return 0;
11217
11201
  }
11218
11202
 
@@ -11774,6 +11758,7 @@ function create_gclass(gclass_name)
11774
11758
  ["EV_ZOOM_SELECTION", ac_zoom_selection, null],
11775
11759
  ["EV_FOCUS_TOPIC", ac_focus_topic, null],
11776
11760
  ["EV_FIND_NODES", ac_find_nodes, null],
11761
+ ["EV_FIND_NEXT", ac_find_next, null],
11777
11762
  ["EV_TOGGLE_FOLD", ac_toggle_fold, null],
11778
11763
  ["EV_SHOW_MORE", ac_show_more, null],
11779
11764
  ["EV_SET_FOLD_LEVEL", ac_set_fold_level, null],
@@ -11837,6 +11822,7 @@ function create_gclass(gclass_name)
11837
11822
  ["EV_ZOOM_SELECTION", 0],
11838
11823
  ["EV_FOCUS_TOPIC", 0],
11839
11824
  ["EV_FIND_NODES", 0],
11825
+ ["EV_FIND_NEXT", 0],
11840
11826
  ["EV_FIND_RESULT", event_flag_t.EVF_OUTPUT_EVENT],
11841
11827
  ["EV_LAYOUT_AUTOSET", event_flag_t.EVF_OUTPUT_EVENT],
11842
11828
  /* Optional: a host that does not persist the viewport is not