@yuneta/gobj-ui 7.23.28 → 7.23.29
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/dist/gobj-ui.cjs.js +330 -25
- package/dist/gobj-ui.es.js +330 -25
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +40 -10
- package/src/c_yui_gobj_tree_js.js +197 -2
- package/src/c_yui_json_graph.js +245 -4
- package/src/lib_graph.css +38 -2
- package/src/yui_graph_camera.js +60 -0
package/src/c_yui_json_graph.js
CHANGED
|
@@ -54,6 +54,8 @@ import {
|
|
|
54
54
|
yui_graph_anchor_item,
|
|
55
55
|
yui_graph_update_anchor,
|
|
56
56
|
yui_graph_center_on,
|
|
57
|
+
yui_graph_place_at,
|
|
58
|
+
yui_graph_viewport_of,
|
|
57
59
|
yui_graph_fold_items,
|
|
58
60
|
yui_graph_refresh_item,
|
|
59
61
|
yui_graph_update_zoom,
|
|
@@ -192,6 +194,7 @@ let PRIVATE_DATA = {
|
|
|
192
194
|
/*---------------- find + collapse ----------------*/
|
|
193
195
|
search: "", // current find term, lower-cased
|
|
194
196
|
collapsed: null, // Set<string> of paths whose children are hidden
|
|
197
|
+
reserved: null, // Map<path,width> the space a fold leaves behind
|
|
195
198
|
match_count: 0, // matches of the last find, for the count chip
|
|
196
199
|
find_timer: null, // rate-limits the find box
|
|
197
200
|
fold_listener: null, // delegated click on the card fold handles
|
|
@@ -206,6 +209,7 @@ let PRIVATE_DATA = {
|
|
|
206
209
|
anchor_state: "off", // "off" | "arming" | "on"
|
|
207
210
|
last_zoom: 1, // tells a wheel zoom from a drag in aftertransform
|
|
208
211
|
center_posted: false, // one re-centring in flight, never a queue of them
|
|
212
|
+
pending_keep: null, // {path,x,y}: a card to put back where it was
|
|
209
213
|
};
|
|
210
214
|
|
|
211
215
|
let __gclass__ = null;
|
|
@@ -331,6 +335,7 @@ function mt_create(gobj)
|
|
|
331
335
|
let priv = gobj.priv;
|
|
332
336
|
|
|
333
337
|
priv.collapsed = new Set();
|
|
338
|
+
priv.reserved = new Map();
|
|
334
339
|
priv.search = "";
|
|
335
340
|
|
|
336
341
|
let name = clean_name(gobj_name(gobj));
|
|
@@ -475,8 +480,15 @@ function build_ui(gobj)
|
|
|
475
480
|
if(evt.type !== "click") {
|
|
476
481
|
return; /* swallowed so G6 never sees a node click */
|
|
477
482
|
}
|
|
478
|
-
|
|
479
|
-
|
|
483
|
+
/* Which CARD the chip sits on, not only what it folds: a
|
|
484
|
+
* header chip folds its own card, a row chip folds a child
|
|
485
|
+
* that has none -- and the card that must not move is the
|
|
486
|
+
* one under the finger either way. */
|
|
487
|
+
let $card = $handle.closest? $handle.closest('.JSON_CARD'): null;
|
|
488
|
+
gobj_send_event(gobj, "EV_TOGGLE_FOLD", {
|
|
489
|
+
path: $handle.getAttribute('data-fold-path'),
|
|
490
|
+
keep: $card? $card.getAttribute('data-json-path'): ""
|
|
491
|
+
}, gobj);
|
|
480
492
|
};
|
|
481
493
|
for(let type of FOLD_SWALLOWED_EVENTS) {
|
|
482
494
|
$mount.addEventListener(type, priv.fold_listener, true);
|
|
@@ -944,6 +956,13 @@ const BEHAVIORS_PICKING = ['drag-canvas', ZOOM_CANVAS];
|
|
|
944
956
|
const CARD_HEADER_H = 24;
|
|
945
957
|
const CARD_ROW_H = 22;
|
|
946
958
|
|
|
959
|
+
/* The tree layout's gaps. Module scope because the width a fold
|
|
960
|
+
* RESERVES has to be measured with the very same numbers the layout
|
|
961
|
+
* packs with, or the hole is the wrong size and everything shifts by
|
|
962
|
+
* the difference. */
|
|
963
|
+
const H_GAP = 60; // horizontal gap between siblings
|
|
964
|
+
const V_GAP = 50; // vertical gap between levels
|
|
965
|
+
|
|
947
966
|
function fold_toggle_html(path, foldable, folded, branches, colors)
|
|
948
967
|
{
|
|
949
968
|
if(!foldable) {
|
|
@@ -1135,6 +1154,10 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
|
|
|
1135
1154
|
*/
|
|
1136
1155
|
if(parent_id && is_pure_collection(kw)) {
|
|
1137
1156
|
if(priv.collapsed.has(path)) {
|
|
1157
|
+
/* Folded, and it draws nothing of its own -- so the space
|
|
1158
|
+
* its children were taking is left HERE, in their place
|
|
1159
|
+
* among the parent's other children. */
|
|
1160
|
+
push_fold_phantom(gobj, path, parent_id, parent_port, nodes, edges);
|
|
1138
1161
|
return parent_id;
|
|
1139
1162
|
}
|
|
1140
1163
|
for(let pending of pending_complex) {
|
|
@@ -1281,6 +1304,9 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
|
|
|
1281
1304
|
data: {
|
|
1282
1305
|
path: path,
|
|
1283
1306
|
is_dict: is_dict,
|
|
1307
|
+
/* Only a folded card carries one; the layout leaves the
|
|
1308
|
+
* space its subtree used to take. */
|
|
1309
|
+
|
|
1284
1310
|
},
|
|
1285
1311
|
style: {
|
|
1286
1312
|
innerHTML: node_html,
|
|
@@ -1334,6 +1360,9 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
|
|
|
1334
1360
|
* Recurse into complex children — unless this card is folded.
|
|
1335
1361
|
*/
|
|
1336
1362
|
if(folded) {
|
|
1363
|
+
/* The card stays; the space under it is held by a phantom of
|
|
1364
|
+
* its own, so its siblings do not close in over it. */
|
|
1365
|
+
push_fold_phantom(gobj, path, group_id, "", nodes, edges);
|
|
1337
1366
|
return group_id;
|
|
1338
1367
|
}
|
|
1339
1368
|
for(let pending of pending_complex) {
|
|
@@ -1395,6 +1424,16 @@ function load_json(gobj)
|
|
|
1395
1424
|
paint_anchor_mark(gobj);
|
|
1396
1425
|
|
|
1397
1426
|
if(preserve_view) {
|
|
1427
|
+
let keep = priv.pending_keep;
|
|
1428
|
+
priv.pending_keep = null;
|
|
1429
|
+
if(keep) {
|
|
1430
|
+
for(let n of nodes) {
|
|
1431
|
+
if(n.data && n.data.path === keep.path) {
|
|
1432
|
+
yui_graph_place_at(graph, n.id, [keep.x, keep.y]);
|
|
1433
|
+
break;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1398
1437
|
yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
|
|
1399
1438
|
return;
|
|
1400
1439
|
}
|
|
@@ -1476,6 +1515,131 @@ function update_find_result(gobj)
|
|
|
1476
1515
|
priv.$find_result.classList.remove("is-hidden");
|
|
1477
1516
|
}
|
|
1478
1517
|
|
|
1518
|
+
/************************************************************
|
|
1519
|
+
* The invisible child a fold leaves behind.
|
|
1520
|
+
*
|
|
1521
|
+
* It is the whole of "nothing moves when I fold": the layout
|
|
1522
|
+
* is a plain tree that centres a parent over its children, so
|
|
1523
|
+
* the only way to keep the survivors still is to leave the
|
|
1524
|
+
* space where it was, in the order it was, as a CHILD. A width
|
|
1525
|
+
* reserved as a number would still have to be placed, and
|
|
1526
|
+
* appending it moves every sibling by half of it.
|
|
1527
|
+
*
|
|
1528
|
+
* A `rect` and not an `html` node: nothing is drawn, and a
|
|
1529
|
+
* DOM element per fold would be a card nobody can see but the
|
|
1530
|
+
* Inspector would show.
|
|
1531
|
+
************************************************************/
|
|
1532
|
+
function push_fold_phantom(gobj, path, parent_id, parent_port, nodes, edges)
|
|
1533
|
+
{
|
|
1534
|
+
let priv = gobj.priv;
|
|
1535
|
+
let width = priv.reserved.get(path) || 0;
|
|
1536
|
+
|
|
1537
|
+
if(!(width > 0) || !parent_id) {
|
|
1538
|
+
return; /* never measured: nothing to reserve */
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
let id = gen_node_id(gobj);
|
|
1542
|
+
nodes.push({
|
|
1543
|
+
id: id,
|
|
1544
|
+
type: 'rect',
|
|
1545
|
+
data: {path: path, phantom: true},
|
|
1546
|
+
style: {
|
|
1547
|
+
size: [width, 1],
|
|
1548
|
+
fill: 'transparent',
|
|
1549
|
+
stroke: 'transparent',
|
|
1550
|
+
opacity: 0,
|
|
1551
|
+
pointerEvents: 'none',
|
|
1552
|
+
},
|
|
1553
|
+
});
|
|
1554
|
+
edges.push({
|
|
1555
|
+
source: parent_id,
|
|
1556
|
+
target: id,
|
|
1557
|
+
style: {opacity: 0, endArrow: false, sourcePort: parent_port || undefined},
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/************************************************************
|
|
1562
|
+
* The width the children a fold is about to hide are taking
|
|
1563
|
+
* RIGHT NOW, measured with the layout's own numbers.
|
|
1564
|
+
*
|
|
1565
|
+
* `card_path` is the card that will lose them; `under` limits
|
|
1566
|
+
* it to the ones hanging from one key of that card, which is
|
|
1567
|
+
* the case of a container that has no card of its own -- its
|
|
1568
|
+
* children hang from the parent, so its fold is a hole in the
|
|
1569
|
+
* PARENT.
|
|
1570
|
+
************************************************************/
|
|
1571
|
+
function group_width_of(gobj, card_path, under)
|
|
1572
|
+
{
|
|
1573
|
+
let priv = gobj.priv;
|
|
1574
|
+
|
|
1575
|
+
if(!priv.graph) {
|
|
1576
|
+
return 0;
|
|
1577
|
+
}
|
|
1578
|
+
try {
|
|
1579
|
+
let by_id = {};
|
|
1580
|
+
let card_id = "";
|
|
1581
|
+
for(let node of priv.graph.getNodeData()) {
|
|
1582
|
+
if(!node || !node.data) {
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
by_id[node.id] = node;
|
|
1586
|
+
if(node.data.path === card_path) {
|
|
1587
|
+
card_id = node.id;
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
if(!card_id) {
|
|
1591
|
+
return 0;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
let prefix = under? (under + "`"): "";
|
|
1595
|
+
let sum = 0;
|
|
1596
|
+
let n = 0;
|
|
1597
|
+
for(let edge of priv.graph.getEdgeData()) {
|
|
1598
|
+
if(!edge || edge.source !== card_id) {
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
let kid = by_id[edge.target];
|
|
1602
|
+
if(!kid) {
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
if(prefix && String(kid.data.path).indexOf(prefix) !== 0) {
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
sum += kid.data.subtree_width || 0;
|
|
1609
|
+
n++;
|
|
1610
|
+
}
|
|
1611
|
+
return n? (sum + H_GAP * (n - 1)): 0;
|
|
1612
|
+
} catch(e) {
|
|
1613
|
+
return 0; /* between renders: nothing measured */
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/************************************************************
|
|
1618
|
+
* The screen spot of the card at `path`, to be given back
|
|
1619
|
+
* after the rebuild. Null when it has no card of its own --
|
|
1620
|
+
* a pure collection -- and then nothing anchors and the
|
|
1621
|
+
* camera simply stays where it was.
|
|
1622
|
+
************************************************************/
|
|
1623
|
+
function keep_of(gobj, path)
|
|
1624
|
+
{
|
|
1625
|
+
let priv = gobj.priv;
|
|
1626
|
+
|
|
1627
|
+
if(empty_string(path) || !priv.graph) {
|
|
1628
|
+
return null;
|
|
1629
|
+
}
|
|
1630
|
+
try {
|
|
1631
|
+
for(let node of priv.graph.getNodeData()) {
|
|
1632
|
+
if(node && node.data && node.data.path === path) {
|
|
1633
|
+
let vp = yui_graph_viewport_of(priv.graph, node.id);
|
|
1634
|
+
return vp? {path: path, x: vp[0], y: vp[1]}: null;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
} catch(e) {
|
|
1638
|
+
/* between renders: nothing to hold on to */
|
|
1639
|
+
}
|
|
1640
|
+
return null;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1479
1643
|
/************************************************************
|
|
1480
1644
|
* Clear and reload.
|
|
1481
1645
|
* {preserve_view: true}
|
|
@@ -1538,12 +1702,35 @@ class JsonTreeLayout extends BaseLayout {
|
|
|
1538
1702
|
node_dims[node.id] = {w, h};
|
|
1539
1703
|
}
|
|
1540
1704
|
|
|
1541
|
-
const H_GAP = 60; // horizontal gap between siblings
|
|
1542
|
-
const V_GAP = 50; // vertical gap between levels
|
|
1543
1705
|
|
|
1544
1706
|
/*
|
|
1545
1707
|
* Calculate subtree widths (bottom-up)
|
|
1546
1708
|
*/
|
|
1709
|
+
/*
|
|
1710
|
+
* A FOLDED node keeps the width its subtree had.
|
|
1711
|
+
*
|
|
1712
|
+
* Without it, folding a branch shrinks its parent's row and
|
|
1713
|
+
* every sibling re-packs -- measured, five surviving cards all
|
|
1714
|
+
* slid 240px sideways for a fold that removed nothing they
|
|
1715
|
+
* could see. Reserving the space trades a hole in the drawing
|
|
1716
|
+
* for a drawing that does not move under the reader, which is
|
|
1717
|
+
* the whole reason to fold something in the first place: to
|
|
1718
|
+
* look at what is left.
|
|
1719
|
+
*
|
|
1720
|
+
* The width comes from the layout's own last pass (stored on
|
|
1721
|
+
* the node below), so a fold and its unfold reserve the same
|
|
1722
|
+
* number and the subtree comes back into the space it left.
|
|
1723
|
+
*/
|
|
1724
|
+
/*
|
|
1725
|
+
* A fold leaves a PHANTOM child in the place its children had
|
|
1726
|
+
* -- an invisible node of exactly their width -- so nothing
|
|
1727
|
+
* here needs to know about folding at all. A hole added as a
|
|
1728
|
+
* number would have to be placed too, and `position_node`
|
|
1729
|
+
* centres a parent over its children: a lump appended at the
|
|
1730
|
+
* end moves every sibling by half of it. A child in the
|
|
1731
|
+
* sequence keeps the order and the space, and this stays a
|
|
1732
|
+
* plain tree layout.
|
|
1733
|
+
*/
|
|
1547
1734
|
let subtree_widths = {};
|
|
1548
1735
|
function calc_subtree_width(node_id) {
|
|
1549
1736
|
let kids = children_map[node_id] || [];
|
|
@@ -1564,6 +1751,18 @@ class JsonTreeLayout extends BaseLayout {
|
|
|
1564
1751
|
calc_subtree_width(root.id);
|
|
1565
1752
|
}
|
|
1566
1753
|
|
|
1754
|
+
/*
|
|
1755
|
+
* What this pass measured, written back onto the node data:
|
|
1756
|
+
* it is the only place the number exists, and the gclass needs
|
|
1757
|
+
* it at the moment somebody folds -- by then the layout has
|
|
1758
|
+
* long finished.
|
|
1759
|
+
*/
|
|
1760
|
+
for(let node of nodes) {
|
|
1761
|
+
if(node.data) {
|
|
1762
|
+
node.data.subtree_width = subtree_widths[node.id];
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1567
1766
|
/*
|
|
1568
1767
|
* Position nodes (top-down)
|
|
1569
1768
|
*/
|
|
@@ -1632,6 +1831,12 @@ class JsonTreeLayout extends BaseLayout {
|
|
|
1632
1831
|
/************************************************************
|
|
1633
1832
|
*
|
|
1634
1833
|
************************************************************/
|
|
1834
|
+
function priv_reset_folds(gobj)
|
|
1835
|
+
{
|
|
1836
|
+
gobj.priv.collapsed.clear();
|
|
1837
|
+
gobj.priv.reserved.clear();
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1635
1840
|
function ac_load_data(gobj, event, kw, src)
|
|
1636
1841
|
{
|
|
1637
1842
|
if(kw.path !== undefined) {
|
|
@@ -1639,6 +1844,9 @@ function ac_load_data(gobj, event, kw, src)
|
|
|
1639
1844
|
}
|
|
1640
1845
|
if(kw.data !== undefined) {
|
|
1641
1846
|
gobj_write_attr(gobj, "json_data", kw.data);
|
|
1847
|
+
/* A new document: the folds and the space they were holding
|
|
1848
|
+
* belonged to the old one. */
|
|
1849
|
+
priv_reset_folds(gobj);
|
|
1642
1850
|
}
|
|
1643
1851
|
|
|
1644
1852
|
refresh_json(gobj);
|
|
@@ -1715,12 +1923,41 @@ function ac_toggle_fold(gobj, event, kw, src)
|
|
|
1715
1923
|
return -1;
|
|
1716
1924
|
}
|
|
1717
1925
|
|
|
1926
|
+
let card = (kw && kw.keep) || path;
|
|
1927
|
+
|
|
1718
1928
|
if(priv.collapsed.has(path)) {
|
|
1719
1929
|
priv.collapsed.delete(path);
|
|
1930
|
+
priv.reserved.delete(path);
|
|
1720
1931
|
} else {
|
|
1932
|
+
/*
|
|
1933
|
+
* Measured BEFORE folding: afterwards those children are not
|
|
1934
|
+
* there to measure, and this number is the whole of what keeps
|
|
1935
|
+
* every surviving card where it is.
|
|
1936
|
+
*
|
|
1937
|
+
* Held against the CARD that loses them, not against the path
|
|
1938
|
+
* folded: a container with no card of its own hangs its
|
|
1939
|
+
* children from its parent, so its fold is a hole in the
|
|
1940
|
+
* parent's row.
|
|
1941
|
+
*/
|
|
1942
|
+
let w = group_width_of(gobj, card, (card === path)? "": path);
|
|
1721
1943
|
priv.collapsed.add(path);
|
|
1944
|
+
if(w > 0) {
|
|
1945
|
+
priv.reserved.set(path, w);
|
|
1946
|
+
}
|
|
1722
1947
|
}
|
|
1723
1948
|
|
|
1949
|
+
/*
|
|
1950
|
+
* Where the card under the finger is RIGHT NOW.
|
|
1951
|
+
*
|
|
1952
|
+
* `preserve_view` alone keeps the CAMERA, and that is not enough:
|
|
1953
|
+
* folding a branch changes the subtree widths, so the LAYOUT moves
|
|
1954
|
+
* every card under a still camera and the one just clicked slides
|
|
1955
|
+
* out from under the pointer. Read its screen spot here, hand it
|
|
1956
|
+
* back once the new layout is drawn, and everything that did not
|
|
1957
|
+
* move in the layout stays where it was.
|
|
1958
|
+
*/
|
|
1959
|
+
priv.pending_keep = keep_of(gobj, (kw && kw.keep) || path);
|
|
1960
|
+
|
|
1724
1961
|
refresh_json(gobj, {preserve_view: true});
|
|
1725
1962
|
update_find_result(gobj);
|
|
1726
1963
|
return 0;
|
|
@@ -1755,6 +1992,7 @@ function ac_expand_all(gobj, event, kw, src)
|
|
|
1755
1992
|
return 0; // nothing folded; a relayout would only move the camera
|
|
1756
1993
|
}
|
|
1757
1994
|
priv.collapsed.clear();
|
|
1995
|
+
priv.reserved.clear(); /* no folds left, so no holes to keep */
|
|
1758
1996
|
refresh_json(gobj);
|
|
1759
1997
|
update_find_result(gobj);
|
|
1760
1998
|
return 0;
|
|
@@ -1780,6 +2018,9 @@ function ac_collapse_all(gobj, event, kw, src)
|
|
|
1780
2018
|
collect_foldable_paths(json_data, path, paths, true);
|
|
1781
2019
|
|
|
1782
2020
|
priv.collapsed = new Set(paths);
|
|
2021
|
+
/* Collapse-all is asking for a COMPACT drawing, so it reserves
|
|
2022
|
+
* nothing: it packs, and the camera refits with it. */
|
|
2023
|
+
priv.reserved.clear();
|
|
1783
2024
|
refresh_json(gobj);
|
|
1784
2025
|
update_find_result(gobj);
|
|
1785
2026
|
return 0;
|
package/src/lib_graph.css
CHANGED
|
@@ -273,6 +273,34 @@
|
|
|
273
273
|
* wherever a graph is.
|
|
274
274
|
*/
|
|
275
275
|
|
|
276
|
+
/*
|
|
277
|
+
* The gobj tree's popover close button, for a FINGER.
|
|
278
|
+
*
|
|
279
|
+
* It is an 18px glyph with 4px of padding either side -- about 26x18
|
|
280
|
+
* of target, which a mouse hits and a thumb does not. The popover
|
|
281
|
+
* opens on a tap, so the thing that closes it has to be reachable by
|
|
282
|
+
* the same thumb that opened it.
|
|
283
|
+
*
|
|
284
|
+
* 44px is the size every touch guideline settles on, and it is applied
|
|
285
|
+
* only under `(pointer: coarse)`: on a mouse the small button is fine
|
|
286
|
+
* and a 44px square in a 26px header row would not be.
|
|
287
|
+
*
|
|
288
|
+
* `!important` because the button carries an inline `cssText` -- a
|
|
289
|
+
* media query has no way to win against one, and the size of a target
|
|
290
|
+
* is not a preference.
|
|
291
|
+
*/
|
|
292
|
+
@media (pointer: coarse) {
|
|
293
|
+
.GOBJ_TREE_POPOVER_CLOSE {
|
|
294
|
+
min-width: 44px !important;
|
|
295
|
+
min-height: 44px !important;
|
|
296
|
+
font-size: 26px !important;
|
|
297
|
+
padding: 0 !important;
|
|
298
|
+
display: inline-flex !important;
|
|
299
|
+
align-items: center !important;
|
|
300
|
+
justify-content: center !important;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
276
304
|
/*
|
|
277
305
|
* Picking the node to anchor: the pointer says a click is expected,
|
|
278
306
|
* which is otherwise announced only by a button at the far end of a
|
|
@@ -299,12 +327,20 @@
|
|
|
299
327
|
* things down one channel. `outline` draws OUTSIDE the box, so it
|
|
300
328
|
* cannot move a single row.
|
|
301
329
|
*
|
|
330
|
+
* DASHED, and the same dash the treedb graph draws inline for its own
|
|
331
|
+
* cards: there, amber already means a find match or a focused topic
|
|
332
|
+
* and blue means the selection, so a third SOLID ring would be a third
|
|
333
|
+
* thing to learn in one channel. A dash is a different channel, and it
|
|
334
|
+
* reads as the provisional thing a camera state is. One feature, one
|
|
335
|
+
* drawing, in all three viewers.
|
|
336
|
+
*
|
|
302
337
|
* Amber, the same "this one, right now" the toolbar's arming state
|
|
303
338
|
* uses, and not one of the palette's action colours -- it is not an
|
|
304
339
|
* action.
|
|
305
340
|
*/
|
|
306
|
-
.JSON_CARD_ANCHORED
|
|
307
|
-
|
|
341
|
+
.JSON_CARD_ANCHORED,
|
|
342
|
+
.GOBJ_CARD_ANCHORED {
|
|
343
|
+
outline: 3px dashed #fa8c16;
|
|
308
344
|
outline-offset: 2px;
|
|
309
345
|
}
|
|
310
346
|
|
package/src/yui_graph_camera.js
CHANGED
|
@@ -235,6 +235,66 @@ export function yui_graph_update_anchor($container, state)
|
|
|
235
235
|
************************************************************/
|
|
236
236
|
const __centring__ = new WeakMap();
|
|
237
237
|
|
|
238
|
+
/************************************************************
|
|
239
|
+
* Put `node_id` at a GIVEN point of the viewport.
|
|
240
|
+
*
|
|
241
|
+
* What "the graph does not jump when I fold something" is
|
|
242
|
+
* made of: read where the card is, rebuild the layout, put
|
|
243
|
+
* that card back where it was. Everything that did not move
|
|
244
|
+
* in the layout stays put with it.
|
|
245
|
+
************************************************************/
|
|
246
|
+
export async function yui_graph_place_at(graph, node_id, viewport_xy)
|
|
247
|
+
{
|
|
248
|
+
if(!graph || !node_id || !viewport_xy) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
if(typeof graph.getElementPosition !== "function" ||
|
|
253
|
+
typeof graph.getCanvasCenter !== "function" ||
|
|
254
|
+
typeof graph.translateTo !== "function") {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
let z = graph.getZoom() || 1;
|
|
258
|
+
let c = graph.getCanvasCenter();
|
|
259
|
+
let n = graph.getElementPosition(node_id);
|
|
260
|
+
|
|
261
|
+
/*
|
|
262
|
+
* Same derivation as the centring below, with the target point
|
|
263
|
+
* left free instead of fixed at the middle:
|
|
264
|
+
*
|
|
265
|
+
* T = (canvasCentre - node) * zoom + (V - canvasCentre)
|
|
266
|
+
*
|
|
267
|
+
* and putting V = canvasCentre gives the centring exactly.
|
|
268
|
+
*/
|
|
269
|
+
await graph.translateTo([
|
|
270
|
+
(c[0] - n[0]) * z + (viewport_xy[0] - c[0]),
|
|
271
|
+
(c[1] - n[1]) * z + (viewport_xy[1] - c[1])
|
|
272
|
+
]);
|
|
273
|
+
return true;
|
|
274
|
+
} catch(e) {
|
|
275
|
+
return false; /* the element is gone, or the graph is between renders */
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/************************************************************
|
|
280
|
+
* Where `node_id` is right now, in viewport pixels.
|
|
281
|
+
*
|
|
282
|
+
* The pair of the call above: read it before a rebuild, hand
|
|
283
|
+
* it back after, and the node has not moved on screen even
|
|
284
|
+
* though the layout under it changed.
|
|
285
|
+
************************************************************/
|
|
286
|
+
export function yui_graph_viewport_of(graph, node_id)
|
|
287
|
+
{
|
|
288
|
+
if(!graph || !node_id) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
return graph.getViewportByCanvas(graph.getElementPosition(node_id));
|
|
293
|
+
} catch(e) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
238
298
|
export async function yui_graph_center_on(graph, node_id)
|
|
239
299
|
{
|
|
240
300
|
if(!graph || !node_id) {
|