@yuneta/gobj-ui 7.23.28 → 7.23.30
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 +163 -2
- package/dist/gobj-ui.cjs.js +1278 -202
- package/dist/gobj-ui.es.js +1279 -203
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +40 -10
- package/src/c_yui_gobj_tree_js.js +630 -245
- package/src/c_yui_json_graph.js +245 -4
- package/src/gclass_describe.js +234 -0
- package/src/gclass_describe.test.js +125 -0
- package/src/gobj_tree_model.js +436 -0
- package/src/gobj_tree_model.test.js +184 -0
- package/src/lib_graph.css +38 -2
- package/src/yui_gclass_view.js +277 -0
- 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;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* gclass_describe.js
|
|
3
|
+
*
|
|
4
|
+
* A gclass, as plain JSON, in the shape the BACKEND answers.
|
|
5
|
+
*
|
|
6
|
+
* There is no gclass viewer in the framework: the C kernel
|
|
7
|
+
* answers `view-gclass` with `gclass2json()`
|
|
8
|
+
* (`kernel/c/gobj-c/src/gobj.c`) and nothing draws it. This
|
|
9
|
+
* module builds the same document for a gclass of the BROWSER
|
|
10
|
+
* yuno, out of the gobj-js registry, so one viewer can show a
|
|
11
|
+
* gclass of either side:
|
|
12
|
+
*
|
|
13
|
+
* {id, gcflag, attrs, commands, gclass_methods,
|
|
14
|
+
* internal_methods, FSM: {events, states}}
|
|
15
|
+
*
|
|
16
|
+
* Two keys the backend does not carry are added at the end,
|
|
17
|
+
* because the browser can answer them for free and the reader
|
|
18
|
+
* asks them first: `instances` and `trace_levels`.
|
|
19
|
+
*
|
|
20
|
+
* Copyright (c) 2026, ArtGins.
|
|
21
|
+
* All Rights Reserved.
|
|
22
|
+
***********************************************************************/
|
|
23
|
+
import {
|
|
24
|
+
data_type_t,
|
|
25
|
+
sdata_flag_t,
|
|
26
|
+
event_flag_t,
|
|
27
|
+
gclass_flag_t,
|
|
28
|
+
gclass_find_by_name,
|
|
29
|
+
} from "@yuneta/gobj-js";
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/***************************************************************
|
|
33
|
+
* Bit tables
|
|
34
|
+
***************************************************************/
|
|
35
|
+
const DATA_TYPE_NAMES = {
|
|
36
|
+
[data_type_t.DTP_STRING]: "DTP_STRING",
|
|
37
|
+
[data_type_t.DTP_BOOLEAN]: "DTP_BOOLEAN",
|
|
38
|
+
[data_type_t.DTP_INTEGER]: "DTP_INTEGER",
|
|
39
|
+
[data_type_t.DTP_REAL]: "DTP_REAL",
|
|
40
|
+
[data_type_t.DTP_LIST]: "DTP_LIST",
|
|
41
|
+
[data_type_t.DTP_DICT]: "DTP_DICT",
|
|
42
|
+
[data_type_t.DTP_JSON]: "DTP_JSON",
|
|
43
|
+
[data_type_t.DTP_POINTER]: "DTP_POINTER",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/************************************************************
|
|
47
|
+
* The names of the bits set in `bits`, in table order.
|
|
48
|
+
************************************************************/
|
|
49
|
+
function bits2names(table, bits)
|
|
50
|
+
{
|
|
51
|
+
let names = [];
|
|
52
|
+
let value = Number(bits) || 0;
|
|
53
|
+
|
|
54
|
+
for(let name of Object.keys(table)) {
|
|
55
|
+
if((value & table[name]) !== 0) {
|
|
56
|
+
names.push(name);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return names;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/************************************************************
|
|
63
|
+
* The name of a data type. An unknown type is reported as
|
|
64
|
+
* the number it is -- silently drawing "DTP_STRING" for a
|
|
65
|
+
* type nobody registered would be worse than saying "17".
|
|
66
|
+
************************************************************/
|
|
67
|
+
function data_type_name(type)
|
|
68
|
+
{
|
|
69
|
+
return DATA_TYPE_NAMES[type] || String(type);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/************************************************************
|
|
73
|
+
* One SDATA descriptor -> one row.
|
|
74
|
+
*
|
|
75
|
+
* A DTP_POINTER default is a live object (a DOM node, a
|
|
76
|
+
* gobj): it is never printed, only named, because a viewer
|
|
77
|
+
* that serializes it would either throw or dump the page.
|
|
78
|
+
************************************************************/
|
|
79
|
+
function sdatadesc2json(it)
|
|
80
|
+
{
|
|
81
|
+
let row = {
|
|
82
|
+
id: it.name,
|
|
83
|
+
type: data_type_name(it.type),
|
|
84
|
+
flag: bits2names(sdata_flag_t, it.flag),
|
|
85
|
+
description: it.description || "",
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if(it.type === data_type_t.DTP_POINTER) {
|
|
89
|
+
row.default_value = (it.default_value === null ||
|
|
90
|
+
it.default_value === undefined)? null: "<pointer>";
|
|
91
|
+
} else {
|
|
92
|
+
row.default_value = (it.default_value === undefined)?
|
|
93
|
+
null: it.default_value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return row;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/************************************************************
|
|
100
|
+
* The attrs table of a gclass.
|
|
101
|
+
************************************************************/
|
|
102
|
+
function attrs2json(attrs_table)
|
|
103
|
+
{
|
|
104
|
+
let rows = [];
|
|
105
|
+
if(!Array.isArray(attrs_table)) {
|
|
106
|
+
return rows;
|
|
107
|
+
}
|
|
108
|
+
for(let it of attrs_table) {
|
|
109
|
+
if(!it || !it.name) {
|
|
110
|
+
continue; /* SDATA_END() */
|
|
111
|
+
}
|
|
112
|
+
rows.push(sdatadesc2json(it));
|
|
113
|
+
}
|
|
114
|
+
return rows;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/************************************************************
|
|
118
|
+
* The command table of a gclass, with the parameters of
|
|
119
|
+
* each command (the `schema` slot of SDATACM).
|
|
120
|
+
************************************************************/
|
|
121
|
+
function commands2json(command_table)
|
|
122
|
+
{
|
|
123
|
+
let rows = [];
|
|
124
|
+
if(!Array.isArray(command_table)) {
|
|
125
|
+
return rows;
|
|
126
|
+
}
|
|
127
|
+
for(let it of command_table) {
|
|
128
|
+
if(!it || !it.name) {
|
|
129
|
+
continue; /* SDATA_END() */
|
|
130
|
+
}
|
|
131
|
+
let row = {
|
|
132
|
+
id: it.name,
|
|
133
|
+
alias: it.alias || [],
|
|
134
|
+
description: it.description || "",
|
|
135
|
+
};
|
|
136
|
+
if(Array.isArray(it.schema)) {
|
|
137
|
+
row.parameters = attrs2json(it.schema);
|
|
138
|
+
}
|
|
139
|
+
rows.push(row);
|
|
140
|
+
}
|
|
141
|
+
return rows;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/************************************************************
|
|
145
|
+
* The methods the gclass actually implements. A table of
|
|
146
|
+
* every possible one, mostly empty, tells the reader nothing.
|
|
147
|
+
************************************************************/
|
|
148
|
+
function methods2json(mt)
|
|
149
|
+
{
|
|
150
|
+
let names = [];
|
|
151
|
+
if(!mt || typeof mt !== "object") {
|
|
152
|
+
return names;
|
|
153
|
+
}
|
|
154
|
+
for(let key of Object.keys(mt)) {
|
|
155
|
+
if(typeof mt[key] === "function") {
|
|
156
|
+
names.push(key);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return names;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/************************************************************
|
|
163
|
+
* The FSM: the events the gclass declares, and what each
|
|
164
|
+
* state does with them.
|
|
165
|
+
************************************************************/
|
|
166
|
+
function fsm2json(gclass)
|
|
167
|
+
{
|
|
168
|
+
let events = [];
|
|
169
|
+
for(let ev of (gclass.dl_events || [])) {
|
|
170
|
+
events.push({
|
|
171
|
+
event_name: ev.event_name,
|
|
172
|
+
event_flag: bits2names(event_flag_t, ev.event_flag),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let states = {};
|
|
177
|
+
for(let st of (gclass.dl_states || [])) {
|
|
178
|
+
let actions = [];
|
|
179
|
+
for(let ac of (st.dl_actions || [])) {
|
|
180
|
+
actions.push({
|
|
181
|
+
event_name: ac.event_name,
|
|
182
|
+
action: ac.action? (ac.action.name || "action"): "",
|
|
183
|
+
next_state: ac.next_state || "",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
states[st.state_name] = actions;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {events: events, states: states};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/************************************************************
|
|
193
|
+
* The description of a gclass REGISTERED IN THIS BROWSER YUNO,
|
|
194
|
+
* or null when the registry does not know that name.
|
|
195
|
+
*
|
|
196
|
+
* Null is the answer for a gclass of a BACKEND yuno too: the
|
|
197
|
+
* browser registry carries the GUI gclasses and nothing else,
|
|
198
|
+
* so the caller asks the backend `view-gclass` for those and
|
|
199
|
+
* shows whatever it answers -- this same shape.
|
|
200
|
+
************************************************************/
|
|
201
|
+
function describe_local_gclass(gclass_name)
|
|
202
|
+
{
|
|
203
|
+
if(!gclass_name) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let gclass = gclass_find_by_name(gclass_name, false);
|
|
208
|
+
if(!gclass) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
id: gclass.gclass_name,
|
|
214
|
+
gcflag: bits2names(gclass_flag_t, gclass.gclass_flag),
|
|
215
|
+
attrs: attrs2json(gclass.attrs_table),
|
|
216
|
+
commands: commands2json(gclass.command_table),
|
|
217
|
+
gclass_methods: methods2json(gclass.gmt),
|
|
218
|
+
internal_methods: methods2json(gclass.lmt),
|
|
219
|
+
FSM: fsm2json(gclass),
|
|
220
|
+
instances: Number(gclass.instances) || 0,
|
|
221
|
+
trace_levels: Array.isArray(gclass.s_user_trace_level)?
|
|
222
|
+
gclass.s_user_trace_level: [],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export {
|
|
227
|
+
bits2names,
|
|
228
|
+
data_type_name,
|
|
229
|
+
attrs2json,
|
|
230
|
+
commands2json,
|
|
231
|
+
methods2json,
|
|
232
|
+
fsm2json,
|
|
233
|
+
describe_local_gclass,
|
|
234
|
+
};
|