@yuneta/gobj-ui 7.23.64 → 7.23.66
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 +55 -0
- package/dist/gobj-ui.cjs.js +1372 -187
- package/dist/gobj-ui.es.js +1372 -187
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +1185 -254
- package/src/c_yui_shell.js +78 -7
- package/src/c_yui_treedb_graph.js +38 -0
- package/src/shell_toolbar_helpers.js +12 -2
- package/src/shell_toolbar_helpers.test.js +55 -1
- package/src/treedb_fold_model.js +592 -0
- package/src/treedb_fold_model.test.js +308 -0
package/dist/gobj-ui.es.js
CHANGED
|
@@ -263,7 +263,8 @@ var TOOLBAR_ACTION_TYPES = [
|
|
|
263
263
|
"navigate",
|
|
264
264
|
"drawer",
|
|
265
265
|
"event",
|
|
266
|
-
"dropdown"
|
|
266
|
+
"dropdown",
|
|
267
|
+
"link"
|
|
267
268
|
];
|
|
268
269
|
/************************************************************
|
|
269
270
|
* Decide which renderer to use for a toolbar item.
|
|
@@ -332,6 +333,7 @@ function validate_action(action, owner_id) {
|
|
|
332
333
|
}
|
|
333
334
|
if (action.type === "navigate" && !_is_non_empty_string(action.route)) warnings.push(`toolbar item '${id}' (action.type:navigate) requires 'route'`);
|
|
334
335
|
if (action.type === "event" && !_is_non_empty_string(action.event)) warnings.push(`toolbar item '${id}' (action.type:event) requires 'event' name`);
|
|
336
|
+
if (action.type === "link" && !_is_non_empty_string(action.url)) warnings.push(`toolbar item '${id}' (action.type:link) requires 'url'`);
|
|
335
337
|
if (action.type === "dropdown") {
|
|
336
338
|
let nested = validate_dropdown_action(action, id);
|
|
337
339
|
for (let w of nested) warnings.push(w);
|
|
@@ -1679,6 +1681,20 @@ function safe_id(s) {
|
|
|
1679
1681
|
* drawer { type:"drawer", op:"toggle"|"open"|"close", menu_id? }
|
|
1680
1682
|
* event { type:"event", event, kw? }
|
|
1681
1683
|
* dropdown { type:"dropdown", items[] } (toolbar-only)
|
|
1684
|
+
* link { type:"link", url, target? }
|
|
1685
|
+
*
|
|
1686
|
+
* `link` leaves the application, so on a toolbar item and on the brand
|
|
1687
|
+
* it is rendered as a real <a href> rather than a <button>:
|
|
1688
|
+
* middle-click, open-in-new-tab and the url in the status bar are what
|
|
1689
|
+
* make a link recognisable as one, and a button that assigns
|
|
1690
|
+
* location.href has none of them. It is also why it does not become
|
|
1691
|
+
* an FSM event -- there is no state of this application to transition;
|
|
1692
|
+
* the page is being left. A target of "_blank" gets
|
|
1693
|
+
* rel="noopener noreferrer" for free.
|
|
1694
|
+
*
|
|
1695
|
+
* INSIDE A DROPDOWN it stays a <button>, deliberately: those rows are
|
|
1696
|
+
* role="menuitemradio" in a menu, and a link is not a choice among
|
|
1697
|
+
* options. There the dispatcher navigates.
|
|
1682
1698
|
*
|
|
1683
1699
|
* Per-item `show_on` is honoured: each rendered item is wrapped with
|
|
1684
1700
|
* the same Bulma-helper logic as zones.
|
|
@@ -1765,6 +1781,26 @@ function badge_node(value) {
|
|
|
1765
1781
|
];
|
|
1766
1782
|
}
|
|
1767
1783
|
/************************************************************
|
|
1784
|
+
* The attributes that turn a toolbar item into a real link, or
|
|
1785
|
+
* null when this item is not one. Answering null is what keeps
|
|
1786
|
+
* every caller's default (a <button> that dispatches an action)
|
|
1787
|
+
* exactly as it was.
|
|
1788
|
+
************************************************************/
|
|
1789
|
+
function toolbar_link_attrs(it) {
|
|
1790
|
+
let action = it && it.action || {};
|
|
1791
|
+
if (action.type !== "link") return null;
|
|
1792
|
+
if (empty_string(action.url)) {
|
|
1793
|
+
log_warning(`C_YUI_SHELL: toolbar item '${it.id || "?"}' has action.type "link" without a url`);
|
|
1794
|
+
return null;
|
|
1795
|
+
}
|
|
1796
|
+
let attrs = { href: action.url };
|
|
1797
|
+
if (!empty_string(action.target)) {
|
|
1798
|
+
attrs.target = action.target;
|
|
1799
|
+
if (action.target === "_blank") attrs.rel = "noopener noreferrer";
|
|
1800
|
+
}
|
|
1801
|
+
return attrs;
|
|
1802
|
+
}
|
|
1803
|
+
/************************************************************
|
|
1768
1804
|
* Renderer for the default ("action") item kind.
|
|
1769
1805
|
************************************************************/
|
|
1770
1806
|
function build_toolbar_action_item(gobj, it) {
|
|
@@ -1803,12 +1839,17 @@ function build_toolbar_action_item(gobj, it) {
|
|
|
1803
1839
|
btn_attrs.title = tip;
|
|
1804
1840
|
btn_attrs["data-i18n-title"] = tip;
|
|
1805
1841
|
}
|
|
1842
|
+
let link = toolbar_link_attrs(it);
|
|
1843
|
+
if (link) {
|
|
1844
|
+
delete btn_attrs.type;
|
|
1845
|
+
Object.assign(btn_attrs, link);
|
|
1846
|
+
}
|
|
1806
1847
|
let $item = createElement2([
|
|
1807
|
-
"button",
|
|
1848
|
+
link ? "a" : "button",
|
|
1808
1849
|
btn_attrs,
|
|
1809
1850
|
children
|
|
1810
1851
|
]);
|
|
1811
|
-
$item.addEventListener("click", (ev) => {
|
|
1852
|
+
if (!link) $item.addEventListener("click", (ev) => {
|
|
1812
1853
|
ev.preventDefault();
|
|
1813
1854
|
handle_toolbar_action(gobj, it, $item);
|
|
1814
1855
|
});
|
|
@@ -1840,7 +1881,11 @@ function build_toolbar_brand_item(gobj, it) {
|
|
|
1840
1881
|
attrs["aria-haspopup"] = "menu";
|
|
1841
1882
|
attrs["aria-expanded"] = "false";
|
|
1842
1883
|
}
|
|
1843
|
-
|
|
1884
|
+
let link = toolbar_link_attrs(it);
|
|
1885
|
+
if (link) {
|
|
1886
|
+
tag = "a";
|
|
1887
|
+
Object.assign(attrs, link);
|
|
1888
|
+
} else if (action_type === "") tag = "div";
|
|
1844
1889
|
else attrs.type = "button";
|
|
1845
1890
|
let $item = createElement2([
|
|
1846
1891
|
tag,
|
|
@@ -1858,7 +1903,7 @@ function build_toolbar_brand_item(gobj, it) {
|
|
|
1858
1903
|
it.wordmark
|
|
1859
1904
|
]]
|
|
1860
1905
|
]);
|
|
1861
|
-
if (action_type !== "") $item.addEventListener("click", (ev) => {
|
|
1906
|
+
if (action_type !== "" && !link) $item.addEventListener("click", (ev) => {
|
|
1862
1907
|
ev.preventDefault();
|
|
1863
1908
|
handle_toolbar_action(gobj, it, $item);
|
|
1864
1909
|
});
|
|
@@ -2030,6 +2075,12 @@ function handle_toolbar_action(gobj, item, $trigger) {
|
|
|
2030
2075
|
case "dropdown":
|
|
2031
2076
|
toggle_toolbar_dropdown(gobj, item, action, $trigger);
|
|
2032
2077
|
break;
|
|
2078
|
+
case "link":
|
|
2079
|
+
if (!empty_string(action.url)) {
|
|
2080
|
+
if (action.target === "_blank") window.open(action.url, "_blank", "noopener,noreferrer");
|
|
2081
|
+
else window.location.assign(action.url);
|
|
2082
|
+
} else log_warning(`C_YUI_SHELL: toolbar item '${item.id || "?"}' has action.type "link" without a url`);
|
|
2083
|
+
break;
|
|
2033
2084
|
default: log_warning(`C_YUI_SHELL: toolbar item '${item.id || "?"}' has no/unknown action.type`);
|
|
2034
2085
|
}
|
|
2035
2086
|
}
|
|
@@ -11198,7 +11249,7 @@ function ensure_drag_canvas_patch() {
|
|
|
11198
11249
|
* Constants
|
|
11199
11250
|
***************************************************************/
|
|
11200
11251
|
var GCLASS_NAME$13 = "C_YUI_JSON_GRAPH";
|
|
11201
|
-
var FOLD_SWALLOWED_EVENTS = [
|
|
11252
|
+
var FOLD_SWALLOWED_EVENTS$1 = [
|
|
11202
11253
|
"pointerdown",
|
|
11203
11254
|
"pointerup",
|
|
11204
11255
|
"mousedown",
|
|
@@ -11506,7 +11557,7 @@ function build_ui$12(gobj) {
|
|
|
11506
11557
|
keep: $card ? $card.getAttribute("data-json-path") : ""
|
|
11507
11558
|
}, gobj);
|
|
11508
11559
|
};
|
|
11509
|
-
for (let type of FOLD_SWALLOWED_EVENTS) $mount.addEventListener(type, priv.fold_listener, true);
|
|
11560
|
+
for (let type of FOLD_SWALLOWED_EVENTS$1) $mount.addEventListener(type, priv.fold_listener, true);
|
|
11510
11561
|
} else log_error(`${gobj_short_name(gobj)}: no canvas mount, fold handles are dead`);
|
|
11511
11562
|
priv.$layout_select = $container.querySelector(".JSON_GRAPH_LAYOUT");
|
|
11512
11563
|
priv.$find_input = $container.querySelector(".JSON_GRAPH_FIND_INPUT");
|
|
@@ -11522,7 +11573,7 @@ function destroy_ui$8(gobj) {
|
|
|
11522
11573
|
let $container = gobj_read_attr(gobj, "$container");
|
|
11523
11574
|
if (priv.fold_listener) {
|
|
11524
11575
|
let $mount = $container ? $container.querySelector("#" + priv.canvas_id) : null;
|
|
11525
|
-
if ($mount) for (let type of FOLD_SWALLOWED_EVENTS) $mount.removeEventListener(type, priv.fold_listener, true);
|
|
11576
|
+
if ($mount) for (let type of FOLD_SWALLOWED_EVENTS$1) $mount.removeEventListener(type, priv.fold_listener, true);
|
|
11526
11577
|
priv.fold_listener = null;
|
|
11527
11578
|
}
|
|
11528
11579
|
if ($container) {
|
|
@@ -12373,7 +12424,7 @@ function ac_change_layout$2(gobj, event, kw, src) {
|
|
|
12373
12424
|
* would throw away where the reader was looking. The toolbar
|
|
12374
12425
|
* pair, which changes everything at once, does refit.
|
|
12375
12426
|
************************************************************/
|
|
12376
|
-
function ac_toggle_fold(gobj, event, kw, src) {
|
|
12427
|
+
function ac_toggle_fold$1(gobj, event, kw, src) {
|
|
12377
12428
|
let priv = gobj.priv;
|
|
12378
12429
|
let path = kw && kw.path || "";
|
|
12379
12430
|
if (empty_string(path)) {
|
|
@@ -12412,7 +12463,7 @@ function ac_find_nodes$3(gobj, event, kw, src) {
|
|
|
12412
12463
|
/************************************************************
|
|
12413
12464
|
* EV_EXPAND_ALL
|
|
12414
12465
|
************************************************************/
|
|
12415
|
-
function ac_expand_all$
|
|
12466
|
+
function ac_expand_all$4(gobj, event, kw, src) {
|
|
12416
12467
|
let priv = gobj.priv;
|
|
12417
12468
|
if (priv.collapsed.size === 0) return 0;
|
|
12418
12469
|
priv.collapsed.clear();
|
|
@@ -12424,7 +12475,7 @@ function ac_expand_all$2(gobj, event, kw, src) {
|
|
|
12424
12475
|
/************************************************************
|
|
12425
12476
|
* EV_COLLAPSE_ALL — fold every card but the root.
|
|
12426
12477
|
************************************************************/
|
|
12427
|
-
function ac_collapse_all$
|
|
12478
|
+
function ac_collapse_all$4(gobj, event, kw, src) {
|
|
12428
12479
|
let priv = gobj.priv;
|
|
12429
12480
|
let json_data = gobj_read_attr(gobj, "json_data");
|
|
12430
12481
|
if (!json_data) return 0;
|
|
@@ -12714,7 +12765,7 @@ function create_gclass$13(gclass_name) {
|
|
|
12714
12765
|
],
|
|
12715
12766
|
[
|
|
12716
12767
|
"EV_TOGGLE_FOLD",
|
|
12717
|
-
ac_toggle_fold,
|
|
12768
|
+
ac_toggle_fold$1,
|
|
12718
12769
|
null
|
|
12719
12770
|
],
|
|
12720
12771
|
[
|
|
@@ -12724,12 +12775,12 @@ function create_gclass$13(gclass_name) {
|
|
|
12724
12775
|
],
|
|
12725
12776
|
[
|
|
12726
12777
|
"EV_EXPAND_ALL",
|
|
12727
|
-
ac_expand_all$
|
|
12778
|
+
ac_expand_all$4,
|
|
12728
12779
|
null
|
|
12729
12780
|
],
|
|
12730
12781
|
[
|
|
12731
12782
|
"EV_COLLAPSE_ALL",
|
|
12732
|
-
ac_collapse_all$
|
|
12783
|
+
ac_collapse_all$4,
|
|
12733
12784
|
null
|
|
12734
12785
|
],
|
|
12735
12786
|
[
|
|
@@ -13794,7 +13845,7 @@ function ac_search$2(gobj, event, kw, src) {
|
|
|
13794
13845
|
/************************************************************
|
|
13795
13846
|
* EV_EXPAND_ALL — expand every already-loaded container
|
|
13796
13847
|
************************************************************/
|
|
13797
|
-
function ac_expand_all$
|
|
13848
|
+
function ac_expand_all$3(gobj, event, kw, src) {
|
|
13798
13849
|
let priv = gobj.priv;
|
|
13799
13850
|
if (priv.root === null || priv.root === void 0) return 0;
|
|
13800
13851
|
let paths = [];
|
|
@@ -13812,7 +13863,7 @@ function ac_expand_all$1(gobj, event, kw, src) {
|
|
|
13812
13863
|
/************************************************************
|
|
13813
13864
|
* EV_COLLAPSE_ALL
|
|
13814
13865
|
************************************************************/
|
|
13815
|
-
function ac_collapse_all$
|
|
13866
|
+
function ac_collapse_all$3(gobj, event, kw, src) {
|
|
13816
13867
|
gobj.priv.expanded.clear();
|
|
13817
13868
|
render_view$1(gobj);
|
|
13818
13869
|
return 0;
|
|
@@ -13997,12 +14048,12 @@ function create_gclass$12(gclass_name) {
|
|
|
13997
14048
|
],
|
|
13998
14049
|
[
|
|
13999
14050
|
"EV_EXPAND_ALL",
|
|
14000
|
-
ac_expand_all$
|
|
14051
|
+
ac_expand_all$3,
|
|
14001
14052
|
null
|
|
14002
14053
|
],
|
|
14003
14054
|
[
|
|
14004
14055
|
"EV_COLLAPSE_ALL",
|
|
14005
|
-
ac_collapse_all$
|
|
14056
|
+
ac_collapse_all$3,
|
|
14006
14057
|
null
|
|
14007
14058
|
],
|
|
14008
14059
|
[
|
|
@@ -19005,7 +19056,7 @@ function ac_toggle_collapse(gobj, event, kw, src) {
|
|
|
19005
19056
|
/************************************************************
|
|
19006
19057
|
* Force every node visible (overrides threshold).
|
|
19007
19058
|
************************************************************/
|
|
19008
|
-
function ac_expand_all(gobj, event, kw, src) {
|
|
19059
|
+
function ac_expand_all$2(gobj, event, kw, src) {
|
|
19009
19060
|
let priv = gobj.priv;
|
|
19010
19061
|
if (!priv.collapse_state) priv.collapse_state = {};
|
|
19011
19062
|
if (priv.node_by_id) for (let id of Object.keys(priv.node_by_id)) {
|
|
@@ -19018,7 +19069,7 @@ function ac_expand_all(gobj, event, kw, src) {
|
|
|
19018
19069
|
/************************************************************
|
|
19019
19070
|
* Collapse every node that has children.
|
|
19020
19071
|
************************************************************/
|
|
19021
|
-
function ac_collapse_all(gobj, event, kw, src) {
|
|
19072
|
+
function ac_collapse_all$2(gobj, event, kw, src) {
|
|
19022
19073
|
let priv = gobj.priv;
|
|
19023
19074
|
if (!priv.collapse_state) priv.collapse_state = {};
|
|
19024
19075
|
if (priv.node_by_id) for (let id of Object.keys(priv.node_by_id)) {
|
|
@@ -19370,12 +19421,12 @@ function create_gclass$9(gclass_name) {
|
|
|
19370
19421
|
],
|
|
19371
19422
|
[
|
|
19372
19423
|
"EV_EXPAND_ALL",
|
|
19373
|
-
ac_expand_all,
|
|
19424
|
+
ac_expand_all$2,
|
|
19374
19425
|
null
|
|
19375
19426
|
],
|
|
19376
19427
|
[
|
|
19377
19428
|
"EV_COLLAPSE_ALL",
|
|
19378
|
-
ac_collapse_all,
|
|
19429
|
+
ac_collapse_all$2,
|
|
19379
19430
|
null
|
|
19380
19431
|
],
|
|
19381
19432
|
[
|
|
@@ -26744,6 +26795,429 @@ function node_label(desc, record) {
|
|
|
26744
26795
|
return id;
|
|
26745
26796
|
}
|
|
26746
26797
|
//#endregion
|
|
26798
|
+
//#region src/treedb_fold_model.js
|
|
26799
|
+
/***********************************************************************
|
|
26800
|
+
* treedb_fold_model.js
|
|
26801
|
+
*
|
|
26802
|
+
* A treedb read as a TREE, and which part of it is on screen.
|
|
26803
|
+
*
|
|
26804
|
+
* A treedb is a graph: a record hangs from every parent one of its
|
|
26805
|
+
* fkeys names, and a parent reaches its children through a hook.
|
|
26806
|
+
* Drawn whole it is a pile -- a nave with a hundred and forty
|
|
26807
|
+
* devices is a row of a hundred and forty cards -- so the graph
|
|
26808
|
+
* opens FOLDED, the way a JSON viewer opens: the roots, a level or
|
|
26809
|
+
* two under them, and a count on every cut saying what went with it.
|
|
26810
|
+
*
|
|
26811
|
+
* This module is the arithmetic of that, and nothing else: no G6,
|
|
26812
|
+
* no DOM, no gobj. The graph engine (c_g6_nodes_tree.js) asks it
|
|
26813
|
+
* what is visible and draws exactly that.
|
|
26814
|
+
*
|
|
26815
|
+
* Vocabulary
|
|
26816
|
+
*
|
|
26817
|
+
* node key "topic^id" -- one record
|
|
26818
|
+
* group the children of ONE node through ONE hook,
|
|
26819
|
+
* keyed "topic^id|hook"; the roots of a topic are
|
|
26820
|
+
* a group of their own, keyed "|topic"
|
|
26821
|
+
* shown how many of a group's children are on screen:
|
|
26822
|
+
* absent means folded, a number means the first
|
|
26823
|
+
* N, in record order, a page at a time
|
|
26824
|
+
*
|
|
26825
|
+
* Two rules that are not obvious:
|
|
26826
|
+
*
|
|
26827
|
+
* - A node is visible when SOME visible parent shows it, or it is
|
|
26828
|
+
* a shown root. A record with two parents (a device hangs from
|
|
26829
|
+
* its place AND its controller) is drawn ONCE, under whichever
|
|
26830
|
+
* opened first, and folding one of the two leaves it where the
|
|
26831
|
+
* other one shows it. The union, never the intersection.
|
|
26832
|
+
*
|
|
26833
|
+
* - A parent that the engine does not draw an edge to is not a
|
|
26834
|
+
* parent here either: the `extended` tier (hooks and no fkeys)
|
|
26835
|
+
* gets no edges in draw_link(), so its children are roots. The
|
|
26836
|
+
* tree on screen and the tree in this model have to agree, or a
|
|
26837
|
+
* pill counts children that no line reaches.
|
|
26838
|
+
*
|
|
26839
|
+
* Copyright (c) 2026, ArtGins.
|
|
26840
|
+
* All Rights Reserved.
|
|
26841
|
+
***********************************************************************/
|
|
26842
|
+
var PAGE_SIZE_DEFAULT = 24;
|
|
26843
|
+
/************************************************************
|
|
26844
|
+
* Keys
|
|
26845
|
+
************************************************************/
|
|
26846
|
+
function fold_node_key(topic_name, id) {
|
|
26847
|
+
return `${topic_name}^${id}`;
|
|
26848
|
+
}
|
|
26849
|
+
function fold_group_key(node_key, hook_name) {
|
|
26850
|
+
return `${node_key}|${hook_name}`;
|
|
26851
|
+
}
|
|
26852
|
+
function fold_root_group_key(topic_name) {
|
|
26853
|
+
return `|${topic_name}`;
|
|
26854
|
+
}
|
|
26855
|
+
function fold_split_group_key(group_key) {
|
|
26856
|
+
let i = group_key.lastIndexOf("|");
|
|
26857
|
+
if (i < 0) return null;
|
|
26858
|
+
return {
|
|
26859
|
+
node_key: group_key.slice(0, i),
|
|
26860
|
+
hook: group_key.slice(i + 1)
|
|
26861
|
+
};
|
|
26862
|
+
}
|
|
26863
|
+
/************************************************************
|
|
26864
|
+
* Every parent a record names, decoded, in column order.
|
|
26865
|
+
*
|
|
26866
|
+
* Only the parents that will be DRAWN: a topic missing from
|
|
26867
|
+
* `descs` (a system topic such as `__assets__`, never fetched)
|
|
26868
|
+
* and an `extended` parent (no edge is drawn to one, see the
|
|
26869
|
+
* header) are left out, so a child of nothing else is a root.
|
|
26870
|
+
************************************************************/
|
|
26871
|
+
function parents_of(descs, desc, record) {
|
|
26872
|
+
let out = [];
|
|
26873
|
+
let cols = desc.cols || [];
|
|
26874
|
+
for (let i = 0; i < cols.length; i++) {
|
|
26875
|
+
let col = cols[i];
|
|
26876
|
+
if (!col.fkey) continue;
|
|
26877
|
+
let fkeys = record[col.id];
|
|
26878
|
+
if (!fkeys) continue;
|
|
26879
|
+
let list = is_array(fkeys) ? fkeys : [fkeys];
|
|
26880
|
+
for (let j = 0; j < list.length; j++) {
|
|
26881
|
+
let ref = treedb_decoder_fkey(col, list[j]);
|
|
26882
|
+
if (!ref || !ref.topic_name || !ref.hook_name) continue;
|
|
26883
|
+
let parent_desc = descs[ref.topic_name];
|
|
26884
|
+
if (!parent_desc) continue;
|
|
26885
|
+
if (parent_desc.node_treedb_type === "extended") continue;
|
|
26886
|
+
out.push({
|
|
26887
|
+
node_key: fold_node_key(ref.topic_name, String(ref.id)),
|
|
26888
|
+
hook: ref.hook_name,
|
|
26889
|
+
fkey_name: col.id
|
|
26890
|
+
});
|
|
26891
|
+
}
|
|
26892
|
+
}
|
|
26893
|
+
return out;
|
|
26894
|
+
}
|
|
26895
|
+
/************************************************************
|
|
26896
|
+
* Build the model from the topic descriptions and their records.
|
|
26897
|
+
*
|
|
26898
|
+
* descs {topic_name: desc}, system topics included (skipped)
|
|
26899
|
+
* records {topic_name: [record, ...]}, in the order the
|
|
26900
|
+
* backend returned them -- that order is the order of
|
|
26901
|
+
* the children on screen, and of the pages
|
|
26902
|
+
*
|
|
26903
|
+
* Returns
|
|
26904
|
+
* {
|
|
26905
|
+
* nodes: Map<node_key, {key, topic_name, id, record,
|
|
26906
|
+
* parents: [{node_key, hook, fkey_name}],
|
|
26907
|
+
* hooks: {hook: [child_key, ...]}}>
|
|
26908
|
+
* groups: Map<group_key, [child_key, ...]> (roots included)
|
|
26909
|
+
* topics: [topic_name, ...] topics with records, in desc order
|
|
26910
|
+
* }
|
|
26911
|
+
************************************************************/
|
|
26912
|
+
function fold_build_model(descs, records) {
|
|
26913
|
+
let nodes = /* @__PURE__ */ new Map();
|
|
26914
|
+
let groups = /* @__PURE__ */ new Map();
|
|
26915
|
+
let topics = [];
|
|
26916
|
+
descs = is_object(descs) ? descs : {};
|
|
26917
|
+
records = is_object(records) ? records : {};
|
|
26918
|
+
for (let topic_name of Object.keys(descs)) {
|
|
26919
|
+
if (topic_name.substring(0, 2) === "__") continue;
|
|
26920
|
+
descs[topic_name];
|
|
26921
|
+
let list = records[topic_name];
|
|
26922
|
+
if (!is_array(list)) continue;
|
|
26923
|
+
topics.push(topic_name);
|
|
26924
|
+
for (let i = 0; i < list.length; i++) {
|
|
26925
|
+
let record = list[i];
|
|
26926
|
+
if (!is_object(record) || record.id === void 0 || record.id === null) continue;
|
|
26927
|
+
let key = fold_node_key(topic_name, String(record.id));
|
|
26928
|
+
nodes.set(key, {
|
|
26929
|
+
key,
|
|
26930
|
+
topic_name,
|
|
26931
|
+
id: String(record.id),
|
|
26932
|
+
record,
|
|
26933
|
+
parents: [],
|
|
26934
|
+
hooks: {}
|
|
26935
|
+
});
|
|
26936
|
+
}
|
|
26937
|
+
}
|
|
26938
|
+
for (let node of nodes.values()) {
|
|
26939
|
+
let desc = descs[node.topic_name];
|
|
26940
|
+
let parents = parents_of(descs, desc, node.record);
|
|
26941
|
+
for (let p of parents) {
|
|
26942
|
+
let parent = nodes.get(p.node_key);
|
|
26943
|
+
if (!parent) continue;
|
|
26944
|
+
node.parents.push(p);
|
|
26945
|
+
if (!parent.hooks[p.hook]) parent.hooks[p.hook] = [];
|
|
26946
|
+
parent.hooks[p.hook].push(node.key);
|
|
26947
|
+
}
|
|
26948
|
+
}
|
|
26949
|
+
for (let node of nodes.values()) for (let hook of Object.keys(node.hooks)) groups.set(fold_group_key(node.key, hook), node.hooks[hook]);
|
|
26950
|
+
let roots = {};
|
|
26951
|
+
for (let topic_name of topics) roots[topic_name] = [];
|
|
26952
|
+
for (let node of nodes.values()) if (node.parents.length === 0) roots[node.topic_name].push(node.key);
|
|
26953
|
+
let reached = reach_from(nodes, [].concat(...Object.values(roots)));
|
|
26954
|
+
for (let node of nodes.values()) if (!reached.has(node.key)) {
|
|
26955
|
+
roots[node.topic_name].push(node.key);
|
|
26956
|
+
for (let k of reach_from(nodes, [node.key])) reached.add(k);
|
|
26957
|
+
}
|
|
26958
|
+
for (let topic_name of topics) if (roots[topic_name].length > 0) groups.set(fold_root_group_key(topic_name), roots[topic_name]);
|
|
26959
|
+
return {
|
|
26960
|
+
nodes,
|
|
26961
|
+
groups,
|
|
26962
|
+
topics
|
|
26963
|
+
};
|
|
26964
|
+
}
|
|
26965
|
+
/************************************************************
|
|
26966
|
+
* Everything reachable downwards from `start`, cycles included.
|
|
26967
|
+
************************************************************/
|
|
26968
|
+
function reach_from(nodes, start) {
|
|
26969
|
+
let seen = /* @__PURE__ */ new Set();
|
|
26970
|
+
let queue = start.slice();
|
|
26971
|
+
while (queue.length) {
|
|
26972
|
+
let key = queue.shift();
|
|
26973
|
+
if (seen.has(key)) continue;
|
|
26974
|
+
seen.add(key);
|
|
26975
|
+
let node = nodes.get(key);
|
|
26976
|
+
if (!node) continue;
|
|
26977
|
+
for (let hook of Object.keys(node.hooks)) for (let child of node.hooks[hook]) if (!seen.has(child)) queue.push(child);
|
|
26978
|
+
}
|
|
26979
|
+
return seen;
|
|
26980
|
+
}
|
|
26981
|
+
/************************************************************
|
|
26982
|
+
* Fold state: which groups are open, and how far.
|
|
26983
|
+
************************************************************/
|
|
26984
|
+
function fold_new_state(page_size) {
|
|
26985
|
+
return {
|
|
26986
|
+
page_size: page_size > 0 ? page_size : PAGE_SIZE_DEFAULT,
|
|
26987
|
+
shown: /* @__PURE__ */ new Map()
|
|
26988
|
+
};
|
|
26989
|
+
}
|
|
26990
|
+
function fold_group_total(model, group_key) {
|
|
26991
|
+
let list = model.groups.get(group_key);
|
|
26992
|
+
return list ? list.length : 0;
|
|
26993
|
+
}
|
|
26994
|
+
function fold_group_shown(model, state, group_key) {
|
|
26995
|
+
let total = fold_group_total(model, group_key);
|
|
26996
|
+
let n = state.shown.get(group_key) || 0;
|
|
26997
|
+
return Math.min(n, total);
|
|
26998
|
+
}
|
|
26999
|
+
function fold_is_open(state, group_key) {
|
|
27000
|
+
return (state.shown.get(group_key) || 0) > 0;
|
|
27001
|
+
}
|
|
27002
|
+
function page_ceiling(state, n) {
|
|
27003
|
+
let p = state.page_size;
|
|
27004
|
+
return Math.ceil(n / p) * p;
|
|
27005
|
+
}
|
|
27006
|
+
/************************************************************
|
|
27007
|
+
* Open a group on its first page, or close it.
|
|
27008
|
+
* Returns true when it ends OPEN.
|
|
27009
|
+
************************************************************/
|
|
27010
|
+
function fold_toggle(model, state, group_key) {
|
|
27011
|
+
if (fold_is_open(state, group_key)) {
|
|
27012
|
+
state.shown.delete(group_key);
|
|
27013
|
+
return false;
|
|
27014
|
+
}
|
|
27015
|
+
if (fold_group_total(model, group_key) === 0) return false;
|
|
27016
|
+
state.shown.set(group_key, state.page_size);
|
|
27017
|
+
return true;
|
|
27018
|
+
}
|
|
27019
|
+
/************************************************************
|
|
27020
|
+
* One more page of a group. Returns how many are shown now.
|
|
27021
|
+
************************************************************/
|
|
27022
|
+
function fold_show_more(model, state, group_key) {
|
|
27023
|
+
let total = fold_group_total(model, group_key);
|
|
27024
|
+
let n = fold_group_shown(model, state, group_key) + state.page_size;
|
|
27025
|
+
n = Math.min(n, total);
|
|
27026
|
+
if (n > 0) state.shown.set(group_key, n);
|
|
27027
|
+
return n;
|
|
27028
|
+
}
|
|
27029
|
+
/************************************************************
|
|
27030
|
+
* Every group open on ALL its children. This is the reader
|
|
27031
|
+
* asking for the whole thing; the pages are for the other way in.
|
|
27032
|
+
************************************************************/
|
|
27033
|
+
function fold_expand_all(model, state) {
|
|
27034
|
+
state.shown.clear();
|
|
27035
|
+
for (let [group_key, list] of model.groups) if (list.length > 0) state.shown.set(group_key, list.length);
|
|
27036
|
+
}
|
|
27037
|
+
/************************************************************
|
|
27038
|
+
* The roots and nothing else -- the first page of each topic's
|
|
27039
|
+
* roots, the way the graph looks before anything is opened.
|
|
27040
|
+
************************************************************/
|
|
27041
|
+
function fold_collapse_all(model, state) {
|
|
27042
|
+
state.shown.clear();
|
|
27043
|
+
for (let topic_name of model.topics) {
|
|
27044
|
+
let key = fold_root_group_key(topic_name);
|
|
27045
|
+
if (fold_group_total(model, key) > 0) state.shown.set(key, state.page_size);
|
|
27046
|
+
}
|
|
27047
|
+
}
|
|
27048
|
+
/************************************************************
|
|
27049
|
+
* Open the tree down to `depth` levels: the roots are level 1,
|
|
27050
|
+
* their children level 2. A node reached by two paths takes the
|
|
27051
|
+
* shallowest. Each group opens on its first page. `depth` of 1
|
|
27052
|
+
* is the roots alone; anything below that is treated as 1.
|
|
27053
|
+
************************************************************/
|
|
27054
|
+
function fold_expand_to_depth(model, state, depth) {
|
|
27055
|
+
fold_collapse_all(model, state);
|
|
27056
|
+
if (!(depth > 1)) return;
|
|
27057
|
+
let level = /* @__PURE__ */ new Map();
|
|
27058
|
+
let queue = [];
|
|
27059
|
+
for (let topic_name of model.topics) {
|
|
27060
|
+
let roots = model.groups.get(fold_root_group_key(topic_name)) || [];
|
|
27061
|
+
for (let key of roots) if (!level.has(key)) {
|
|
27062
|
+
level.set(key, 1);
|
|
27063
|
+
queue.push(key);
|
|
27064
|
+
}
|
|
27065
|
+
}
|
|
27066
|
+
while (queue.length) {
|
|
27067
|
+
let key = queue.shift();
|
|
27068
|
+
let lv = level.get(key);
|
|
27069
|
+
if (lv >= depth) continue;
|
|
27070
|
+
let node = model.nodes.get(key);
|
|
27071
|
+
if (!node) continue;
|
|
27072
|
+
for (let hook of Object.keys(node.hooks)) {
|
|
27073
|
+
let children = node.hooks[hook];
|
|
27074
|
+
if (children.length === 0) continue;
|
|
27075
|
+
state.shown.set(fold_group_key(key, hook), state.page_size);
|
|
27076
|
+
for (let child of children) if (!level.has(child)) {
|
|
27077
|
+
level.set(child, lv + 1);
|
|
27078
|
+
queue.push(child);
|
|
27079
|
+
}
|
|
27080
|
+
}
|
|
27081
|
+
}
|
|
27082
|
+
}
|
|
27083
|
+
/************************************************************
|
|
27084
|
+
* The set of node keys on screen, from the roots down through
|
|
27085
|
+
* every open group -- and only through open groups whose parent
|
|
27086
|
+
* is itself on screen: a group remembered open under a folded
|
|
27087
|
+
* ancestor stays remembered, not shown.
|
|
27088
|
+
************************************************************/
|
|
27089
|
+
function fold_visible_set(model, state) {
|
|
27090
|
+
let visible = /* @__PURE__ */ new Set();
|
|
27091
|
+
let queue = [];
|
|
27092
|
+
let push_group = (group_key, children) => {
|
|
27093
|
+
let n = fold_group_shown(model, state, group_key);
|
|
27094
|
+
for (let i = 0; i < n; i++) {
|
|
27095
|
+
let key = children[i];
|
|
27096
|
+
if (!visible.has(key)) {
|
|
27097
|
+
visible.add(key);
|
|
27098
|
+
queue.push(key);
|
|
27099
|
+
}
|
|
27100
|
+
}
|
|
27101
|
+
};
|
|
27102
|
+
for (let topic_name of model.topics) {
|
|
27103
|
+
let key = fold_root_group_key(topic_name);
|
|
27104
|
+
let roots = model.groups.get(key);
|
|
27105
|
+
if (roots) push_group(key, roots);
|
|
27106
|
+
}
|
|
27107
|
+
while (queue.length) {
|
|
27108
|
+
let key = queue.shift();
|
|
27109
|
+
let node = model.nodes.get(key);
|
|
27110
|
+
if (!node) continue;
|
|
27111
|
+
for (let hook of Object.keys(node.hooks)) push_group(fold_group_key(key, hook), node.hooks[hook]);
|
|
27112
|
+
}
|
|
27113
|
+
return visible;
|
|
27114
|
+
}
|
|
27115
|
+
/************************************************************
|
|
27116
|
+
* The open groups that still have children off screen, with the
|
|
27117
|
+
* parent that owns each -- what the "+N" chips are drawn for.
|
|
27118
|
+
* Only groups whose parent is visible (or root groups).
|
|
27119
|
+
*
|
|
27120
|
+
* Returns [{group_key, node_key, hook, shown, total, rest}]
|
|
27121
|
+
************************************************************/
|
|
27122
|
+
function fold_pending_groups(model, state, visible) {
|
|
27123
|
+
let out = [];
|
|
27124
|
+
for (let [group_key, list] of model.groups) {
|
|
27125
|
+
let shown = fold_group_shown(model, state, group_key);
|
|
27126
|
+
if (shown === 0 || shown >= list.length) continue;
|
|
27127
|
+
let g = fold_split_group_key(group_key);
|
|
27128
|
+
if (g.node_key && !visible.has(g.node_key)) continue;
|
|
27129
|
+
out.push({
|
|
27130
|
+
group_key,
|
|
27131
|
+
node_key: g.node_key,
|
|
27132
|
+
hook: g.hook,
|
|
27133
|
+
shown,
|
|
27134
|
+
total: list.length,
|
|
27135
|
+
rest: list.length - shown
|
|
27136
|
+
});
|
|
27137
|
+
}
|
|
27138
|
+
return out;
|
|
27139
|
+
}
|
|
27140
|
+
/************************************************************
|
|
27141
|
+
* The pills of one card: one per hook that has children.
|
|
27142
|
+
*
|
|
27143
|
+
* Returns [{hook, group_key, total, shown, open, child_topic}]
|
|
27144
|
+
* `child_topic` is the topic of the first child, which is what
|
|
27145
|
+
* the pill is coloured with (a hook can name several topics in
|
|
27146
|
+
* the schema, but the children it has are what it links).
|
|
27147
|
+
************************************************************/
|
|
27148
|
+
function fold_pills_of(model, state, node_key) {
|
|
27149
|
+
let node = model.nodes.get(node_key);
|
|
27150
|
+
if (!node) return [];
|
|
27151
|
+
let out = [];
|
|
27152
|
+
for (let hook of Object.keys(node.hooks)) {
|
|
27153
|
+
let children = node.hooks[hook];
|
|
27154
|
+
if (children.length === 0) continue;
|
|
27155
|
+
let group_key = fold_group_key(node_key, hook);
|
|
27156
|
+
let first = model.nodes.get(children[0]);
|
|
27157
|
+
out.push({
|
|
27158
|
+
hook,
|
|
27159
|
+
group_key,
|
|
27160
|
+
total: children.length,
|
|
27161
|
+
shown: fold_group_shown(model, state, group_key),
|
|
27162
|
+
open: fold_is_open(state, group_key),
|
|
27163
|
+
child_topic: first ? first.topic_name : ""
|
|
27164
|
+
});
|
|
27165
|
+
}
|
|
27166
|
+
return out;
|
|
27167
|
+
}
|
|
27168
|
+
/************************************************************
|
|
27169
|
+
* Make ONE node visible, opening the groups on a path from a
|
|
27170
|
+
* root to it, and as many pages of each as it takes for the
|
|
27171
|
+
* node to be inside the shown prefix. Prefers the shortest path
|
|
27172
|
+
* (breadth-first upwards). Returns false if no root reaches it,
|
|
27173
|
+
* which cannot happen with a model built here (see roots) but
|
|
27174
|
+
* is the honest answer for a key the model does not know.
|
|
27175
|
+
************************************************************/
|
|
27176
|
+
function fold_reveal(model, state, node_key) {
|
|
27177
|
+
if (!model.nodes.get(node_key)) return false;
|
|
27178
|
+
let root_group_of = (key) => {
|
|
27179
|
+
let rg = fold_root_group_key(model.nodes.get(key).topic_name);
|
|
27180
|
+
return (model.groups.get(rg) || []).indexOf(key) >= 0 ? rg : null;
|
|
27181
|
+
};
|
|
27182
|
+
let via = /* @__PURE__ */ new Map();
|
|
27183
|
+
let queue = [node_key];
|
|
27184
|
+
via.set(node_key, null);
|
|
27185
|
+
let root_key = null;
|
|
27186
|
+
while (queue.length) {
|
|
27187
|
+
let key = queue.shift();
|
|
27188
|
+
if (root_group_of(key)) {
|
|
27189
|
+
root_key = key;
|
|
27190
|
+
break;
|
|
27191
|
+
}
|
|
27192
|
+
let n = model.nodes.get(key);
|
|
27193
|
+
for (let p of n.parents) {
|
|
27194
|
+
if (!model.nodes.has(p.node_key) || via.has(p.node_key)) continue;
|
|
27195
|
+
via.set(p.node_key, {
|
|
27196
|
+
child_key: key,
|
|
27197
|
+
hook: p.hook
|
|
27198
|
+
});
|
|
27199
|
+
queue.push(p.node_key);
|
|
27200
|
+
}
|
|
27201
|
+
}
|
|
27202
|
+
if (root_key === null) return false;
|
|
27203
|
+
let open_up_to = (group_key, child_key) => {
|
|
27204
|
+
let list = model.groups.get(group_key) || [];
|
|
27205
|
+
let idx = list.indexOf(child_key);
|
|
27206
|
+
if (idx < 0) return;
|
|
27207
|
+
let need = page_ceiling(state, idx + 1);
|
|
27208
|
+
if (need > (state.shown.get(group_key) || 0)) state.shown.set(group_key, Math.min(need, list.length));
|
|
27209
|
+
};
|
|
27210
|
+
open_up_to(root_group_of(root_key), root_key);
|
|
27211
|
+
let key = root_key;
|
|
27212
|
+
while (true) {
|
|
27213
|
+
let step = via.get(key);
|
|
27214
|
+
if (!step) break;
|
|
27215
|
+
open_up_to(fold_group_key(key, step.hook), step.child_key);
|
|
27216
|
+
key = step.child_key;
|
|
27217
|
+
}
|
|
27218
|
+
return true;
|
|
27219
|
+
}
|
|
27220
|
+
//#endregion
|
|
26747
27221
|
//#region node_modules/@babel/runtime/helpers/esm/typeof.js
|
|
26748
27222
|
function _typeof(o) {
|
|
26749
27223
|
"@babel/helpers - typeof";
|
|
@@ -53488,6 +53962,23 @@ function ensure_brush_select_owned() {
|
|
|
53488
53962
|
* record: null, // Data of node
|
|
53489
53963
|
* }
|
|
53490
53964
|
*
|
|
53965
|
+
* The graph opens FOLDED, like a JSON viewer.
|
|
53966
|
+
*
|
|
53967
|
+
* Every record of every topic is fetched, but only the ones on
|
|
53968
|
+
* screen become G6 nodes: the roots, `expand_depth` levels under
|
|
53969
|
+
* them, one page (`fold_page_size`) of children per hook. Each
|
|
53970
|
+
* card carries a pill per hook that has children -- `▸ devices
|
|
53971
|
+
* 142` -- which opens or folds that hook; an open hook with more
|
|
53972
|
+
* children than its page ends in a `+N` chip that opens the next
|
|
53973
|
+
* page. The arithmetic lives in treedb_fold_model.js; this file
|
|
53974
|
+
* reconciles the G6 data with what that model says is visible
|
|
53975
|
+
* (see reconcile_fold) and lays it out again.
|
|
53976
|
+
*
|
|
53977
|
+
* `dagre` reads LEFT TO RIGHT: the children of a node are a
|
|
53978
|
+
* column beside it, the ports move to the sides with it, and
|
|
53979
|
+
* the edges are horizontal beziers. `antv-dagre` keeps the
|
|
53980
|
+
* top-down reading.
|
|
53981
|
+
*
|
|
53491
53982
|
* Copyright (c) 2020-2021 Niyamaka.
|
|
53492
53983
|
* Copyright (c) 2025-2026, ArtGins.
|
|
53493
53984
|
* All Rights Reserved.
|
|
@@ -53547,8 +54038,18 @@ var SELECT_RING = "rgba(59,130,246,0.95)";
|
|
|
53547
54038
|
***************************************************************/
|
|
53548
54039
|
var _layouts = {
|
|
53549
54040
|
"manual": { type: "manual" },
|
|
53550
|
-
"dagre": {
|
|
53551
|
-
|
|
54041
|
+
"dagre": {
|
|
54042
|
+
type: "dagre",
|
|
54043
|
+
rankdir: "LR",
|
|
54044
|
+
nodesep: 18,
|
|
54045
|
+
ranksep: 110
|
|
54046
|
+
},
|
|
54047
|
+
"antv-dagre": {
|
|
54048
|
+
type: "antv-dagre",
|
|
54049
|
+
rankdir: "TB",
|
|
54050
|
+
nodesep: 30,
|
|
54051
|
+
ranksep: 80
|
|
54052
|
+
},
|
|
53552
54053
|
"d3-force": {
|
|
53553
54054
|
type: "d3-force",
|
|
53554
54055
|
link: {
|
|
@@ -53596,6 +54097,8 @@ var attrs_table$4 = [
|
|
|
53596
54097
|
SDATA(data_type_t.DTP_LIST, "layout_names", sdata_flag_t.SDF_RD, JSON.stringify(Object.keys(_layouts)), "Available layout names (read-only, for parent to query)"),
|
|
53597
54098
|
SDATA(data_type_t.DTP_STRING, "hook_port_position", 0, "bottom", "Hook port position"),
|
|
53598
54099
|
SDATA(data_type_t.DTP_STRING, "fkey_port_position", 0, "top", "Fkey port position"),
|
|
54100
|
+
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"),
|
|
54101
|
+
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"),
|
|
53599
54102
|
SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
|
|
53600
54103
|
SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
|
|
53601
54104
|
SDATA(data_type_t.DTP_STRING, "wide", 0, "40px", "Height of header"),
|
|
@@ -53667,7 +54170,14 @@ var PRIVATE_DATA$4 = {
|
|
|
53667
54170
|
_pending_find: null,
|
|
53668
54171
|
_layout_asked: "",
|
|
53669
54172
|
_nodes_total: 0,
|
|
53670
|
-
_nodes_placed: 0
|
|
54173
|
+
_nodes_placed: 0,
|
|
54174
|
+
expand_depth: 2,
|
|
54175
|
+
fold_page_size: 24,
|
|
54176
|
+
_fold_model: null,
|
|
54177
|
+
_fold_state: null,
|
|
54178
|
+
_fold_listener: null,
|
|
54179
|
+
_pill_sig: null,
|
|
54180
|
+
_fold_busy: false
|
|
53671
54181
|
};
|
|
53672
54182
|
var __gclass__$4 = null;
|
|
53673
54183
|
/******************************
|
|
@@ -53687,6 +54197,7 @@ function mt_create$4(gobj) {
|
|
|
53687
54197
|
build_ui$4(gobj);
|
|
53688
54198
|
register_layouts(gobj);
|
|
53689
54199
|
build_graph$1(gobj);
|
|
54200
|
+
install_fold_listener(gobj);
|
|
53690
54201
|
if (typeof ResizeObserver !== "undefined" && priv.$container) {
|
|
53691
54202
|
let ro = new ResizeObserver(() => {
|
|
53692
54203
|
if (priv._resize_raf) cancelAnimationFrame(priv._resize_raf);
|
|
@@ -53726,6 +54237,7 @@ function mt_destroy$4(gobj) {
|
|
|
53726
54237
|
i18next.off("languageChanged", priv._on_language_changed);
|
|
53727
54238
|
priv._on_language_changed = null;
|
|
53728
54239
|
}
|
|
54240
|
+
uninstall_fold_listener(gobj);
|
|
53729
54241
|
if (priv._on_pointerdown_focus) {
|
|
53730
54242
|
priv.$container.removeEventListener("pointerdown", priv._on_pointerdown_focus, true);
|
|
53731
54243
|
priv._on_pointerdown_focus = null;
|
|
@@ -53913,6 +54425,7 @@ function configure_events(gobj) {
|
|
|
53913
54425
|
});
|
|
53914
54426
|
priv._on_language_changed = () => {
|
|
53915
54427
|
update_toolbar(gobj);
|
|
54428
|
+
repaint_more_chips(gobj);
|
|
53916
54429
|
};
|
|
53917
54430
|
i18next.on("languageChanged", priv._on_language_changed);
|
|
53918
54431
|
priv._on_pointerdown_focus = (ev) => {
|
|
@@ -54410,15 +54923,79 @@ function build_ports(gobj, desc) {
|
|
|
54410
54923
|
top_ports.push(port);
|
|
54411
54924
|
}
|
|
54412
54925
|
}
|
|
54413
|
-
|
|
54414
|
-
for (let i = 0; i < bottom_ports.length; i++) {
|
|
54415
|
-
let point = getPointPosition(bottom_ports.length, i);
|
|
54416
|
-
if (top_ports.length === 0) bottom_ports[i].placement = [0, .5];
|
|
54417
|
-
else bottom_ports[i].placement = [point, 1];
|
|
54418
|
-
}
|
|
54926
|
+
place_ports(gobj, top_ports, bottom_ports);
|
|
54419
54927
|
return [...top_ports, ...bottom_ports];
|
|
54420
54928
|
}
|
|
54421
54929
|
/************************************************************
|
|
54930
|
+
* Which way the graph reads. `dagre` is the left-to-right
|
|
54931
|
+
* layout; everything else keeps the top-down reading, `manual`
|
|
54932
|
+
* included, because the saved arrangements were made with the
|
|
54933
|
+
* ports on the top and bottom edges.
|
|
54934
|
+
************************************************************/
|
|
54935
|
+
function layout_direction(gobj) {
|
|
54936
|
+
return gobj.priv.layout === "dagre" ? "LR" : "TB";
|
|
54937
|
+
}
|
|
54938
|
+
function edge_type_for(gobj) {
|
|
54939
|
+
return layout_direction(gobj) === "LR" ? "cubic-horizontal" : "cubic";
|
|
54940
|
+
}
|
|
54941
|
+
/************************************************************
|
|
54942
|
+
* Where the ports sit, by direction: fkeys (the way in from a
|
|
54943
|
+
* parent) all on one point of the leading edge, hooks (the way
|
|
54944
|
+
* out to the children) spread along the trailing edge. Left and
|
|
54945
|
+
* right when the graph reads left to right; top and bottom
|
|
54946
|
+
* otherwise.
|
|
54947
|
+
************************************************************/
|
|
54948
|
+
function place_ports(gobj, fkey_ports, hook_ports) {
|
|
54949
|
+
let lr = layout_direction(gobj) === "LR";
|
|
54950
|
+
for (let i = 0; i < fkey_ports.length; i++) fkey_ports[i].placement = lr ? [0, .5] : [.5, 0];
|
|
54951
|
+
for (let i = 0; i < hook_ports.length; i++) {
|
|
54952
|
+
let point = getPointPosition(hook_ports.length, i);
|
|
54953
|
+
if (fkey_ports.length === 0 && !lr) hook_ports[i].placement = [0, .5];
|
|
54954
|
+
else if (lr) hook_ports[i].placement = [1, point];
|
|
54955
|
+
else hook_ports[i].placement = [point, 1];
|
|
54956
|
+
}
|
|
54957
|
+
}
|
|
54958
|
+
/************************************************************
|
|
54959
|
+
* The layout changed direction: move every port to the edge
|
|
54960
|
+
* the new reading needs, and give every edge the curve that
|
|
54961
|
+
* goes with it. Called before the layout runs, so the lines
|
|
54962
|
+
* are drawn once, in the right place.
|
|
54963
|
+
************************************************************/
|
|
54964
|
+
function apply_layout_direction(gobj) {
|
|
54965
|
+
let graph = gobj.priv.graph;
|
|
54966
|
+
if (!graph) return;
|
|
54967
|
+
let node_updates = [];
|
|
54968
|
+
for (let nd of graph.getNodeData() || []) {
|
|
54969
|
+
if (!nd || !nd.data || !nd.data.desc || !nd.style || !is_array(nd.style.ports)) continue;
|
|
54970
|
+
let desc = nd.data.desc;
|
|
54971
|
+
let fkey_ports = [];
|
|
54972
|
+
let hook_ports = [];
|
|
54973
|
+
for (let port of nd.style.ports) {
|
|
54974
|
+
let col = get_col_by_id(desc, port.key);
|
|
54975
|
+
let copy = Object.assign({}, port);
|
|
54976
|
+
if (col && col.fkey) fkey_ports.push(copy);
|
|
54977
|
+
else hook_ports.push(copy);
|
|
54978
|
+
}
|
|
54979
|
+
place_ports(gobj, fkey_ports, hook_ports);
|
|
54980
|
+
node_updates.push({
|
|
54981
|
+
id: nd.id,
|
|
54982
|
+
style: { ports: [...fkey_ports, ...hook_ports] }
|
|
54983
|
+
});
|
|
54984
|
+
}
|
|
54985
|
+
let type = edge_type_for(gobj);
|
|
54986
|
+
let edge_updates = [];
|
|
54987
|
+
for (let ed of graph.getEdgeData() || []) if (ed && ed.type !== type) edge_updates.push({
|
|
54988
|
+
id: ed.id,
|
|
54989
|
+
type
|
|
54990
|
+
});
|
|
54991
|
+
try {
|
|
54992
|
+
if (node_updates.length) graph.updateNodeData(node_updates);
|
|
54993
|
+
if (edge_updates.length) graph.updateEdgeData(edge_updates);
|
|
54994
|
+
} catch (e) {
|
|
54995
|
+
log_error(`${gobj_short_name(gobj)}: cannot turn the ports: ${e}`);
|
|
54996
|
+
}
|
|
54997
|
+
}
|
|
54998
|
+
/************************************************************
|
|
54422
54999
|
* Create a topic node in the G6 graph
|
|
54423
55000
|
************************************************************/
|
|
54424
55001
|
function create_topic_node(gobj, desc, record) {
|
|
@@ -54427,8 +55004,6 @@ function create_topic_node(gobj, desc, record) {
|
|
|
54427
55004
|
let node_name = build_node_name(gobj, desc.topic_name, record.id);
|
|
54428
55005
|
let xy = get_default_ne_xy(gobj);
|
|
54429
55006
|
let geometry = get_node_graph_props(gobj, desc.topic_name, record.id, record);
|
|
54430
|
-
priv._nodes_total++;
|
|
54431
|
-
if (geometry_has_position(geometry)) priv._nodes_placed++;
|
|
54432
55007
|
let x = kw_get_int(gobj, geometry, "x", xy, kw_flag_t.KW_CREATE);
|
|
54433
55008
|
let y = kw_get_int(gobj, geometry, "y", xy, kw_flag_t.KW_CREATE);
|
|
54434
55009
|
let ports = build_ports(gobj, desc);
|
|
@@ -54441,24 +55016,26 @@ function create_topic_node(gobj, desc, record) {
|
|
|
54441
55016
|
stroke: getStrokeColor(desc.color),
|
|
54442
55017
|
lineWidth: 1
|
|
54443
55018
|
};
|
|
55019
|
+
let pills_html = pills_html_of_key(gobj, fold_node_key(desc.topic_name, String(record.id)));
|
|
55020
|
+
let size = card_size_for(node_treedb_type, !!pills_html);
|
|
54444
55021
|
if (node_treedb_type === "child") {
|
|
54445
55022
|
node_graph_type = "html";
|
|
54446
|
-
style.size =
|
|
54447
|
-
style.dx = -
|
|
54448
|
-
style.dy = -
|
|
55023
|
+
style.size = size;
|
|
55024
|
+
style.dx = -size[0] / 2;
|
|
55025
|
+
style.dy = -size[1] / 2;
|
|
54449
55026
|
style.innerHTML = build_chip_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), record.id);
|
|
54450
55027
|
} else if (node_treedb_type === "extended") {
|
|
54451
55028
|
node_graph_type = "html";
|
|
54452
|
-
style.size =
|
|
54453
|
-
style.dx = -
|
|
54454
|
-
style.dy = -
|
|
54455
|
-
style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, true, record.id);
|
|
55029
|
+
style.size = size;
|
|
55030
|
+
style.dx = -size[0] / 2;
|
|
55031
|
+
style.dy = -size[1] / 2;
|
|
55032
|
+
style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, true, record.id, false, false, false, pills_html);
|
|
54456
55033
|
} else {
|
|
54457
55034
|
node_graph_type = "html";
|
|
54458
|
-
style.size =
|
|
54459
|
-
style.dx = -
|
|
54460
|
-
style.dy = -
|
|
54461
|
-
style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, false, record.id);
|
|
55035
|
+
style.size = size;
|
|
55036
|
+
style.dx = -size[0] / 2;
|
|
55037
|
+
style.dy = -size[1] / 2;
|
|
55038
|
+
style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, false, record.id, false, false, false, pills_html);
|
|
54462
55039
|
}
|
|
54463
55040
|
let topic_props = priv._graph_properties[desc.topic_name];
|
|
54464
55041
|
let topic_defaults = is_object(topic_props) && is_object(topic_props.defaults) ? topic_props.defaults : null;
|
|
@@ -54515,8 +55092,7 @@ function create_topic_node(gobj, desc, record) {
|
|
|
54515
55092
|
* Update topic node data
|
|
54516
55093
|
************************************************************/
|
|
54517
55094
|
function update_topic_node(gobj, desc, node_name, record) {
|
|
54518
|
-
let
|
|
54519
|
-
let graph = priv.graph;
|
|
55095
|
+
let graph = gobj.priv.graph;
|
|
54520
55096
|
try {
|
|
54521
55097
|
let nodedata = graph.getNodeData(node_name);
|
|
54522
55098
|
if (!nodedata) {
|
|
@@ -54524,18 +55100,7 @@ function update_topic_node(gobj, desc, node_name, record) {
|
|
|
54524
55100
|
return;
|
|
54525
55101
|
}
|
|
54526
55102
|
nodedata.data.record = record;
|
|
54527
|
-
|
|
54528
|
-
id: node_name,
|
|
54529
|
-
style: { innerHTML: build_chip_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), record.id) }
|
|
54530
|
-
}]);
|
|
54531
|
-
else if (desc.node_treedb_type === "extended") graph.updateNodeData([{
|
|
54532
|
-
id: node_name,
|
|
54533
|
-
style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, true, record.id) }
|
|
54534
|
-
}]);
|
|
54535
|
-
else if (desc.node_treedb_type === "hierarchical") graph.updateNodeData([{
|
|
54536
|
-
id: node_name,
|
|
54537
|
-
style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, false, record.id) }
|
|
54538
|
-
}]);
|
|
55103
|
+
repaint_cards(gobj, /* @__PURE__ */ new Set([node_name]));
|
|
54539
55104
|
} catch (e) {
|
|
54540
55105
|
log_error(e.message);
|
|
54541
55106
|
}
|
|
@@ -54574,19 +55139,6 @@ function remove_local_node(gobj, topic_name, node) {
|
|
|
54574
55139
|
}
|
|
54575
55140
|
}
|
|
54576
55141
|
/************************************************************
|
|
54577
|
-
* Create all links from records, initial load from parent
|
|
54578
|
-
************************************************************/
|
|
54579
|
-
function create_links(gobj) {
|
|
54580
|
-
let priv = gobj.priv;
|
|
54581
|
-
for (const [topic_name, records] of Object.entries(priv.records)) {
|
|
54582
|
-
let desc = priv.descs[topic_name];
|
|
54583
|
-
for (let i = 0; i < records.length; i++) {
|
|
54584
|
-
let record = records[i];
|
|
54585
|
-
draw_links(gobj, desc, record, true);
|
|
54586
|
-
}
|
|
54587
|
-
}
|
|
54588
|
-
}
|
|
54589
|
-
/************************************************************
|
|
54590
55142
|
* Collect all fkey references from a record as a Set.
|
|
54591
55143
|
*
|
|
54592
55144
|
* Each entry is a string: "col_id\tcol_idx\tfkey_value"
|
|
@@ -54666,6 +55218,17 @@ function draw_link(gobj, child_topic, child_id, source_col, fkey, verbose) {
|
|
|
54666
55218
|
return;
|
|
54667
55219
|
}
|
|
54668
55220
|
let child_node = build_node_name(gobj, child_topic, child_id);
|
|
55221
|
+
let child_cell = null;
|
|
55222
|
+
try {
|
|
55223
|
+
child_cell = graph.getNodeData(child_node);
|
|
55224
|
+
} catch (e) {
|
|
55225
|
+
child_cell = null;
|
|
55226
|
+
}
|
|
55227
|
+
if (!child_cell) {
|
|
55228
|
+
if (verbose) log_error(`${gobj_short_name(gobj)}: child node NOT FOUND: ${child_node}`);
|
|
55229
|
+
return;
|
|
55230
|
+
}
|
|
55231
|
+
if (get_edge_by_link(gobj, parent_topic, parent_id, hook_name, child_topic, child_id, fkey_name)) return;
|
|
54669
55232
|
let dark = priv.theme === "dark";
|
|
54670
55233
|
let is_tree = parent_topic === child_topic;
|
|
54671
55234
|
let saved_lineWidth = is_tree ? 2 : 1.6;
|
|
@@ -54704,7 +55267,7 @@ function draw_link(gobj, child_topic, child_id, source_col, fkey, verbose) {
|
|
|
54704
55267
|
}
|
|
54705
55268
|
let edge = {
|
|
54706
55269
|
id: build_edge_id(gobj),
|
|
54707
|
-
type:
|
|
55270
|
+
type: edge_type_for(gobj),
|
|
54708
55271
|
source: parent_node,
|
|
54709
55272
|
target: child_node,
|
|
54710
55273
|
style: {
|
|
@@ -54835,7 +55398,10 @@ function save_geometry(gobj) {
|
|
|
54835
55398
|
let priv = gobj.priv;
|
|
54836
55399
|
let graph = priv.graph;
|
|
54837
55400
|
const nodes = graph.getData().nodes;
|
|
54838
|
-
for (let i = 0; i < nodes.length; i++)
|
|
55401
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
55402
|
+
if (!nodes[i].data || !nodes[i].data.desc) continue;
|
|
55403
|
+
update_geometry(gobj, nodes[i].id);
|
|
55404
|
+
}
|
|
54839
55405
|
const edges = graph.getData().edges;
|
|
54840
55406
|
for (let i = 0; i < edges.length; i++) update_edge_geometry(gobj, edges[i].id);
|
|
54841
55407
|
let origin = gobj_read_str_attr(gobj_yuno(), "node_uuid");
|
|
@@ -55092,38 +55658,72 @@ async function graph_fit_selection(gobj, ids) {
|
|
|
55092
55658
|
************************************************************/
|
|
55093
55659
|
function graph_focus_topic(gobj, topic) {
|
|
55094
55660
|
let priv = gobj.priv;
|
|
55095
|
-
|
|
55096
|
-
if (!graph || !priv.graph_rendered) {
|
|
55661
|
+
if (!priv.graph || !priv.graph_rendered || !priv._fold_model) {
|
|
55097
55662
|
priv._pending_focus_topic = topic;
|
|
55098
55663
|
return;
|
|
55099
55664
|
}
|
|
55100
|
-
let
|
|
55101
|
-
|
|
55102
|
-
|
|
55103
|
-
|
|
55104
|
-
|
|
55105
|
-
|
|
55665
|
+
let model = priv._fold_model;
|
|
55666
|
+
let state = priv._fold_state;
|
|
55667
|
+
let keys = [];
|
|
55668
|
+
if (!empty_string(topic)) {
|
|
55669
|
+
for (let node of model.nodes.values()) if (node.topic_name === topic) keys.push(node.key);
|
|
55670
|
+
if (keys.length === 0) log_warning(`${gobj_short_name(gobj)}: focus topic '${topic}' has no nodes`);
|
|
55671
|
+
let visible = fold_visible_set(model, state);
|
|
55672
|
+
let revealed = 0;
|
|
55673
|
+
for (let key of keys) {
|
|
55674
|
+
if (visible.has(key)) continue;
|
|
55675
|
+
if (revealed >= state.page_size) break;
|
|
55676
|
+
fold_reveal(model, state, key);
|
|
55677
|
+
revealed++;
|
|
55678
|
+
}
|
|
55106
55679
|
}
|
|
55107
|
-
|
|
55680
|
+
priv._focus_topic = topic || null;
|
|
55681
|
+
reconcile_fold(gobj, {}).then(() => {
|
|
55682
|
+
apply_focus_ids(gobj, keys, "focus topic");
|
|
55683
|
+
});
|
|
55684
|
+
}
|
|
55685
|
+
/************************************************************
|
|
55686
|
+
* Paint the highlight on these records (by model key) and put
|
|
55687
|
+
* the first of them in the middle. Shared by the find and the
|
|
55688
|
+
* topic focus, after the reconcile that brought them on screen.
|
|
55689
|
+
* Keys that are still off screen are skipped: a match beyond the
|
|
55690
|
+
* page a reveal opened is counted, not painted.
|
|
55691
|
+
************************************************************/
|
|
55692
|
+
function apply_focus_ids(gobj, keys, what) {
|
|
55693
|
+
let priv = gobj.priv;
|
|
55694
|
+
let graph = priv.graph;
|
|
55695
|
+
if (!graph) return;
|
|
55108
55696
|
let states = {};
|
|
55109
55697
|
for (let id of priv._focus_ids || []) states[id] = [];
|
|
55110
55698
|
let ids = [];
|
|
55111
|
-
|
|
55112
|
-
|
|
55113
|
-
|
|
55114
|
-
|
|
55699
|
+
for (let key of keys) {
|
|
55700
|
+
let id = node_id_of_key(gobj, key);
|
|
55701
|
+
let nd = null;
|
|
55702
|
+
try {
|
|
55703
|
+
nd = graph.getNodeData(id);
|
|
55704
|
+
} catch (e) {
|
|
55705
|
+
nd = null;
|
|
55706
|
+
}
|
|
55707
|
+
if (nd) {
|
|
55708
|
+
ids.push(id);
|
|
55709
|
+
states[id] = ["active"];
|
|
55710
|
+
}
|
|
55115
55711
|
}
|
|
55116
55712
|
let prev_ids = priv._focus_ids || [];
|
|
55117
55713
|
priv._focus_ids = ids;
|
|
55118
|
-
priv._focus_topic = topic || null;
|
|
55119
55714
|
try {
|
|
55120
55715
|
if (typeof graph.setElementState === "function") graph.setElementState(states);
|
|
55121
55716
|
apply_node_highlight(gobj, prev_ids, ids);
|
|
55122
|
-
if (ids.length
|
|
55717
|
+
if (ids.length) yui_graph_center_on(graph, ids[0]);
|
|
55123
55718
|
} catch (e) {
|
|
55124
|
-
log_error(`${gobj_short_name(gobj)}:
|
|
55719
|
+
log_error(`${gobj_short_name(gobj)}: ${what} apply failed: ${e}`);
|
|
55125
55720
|
}
|
|
55126
55721
|
}
|
|
55722
|
+
function node_id_of_key(gobj, key) {
|
|
55723
|
+
let i = key.indexOf("^");
|
|
55724
|
+
if (i < 0) return "";
|
|
55725
|
+
return build_node_name(gobj, key.slice(0, i), key.slice(i + 1));
|
|
55726
|
+
}
|
|
55127
55727
|
/************************************************************
|
|
55128
55728
|
* Find the nodes whose name, id or topic contains `term`, highlight
|
|
55129
55729
|
* them with the same amber 'active' state the topic focus uses, and
|
|
@@ -55140,52 +55740,43 @@ function graph_focus_topic(gobj, topic) {
|
|
|
55140
55740
|
************************************************************/
|
|
55141
55741
|
function graph_find_nodes(gobj, term) {
|
|
55142
55742
|
let priv = gobj.priv;
|
|
55143
|
-
|
|
55144
|
-
if (!graph || !priv.graph_rendered) {
|
|
55743
|
+
if (!priv.graph || !priv.graph_rendered || !priv._fold_model) {
|
|
55145
55744
|
priv._pending_find = term;
|
|
55146
55745
|
return 0;
|
|
55147
55746
|
}
|
|
55148
|
-
let
|
|
55149
|
-
|
|
55150
|
-
|
|
55151
|
-
} catch (e) {
|
|
55152
|
-
log_error(`${gobj_short_name(gobj)}: graph.getData() failed: ${e}`);
|
|
55153
|
-
return 0;
|
|
55154
|
-
}
|
|
55155
|
-
let nodes = is_array(data.nodes) ? data.nodes : [];
|
|
55156
|
-
let states = {};
|
|
55157
|
-
for (let id of priv._focus_ids || []) states[id] = [];
|
|
55158
|
-
let ids = [];
|
|
55747
|
+
let model = priv._fold_model;
|
|
55748
|
+
let state = priv._fold_state;
|
|
55749
|
+
let keys = [];
|
|
55159
55750
|
if (!empty_string(term)) {
|
|
55160
55751
|
let needle = String(term).toLowerCase();
|
|
55161
|
-
for (let
|
|
55162
|
-
|
|
55163
|
-
let record = n.data.record || {};
|
|
55752
|
+
for (let node of model.nodes.values()) {
|
|
55753
|
+
let record = node.record || {};
|
|
55164
55754
|
let label = "";
|
|
55165
55755
|
try {
|
|
55166
|
-
label = node_label(
|
|
55756
|
+
label = node_label(priv.descs[node.topic_name] || {}, record) || "";
|
|
55167
55757
|
} catch (e) {
|
|
55168
55758
|
label = "";
|
|
55169
55759
|
}
|
|
55170
55760
|
if ([
|
|
55171
55761
|
label,
|
|
55172
55762
|
record.id,
|
|
55173
|
-
|
|
55174
|
-
].filter((v) => typeof v === "string").join(" ").toLowerCase().includes(needle))
|
|
55763
|
+
node.topic_name
|
|
55764
|
+
].filter((v) => typeof v === "string").join(" ").toLowerCase().includes(needle)) keys.push(node.key);
|
|
55175
55765
|
}
|
|
55176
|
-
for (let id of ids) states[id] = ["active"];
|
|
55177
55766
|
}
|
|
55178
|
-
let
|
|
55179
|
-
|
|
55180
|
-
|
|
55181
|
-
|
|
55182
|
-
if (
|
|
55183
|
-
|
|
55184
|
-
|
|
55185
|
-
} catch (e) {
|
|
55186
|
-
log_error(`${gobj_short_name(gobj)}: find apply failed: ${e}`);
|
|
55767
|
+
let visible = fold_visible_set(model, state);
|
|
55768
|
+
let revealed = 0;
|
|
55769
|
+
for (let key of keys) {
|
|
55770
|
+
if (visible.has(key)) continue;
|
|
55771
|
+
if (revealed >= state.page_size) break;
|
|
55772
|
+
fold_reveal(model, state, key);
|
|
55773
|
+
revealed++;
|
|
55187
55774
|
}
|
|
55188
|
-
|
|
55775
|
+
priv._focus_topic = null;
|
|
55776
|
+
reconcile_fold(gobj, {}).then(() => {
|
|
55777
|
+
apply_focus_ids(gobj, keys, "find");
|
|
55778
|
+
});
|
|
55779
|
+
return keys.length;
|
|
55189
55780
|
}
|
|
55190
55781
|
async function graph_zoom_in(gobj) {
|
|
55191
55782
|
let graph = gobj.priv.graph;
|
|
@@ -55298,20 +55889,43 @@ var LightNode = class extends Circle {
|
|
|
55298
55889
|
************************************************************/
|
|
55299
55890
|
var ManualLayout = class extends BaseLayout {
|
|
55300
55891
|
async execute(data, options) {
|
|
55301
|
-
const { nodes = [] } = data;
|
|
55302
|
-
|
|
55303
|
-
|
|
55304
|
-
|
|
55892
|
+
const { nodes = [], edges = [] } = data;
|
|
55893
|
+
let placed = {};
|
|
55894
|
+
let out = [];
|
|
55895
|
+
let chips = [];
|
|
55896
|
+
for (let node of nodes) {
|
|
55897
|
+
let d = node.data || {};
|
|
55898
|
+
if (d.more) {
|
|
55899
|
+
chips.push(node);
|
|
55900
|
+
continue;
|
|
55901
|
+
}
|
|
55902
|
+
let gp = d.graph_props;
|
|
55903
|
+
let geo = d.record ? d.record._geometry : null;
|
|
55305
55904
|
let x = gp && gp.x != null ? gp.x : geo && geo.x != null ? geo.x : 0;
|
|
55306
55905
|
let y = gp && gp.y != null ? gp.y : geo && geo.y != null ? geo.y : 0;
|
|
55307
|
-
|
|
55906
|
+
placed[node.id] = [x, y];
|
|
55907
|
+
out.push({
|
|
55308
55908
|
id: node.id,
|
|
55309
55909
|
style: {
|
|
55310
55910
|
x,
|
|
55311
55911
|
y
|
|
55312
55912
|
}
|
|
55313
|
-
};
|
|
55314
|
-
}
|
|
55913
|
+
});
|
|
55914
|
+
}
|
|
55915
|
+
for (let node of chips) {
|
|
55916
|
+
let edge = edges.find((e) => e.target === node.id);
|
|
55917
|
+
let p = edge ? placed[edge.source] : null;
|
|
55918
|
+
let x = p ? p[0] : node.style && node.style.x || 0;
|
|
55919
|
+
let y = p ? p[1] + 80 : node.style && node.style.y || 0;
|
|
55920
|
+
out.push({
|
|
55921
|
+
id: node.id,
|
|
55922
|
+
style: {
|
|
55923
|
+
x,
|
|
55924
|
+
y
|
|
55925
|
+
}
|
|
55926
|
+
});
|
|
55927
|
+
}
|
|
55928
|
+
return { nodes: out };
|
|
55315
55929
|
}
|
|
55316
55930
|
};
|
|
55317
55931
|
/************************************************************
|
|
@@ -56815,7 +57429,7 @@ function show_node_popover(gobj) {
|
|
|
56815
57429
|
};
|
|
56816
57430
|
if (node_graph_type === "hierarchical") {
|
|
56817
57431
|
let record = nodeData.data.record || {};
|
|
56818
|
-
updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, node_label(nodeData.data.desc, record), nodeData.data.desc.topic_name, false, record.id);
|
|
57432
|
+
updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, node_label(nodeData.data.desc, record), nodeData.data.desc.topic_name, false, record.id, false, false, false, pills_html_of(gobj, nodeData));
|
|
56819
57433
|
}
|
|
56820
57434
|
graph.updateNodeData([{
|
|
56821
57435
|
id: node_id,
|
|
@@ -56855,7 +57469,7 @@ function show_node_popover(gobj) {
|
|
|
56855
57469
|
};
|
|
56856
57470
|
if (node_graph_type === "hierarchical") {
|
|
56857
57471
|
let record = nodeData.data.record || {};
|
|
56858
|
-
restoreStyle.innerHTML = build_node_innerHTML(origFill, priv.theme, record.icon, node_label(nodeData.data.desc, record), nodeData.data.desc.topic_name, false, record.id);
|
|
57472
|
+
restoreStyle.innerHTML = build_node_innerHTML(origFill, priv.theme, record.icon, node_label(nodeData.data.desc, record), nodeData.data.desc.topic_name, false, record.id, false, false, false, pills_html_of(gobj, nodeData));
|
|
56859
57473
|
}
|
|
56860
57474
|
graph.updateNodeData([{
|
|
56861
57475
|
id: node_id,
|
|
@@ -57040,6 +57654,7 @@ function refresh_minimap(gobj) {
|
|
|
57040
57654
|
let nd = graph.getNodeData(id);
|
|
57041
57655
|
let size = nd && nd.style && nd.style.size || [120, 60];
|
|
57042
57656
|
let color = nd && nd.data && nd.data.desc && nd.data.desc.color || "#94a3b8";
|
|
57657
|
+
if (is_more_node(nd)) color = "rgba(148, 163, 184, 0.35)";
|
|
57043
57658
|
return new Rect({ style: {
|
|
57044
57659
|
x: -size[0] / 2,
|
|
57045
57660
|
y: -size[1] / 2,
|
|
@@ -57062,15 +57677,15 @@ function refresh_minimap(gobj) {
|
|
|
57062
57677
|
* same three-way choice — it lived inline in the theme refresh and is
|
|
57063
57678
|
* shared now. Returns null for a node that carries no desc.
|
|
57064
57679
|
************************************************************/
|
|
57065
|
-
function node_innerHTML_of(nd, theme, highlight, selected, anchored) {
|
|
57680
|
+
function node_innerHTML_of(nd, theme, highlight, selected, anchored, pills_html) {
|
|
57066
57681
|
if (!nd || !nd.data || !nd.data.desc) return null;
|
|
57067
57682
|
let desc = nd.data.desc;
|
|
57068
57683
|
let record = nd.data.record || {};
|
|
57069
57684
|
let label = node_label(desc, record);
|
|
57070
57685
|
switch (desc.node_treedb_type) {
|
|
57071
57686
|
case "child": return build_chip_innerHTML(desc.color, theme, record.icon, label, record.id, highlight, selected, anchored);
|
|
57072
|
-
case "extended": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, true, record.id, highlight, selected, anchored);
|
|
57073
|
-
case "hierarchical": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, false, record.id, highlight, selected, anchored);
|
|
57687
|
+
case "extended": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, true, record.id, highlight, selected, anchored, pills_html);
|
|
57688
|
+
case "hierarchical": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, false, record.id, highlight, selected, anchored, pills_html);
|
|
57074
57689
|
}
|
|
57075
57690
|
return null;
|
|
57076
57691
|
}
|
|
@@ -57091,7 +57706,13 @@ function repaint_cards(gobj, ids) {
|
|
|
57091
57706
|
let selected = new Set(priv._selected_paint_ids || []);
|
|
57092
57707
|
let updates = [];
|
|
57093
57708
|
for (let id of ids) {
|
|
57094
|
-
let
|
|
57709
|
+
let nd;
|
|
57710
|
+
try {
|
|
57711
|
+
nd = graph.getNodeData(id);
|
|
57712
|
+
} catch (e) {
|
|
57713
|
+
continue;
|
|
57714
|
+
}
|
|
57715
|
+
let html = node_innerHTML_of(nd, priv.theme, focus.has(id), selected.has(id), priv.anchor_state === "on" && id === priv.anchor_id, pills_html_of(gobj, nd));
|
|
57095
57716
|
if (html !== null) updates.push({
|
|
57096
57717
|
id,
|
|
57097
57718
|
style: { innerHTML: html }
|
|
@@ -57127,7 +57748,9 @@ function refresh_html_nodes_theme(gobj, theme) {
|
|
|
57127
57748
|
let updates = [];
|
|
57128
57749
|
for (let i = 0; i < nodes.length; i++) {
|
|
57129
57750
|
let id = nodes[i].id;
|
|
57130
|
-
let
|
|
57751
|
+
let nd = graph.getNodeData(id);
|
|
57752
|
+
let html = node_innerHTML_of(nd, theme, highlighted.has(id), selected.has(id), priv.anchor_state === "on" && id === priv.anchor_id, pills_html_of(gobj, nd));
|
|
57753
|
+
if (html === null) html = more_innerHTML_of(gobj, nd, theme);
|
|
57131
57754
|
if (html !== null) updates.push({
|
|
57132
57755
|
id,
|
|
57133
57756
|
style: { innerHTML: html }
|
|
@@ -57242,7 +57865,7 @@ function build_chip_innerHTML(color, theme, icon, label, key, highlight, selecte
|
|
|
57242
57865
|
* colour is kept (per-topic differentiation) but softened via
|
|
57243
57866
|
* color-mix instead of a harsh saturated fill. Theme-aware.
|
|
57244
57867
|
************************************************************/
|
|
57245
|
-
function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key, highlight, selected, anchored) {
|
|
57868
|
+
function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key, highlight, selected, anchored, pills_html) {
|
|
57246
57869
|
let title = key || label;
|
|
57247
57870
|
let dark = theme === "dark";
|
|
57248
57871
|
let surface = dark ? "#1b2230" : "#ffffff";
|
|
@@ -57276,6 +57899,12 @@ function build_node_innerHTML(color, theme, icon, label, topic_name, structural,
|
|
|
57276
57899
|
margin-top: 2px; max-width: 100%;
|
|
57277
57900
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
57278
57901
|
">${escapeHtml(topic_name)}</div>`;
|
|
57902
|
+
let pills_row = "";
|
|
57903
|
+
if (pills_html) pills_row = `
|
|
57904
|
+
<div class="TREEDB_PILLS" style="
|
|
57905
|
+
display: flex; flex-wrap: nowrap; gap: 4px; justify-content: center;
|
|
57906
|
+
margin-top: 6px; max-width: 100%; overflow: hidden;
|
|
57907
|
+
">${pills_html}</div>`;
|
|
57279
57908
|
return `
|
|
57280
57909
|
<div class="TREEDB_CARD" title="${escapeHtml(title)}" style="
|
|
57281
57910
|
box-sizing: border-box;
|
|
@@ -57301,7 +57930,7 @@ function build_node_innerHTML(color, theme, icon, label, topic_name, structural,
|
|
|
57301
57930
|
max-width: 100%; overflow: hidden; text-overflow: ellipsis;
|
|
57302
57931
|
display: -webkit-box; -webkit-line-clamp: 2;
|
|
57303
57932
|
-webkit-box-orient: vertical; word-break: break-word;
|
|
57304
|
-
">${escapeHtml(label)}</div>${sub_html}
|
|
57933
|
+
">${escapeHtml(label)}</div>${sub_html}${pills_row}
|
|
57305
57934
|
</div>
|
|
57306
57935
|
`;
|
|
57307
57936
|
}
|
|
@@ -57334,7 +57963,7 @@ function execute_delete_node(gobj, nodeData) {
|
|
|
57334
57963
|
function select_all_nodes(gobj) {
|
|
57335
57964
|
let graph = gobj.priv.graph;
|
|
57336
57965
|
if (!graph) return;
|
|
57337
|
-
set_selection(gobj, ((graph.getData() || {}).nodes || []).map((nd) => nd.id));
|
|
57966
|
+
set_selection(gobj, ((graph.getData() || {}).nodes || []).filter((nd) => nd.data && nd.data.desc).map((nd) => nd.id));
|
|
57338
57967
|
}
|
|
57339
57968
|
/************************************************************
|
|
57340
57969
|
* The nodes currently selected, as their node data.
|
|
@@ -57598,7 +58227,7 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope) {
|
|
|
57598
58227
|
};
|
|
57599
58228
|
if (nd.data.desc.node_treedb_type === "hierarchical") {
|
|
57600
58229
|
let record = nd.data.record || {};
|
|
57601
|
-
updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, false, record.id);
|
|
58230
|
+
updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, false, record.id, false, false, false, pills_html_of(gobj, nd));
|
|
57602
58231
|
}
|
|
57603
58232
|
updates.push({
|
|
57604
58233
|
id: nodes[i].id,
|
|
@@ -57674,7 +58303,7 @@ function apply_edge_properties(gobj, edge_id, lineWidth, stroke, scope) {
|
|
|
57674
58303
|
function update_edge_geometry(gobj, edge_id) {
|
|
57675
58304
|
let priv = gobj.priv;
|
|
57676
58305
|
let edgeData = priv.graph.getEdgeData(edge_id);
|
|
57677
|
-
if (!edgeData || !edgeData.data) return;
|
|
58306
|
+
if (!edgeData || !edgeData.data || !edgeData.data.parent_topic) return;
|
|
57678
58307
|
let style = edgeData.style || {};
|
|
57679
58308
|
let lineWidth = style.lineWidth;
|
|
57680
58309
|
let stroke = style.stroke;
|
|
@@ -57815,7 +58444,7 @@ function copy_size_to_nodes(gobj, same_topic_only) {
|
|
|
57815
58444
|
const nodes = graph.getData().nodes;
|
|
57816
58445
|
for (let i = 0; i < nodes.length; i++) {
|
|
57817
58446
|
let nd = graph.getNodeData(nodes[i].id);
|
|
57818
|
-
if (!nd || !nd.data) continue;
|
|
58447
|
+
if (!nd || !nd.data || !nd.data.desc) continue;
|
|
57819
58448
|
if (same_topic_only && nd.data.topic_name !== source_topic) continue;
|
|
57820
58449
|
if (nodes[i].id === node_id) continue;
|
|
57821
58450
|
let updateStyle = { size: [...source_size] };
|
|
@@ -57903,6 +58532,415 @@ function copy_size_to_ports(gobj, same_topic_only) {
|
|
|
57903
58532
|
}
|
|
57904
58533
|
}
|
|
57905
58534
|
/***************************
|
|
58535
|
+
* Folding
|
|
58536
|
+
***************************/
|
|
58537
|
+
var PILL_ROW_HEIGHT = 24;
|
|
58538
|
+
var MORE_CHIP_SIZE = [76, 26];
|
|
58539
|
+
/************************************************************
|
|
58540
|
+
* The three card sizes, with room for the pill row when there
|
|
58541
|
+
* is one. A chip (pure child) never has pills: it has no hooks.
|
|
58542
|
+
************************************************************/
|
|
58543
|
+
function card_size_for(node_treedb_type, has_pills) {
|
|
58544
|
+
let extra = has_pills ? PILL_ROW_HEIGHT : 0;
|
|
58545
|
+
switch (node_treedb_type) {
|
|
58546
|
+
case "child": return [116, 40];
|
|
58547
|
+
case "extended": return [144, 66 + extra];
|
|
58548
|
+
default: return [172, 96 + extra];
|
|
58549
|
+
}
|
|
58550
|
+
}
|
|
58551
|
+
/************************************************************
|
|
58552
|
+
* The model's key of a G6 record node, or "" for anything else.
|
|
58553
|
+
************************************************************/
|
|
58554
|
+
function node_key_of(nd) {
|
|
58555
|
+
if (!nd || !nd.data || !nd.data.desc || !nd.data.record) return "";
|
|
58556
|
+
return fold_node_key(nd.data.desc.topic_name, String(nd.data.record.id));
|
|
58557
|
+
}
|
|
58558
|
+
function is_more_node(nd) {
|
|
58559
|
+
return !!(nd && nd.data && nd.data.more);
|
|
58560
|
+
}
|
|
58561
|
+
function more_node_id(gobj, group_key) {
|
|
58562
|
+
return sprintf("more-%s-%s", gobj.priv.treedb_name, group_key);
|
|
58563
|
+
}
|
|
58564
|
+
function more_edge_id(gobj, group_key) {
|
|
58565
|
+
return sprintf("more-edge-%s-%s", gobj.priv.treedb_name, group_key);
|
|
58566
|
+
}
|
|
58567
|
+
/************************************************************
|
|
58568
|
+
* The colours of a pill or a chip, from the topic colour it
|
|
58569
|
+
* stands for -- the CHILD topic, the same colour its hook port
|
|
58570
|
+
* wears, so the pill and the port say the same thing.
|
|
58571
|
+
************************************************************/
|
|
58572
|
+
function pill_colors(color, theme) {
|
|
58573
|
+
let dark = theme === "dark";
|
|
58574
|
+
return {
|
|
58575
|
+
bg: dark ? `color-mix(in srgb, ${color} 45%, #1b2230)` : `color-mix(in srgb, ${color} 22%, #ffffff)`,
|
|
58576
|
+
fg: dark ? `color-mix(in srgb, ${color} 35%, #ffffff)` : `color-mix(in srgb, ${color} 55%, #0f172a)`,
|
|
58577
|
+
border: dark ? `color-mix(in srgb, ${color} 80%, #ffffff)` : color
|
|
58578
|
+
};
|
|
58579
|
+
}
|
|
58580
|
+
/************************************************************
|
|
58581
|
+
* One pill: `▸ devices 142` folded, `▾ devices 24/142` open on a
|
|
58582
|
+
* page, `▾ devices 142` open whole. A glyph and numbers on
|
|
58583
|
+
* purpose: the card is an innerHTML string where a WORD could
|
|
58584
|
+
* carry no i18n key, and the hook name is a schema identifier,
|
|
58585
|
+
* like the topic subtitle above it.
|
|
58586
|
+
************************************************************/
|
|
58587
|
+
function build_pill_html(pill, color, theme) {
|
|
58588
|
+
let c = pill_colors(color, theme);
|
|
58589
|
+
let glyph = pill.open ? "▾" : "▸";
|
|
58590
|
+
let count = pill.open && pill.shown < pill.total ? `${pill.shown}/${pill.total}` : `${pill.total}`;
|
|
58591
|
+
return `<span class="TREEDB_PILL" data-fold-group="${escapeHtml(pill.group_key)}"
|
|
58592
|
+
title="${escapeHtml(pill.hook)}" style="
|
|
58593
|
+
display: inline-flex; align-items: center; gap: 3px; flex: 0 1 auto;
|
|
58594
|
+
height: 16px; padding: 0 6px; border-radius: 8px;
|
|
58595
|
+
background: ${c.bg}; color: ${c.fg}; border: 1px solid ${c.border};
|
|
58596
|
+
font-size: 10px; font-weight: 700; line-height: 1; white-space: nowrap;
|
|
58597
|
+
overflow: hidden; text-overflow: ellipsis;
|
|
58598
|
+
cursor: pointer; user-select: none;
|
|
58599
|
+
"><span style="font-size: 9px;">${glyph}</span>${escapeHtml(pill.hook)}<span style="opacity: .75; font-weight: 600;">${count}</span></span>`;
|
|
58600
|
+
}
|
|
58601
|
+
/************************************************************
|
|
58602
|
+
* The pill row of one node, as html; "" when it has nothing to
|
|
58603
|
+
* fold or the model is not built yet.
|
|
58604
|
+
************************************************************/
|
|
58605
|
+
function pills_html_of_key(gobj, node_key) {
|
|
58606
|
+
let priv = gobj.priv;
|
|
58607
|
+
if (!priv._fold_model || !priv._fold_state || !node_key) return "";
|
|
58608
|
+
let pills = fold_pills_of(priv._fold_model, priv._fold_state, node_key);
|
|
58609
|
+
if (!pills.length) return "";
|
|
58610
|
+
let out = [];
|
|
58611
|
+
for (let pill of pills) {
|
|
58612
|
+
let child_desc = priv.descs ? priv.descs[pill.child_topic] : null;
|
|
58613
|
+
let color = child_desc && child_desc.color || "#94a3b8";
|
|
58614
|
+
out.push(build_pill_html(pill, color, priv.theme));
|
|
58615
|
+
}
|
|
58616
|
+
return out.join("");
|
|
58617
|
+
}
|
|
58618
|
+
function pills_html_of(gobj, nd) {
|
|
58619
|
+
return pills_html_of_key(gobj, node_key_of(nd));
|
|
58620
|
+
}
|
|
58621
|
+
function pills_signature(gobj, node_key) {
|
|
58622
|
+
let priv = gobj.priv;
|
|
58623
|
+
if (!priv._fold_model || !priv._fold_state) return "";
|
|
58624
|
+
return fold_pills_of(priv._fold_model, priv._fold_state, node_key).map((p) => `${p.hook}:${p.open ? p.shown : "-"}/${p.total}`).join(",");
|
|
58625
|
+
}
|
|
58626
|
+
/************************************************************
|
|
58627
|
+
* The `+N` chip at the end of a page: the rest of the group,
|
|
58628
|
+
* in the child topic's colour, dashed like a cut. Its title is
|
|
58629
|
+
* the one WORD of the folding, and it is translated at build
|
|
58630
|
+
* time: the chips are rebuilt on a language change.
|
|
58631
|
+
************************************************************/
|
|
58632
|
+
function build_more_innerHTML(color, theme, rest) {
|
|
58633
|
+
let c = pill_colors(color, theme);
|
|
58634
|
+
return `
|
|
58635
|
+
<div class="TREEDB_MORE" title="${escapeHtml(t("show more"))}" style="
|
|
58636
|
+
box-sizing: border-box; width: 100%; height: 100%;
|
|
58637
|
+
display: flex; align-items: center; justify-content: center; gap: 4px;
|
|
58638
|
+
background: ${c.bg}; color: ${c.fg}; border: 1px dashed ${c.border};
|
|
58639
|
+
border-radius: 13px;
|
|
58640
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
58641
|
+
font-size: 12px; font-weight: 700; line-height: 1;
|
|
58642
|
+
cursor: pointer; user-select: none;
|
|
58643
|
+
">+${rest}<span style="font-size: 10px; opacity: .8;">…</span></div>
|
|
58644
|
+
`;
|
|
58645
|
+
}
|
|
58646
|
+
function more_innerHTML_of(gobj, nd, theme) {
|
|
58647
|
+
if (!is_more_node(nd)) return null;
|
|
58648
|
+
let priv = gobj.priv;
|
|
58649
|
+
let child_desc = priv.descs ? priv.descs[nd.data.child_topic] : null;
|
|
58650
|
+
return build_more_innerHTML(child_desc && child_desc.color || "#94a3b8", theme, nd.data.rest);
|
|
58651
|
+
}
|
|
58652
|
+
/************************************************************
|
|
58653
|
+
* The pills live inside an `innerHTML` string, so no handler can
|
|
58654
|
+
* be attached to them: the click is delegated from the container
|
|
58655
|
+
* in the CAPTURE phase over four event types -- the same door the
|
|
58656
|
+
* JSON graph opens for its fold handles, for the same reasons: G6
|
|
58657
|
+
* binds on the html node element, a descendant, so only capture
|
|
58658
|
+
* runs first; and G6 never reads the DOM `click`, it builds its
|
|
58659
|
+
* own from the pointer pair, so `pointerdown`/`pointerup` are the
|
|
58660
|
+
* ones that have to be cut or the card is ALSO clicked.
|
|
58661
|
+
************************************************************/
|
|
58662
|
+
var FOLD_SWALLOWED_EVENTS = [
|
|
58663
|
+
"pointerdown",
|
|
58664
|
+
"pointerup",
|
|
58665
|
+
"mousedown",
|
|
58666
|
+
"click"
|
|
58667
|
+
];
|
|
58668
|
+
function install_fold_listener(gobj) {
|
|
58669
|
+
let priv = gobj.priv;
|
|
58670
|
+
if (!priv.$container) {
|
|
58671
|
+
log_error(`${gobj_short_name(gobj)}: no container, the fold pills are dead`);
|
|
58672
|
+
return;
|
|
58673
|
+
}
|
|
58674
|
+
priv._fold_listener = function(evt) {
|
|
58675
|
+
let $pill = evt.target && evt.target.closest ? evt.target.closest("[data-fold-group]") : null;
|
|
58676
|
+
if (!$pill) return;
|
|
58677
|
+
evt.stopPropagation();
|
|
58678
|
+
evt.preventDefault();
|
|
58679
|
+
if (evt.type !== "click") return;
|
|
58680
|
+
gobj_send_event(gobj, "EV_TOGGLE_FOLD", { group: $pill.getAttribute("data-fold-group") }, gobj);
|
|
58681
|
+
};
|
|
58682
|
+
for (let type of FOLD_SWALLOWED_EVENTS) priv.$container.addEventListener(type, priv._fold_listener, true);
|
|
58683
|
+
}
|
|
58684
|
+
function uninstall_fold_listener(gobj) {
|
|
58685
|
+
let priv = gobj.priv;
|
|
58686
|
+
if (priv._fold_listener && priv.$container) for (let type of FOLD_SWALLOWED_EVENTS) priv.$container.removeEventListener(type, priv._fold_listener, true);
|
|
58687
|
+
priv._fold_listener = null;
|
|
58688
|
+
}
|
|
58689
|
+
/************************************************************
|
|
58690
|
+
* (Re)build the tree of the records. Cheap -- one pass over the
|
|
58691
|
+
* records -- so it runs again on every create/update/delete
|
|
58692
|
+
* rather than being patched. The fold state survives: it is
|
|
58693
|
+
* keyed by group, and a group that is gone is simply never read.
|
|
58694
|
+
************************************************************/
|
|
58695
|
+
function rebuild_fold_model(gobj) {
|
|
58696
|
+
let priv = gobj.priv;
|
|
58697
|
+
priv._fold_model = fold_build_model(priv.descs || {}, priv.records || {});
|
|
58698
|
+
if (!priv._fold_state) priv._fold_state = fold_new_state(gobj_read_integer_attr(gobj, "fold_page_size"));
|
|
58699
|
+
if (!priv._pill_sig) priv._pill_sig = /* @__PURE__ */ new Map();
|
|
58700
|
+
}
|
|
58701
|
+
/************************************************************
|
|
58702
|
+
* How many records there are, and how many carry a saved
|
|
58703
|
+
* position -- what auto_layout decides on. Counted over the
|
|
58704
|
+
* RECORDS, because the nodes on screen are a fraction of them.
|
|
58705
|
+
************************************************************/
|
|
58706
|
+
function count_placed_records(gobj) {
|
|
58707
|
+
let priv = gobj.priv;
|
|
58708
|
+
priv._nodes_total = 0;
|
|
58709
|
+
priv._nodes_placed = 0;
|
|
58710
|
+
for (const [topic_name, records] of Object.entries(priv.records || {})) {
|
|
58711
|
+
if (topic_name.substring(0, 2) === "__" || !is_array(records)) continue;
|
|
58712
|
+
for (let record of records) {
|
|
58713
|
+
priv._nodes_total++;
|
|
58714
|
+
if (geometry_has_position(get_node_graph_props(gobj, topic_name, record.id, record))) priv._nodes_placed++;
|
|
58715
|
+
}
|
|
58716
|
+
}
|
|
58717
|
+
}
|
|
58718
|
+
/************************************************************
|
|
58719
|
+
* Make the G6 data say what the fold model says is visible.
|
|
58720
|
+
*
|
|
58721
|
+
* Record nodes not visible any more are removed (their edges go
|
|
58722
|
+
* with them), the visible ones missing are created, the links
|
|
58723
|
+
* between visible nodes are drawn, the `+N` chips are placed or
|
|
58724
|
+
* taken away, and every card whose pills changed is repainted.
|
|
58725
|
+
* Then a draw, a layout if the set of nodes changed, and the
|
|
58726
|
+
* camera: fit the whole thing on the first load, or put `keep`
|
|
58727
|
+
* back where it was on screen before the change.
|
|
58728
|
+
*
|
|
58729
|
+
* Serialised: one reconcile at a time, in order. Two pills tapped
|
|
58730
|
+
* quickly would otherwise interleave their draws.
|
|
58731
|
+
*
|
|
58732
|
+
* opts.relayout run the layout even if no node came or went
|
|
58733
|
+
* opts.fit fit the viewport afterwards
|
|
58734
|
+
* opts.keep node id to hold still on screen
|
|
58735
|
+
* opts.keep_vp where it was ([x, y] viewport px)
|
|
58736
|
+
************************************************************/
|
|
58737
|
+
function reconcile_fold(gobj, opts) {
|
|
58738
|
+
let priv = gobj.priv;
|
|
58739
|
+
let run = () => reconcile_fold_now(gobj, opts || {}).catch((e) => {
|
|
58740
|
+
log_error(`${gobj_short_name(gobj)}: reconcile failed: ${e}`);
|
|
58741
|
+
});
|
|
58742
|
+
priv._fold_chain = (priv._fold_chain || Promise.resolve()).then(run, run);
|
|
58743
|
+
return priv._fold_chain;
|
|
58744
|
+
}
|
|
58745
|
+
async function reconcile_fold_now(gobj, opts) {
|
|
58746
|
+
let priv = gobj.priv;
|
|
58747
|
+
let graph = priv.graph;
|
|
58748
|
+
let model = priv._fold_model;
|
|
58749
|
+
let state = priv._fold_state;
|
|
58750
|
+
if (!graph || !model || !state || gobj_is_destroying(gobj)) return;
|
|
58751
|
+
let visible = fold_visible_set(model, state);
|
|
58752
|
+
let changed = false;
|
|
58753
|
+
history_pause(gobj);
|
|
58754
|
+
try {
|
|
58755
|
+
changed = await reconcile_fold_apply(gobj, opts, visible);
|
|
58756
|
+
} finally {
|
|
58757
|
+
history_resume(gobj);
|
|
58758
|
+
}
|
|
58759
|
+
return changed;
|
|
58760
|
+
}
|
|
58761
|
+
async function reconcile_fold_apply(gobj, opts, visible) {
|
|
58762
|
+
let priv = gobj.priv;
|
|
58763
|
+
let graph = priv.graph;
|
|
58764
|
+
let model = priv._fold_model;
|
|
58765
|
+
let state = priv._fold_state;
|
|
58766
|
+
let changed = false;
|
|
58767
|
+
let present = /* @__PURE__ */ new Map();
|
|
58768
|
+
let more_present = /* @__PURE__ */ new Map();
|
|
58769
|
+
for (let nd of graph.getNodeData() || []) {
|
|
58770
|
+
if (is_more_node(nd)) {
|
|
58771
|
+
more_present.set(nd.data.more, nd.id);
|
|
58772
|
+
continue;
|
|
58773
|
+
}
|
|
58774
|
+
let key = node_key_of(nd);
|
|
58775
|
+
if (key) present.set(key, nd.id);
|
|
58776
|
+
}
|
|
58777
|
+
let to_remove = [];
|
|
58778
|
+
for (let [key, id] of present) if (!visible.has(key)) to_remove.push(id);
|
|
58779
|
+
if (to_remove.length) {
|
|
58780
|
+
let gone = new Set(to_remove);
|
|
58781
|
+
if (priv._selected_node_id && gone.has(priv._selected_node_id)) deselect_node(gobj);
|
|
58782
|
+
if (priv._detail_node_id && gone.has(priv._detail_node_id)) hide_node_detail(gobj);
|
|
58783
|
+
priv._focus_ids = (priv._focus_ids || []).filter((id) => !gone.has(id));
|
|
58784
|
+
priv._selected_paint_ids = (priv._selected_paint_ids || []).filter((id) => !gone.has(id));
|
|
58785
|
+
if (priv.anchor_state === "on" && gone.has(priv.anchor_id)) {
|
|
58786
|
+
priv.anchor_id = "";
|
|
58787
|
+
priv.anchor_state = "off";
|
|
58788
|
+
update_toolbar(gobj);
|
|
58789
|
+
}
|
|
58790
|
+
for (let id of to_remove) priv._pill_sig.delete(id);
|
|
58791
|
+
graph.removeNodeData(to_remove);
|
|
58792
|
+
changed = true;
|
|
58793
|
+
}
|
|
58794
|
+
for (let key of visible) {
|
|
58795
|
+
if (present.has(key)) continue;
|
|
58796
|
+
let node = model.nodes.get(key);
|
|
58797
|
+
let desc = priv.descs[node.topic_name];
|
|
58798
|
+
if (!desc) {
|
|
58799
|
+
log_error(`${gobj_short_name(gobj)}: no desc for ${key}`);
|
|
58800
|
+
continue;
|
|
58801
|
+
}
|
|
58802
|
+
create_topic_node(gobj, desc, node.record);
|
|
58803
|
+
priv._pill_sig.set(build_node_name(gobj, node.topic_name, node.id), pills_signature(gobj, key));
|
|
58804
|
+
changed = true;
|
|
58805
|
+
}
|
|
58806
|
+
for (let key of visible) {
|
|
58807
|
+
let node = model.nodes.get(key);
|
|
58808
|
+
let desc = priv.descs[node.topic_name];
|
|
58809
|
+
if (desc) draw_links(gobj, desc, node.record, false);
|
|
58810
|
+
}
|
|
58811
|
+
let pending = fold_pending_groups(model, state, visible);
|
|
58812
|
+
let wanted = /* @__PURE__ */ new Map();
|
|
58813
|
+
for (let p of pending) wanted.set(p.group_key, p);
|
|
58814
|
+
let more_gone = [];
|
|
58815
|
+
for (let [group_key, id] of more_present) if (!wanted.has(group_key)) more_gone.push(id);
|
|
58816
|
+
if (more_gone.length) {
|
|
58817
|
+
graph.removeNodeData(more_gone);
|
|
58818
|
+
changed = true;
|
|
58819
|
+
}
|
|
58820
|
+
for (let p of pending) {
|
|
58821
|
+
let first = model.nodes.get((model.groups.get(p.group_key) || [])[0]);
|
|
58822
|
+
let child_topic = first ? first.topic_name : "";
|
|
58823
|
+
let child_desc = priv.descs[child_topic];
|
|
58824
|
+
let color = child_desc && child_desc.color || "#94a3b8";
|
|
58825
|
+
let id = more_node_id(gobj, p.group_key);
|
|
58826
|
+
if (more_present.has(p.group_key)) {
|
|
58827
|
+
let nd = graph.getNodeData(id);
|
|
58828
|
+
if (nd && nd.data.rest !== p.rest) {
|
|
58829
|
+
nd.data.rest = p.rest;
|
|
58830
|
+
graph.updateNodeData([{
|
|
58831
|
+
id,
|
|
58832
|
+
style: { innerHTML: build_more_innerHTML(color, priv.theme, p.rest) }
|
|
58833
|
+
}]);
|
|
58834
|
+
}
|
|
58835
|
+
continue;
|
|
58836
|
+
}
|
|
58837
|
+
let xy = get_default_ne_xy(gobj);
|
|
58838
|
+
graph.addNodeData([{
|
|
58839
|
+
id,
|
|
58840
|
+
type: "html",
|
|
58841
|
+
style: {
|
|
58842
|
+
x: xy,
|
|
58843
|
+
y: xy,
|
|
58844
|
+
size: [...MORE_CHIP_SIZE],
|
|
58845
|
+
dx: -MORE_CHIP_SIZE[0] / 2,
|
|
58846
|
+
dy: -MORE_CHIP_SIZE[1] / 2,
|
|
58847
|
+
innerHTML: build_more_innerHTML(color, priv.theme, p.rest)
|
|
58848
|
+
},
|
|
58849
|
+
data: {
|
|
58850
|
+
more: p.group_key,
|
|
58851
|
+
child_topic,
|
|
58852
|
+
rest: p.rest
|
|
58853
|
+
}
|
|
58854
|
+
}]);
|
|
58855
|
+
if (p.node_key) {
|
|
58856
|
+
let parent = model.nodes.get(p.node_key);
|
|
58857
|
+
let dark = priv.theme === "dark";
|
|
58858
|
+
graph.addEdgeData([{
|
|
58859
|
+
id: more_edge_id(gobj, p.group_key),
|
|
58860
|
+
type: edge_type_for(gobj),
|
|
58861
|
+
source: build_node_name(gobj, parent.topic_name, parent.id),
|
|
58862
|
+
target: id,
|
|
58863
|
+
style: {
|
|
58864
|
+
sourcePort: p.hook,
|
|
58865
|
+
lineWidth: 1,
|
|
58866
|
+
lineDash: [4, 3],
|
|
58867
|
+
stroke: dark ? "#8b94a3" : "#94a3b8",
|
|
58868
|
+
startArrow: false,
|
|
58869
|
+
endArrow: false
|
|
58870
|
+
},
|
|
58871
|
+
data: { more: p.group_key }
|
|
58872
|
+
}]);
|
|
58873
|
+
}
|
|
58874
|
+
changed = true;
|
|
58875
|
+
}
|
|
58876
|
+
let repaint = /* @__PURE__ */ new Set();
|
|
58877
|
+
let resize = [];
|
|
58878
|
+
for (let key of visible) {
|
|
58879
|
+
let node = model.nodes.get(key);
|
|
58880
|
+
let id = build_node_name(gobj, node.topic_name, node.id);
|
|
58881
|
+
let sig = pills_signature(gobj, key);
|
|
58882
|
+
if (priv._pill_sig.get(id) === sig) continue;
|
|
58883
|
+
let had = !!priv._pill_sig.get(id);
|
|
58884
|
+
priv._pill_sig.set(id, sig);
|
|
58885
|
+
repaint.add(id);
|
|
58886
|
+
if (had !== !!sig) {
|
|
58887
|
+
let nd = graph.getNodeData(id);
|
|
58888
|
+
let desc = nd && nd.data && nd.data.desc;
|
|
58889
|
+
let geometry = nd && nd.data && nd.data.graph_props || {};
|
|
58890
|
+
if (desc && !is_array(geometry.size)) {
|
|
58891
|
+
let size = card_size_for(desc.node_treedb_type, !!sig);
|
|
58892
|
+
resize.push({
|
|
58893
|
+
id,
|
|
58894
|
+
style: {
|
|
58895
|
+
size,
|
|
58896
|
+
dx: -size[0] / 2,
|
|
58897
|
+
dy: -size[1] / 2
|
|
58898
|
+
}
|
|
58899
|
+
});
|
|
58900
|
+
}
|
|
58901
|
+
}
|
|
58902
|
+
}
|
|
58903
|
+
if (resize.length) graph.updateNodeData(resize);
|
|
58904
|
+
if (repaint.size) repaint_cards(gobj, repaint);
|
|
58905
|
+
try {
|
|
58906
|
+
refresh_minimap(gobj);
|
|
58907
|
+
} catch (e) {
|
|
58908
|
+
log_error(`${gobj_short_name(gobj)}: refresh_minimap failed: ${e}`);
|
|
58909
|
+
}
|
|
58910
|
+
await graph_draw(gobj);
|
|
58911
|
+
if (changed || opts.relayout) await graph_layout(gobj);
|
|
58912
|
+
if (opts.fit) await graph_fit_readable(gobj);
|
|
58913
|
+
else if (opts.keep && opts.keep_vp) await yui_graph_place_at(graph, opts.keep, opts.keep_vp);
|
|
58914
|
+
update_resize_handles_position(gobj);
|
|
58915
|
+
update_port_resize_handles_position(gobj);
|
|
58916
|
+
update_edge_icon_position(gobj);
|
|
58917
|
+
update_node_icon_position(gobj);
|
|
58918
|
+
update_link_icon_position(gobj);
|
|
58919
|
+
return changed;
|
|
58920
|
+
}
|
|
58921
|
+
/************************************************************
|
|
58922
|
+
* Repaint the `+N` chips (theme or language changed).
|
|
58923
|
+
************************************************************/
|
|
58924
|
+
function repaint_more_chips(gobj) {
|
|
58925
|
+
let priv = gobj.priv;
|
|
58926
|
+
let graph = priv.graph;
|
|
58927
|
+
if (!graph) return;
|
|
58928
|
+
let updates = [];
|
|
58929
|
+
for (let nd of graph.getNodeData() || []) {
|
|
58930
|
+
let html = more_innerHTML_of(gobj, nd, priv.theme);
|
|
58931
|
+
if (html !== null) updates.push({
|
|
58932
|
+
id: nd.id,
|
|
58933
|
+
style: { innerHTML: html }
|
|
58934
|
+
});
|
|
58935
|
+
}
|
|
58936
|
+
if (updates.length) try {
|
|
58937
|
+
graph.updateNodeData(updates);
|
|
58938
|
+
graph.draw();
|
|
58939
|
+
} catch (e) {
|
|
58940
|
+
log_error(`${gobj_short_name(gobj)}: cannot repaint the chips: ${e}`);
|
|
58941
|
+
}
|
|
58942
|
+
}
|
|
58943
|
+
/***************************
|
|
57906
58944
|
* Actions
|
|
57907
58945
|
***************************/
|
|
57908
58946
|
/************************************************************
|
|
@@ -57931,6 +58969,9 @@ function ac_clear_data(gobj, event, kw, src) {
|
|
|
57931
58969
|
gobj_write_attr(gobj, "records", {});
|
|
57932
58970
|
priv._nodes_total = 0;
|
|
57933
58971
|
priv._nodes_placed = 0;
|
|
58972
|
+
priv._fold_model = null;
|
|
58973
|
+
priv._fold_state = null;
|
|
58974
|
+
priv._pill_sig = null;
|
|
57934
58975
|
graph_remove_plugin(gobj, "history");
|
|
57935
58976
|
update_history_buttons(gobj);
|
|
57936
58977
|
graph_clear(gobj);
|
|
@@ -57961,53 +59002,43 @@ function ac_load_data(gobj, event, kw, src) {
|
|
|
57961
59002
|
}
|
|
57962
59003
|
return 0;
|
|
57963
59004
|
}
|
|
57964
|
-
|
|
59005
|
+
priv.descs[topic_name];
|
|
57965
59006
|
priv.records[topic_name] = data;
|
|
57966
59007
|
priv.descs[topic_name].loaded = true;
|
|
57967
|
-
|
|
57968
|
-
let record = data[i];
|
|
57969
|
-
create_topic_node(gobj, desc, record);
|
|
57970
|
-
}
|
|
57971
|
-
let do_links = true;
|
|
59008
|
+
let all_loaded = true;
|
|
57972
59009
|
for (const [topic_name, desc] of Object.entries(priv.descs)) {
|
|
57973
59010
|
if (topic_name.substring(0, 2) === "__") continue;
|
|
57974
59011
|
if (!desc.loaded) {
|
|
57975
|
-
|
|
59012
|
+
all_loaded = false;
|
|
57976
59013
|
break;
|
|
57977
59014
|
}
|
|
57978
59015
|
}
|
|
57979
|
-
if (
|
|
59016
|
+
if (all_loaded && priv.graph) {
|
|
59017
|
+
rebuild_fold_model(gobj);
|
|
59018
|
+
fold_expand_to_depth(priv._fold_model, priv._fold_state, gobj_read_integer_attr(gobj, "expand_depth"));
|
|
59019
|
+
count_placed_records(gobj);
|
|
59020
|
+
let auto_laid = false;
|
|
57980
59021
|
try {
|
|
57981
|
-
|
|
59022
|
+
auto_laid = auto_layout(gobj);
|
|
57982
59023
|
} catch (e) {
|
|
57983
|
-
log_error(`${gobj_short_name(gobj)}:
|
|
57984
|
-
}
|
|
57985
|
-
|
|
57986
|
-
|
|
57987
|
-
|
|
57988
|
-
|
|
57989
|
-
|
|
57990
|
-
|
|
57991
|
-
|
|
57992
|
-
|
|
57993
|
-
|
|
57994
|
-
|
|
57995
|
-
|
|
57996
|
-
|
|
57997
|
-
|
|
57998
|
-
|
|
57999
|
-
|
|
58000
|
-
|
|
58001
|
-
priv._pending_focus_topic = null;
|
|
58002
|
-
graph_focus_topic(gobj, ft);
|
|
58003
|
-
}
|
|
58004
|
-
if (priv._pending_find !== null) {
|
|
58005
|
-
let term = priv._pending_find;
|
|
58006
|
-
priv._pending_find = null;
|
|
58007
|
-
publish_find_result(gobj, term, graph_find_nodes(gobj, term));
|
|
58008
|
-
}
|
|
58009
|
-
});
|
|
58010
|
-
});
|
|
59024
|
+
log_error(`${gobj_short_name(gobj)}: auto_layout failed: ${e}`);
|
|
59025
|
+
}
|
|
59026
|
+
apply_layout_direction(gobj);
|
|
59027
|
+
reconcile_fold(gobj, {
|
|
59028
|
+
relayout: true,
|
|
59029
|
+
fit: auto_laid
|
|
59030
|
+
}).then(() => {
|
|
59031
|
+
sync_history_plugin(gobj);
|
|
59032
|
+
if (priv._pending_focus_topic !== null) {
|
|
59033
|
+
let ft = priv._pending_focus_topic;
|
|
59034
|
+
priv._pending_focus_topic = null;
|
|
59035
|
+
graph_focus_topic(gobj, ft);
|
|
59036
|
+
}
|
|
59037
|
+
if (priv._pending_find !== null) {
|
|
59038
|
+
let term = priv._pending_find;
|
|
59039
|
+
priv._pending_find = null;
|
|
59040
|
+
publish_find_result(gobj, term, graph_find_nodes(gobj, term));
|
|
59041
|
+
}
|
|
58011
59042
|
});
|
|
58012
59043
|
}
|
|
58013
59044
|
return 0;
|
|
@@ -58043,19 +59074,15 @@ function ac_node_created(gobj, event, kw, src) {
|
|
|
58043
59074
|
build_graph_properties(gobj);
|
|
58044
59075
|
return 0;
|
|
58045
59076
|
}
|
|
58046
|
-
|
|
58047
|
-
if (!desc) {
|
|
59077
|
+
if (!priv.descs[topic_name]) {
|
|
58048
59078
|
log_error(`ac_node_created: unknown topic: ${topic_name}`);
|
|
58049
59079
|
return 0;
|
|
58050
59080
|
}
|
|
58051
59081
|
if (!priv.records[topic_name]) priv.records[topic_name] = [];
|
|
58052
59082
|
priv.records[topic_name].push(node);
|
|
58053
|
-
|
|
58054
|
-
|
|
58055
|
-
|
|
58056
|
-
graph_draw(gobj).then(() => {
|
|
58057
|
-
history_resume(gobj);
|
|
58058
|
-
});
|
|
59083
|
+
rebuild_fold_model(gobj);
|
|
59084
|
+
fold_reveal(priv._fold_model, priv._fold_state, fold_node_key(topic_name, String(node.id)));
|
|
59085
|
+
reconcile_fold(gobj, {});
|
|
58059
59086
|
return 0;
|
|
58060
59087
|
}
|
|
58061
59088
|
/************************************************************
|
|
@@ -58104,8 +59131,14 @@ function ac_node_updated(gobj, event, kw, src) {
|
|
|
58104
59131
|
let [col_idx, fkey_value] = ref.split(" ");
|
|
58105
59132
|
clear_link(gobj, topic_name, node.id, cols[parseInt(col_idx)], fkey_value, false);
|
|
58106
59133
|
}
|
|
58107
|
-
update_topic_node(gobj, desc, node_name, node);
|
|
58108
59134
|
update_local_node(gobj, topic_name, node);
|
|
59135
|
+
let on_screen = false;
|
|
59136
|
+
try {
|
|
59137
|
+
on_screen = !!graph.getNodeData(node_name);
|
|
59138
|
+
} catch (e) {
|
|
59139
|
+
on_screen = false;
|
|
59140
|
+
}
|
|
59141
|
+
if (on_screen) update_topic_node(gobj, desc, node_name, node);
|
|
58109
59142
|
for (let ref of new_refs) if (!old_refs.has(ref)) {
|
|
58110
59143
|
let [col_idx, fkey_value] = ref.split(" ");
|
|
58111
59144
|
draw_link(gobj, topic_name, node.id, cols[parseInt(col_idx)], fkey_value, false);
|
|
@@ -58113,6 +59146,8 @@ function ac_node_updated(gobj, event, kw, src) {
|
|
|
58113
59146
|
graph_draw(gobj).then(() => {
|
|
58114
59147
|
history_resume(gobj);
|
|
58115
59148
|
});
|
|
59149
|
+
rebuild_fold_model(gobj);
|
|
59150
|
+
reconcile_fold(gobj, {});
|
|
58116
59151
|
return 0;
|
|
58117
59152
|
}
|
|
58118
59153
|
/************************************************************
|
|
@@ -58138,9 +59173,12 @@ function ac_node_deleted(gobj, event, kw, src) {
|
|
|
58138
59173
|
clear_links(gobj, desc, node, false);
|
|
58139
59174
|
remove_topic_node(gobj, node_name);
|
|
58140
59175
|
remove_local_node(gobj, topic_name, node);
|
|
59176
|
+
if (priv._pill_sig) priv._pill_sig.delete(node_name);
|
|
58141
59177
|
graph_draw(gobj).then(() => {
|
|
58142
59178
|
history_resume(gobj);
|
|
58143
59179
|
});
|
|
59180
|
+
rebuild_fold_model(gobj);
|
|
59181
|
+
reconcile_fold(gobj, {});
|
|
58144
59182
|
return 0;
|
|
58145
59183
|
}
|
|
58146
59184
|
/************************************************************
|
|
@@ -58199,6 +59237,7 @@ function ac_theme$1(gobj, event, kw, src) {
|
|
|
58199
59237
|
});
|
|
58200
59238
|
refresh_html_nodes_theme(gobj, theme);
|
|
58201
59239
|
refresh_default_edges_theme(gobj, theme);
|
|
59240
|
+
repaint_more_chips(gobj);
|
|
58202
59241
|
graph_draw(gobj).then(() => {
|
|
58203
59242
|
update_history_buttons(gobj);
|
|
58204
59243
|
});
|
|
@@ -58313,6 +59352,85 @@ function ac_focus_topic(gobj, event, kw, src) {
|
|
|
58313
59352
|
return 0;
|
|
58314
59353
|
}
|
|
58315
59354
|
/************************************************************
|
|
59355
|
+
* A pill was tapped: open that hook on its first page, or fold
|
|
59356
|
+
* it. The card the pill is on stays where it was on screen --
|
|
59357
|
+
* the layout moves everything else around it.
|
|
59358
|
+
************************************************************/
|
|
59359
|
+
function ac_toggle_fold(gobj, event, kw, src) {
|
|
59360
|
+
let priv = gobj.priv;
|
|
59361
|
+
let group = kw && kw.group || "";
|
|
59362
|
+
if (!priv._fold_model || !group) {
|
|
59363
|
+
log_error(`${gobj_short_name(gobj)}: fold of '${group}' with no tree loaded`);
|
|
59364
|
+
return -1;
|
|
59365
|
+
}
|
|
59366
|
+
let g = fold_split_group_key(group);
|
|
59367
|
+
if (!g) {
|
|
59368
|
+
log_error(`${gobj_short_name(gobj)}: bad fold group '${group}'`);
|
|
59369
|
+
return -1;
|
|
59370
|
+
}
|
|
59371
|
+
let node_id = g.node_key ? node_id_of_key(gobj, g.node_key) : "";
|
|
59372
|
+
let vp = node_id ? yui_graph_viewport_of(priv.graph, node_id) : null;
|
|
59373
|
+
fold_toggle(priv._fold_model, priv._fold_state, group);
|
|
59374
|
+
reconcile_fold(gobj, {
|
|
59375
|
+
keep: node_id,
|
|
59376
|
+
keep_vp: vp
|
|
59377
|
+
});
|
|
59378
|
+
return 0;
|
|
59379
|
+
}
|
|
59380
|
+
/************************************************************
|
|
59381
|
+
* A `+N` chip was tapped: the next page of its group. The
|
|
59382
|
+
* parent holds still; the chip itself moves down the column.
|
|
59383
|
+
************************************************************/
|
|
59384
|
+
function ac_show_more(gobj, event, kw, src) {
|
|
59385
|
+
let priv = gobj.priv;
|
|
59386
|
+
let group = kw && kw.group || "";
|
|
59387
|
+
if (!priv._fold_model || !group) {
|
|
59388
|
+
log_error(`${gobj_short_name(gobj)}: show more of '${group}' with no tree loaded`);
|
|
59389
|
+
return -1;
|
|
59390
|
+
}
|
|
59391
|
+
let g = fold_split_group_key(group);
|
|
59392
|
+
if (!g) {
|
|
59393
|
+
log_error(`${gobj_short_name(gobj)}: bad fold group '${group}'`);
|
|
59394
|
+
return -1;
|
|
59395
|
+
}
|
|
59396
|
+
let node_id = g.node_key ? node_id_of_key(gobj, g.node_key) : "";
|
|
59397
|
+
let vp = node_id ? yui_graph_viewport_of(priv.graph, node_id) : null;
|
|
59398
|
+
fold_show_more(priv._fold_model, priv._fold_state, group);
|
|
59399
|
+
reconcile_fold(gobj, {
|
|
59400
|
+
keep: node_id,
|
|
59401
|
+
keep_vp: vp
|
|
59402
|
+
});
|
|
59403
|
+
return 0;
|
|
59404
|
+
}
|
|
59405
|
+
/************************************************************
|
|
59406
|
+
* The whole treedb, every hook open on all its children. This
|
|
59407
|
+
* is the reader asking for the pile on purpose; the pages are
|
|
59408
|
+
* the other way in.
|
|
59409
|
+
************************************************************/
|
|
59410
|
+
function ac_expand_all$1(gobj, event, kw, src) {
|
|
59411
|
+
let priv = gobj.priv;
|
|
59412
|
+
if (!priv._fold_model) {
|
|
59413
|
+
log_error(`${gobj_short_name(gobj)}: expand all with no tree loaded`);
|
|
59414
|
+
return -1;
|
|
59415
|
+
}
|
|
59416
|
+
fold_expand_all(priv._fold_model, priv._fold_state);
|
|
59417
|
+
reconcile_fold(gobj, { fit: true });
|
|
59418
|
+
return 0;
|
|
59419
|
+
}
|
|
59420
|
+
/************************************************************
|
|
59421
|
+
* The roots alone.
|
|
59422
|
+
************************************************************/
|
|
59423
|
+
function ac_collapse_all$1(gobj, event, kw, src) {
|
|
59424
|
+
let priv = gobj.priv;
|
|
59425
|
+
if (!priv._fold_model) {
|
|
59426
|
+
log_error(`${gobj_short_name(gobj)}: collapse all with no tree loaded`);
|
|
59427
|
+
return -1;
|
|
59428
|
+
}
|
|
59429
|
+
fold_collapse_all(priv._fold_model, priv._fold_state);
|
|
59430
|
+
reconcile_fold(gobj, { fit: true });
|
|
59431
|
+
return 0;
|
|
59432
|
+
}
|
|
59433
|
+
/************************************************************
|
|
58316
59434
|
* Tell whoever asked how many nodes the term matched. The count is
|
|
58317
59435
|
* the answer to the question the box asks; without it "nothing moved"
|
|
58318
59436
|
* and "nothing matched" look the same.
|
|
@@ -58343,7 +59461,9 @@ function ac_fullscreen(gobj, event, kw, src) {
|
|
|
58343
59461
|
************************************************************/
|
|
58344
59462
|
function ac_set_layout$1(gobj, event, kw, src) {
|
|
58345
59463
|
gobj.priv;
|
|
58346
|
-
|
|
59464
|
+
let layout = select_layout(gobj, kw.layout);
|
|
59465
|
+
apply_layout_direction(gobj);
|
|
59466
|
+
graph_set_layout(gobj, layout).then(() => {
|
|
58347
59467
|
configure_behaviour(gobj);
|
|
58348
59468
|
update_toolbar(gobj);
|
|
58349
59469
|
});
|
|
@@ -58450,6 +59570,16 @@ function ac_node_click$2(gobj, event, kw, src) {
|
|
|
58450
59570
|
let priv = gobj.priv;
|
|
58451
59571
|
let graph = priv.graph;
|
|
58452
59572
|
let node_id = kw.evt.target.id;
|
|
59573
|
+
let more_nd = null;
|
|
59574
|
+
try {
|
|
59575
|
+
more_nd = graph.getNodeData(node_id);
|
|
59576
|
+
} catch (e) {
|
|
59577
|
+
more_nd = null;
|
|
59578
|
+
}
|
|
59579
|
+
if (is_more_node(more_nd)) {
|
|
59580
|
+
gobj_send_event(gobj, "EV_SHOW_MORE", { group: more_nd.data.more }, gobj);
|
|
59581
|
+
return 0;
|
|
59582
|
+
}
|
|
58453
59583
|
if (priv.anchor_state === "arming") {
|
|
58454
59584
|
priv.anchor_id = node_id;
|
|
58455
59585
|
priv.anchor_state = "on";
|
|
@@ -58754,6 +59884,26 @@ function create_gclass$4(gclass_name) {
|
|
|
58754
59884
|
ac_find_nodes$1,
|
|
58755
59885
|
null
|
|
58756
59886
|
],
|
|
59887
|
+
[
|
|
59888
|
+
"EV_TOGGLE_FOLD",
|
|
59889
|
+
ac_toggle_fold,
|
|
59890
|
+
null
|
|
59891
|
+
],
|
|
59892
|
+
[
|
|
59893
|
+
"EV_SHOW_MORE",
|
|
59894
|
+
ac_show_more,
|
|
59895
|
+
null
|
|
59896
|
+
],
|
|
59897
|
+
[
|
|
59898
|
+
"EV_EXPAND_ALL",
|
|
59899
|
+
ac_expand_all$1,
|
|
59900
|
+
null
|
|
59901
|
+
],
|
|
59902
|
+
[
|
|
59903
|
+
"EV_COLLAPSE_ALL",
|
|
59904
|
+
ac_collapse_all$1,
|
|
59905
|
+
null
|
|
59906
|
+
],
|
|
58757
59907
|
[
|
|
58758
59908
|
"EV_CENTER",
|
|
58759
59909
|
ac_center,
|
|
@@ -58851,6 +60001,10 @@ function create_gclass$4(gclass_name) {
|
|
|
58851
60001
|
["EV_FIND_NODES", 0],
|
|
58852
60002
|
["EV_FIND_RESULT", event_flag_t.EVF_OUTPUT_EVENT],
|
|
58853
60003
|
["EV_LAYOUT_AUTOSET", event_flag_t.EVF_OUTPUT_EVENT],
|
|
60004
|
+
["EV_TOGGLE_FOLD", 0],
|
|
60005
|
+
["EV_SHOW_MORE", 0],
|
|
60006
|
+
["EV_EXPAND_ALL", 0],
|
|
60007
|
+
["EV_COLLAPSE_ALL", 0],
|
|
58854
60008
|
["EV_CENTER", 0],
|
|
58855
60009
|
["EV_FULLSCREEN", 0],
|
|
58856
60010
|
["EV_SET_LAYOUT", 0],
|
|
@@ -58908,6 +60062,8 @@ var attrs_table$3 = [
|
|
|
58908
60062
|
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
|
|
58909
60063
|
SDATA(data_type_t.DTP_LIST, "operation_modes", 0, "[\"reading\", \"operation\", \"writing\", \"edition\"]", "Available **permission** or behaviour modes. These operation modes are required to be accomplish by the graph handler (G6 child). TODO permissions must match treedb permissions."),
|
|
58910
60064
|
SDATA(data_type_t.DTP_BOOLEAN, "with_treedb_tables", 0, false, "Include treedb tables"),
|
|
60065
|
+
SDATA(data_type_t.DTP_INTEGER, "expand_depth", 0, 2, "Levels of the treedb open when it loads: 1 = the roots alone, 2 = the roots and their children. Forwarded to the G6 child"),
|
|
60066
|
+
SDATA(data_type_t.DTP_INTEGER, "fold_page_size", 0, 24, "Children of one hook shown per page in the graph; a `+N` chip opens the next page. Forwarded to the G6 child"),
|
|
58911
60067
|
SDATA(data_type_t.DTP_STRING, "operation_mode", sdata_flag_t.SDF_PERSIST, "reading", "Current operation mode (internal behaviour or role). Changed by the user trough the gui."),
|
|
58912
60068
|
SDATA(data_type_t.DTP_STRING, "layout", sdata_flag_t.SDF_PERSIST, "", "Current graph layout. User preference. Changed by the user through the gui."),
|
|
58913
60069
|
SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
|
|
@@ -58981,7 +60137,9 @@ function mt_create$3(gobj) {
|
|
|
58981
60137
|
layout: priv.layout,
|
|
58982
60138
|
with_treedb_tables: priv.with_treedb_tables,
|
|
58983
60139
|
hook_port_position: "bottom",
|
|
58984
|
-
fkey_port_position: "top"
|
|
60140
|
+
fkey_port_position: "top",
|
|
60141
|
+
expand_depth: gobj_read_integer_attr(gobj, "expand_depth"),
|
|
60142
|
+
fold_page_size: gobj_read_integer_attr(gobj, "fold_page_size")
|
|
58985
60143
|
}, gobj);
|
|
58986
60144
|
populate_nodes_tree_options(gobj);
|
|
58987
60145
|
if (priv.with_treedb_tables) {}
|
|
@@ -59239,6 +60397,7 @@ function make_toolbar(gobj) {
|
|
|
59239
60397
|
gobj_send_event(gobj, "EV_REFRESH_TREEDB", { evt }, gobj);
|
|
59240
60398
|
} }
|
|
59241
60399
|
],
|
|
60400
|
+
...yui_graph_fold_items(gobj, gobj_read_str_attr(gobj, "wide")),
|
|
59242
60401
|
[
|
|
59243
60402
|
"button",
|
|
59244
60403
|
{
|
|
@@ -60380,6 +61539,20 @@ function ac_find_nodes(gobj, event, kw, src) {
|
|
|
60380
61539
|
return 0;
|
|
60381
61540
|
}
|
|
60382
61541
|
/************************************************************
|
|
61542
|
+
* The fold pair of the toolbar, forwarded to the G6 child,
|
|
61543
|
+
* which owns the tree.
|
|
61544
|
+
************************************************************/
|
|
61545
|
+
function ac_expand_all(gobj, event, kw, src) {
|
|
61546
|
+
let priv = gobj.priv;
|
|
61547
|
+
if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_EXPAND_ALL", {}, gobj);
|
|
61548
|
+
return 0;
|
|
61549
|
+
}
|
|
61550
|
+
function ac_collapse_all(gobj, event, kw, src) {
|
|
61551
|
+
let priv = gobj.priv;
|
|
61552
|
+
if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_COLLAPSE_ALL", {}, gobj);
|
|
61553
|
+
return 0;
|
|
61554
|
+
}
|
|
61555
|
+
/************************************************************
|
|
60383
61556
|
* How many nodes the term matched, from the graph child. Zero with a
|
|
60384
61557
|
* term typed is an ANSWER and is shown; an empty box shows nothing.
|
|
60385
61558
|
************************************************************/
|
|
@@ -60572,6 +61745,16 @@ function create_gclass$3(gclass_name) {
|
|
|
60572
61745
|
ac_find_nodes,
|
|
60573
61746
|
null
|
|
60574
61747
|
],
|
|
61748
|
+
[
|
|
61749
|
+
"EV_EXPAND_ALL",
|
|
61750
|
+
ac_expand_all,
|
|
61751
|
+
null
|
|
61752
|
+
],
|
|
61753
|
+
[
|
|
61754
|
+
"EV_COLLAPSE_ALL",
|
|
61755
|
+
ac_collapse_all,
|
|
61756
|
+
null
|
|
61757
|
+
],
|
|
60575
61758
|
[
|
|
60576
61759
|
"EV_LAYOUT_AUTOSET",
|
|
60577
61760
|
ac_layout_autoset,
|
|
@@ -60634,6 +61817,8 @@ function create_gclass$3(gclass_name) {
|
|
|
60634
61817
|
["EV_SET_OPERATION_MODE", 0],
|
|
60635
61818
|
["EV_SET_FOCUS_TOPIC", 0],
|
|
60636
61819
|
["EV_FIND_NODES", 0],
|
|
61820
|
+
["EV_EXPAND_ALL", 0],
|
|
61821
|
+
["EV_COLLAPSE_ALL", 0],
|
|
60637
61822
|
["EV_FIND_RESULT", 0],
|
|
60638
61823
|
["EV_LAYOUT_AUTOSET", 0],
|
|
60639
61824
|
["EV_TOGGLE_LEGEND", 0],
|