@yuneta/gobj-ui 7.3.0 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gobj-ui.cjs.js +206 -17
- package/dist/gobj-ui.es.js +206 -17
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +81 -1
- package/src/c_yui_treedb_graph.js +149 -1
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -18595,6 +18595,7 @@ var PRIVATE_DATA$4 = {
|
|
|
18595
18595
|
records: {},
|
|
18596
18596
|
gobj_nodes_tree: null,
|
|
18597
18597
|
find_timer: null,
|
|
18598
|
+
focus_topic: "",
|
|
18598
18599
|
gobj_treedb_tables: null,
|
|
18599
18600
|
hook_data_viewer: null,
|
|
18600
18601
|
json_gobj: null,
|
|
@@ -18693,26 +18694,37 @@ function build_ui$4(gobj) {
|
|
|
18693
18694
|
class: "C_YUI_TREEDB_GRAPH",
|
|
18694
18695
|
style: `height:100%; display:flex; flex-direction:column;`
|
|
18695
18696
|
},
|
|
18696
|
-
[
|
|
18697
|
-
|
|
18698
|
-
|
|
18699
|
-
|
|
18700
|
-
|
|
18701
|
-
|
|
18702
|
-
|
|
18703
|
-
class: `GRAPH_BODY is-flex-grow-1 ${padding}`,
|
|
18704
|
-
style: "height:100%; min-height:0; overflow:hidden;"
|
|
18705
|
-
},
|
|
18706
|
-
[[
|
|
18697
|
+
[
|
|
18698
|
+
[
|
|
18699
|
+
"div",
|
|
18700
|
+
{ class: "GRAPH_TOOLBAR_ROW is-flex-grow-0 is-flex is-align-items-center" },
|
|
18701
|
+
$back ? [$back, $toolbar] : [$toolbar]
|
|
18702
|
+
],
|
|
18703
|
+
[
|
|
18707
18704
|
"div",
|
|
18708
18705
|
{
|
|
18709
|
-
|
|
18710
|
-
|
|
18711
|
-
style: "height:100%; min-height:0;border: 1px solid var(--bulma-border-weak);border-radius:0.2rem;"
|
|
18706
|
+
class: "GRAPH_LEGEND is-hidden is-flex-grow-0",
|
|
18707
|
+
style: "display:flex; flex-wrap:wrap; align-items:center; gap:.4rem; padding:.35rem .5rem;"
|
|
18712
18708
|
},
|
|
18713
18709
|
[]
|
|
18714
|
-
]
|
|
18715
|
-
|
|
18710
|
+
],
|
|
18711
|
+
[
|
|
18712
|
+
"div",
|
|
18713
|
+
{
|
|
18714
|
+
class: `GRAPH_BODY is-flex-grow-1 ${padding}`,
|
|
18715
|
+
style: "height:100%; min-height:0; overflow:hidden;"
|
|
18716
|
+
},
|
|
18717
|
+
[[
|
|
18718
|
+
"div",
|
|
18719
|
+
{
|
|
18720
|
+
id: priv.canvas_id,
|
|
18721
|
+
class: `GRAPH_CANVAS graph-container`,
|
|
18722
|
+
style: "height:100%; min-height:0;border: 1px solid var(--bulma-border-weak);border-radius:0.2rem;"
|
|
18723
|
+
},
|
|
18724
|
+
[]
|
|
18725
|
+
]]
|
|
18726
|
+
]
|
|
18727
|
+
]
|
|
18716
18728
|
]);
|
|
18717
18729
|
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", $container);
|
|
18718
18730
|
(0, _yuneta_gobj_js.refresh_language)($container, i18next.t);
|
|
@@ -18845,6 +18857,29 @@ function make_toolbar(gobj) {
|
|
|
18845
18857
|
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REFRESH_TREEDB", { evt }, gobj);
|
|
18846
18858
|
} }
|
|
18847
18859
|
],
|
|
18860
|
+
[
|
|
18861
|
+
"button",
|
|
18862
|
+
{
|
|
18863
|
+
class: "GRAPH_LEGEND_BTN button ml-2",
|
|
18864
|
+
title: (0, i18next.t)("legend"),
|
|
18865
|
+
"aria-label": (0, i18next.t)("legend"),
|
|
18866
|
+
"data-i18n-title": "legend",
|
|
18867
|
+
"data-i18n-aria-label": "legend"
|
|
18868
|
+
},
|
|
18869
|
+
[["i", { class: "yi-square" }], [
|
|
18870
|
+
"span",
|
|
18871
|
+
{
|
|
18872
|
+
class: "is-hidden-mobile",
|
|
18873
|
+
style: "padding-left:5px;",
|
|
18874
|
+
i18n: "legend"
|
|
18875
|
+
},
|
|
18876
|
+
"legend"
|
|
18877
|
+
]],
|
|
18878
|
+
{ click: (evt) => {
|
|
18879
|
+
evt.stopPropagation();
|
|
18880
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_TOGGLE_LEGEND", {}, gobj);
|
|
18881
|
+
} }
|
|
18882
|
+
],
|
|
18848
18883
|
[
|
|
18849
18884
|
"button",
|
|
18850
18885
|
{
|
|
@@ -19854,6 +19889,86 @@ function ac_set_operation_mode$1(gobj, event, kw, src) {
|
|
|
19854
19889
|
* until its data is loaded).
|
|
19855
19890
|
************************************************************/
|
|
19856
19891
|
/************************************************************
|
|
19892
|
+
* Fill the legend strip with one entry per topic: a swatch in the
|
|
19893
|
+
* topic's colour and its name. The colours belong to the graph child —
|
|
19894
|
+
* it assigns them from its palette when the schema arrives — so they
|
|
19895
|
+
* are read from there and never guessed twice.
|
|
19896
|
+
*
|
|
19897
|
+
* An entry is a BUTTON: clicking it focuses that topic, clicking the
|
|
19898
|
+
* focused one clears the focus. The focus travels the same way a topic
|
|
19899
|
+
* card's graph icon travels — as EV_TOPIC_SELECTED, which the host turns
|
|
19900
|
+
* into the URL, so what you are looking at stays linkable.
|
|
19901
|
+
************************************************************/
|
|
19902
|
+
function refresh_legend(gobj) {
|
|
19903
|
+
let priv = gobj.priv;
|
|
19904
|
+
let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
|
|
19905
|
+
if (!$container) return;
|
|
19906
|
+
let $legend = $container.querySelector(".GRAPH_LEGEND");
|
|
19907
|
+
if (!$legend) return;
|
|
19908
|
+
let descs = priv.gobj_nodes_tree ? (0, _yuneta_gobj_js.gobj_read_attr)(priv.gobj_nodes_tree, "descs") : null;
|
|
19909
|
+
$legend.textContent = "";
|
|
19910
|
+
if (!descs) return;
|
|
19911
|
+
for (const [topic_name, desc] of Object.entries(descs)) {
|
|
19912
|
+
if (topic_name.substring(0, 2) === "__") continue;
|
|
19913
|
+
if (!desc || !desc.color) continue;
|
|
19914
|
+
let focused = priv.focus_topic === topic_name;
|
|
19915
|
+
let $item = (0, _yuneta_gobj_js.createElement2)([
|
|
19916
|
+
"button",
|
|
19917
|
+
{
|
|
19918
|
+
class: "GRAPH_LEGEND_ITEM button is-small" + (focused ? " is-primary" : ""),
|
|
19919
|
+
title: topic_name,
|
|
19920
|
+
"aria-label": topic_name,
|
|
19921
|
+
"aria-pressed": focused ? "true" : "false"
|
|
19922
|
+
},
|
|
19923
|
+
[["span", {
|
|
19924
|
+
class: "GRAPH_LEGEND_SWATCH",
|
|
19925
|
+
style: `display:inline-block; width:.8rem; height:.8rem; border-radius:3px; margin-right:.4rem; background:${desc.color}; border:1px solid rgba(0,0,0,.25);`
|
|
19926
|
+
}], [
|
|
19927
|
+
"span",
|
|
19928
|
+
{},
|
|
19929
|
+
topic_name
|
|
19930
|
+
]],
|
|
19931
|
+
{ click: (evt) => {
|
|
19932
|
+
evt.stopPropagation();
|
|
19933
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_LEGEND_TOPIC", { topic: topic_name }, gobj);
|
|
19934
|
+
} }
|
|
19935
|
+
]);
|
|
19936
|
+
$legend.appendChild($item);
|
|
19937
|
+
}
|
|
19938
|
+
}
|
|
19939
|
+
/************************************************************
|
|
19940
|
+
*
|
|
19941
|
+
************************************************************/
|
|
19942
|
+
function ac_toggle_legend(gobj, event, kw, src) {
|
|
19943
|
+
let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
|
|
19944
|
+
if (!$container) return 0;
|
|
19945
|
+
let $legend = $container.querySelector(".GRAPH_LEGEND");
|
|
19946
|
+
if (!$legend) {
|
|
19947
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no legend strip to toggle`);
|
|
19948
|
+
return -1;
|
|
19949
|
+
}
|
|
19950
|
+
if ($legend.classList.contains("is-hidden")) {
|
|
19951
|
+
refresh_legend(gobj);
|
|
19952
|
+
$legend.classList.remove("is-hidden");
|
|
19953
|
+
} else $legend.classList.add("is-hidden");
|
|
19954
|
+
return 0;
|
|
19955
|
+
}
|
|
19956
|
+
/************************************************************
|
|
19957
|
+
* A legend entry was clicked: focus that topic, or clear the focus if
|
|
19958
|
+
* it was already the focused one. Published UP rather than applied
|
|
19959
|
+
* here, so the URL follows — the host owns the route.
|
|
19960
|
+
************************************************************/
|
|
19961
|
+
function ac_legend_topic(gobj, event, kw, src) {
|
|
19962
|
+
let priv = gobj.priv;
|
|
19963
|
+
let topic = kw && kw.topic || "";
|
|
19964
|
+
if (!topic) {
|
|
19965
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: legend click with no topic`);
|
|
19966
|
+
return -1;
|
|
19967
|
+
}
|
|
19968
|
+
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_TOPIC_SELECTED", { topic: priv.focus_topic === topic ? "" : topic });
|
|
19969
|
+
return 0;
|
|
19970
|
+
}
|
|
19971
|
+
/************************************************************
|
|
19857
19972
|
* Forward the find down to the graph child.
|
|
19858
19973
|
************************************************************/
|
|
19859
19974
|
function ac_find_nodes$1(gobj, event, kw, src) {
|
|
@@ -19882,7 +19997,11 @@ function ac_find_result(gobj, event, kw, src) {
|
|
|
19882
19997
|
}
|
|
19883
19998
|
function ac_set_focus_topic(gobj, event, kw, src) {
|
|
19884
19999
|
let priv = gobj.priv;
|
|
19885
|
-
|
|
20000
|
+
priv.focus_topic = kw && kw.topic || "";
|
|
20001
|
+
if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_FOCUS_TOPIC", { topic: priv.focus_topic }, gobj);
|
|
20002
|
+
let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
|
|
20003
|
+
let $legend = $container ? $container.querySelector(".GRAPH_LEGEND") : null;
|
|
20004
|
+
if ($legend && !$legend.classList.contains("is-hidden")) refresh_legend(gobj);
|
|
19886
20005
|
return 0;
|
|
19887
20006
|
}
|
|
19888
20007
|
/************************************************************
|
|
@@ -20050,6 +20169,16 @@ function create_gclass$4(gclass_name) {
|
|
|
20050
20169
|
ac_find_nodes$1,
|
|
20051
20170
|
null
|
|
20052
20171
|
],
|
|
20172
|
+
[
|
|
20173
|
+
"EV_TOGGLE_LEGEND",
|
|
20174
|
+
ac_toggle_legend,
|
|
20175
|
+
null
|
|
20176
|
+
],
|
|
20177
|
+
[
|
|
20178
|
+
"EV_LEGEND_TOPIC",
|
|
20179
|
+
ac_legend_topic,
|
|
20180
|
+
null
|
|
20181
|
+
],
|
|
20053
20182
|
[
|
|
20054
20183
|
"EV_FIND_RESULT",
|
|
20055
20184
|
ac_find_result,
|
|
@@ -20098,6 +20227,9 @@ function create_gclass$4(gclass_name) {
|
|
|
20098
20227
|
["EV_SET_FOCUS_TOPIC", 0],
|
|
20099
20228
|
["EV_FIND_NODES", 0],
|
|
20100
20229
|
["EV_FIND_RESULT", 0],
|
|
20230
|
+
["EV_TOGGLE_LEGEND", 0],
|
|
20231
|
+
["EV_LEGEND_TOPIC", 0],
|
|
20232
|
+
["EV_TOPIC_SELECTED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
20101
20233
|
["EV_OPERATION_MODE_CHANGED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
20102
20234
|
["EV_RECORD_WRITTEN", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
20103
20235
|
["EV_SHOW", 0],
|
|
@@ -52704,6 +52836,7 @@ var attrs_table$1 = [
|
|
|
52704
52836
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_gridline", 0, true, "Use gridline plugin"),
|
|
52705
52837
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_fullscreen", 0, true, "Use fullscreen plugin"),
|
|
52706
52838
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_toolbar", 0, true, "Use toolbar plugin"),
|
|
52839
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "minimap_min_nodes", 0, 30, "Show the minimap from this many nodes on (0 = never). A minimap of a graph that fits on screen is decoration; one of two hundred cards is the only way to know where you are"),
|
|
52707
52840
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "toolbar_position", 0, "right-top", "Toolbar position: top-left, top-right, bottom-left, bottom-right, left-top, right-top"),
|
|
52708
52841
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_LIST, "layout_names", _yuneta_gobj_js.sdata_flag_t.SDF_RD, JSON.stringify(Object.keys(_layouts)), "Available layout names (read-only, for parent to query)"),
|
|
52709
52842
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "hook_port_position", 0, "bottom", "Hook port position"),
|
|
@@ -55543,6 +55676,61 @@ function show_node_detail_popover(gobj, node_id) {
|
|
|
55543
55676
|
* junction diamonds get their neutral fill/stroke re-themed.
|
|
55544
55677
|
************************************************************/
|
|
55545
55678
|
/************************************************************
|
|
55679
|
+
* Show or hide the minimap, deciding by the SIZE of the graph.
|
|
55680
|
+
*
|
|
55681
|
+
* A minimap of a graph that already fits on screen is decoration; one
|
|
55682
|
+
* of two hundred cards is the only way to know where you are. So it is
|
|
55683
|
+
* not a preference the reader has to find and set — it appears when
|
|
55684
|
+
* there is something to be lost in, from `minimap_min_nodes` on.
|
|
55685
|
+
*
|
|
55686
|
+
* Its shapes are drawn by hand. G6's minimap clones each element's key
|
|
55687
|
+
* shape into its own canvas, and every node here is an `html` node
|
|
55688
|
+
* whose key shape is a DOM element — the same reason the `active`
|
|
55689
|
+
* state never painted (see HIGHLIGHT_COLOR). A block in the topic's
|
|
55690
|
+
* colour is also what a minimap of cards SHOULD show: at that scale a
|
|
55691
|
+
* card is a rectangle anyway.
|
|
55692
|
+
************************************************************/
|
|
55693
|
+
function refresh_minimap(gobj) {
|
|
55694
|
+
let graph = gobj.priv.graph;
|
|
55695
|
+
if (!graph) return;
|
|
55696
|
+
let threshold = (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "minimap_min_nodes");
|
|
55697
|
+
let n = 0;
|
|
55698
|
+
try {
|
|
55699
|
+
let data = graph.getData() || {};
|
|
55700
|
+
n = (0, _yuneta_gobj_js.is_array)(data.nodes) ? data.nodes.length : 0;
|
|
55701
|
+
} catch (e) {
|
|
55702
|
+
n = 0;
|
|
55703
|
+
}
|
|
55704
|
+
if (!threshold || n < threshold) {
|
|
55705
|
+
graph_remove_plugin(gobj, "minimap");
|
|
55706
|
+
return;
|
|
55707
|
+
}
|
|
55708
|
+
if (graph_get_plugin(gobj, "minimap")) return;
|
|
55709
|
+
graph_add_plugin(gobj, "minimap", {
|
|
55710
|
+
size: [200, 140],
|
|
55711
|
+
position: "bottom-left",
|
|
55712
|
+
shape: (id, element_type, element) => {
|
|
55713
|
+
if (element_type !== "node") return element;
|
|
55714
|
+
try {
|
|
55715
|
+
let nd = graph.getNodeData(id);
|
|
55716
|
+
let size = nd && nd.style && nd.style.size || [120, 60];
|
|
55717
|
+
let color = nd && nd.data && nd.data.desc && nd.data.desc.color || "#94a3b8";
|
|
55718
|
+
return new Rect({ style: {
|
|
55719
|
+
x: -size[0] / 2,
|
|
55720
|
+
y: -size[1] / 2,
|
|
55721
|
+
width: size[0],
|
|
55722
|
+
height: size[1],
|
|
55723
|
+
fill: color,
|
|
55724
|
+
radius: 4
|
|
55725
|
+
} });
|
|
55726
|
+
} catch (e) {
|
|
55727
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: minimap shape failed: ${e}`);
|
|
55728
|
+
return element;
|
|
55729
|
+
}
|
|
55730
|
+
}
|
|
55731
|
+
});
|
|
55732
|
+
}
|
|
55733
|
+
/************************************************************
|
|
55546
55734
|
* The innerHTML of ONE node, in the given theme and highlight state.
|
|
55547
55735
|
* The three treedb tiers (hierarchical / extended / child) each have
|
|
55548
55736
|
* their own card, and both the theme refresh and the highlight need the
|
|
@@ -56339,6 +56527,7 @@ function ac_load_data(gobj, event, kw, src) {
|
|
|
56339
56527
|
graph_layout(gobj).then(() => {
|
|
56340
56528
|
if (priv.edit_mode) graph_add_plugin(gobj, "history");
|
|
56341
56529
|
else graph_remove_plugin(gobj, "history");
|
|
56530
|
+
refresh_minimap(gobj);
|
|
56342
56531
|
if (priv._pending_focus_topic !== null) {
|
|
56343
56532
|
let ft = priv._pending_focus_topic;
|
|
56344
56533
|
priv._pending_focus_topic = null;
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -18582,6 +18582,7 @@ var PRIVATE_DATA$4 = {
|
|
|
18582
18582
|
records: {},
|
|
18583
18583
|
gobj_nodes_tree: null,
|
|
18584
18584
|
find_timer: null,
|
|
18585
|
+
focus_topic: "",
|
|
18585
18586
|
gobj_treedb_tables: null,
|
|
18586
18587
|
hook_data_viewer: null,
|
|
18587
18588
|
json_gobj: null,
|
|
@@ -18680,26 +18681,37 @@ function build_ui$4(gobj) {
|
|
|
18680
18681
|
class: "C_YUI_TREEDB_GRAPH",
|
|
18681
18682
|
style: `height:100%; display:flex; flex-direction:column;`
|
|
18682
18683
|
},
|
|
18683
|
-
[
|
|
18684
|
-
|
|
18685
|
-
|
|
18686
|
-
|
|
18687
|
-
|
|
18688
|
-
|
|
18689
|
-
|
|
18690
|
-
class: `GRAPH_BODY is-flex-grow-1 ${padding}`,
|
|
18691
|
-
style: "height:100%; min-height:0; overflow:hidden;"
|
|
18692
|
-
},
|
|
18693
|
-
[[
|
|
18684
|
+
[
|
|
18685
|
+
[
|
|
18686
|
+
"div",
|
|
18687
|
+
{ class: "GRAPH_TOOLBAR_ROW is-flex-grow-0 is-flex is-align-items-center" },
|
|
18688
|
+
$back ? [$back, $toolbar] : [$toolbar]
|
|
18689
|
+
],
|
|
18690
|
+
[
|
|
18694
18691
|
"div",
|
|
18695
18692
|
{
|
|
18696
|
-
|
|
18697
|
-
|
|
18698
|
-
style: "height:100%; min-height:0;border: 1px solid var(--bulma-border-weak);border-radius:0.2rem;"
|
|
18693
|
+
class: "GRAPH_LEGEND is-hidden is-flex-grow-0",
|
|
18694
|
+
style: "display:flex; flex-wrap:wrap; align-items:center; gap:.4rem; padding:.35rem .5rem;"
|
|
18699
18695
|
},
|
|
18700
18696
|
[]
|
|
18701
|
-
]
|
|
18702
|
-
|
|
18697
|
+
],
|
|
18698
|
+
[
|
|
18699
|
+
"div",
|
|
18700
|
+
{
|
|
18701
|
+
class: `GRAPH_BODY is-flex-grow-1 ${padding}`,
|
|
18702
|
+
style: "height:100%; min-height:0; overflow:hidden;"
|
|
18703
|
+
},
|
|
18704
|
+
[[
|
|
18705
|
+
"div",
|
|
18706
|
+
{
|
|
18707
|
+
id: priv.canvas_id,
|
|
18708
|
+
class: `GRAPH_CANVAS graph-container`,
|
|
18709
|
+
style: "height:100%; min-height:0;border: 1px solid var(--bulma-border-weak);border-radius:0.2rem;"
|
|
18710
|
+
},
|
|
18711
|
+
[]
|
|
18712
|
+
]]
|
|
18713
|
+
]
|
|
18714
|
+
]
|
|
18703
18715
|
]);
|
|
18704
18716
|
gobj_write_attr(gobj, "$container", $container);
|
|
18705
18717
|
refresh_language($container, t);
|
|
@@ -18832,6 +18844,29 @@ function make_toolbar(gobj) {
|
|
|
18832
18844
|
gobj_send_event(gobj, "EV_REFRESH_TREEDB", { evt }, gobj);
|
|
18833
18845
|
} }
|
|
18834
18846
|
],
|
|
18847
|
+
[
|
|
18848
|
+
"button",
|
|
18849
|
+
{
|
|
18850
|
+
class: "GRAPH_LEGEND_BTN button ml-2",
|
|
18851
|
+
title: t("legend"),
|
|
18852
|
+
"aria-label": t("legend"),
|
|
18853
|
+
"data-i18n-title": "legend",
|
|
18854
|
+
"data-i18n-aria-label": "legend"
|
|
18855
|
+
},
|
|
18856
|
+
[["i", { class: "yi-square" }], [
|
|
18857
|
+
"span",
|
|
18858
|
+
{
|
|
18859
|
+
class: "is-hidden-mobile",
|
|
18860
|
+
style: "padding-left:5px;",
|
|
18861
|
+
i18n: "legend"
|
|
18862
|
+
},
|
|
18863
|
+
"legend"
|
|
18864
|
+
]],
|
|
18865
|
+
{ click: (evt) => {
|
|
18866
|
+
evt.stopPropagation();
|
|
18867
|
+
gobj_send_event(gobj, "EV_TOGGLE_LEGEND", {}, gobj);
|
|
18868
|
+
} }
|
|
18869
|
+
],
|
|
18835
18870
|
[
|
|
18836
18871
|
"button",
|
|
18837
18872
|
{
|
|
@@ -19841,6 +19876,86 @@ function ac_set_operation_mode$1(gobj, event, kw, src) {
|
|
|
19841
19876
|
* until its data is loaded).
|
|
19842
19877
|
************************************************************/
|
|
19843
19878
|
/************************************************************
|
|
19879
|
+
* Fill the legend strip with one entry per topic: a swatch in the
|
|
19880
|
+
* topic's colour and its name. The colours belong to the graph child —
|
|
19881
|
+
* it assigns them from its palette when the schema arrives — so they
|
|
19882
|
+
* are read from there and never guessed twice.
|
|
19883
|
+
*
|
|
19884
|
+
* An entry is a BUTTON: clicking it focuses that topic, clicking the
|
|
19885
|
+
* focused one clears the focus. The focus travels the same way a topic
|
|
19886
|
+
* card's graph icon travels — as EV_TOPIC_SELECTED, which the host turns
|
|
19887
|
+
* into the URL, so what you are looking at stays linkable.
|
|
19888
|
+
************************************************************/
|
|
19889
|
+
function refresh_legend(gobj) {
|
|
19890
|
+
let priv = gobj.priv;
|
|
19891
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
19892
|
+
if (!$container) return;
|
|
19893
|
+
let $legend = $container.querySelector(".GRAPH_LEGEND");
|
|
19894
|
+
if (!$legend) return;
|
|
19895
|
+
let descs = priv.gobj_nodes_tree ? gobj_read_attr(priv.gobj_nodes_tree, "descs") : null;
|
|
19896
|
+
$legend.textContent = "";
|
|
19897
|
+
if (!descs) return;
|
|
19898
|
+
for (const [topic_name, desc] of Object.entries(descs)) {
|
|
19899
|
+
if (topic_name.substring(0, 2) === "__") continue;
|
|
19900
|
+
if (!desc || !desc.color) continue;
|
|
19901
|
+
let focused = priv.focus_topic === topic_name;
|
|
19902
|
+
let $item = createElement2([
|
|
19903
|
+
"button",
|
|
19904
|
+
{
|
|
19905
|
+
class: "GRAPH_LEGEND_ITEM button is-small" + (focused ? " is-primary" : ""),
|
|
19906
|
+
title: topic_name,
|
|
19907
|
+
"aria-label": topic_name,
|
|
19908
|
+
"aria-pressed": focused ? "true" : "false"
|
|
19909
|
+
},
|
|
19910
|
+
[["span", {
|
|
19911
|
+
class: "GRAPH_LEGEND_SWATCH",
|
|
19912
|
+
style: `display:inline-block; width:.8rem; height:.8rem; border-radius:3px; margin-right:.4rem; background:${desc.color}; border:1px solid rgba(0,0,0,.25);`
|
|
19913
|
+
}], [
|
|
19914
|
+
"span",
|
|
19915
|
+
{},
|
|
19916
|
+
topic_name
|
|
19917
|
+
]],
|
|
19918
|
+
{ click: (evt) => {
|
|
19919
|
+
evt.stopPropagation();
|
|
19920
|
+
gobj_send_event(gobj, "EV_LEGEND_TOPIC", { topic: topic_name }, gobj);
|
|
19921
|
+
} }
|
|
19922
|
+
]);
|
|
19923
|
+
$legend.appendChild($item);
|
|
19924
|
+
}
|
|
19925
|
+
}
|
|
19926
|
+
/************************************************************
|
|
19927
|
+
*
|
|
19928
|
+
************************************************************/
|
|
19929
|
+
function ac_toggle_legend(gobj, event, kw, src) {
|
|
19930
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
19931
|
+
if (!$container) return 0;
|
|
19932
|
+
let $legend = $container.querySelector(".GRAPH_LEGEND");
|
|
19933
|
+
if (!$legend) {
|
|
19934
|
+
log_error(`${gobj_short_name(gobj)}: no legend strip to toggle`);
|
|
19935
|
+
return -1;
|
|
19936
|
+
}
|
|
19937
|
+
if ($legend.classList.contains("is-hidden")) {
|
|
19938
|
+
refresh_legend(gobj);
|
|
19939
|
+
$legend.classList.remove("is-hidden");
|
|
19940
|
+
} else $legend.classList.add("is-hidden");
|
|
19941
|
+
return 0;
|
|
19942
|
+
}
|
|
19943
|
+
/************************************************************
|
|
19944
|
+
* A legend entry was clicked: focus that topic, or clear the focus if
|
|
19945
|
+
* it was already the focused one. Published UP rather than applied
|
|
19946
|
+
* here, so the URL follows — the host owns the route.
|
|
19947
|
+
************************************************************/
|
|
19948
|
+
function ac_legend_topic(gobj, event, kw, src) {
|
|
19949
|
+
let priv = gobj.priv;
|
|
19950
|
+
let topic = kw && kw.topic || "";
|
|
19951
|
+
if (!topic) {
|
|
19952
|
+
log_error(`${gobj_short_name(gobj)}: legend click with no topic`);
|
|
19953
|
+
return -1;
|
|
19954
|
+
}
|
|
19955
|
+
gobj_publish_event(gobj, "EV_TOPIC_SELECTED", { topic: priv.focus_topic === topic ? "" : topic });
|
|
19956
|
+
return 0;
|
|
19957
|
+
}
|
|
19958
|
+
/************************************************************
|
|
19844
19959
|
* Forward the find down to the graph child.
|
|
19845
19960
|
************************************************************/
|
|
19846
19961
|
function ac_find_nodes$1(gobj, event, kw, src) {
|
|
@@ -19869,7 +19984,11 @@ function ac_find_result(gobj, event, kw, src) {
|
|
|
19869
19984
|
}
|
|
19870
19985
|
function ac_set_focus_topic(gobj, event, kw, src) {
|
|
19871
19986
|
let priv = gobj.priv;
|
|
19872
|
-
|
|
19987
|
+
priv.focus_topic = kw && kw.topic || "";
|
|
19988
|
+
if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_FOCUS_TOPIC", { topic: priv.focus_topic }, gobj);
|
|
19989
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
19990
|
+
let $legend = $container ? $container.querySelector(".GRAPH_LEGEND") : null;
|
|
19991
|
+
if ($legend && !$legend.classList.contains("is-hidden")) refresh_legend(gobj);
|
|
19873
19992
|
return 0;
|
|
19874
19993
|
}
|
|
19875
19994
|
/************************************************************
|
|
@@ -20037,6 +20156,16 @@ function create_gclass$4(gclass_name) {
|
|
|
20037
20156
|
ac_find_nodes$1,
|
|
20038
20157
|
null
|
|
20039
20158
|
],
|
|
20159
|
+
[
|
|
20160
|
+
"EV_TOGGLE_LEGEND",
|
|
20161
|
+
ac_toggle_legend,
|
|
20162
|
+
null
|
|
20163
|
+
],
|
|
20164
|
+
[
|
|
20165
|
+
"EV_LEGEND_TOPIC",
|
|
20166
|
+
ac_legend_topic,
|
|
20167
|
+
null
|
|
20168
|
+
],
|
|
20040
20169
|
[
|
|
20041
20170
|
"EV_FIND_RESULT",
|
|
20042
20171
|
ac_find_result,
|
|
@@ -20085,6 +20214,9 @@ function create_gclass$4(gclass_name) {
|
|
|
20085
20214
|
["EV_SET_FOCUS_TOPIC", 0],
|
|
20086
20215
|
["EV_FIND_NODES", 0],
|
|
20087
20216
|
["EV_FIND_RESULT", 0],
|
|
20217
|
+
["EV_TOGGLE_LEGEND", 0],
|
|
20218
|
+
["EV_LEGEND_TOPIC", 0],
|
|
20219
|
+
["EV_TOPIC_SELECTED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
|
20088
20220
|
["EV_OPERATION_MODE_CHANGED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
|
20089
20221
|
["EV_RECORD_WRITTEN", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
|
20090
20222
|
["EV_SHOW", 0],
|
|
@@ -52691,6 +52823,7 @@ var attrs_table$1 = [
|
|
|
52691
52823
|
SDATA(data_type_t.DTP_BOOLEAN, "with_gridline", 0, true, "Use gridline plugin"),
|
|
52692
52824
|
SDATA(data_type_t.DTP_BOOLEAN, "with_fullscreen", 0, true, "Use fullscreen plugin"),
|
|
52693
52825
|
SDATA(data_type_t.DTP_BOOLEAN, "with_toolbar", 0, true, "Use toolbar plugin"),
|
|
52826
|
+
SDATA(data_type_t.DTP_INTEGER, "minimap_min_nodes", 0, 30, "Show the minimap from this many nodes on (0 = never). A minimap of a graph that fits on screen is decoration; one of two hundred cards is the only way to know where you are"),
|
|
52694
52827
|
SDATA(data_type_t.DTP_STRING, "toolbar_position", 0, "right-top", "Toolbar position: top-left, top-right, bottom-left, bottom-right, left-top, right-top"),
|
|
52695
52828
|
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)"),
|
|
52696
52829
|
SDATA(data_type_t.DTP_STRING, "hook_port_position", 0, "bottom", "Hook port position"),
|
|
@@ -55530,6 +55663,61 @@ function show_node_detail_popover(gobj, node_id) {
|
|
|
55530
55663
|
* junction diamonds get their neutral fill/stroke re-themed.
|
|
55531
55664
|
************************************************************/
|
|
55532
55665
|
/************************************************************
|
|
55666
|
+
* Show or hide the minimap, deciding by the SIZE of the graph.
|
|
55667
|
+
*
|
|
55668
|
+
* A minimap of a graph that already fits on screen is decoration; one
|
|
55669
|
+
* of two hundred cards is the only way to know where you are. So it is
|
|
55670
|
+
* not a preference the reader has to find and set — it appears when
|
|
55671
|
+
* there is something to be lost in, from `minimap_min_nodes` on.
|
|
55672
|
+
*
|
|
55673
|
+
* Its shapes are drawn by hand. G6's minimap clones each element's key
|
|
55674
|
+
* shape into its own canvas, and every node here is an `html` node
|
|
55675
|
+
* whose key shape is a DOM element — the same reason the `active`
|
|
55676
|
+
* state never painted (see HIGHLIGHT_COLOR). A block in the topic's
|
|
55677
|
+
* colour is also what a minimap of cards SHOULD show: at that scale a
|
|
55678
|
+
* card is a rectangle anyway.
|
|
55679
|
+
************************************************************/
|
|
55680
|
+
function refresh_minimap(gobj) {
|
|
55681
|
+
let graph = gobj.priv.graph;
|
|
55682
|
+
if (!graph) return;
|
|
55683
|
+
let threshold = gobj_read_integer_attr(gobj, "minimap_min_nodes");
|
|
55684
|
+
let n = 0;
|
|
55685
|
+
try {
|
|
55686
|
+
let data = graph.getData() || {};
|
|
55687
|
+
n = is_array(data.nodes) ? data.nodes.length : 0;
|
|
55688
|
+
} catch (e) {
|
|
55689
|
+
n = 0;
|
|
55690
|
+
}
|
|
55691
|
+
if (!threshold || n < threshold) {
|
|
55692
|
+
graph_remove_plugin(gobj, "minimap");
|
|
55693
|
+
return;
|
|
55694
|
+
}
|
|
55695
|
+
if (graph_get_plugin(gobj, "minimap")) return;
|
|
55696
|
+
graph_add_plugin(gobj, "minimap", {
|
|
55697
|
+
size: [200, 140],
|
|
55698
|
+
position: "bottom-left",
|
|
55699
|
+
shape: (id, element_type, element) => {
|
|
55700
|
+
if (element_type !== "node") return element;
|
|
55701
|
+
try {
|
|
55702
|
+
let nd = graph.getNodeData(id);
|
|
55703
|
+
let size = nd && nd.style && nd.style.size || [120, 60];
|
|
55704
|
+
let color = nd && nd.data && nd.data.desc && nd.data.desc.color || "#94a3b8";
|
|
55705
|
+
return new Rect({ style: {
|
|
55706
|
+
x: -size[0] / 2,
|
|
55707
|
+
y: -size[1] / 2,
|
|
55708
|
+
width: size[0],
|
|
55709
|
+
height: size[1],
|
|
55710
|
+
fill: color,
|
|
55711
|
+
radius: 4
|
|
55712
|
+
} });
|
|
55713
|
+
} catch (e) {
|
|
55714
|
+
log_error(`${gobj_short_name(gobj)}: minimap shape failed: ${e}`);
|
|
55715
|
+
return element;
|
|
55716
|
+
}
|
|
55717
|
+
}
|
|
55718
|
+
});
|
|
55719
|
+
}
|
|
55720
|
+
/************************************************************
|
|
55533
55721
|
* The innerHTML of ONE node, in the given theme and highlight state.
|
|
55534
55722
|
* The three treedb tiers (hierarchical / extended / child) each have
|
|
55535
55723
|
* their own card, and both the theme refresh and the highlight need the
|
|
@@ -56326,6 +56514,7 @@ function ac_load_data(gobj, event, kw, src) {
|
|
|
56326
56514
|
graph_layout(gobj).then(() => {
|
|
56327
56515
|
if (priv.edit_mode) graph_add_plugin(gobj, "history");
|
|
56328
56516
|
else graph_remove_plugin(gobj, "history");
|
|
56517
|
+
refresh_minimap(gobj);
|
|
56329
56518
|
if (priv._pending_focus_topic !== null) {
|
|
56330
56519
|
let ft = priv._pending_focus_topic;
|
|
56331
56520
|
priv._pending_focus_topic = null;
|
package/package.json
CHANGED
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
gobj_read_attr,
|
|
32
32
|
gobj_read_str_attr,
|
|
33
33
|
gobj_read_bool_attr,
|
|
34
|
+
gobj_read_integer_attr,
|
|
34
35
|
gobj_write_attr,
|
|
35
36
|
gobj_write_str_attr,
|
|
36
37
|
gobj_parent,
|
|
@@ -93,7 +94,7 @@ import {
|
|
|
93
94
|
register,
|
|
94
95
|
} from '@antv/g6';
|
|
95
96
|
|
|
96
|
-
import {Circle as CircleGeometry} from '@antv/g';
|
|
97
|
+
import {Circle as CircleGeometry, Rect as RectGeometry} from '@antv/g';
|
|
97
98
|
import i18next, {t} from "i18next";
|
|
98
99
|
|
|
99
100
|
import {inject_svg_icons} from "./lib_icons.js";
|
|
@@ -218,6 +219,7 @@ SDATA(data_type_t.DTP_BOOLEAN, "with_treedb_tables", 0, false, "Include tre
|
|
|
218
219
|
SDATA(data_type_t.DTP_BOOLEAN, "with_gridline", 0, true, "Use gridline plugin"),
|
|
219
220
|
SDATA(data_type_t.DTP_BOOLEAN, "with_fullscreen", 0, true, "Use fullscreen plugin"),
|
|
220
221
|
SDATA(data_type_t.DTP_BOOLEAN, "with_toolbar", 0, true, "Use toolbar plugin"),
|
|
222
|
+
SDATA(data_type_t.DTP_INTEGER, "minimap_min_nodes", 0, 30, "Show the minimap from this many nodes on (0 = never). A minimap of a graph that fits on screen is decoration; one of two hundred cards is the only way to know where you are"),
|
|
221
223
|
SDATA(data_type_t.DTP_STRING, "toolbar_position", 0, "right-top",
|
|
222
224
|
"Toolbar position: top-left, top-right, bottom-left, bottom-right, left-top, right-top"),
|
|
223
225
|
SDATA(data_type_t.DTP_LIST, "layout_names", sdata_flag_t.SDF_RD,
|
|
@@ -4481,6 +4483,81 @@ function show_node_detail_popover(gobj, node_id)
|
|
|
4481
4483
|
* not re-themed by setTheme()) are regenerated; structural
|
|
4482
4484
|
* junction diamonds get their neutral fill/stroke re-themed.
|
|
4483
4485
|
************************************************************/
|
|
4486
|
+
/************************************************************
|
|
4487
|
+
* Show or hide the minimap, deciding by the SIZE of the graph.
|
|
4488
|
+
*
|
|
4489
|
+
* A minimap of a graph that already fits on screen is decoration; one
|
|
4490
|
+
* of two hundred cards is the only way to know where you are. So it is
|
|
4491
|
+
* not a preference the reader has to find and set — it appears when
|
|
4492
|
+
* there is something to be lost in, from `minimap_min_nodes` on.
|
|
4493
|
+
*
|
|
4494
|
+
* Its shapes are drawn by hand. G6's minimap clones each element's key
|
|
4495
|
+
* shape into its own canvas, and every node here is an `html` node
|
|
4496
|
+
* whose key shape is a DOM element — the same reason the `active`
|
|
4497
|
+
* state never painted (see HIGHLIGHT_COLOR). A block in the topic's
|
|
4498
|
+
* colour is also what a minimap of cards SHOULD show: at that scale a
|
|
4499
|
+
* card is a rectangle anyway.
|
|
4500
|
+
************************************************************/
|
|
4501
|
+
function refresh_minimap(gobj)
|
|
4502
|
+
{
|
|
4503
|
+
let priv = gobj.priv;
|
|
4504
|
+
let graph = priv.graph;
|
|
4505
|
+
if(!graph) {
|
|
4506
|
+
return;
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
let threshold = gobj_read_integer_attr(gobj, "minimap_min_nodes");
|
|
4510
|
+
let n = 0;
|
|
4511
|
+
try {
|
|
4512
|
+
let data = graph.getData() || {};
|
|
4513
|
+
n = is_array(data.nodes)? data.nodes.length : 0;
|
|
4514
|
+
} catch(e) {
|
|
4515
|
+
n = 0;
|
|
4516
|
+
}
|
|
4517
|
+
|
|
4518
|
+
if(!threshold || n < threshold) {
|
|
4519
|
+
graph_remove_plugin(gobj, "minimap");
|
|
4520
|
+
return;
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
if(graph_get_plugin(gobj, "minimap")) {
|
|
4524
|
+
return; /* already up */
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4527
|
+
graph_add_plugin(
|
|
4528
|
+
gobj,
|
|
4529
|
+
"minimap",
|
|
4530
|
+
{
|
|
4531
|
+
size: [200, 140],
|
|
4532
|
+
position: "bottom-left",
|
|
4533
|
+
shape: (id, element_type, element) => {
|
|
4534
|
+
if(element_type !== "node") {
|
|
4535
|
+
return element; /* edges clone themselves fine */
|
|
4536
|
+
}
|
|
4537
|
+
try {
|
|
4538
|
+
let nd = graph.getNodeData(id);
|
|
4539
|
+
let size = (nd && nd.style && nd.style.size) || [120, 60];
|
|
4540
|
+
let color = (nd && nd.data && nd.data.desc && nd.data.desc.color)
|
|
4541
|
+
|| "#94a3b8";
|
|
4542
|
+
return new RectGeometry({
|
|
4543
|
+
style: {
|
|
4544
|
+
x: -size[0] / 2,
|
|
4545
|
+
y: -size[1] / 2,
|
|
4546
|
+
width: size[0],
|
|
4547
|
+
height: size[1],
|
|
4548
|
+
fill: color,
|
|
4549
|
+
radius: 4
|
|
4550
|
+
}
|
|
4551
|
+
});
|
|
4552
|
+
} catch(e) {
|
|
4553
|
+
log_error(`${gobj_short_name(gobj)}: minimap shape failed: ${e}`);
|
|
4554
|
+
return element;
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
);
|
|
4559
|
+
}
|
|
4560
|
+
|
|
4484
4561
|
/************************************************************
|
|
4485
4562
|
* The innerHTML of ONE node, in the given theme and highlight state.
|
|
4486
4563
|
* The three treedb tiers (hierarchical / extended / child) each have
|
|
@@ -5686,6 +5763,9 @@ function ac_load_data(gobj, event, kw, src)
|
|
|
5686
5763
|
} else {
|
|
5687
5764
|
graph_remove_plugin(gobj, "history");
|
|
5688
5765
|
}
|
|
5766
|
+
/* The graph knows its size now: a minimap appears only
|
|
5767
|
+
* when there is something to be lost in. */
|
|
5768
|
+
refresh_minimap(gobj);
|
|
5689
5769
|
/* Nodes exist and are positioned now: apply a focus
|
|
5690
5770
|
* requested before the data was ready (deep link). */
|
|
5691
5771
|
if(priv._pending_focus_topic !== null) {
|
|
@@ -148,6 +148,7 @@ let PRIVATE_DATA = {
|
|
|
148
148
|
records: {},
|
|
149
149
|
gobj_nodes_tree: null,
|
|
150
150
|
find_timer: null, // rate-limits the find box (see make_toolbar)
|
|
151
|
+
focus_topic: "", // topic the graph is focused on (marks the legend)
|
|
151
152
|
gobj_treedb_tables: null,
|
|
152
153
|
hook_data_viewer: null,
|
|
153
154
|
json_gobj: null, /* C_YUI_JSON viewer of the raw tranger (or null) */
|
|
@@ -354,6 +355,13 @@ function build_ui(gobj)
|
|
|
354
355
|
// Don't use is-flex, don't work well with is-hidden
|
|
355
356
|
['div', {class: 'C_YUI_TREEDB_GRAPH', style: `height:100%; display:flex; flex-direction:column;`}, [
|
|
356
357
|
['div', {class: 'GRAPH_TOOLBAR_ROW is-flex-grow-0 is-flex is-align-items-center'}, row_items],
|
|
358
|
+
/* Topic colour legend: a strip, not an overlay. It is opened to
|
|
359
|
+
* be READ against the graph, and an overlay would cover the
|
|
360
|
+
* thing it explains. Built on first open (the colours are the
|
|
361
|
+
* child's, assigned when the schema arrives). */
|
|
362
|
+
['div', {class: 'GRAPH_LEGEND is-hidden is-flex-grow-0',
|
|
363
|
+
style: 'display:flex; flex-wrap:wrap; align-items:center; ' +
|
|
364
|
+
'gap:.4rem; padding:.35rem .5rem;'}, []],
|
|
357
365
|
['div', {class: `GRAPH_BODY is-flex-grow-1 ${padding}`, style: 'height:100%; min-height:0; overflow:hidden;'}, [
|
|
358
366
|
['div', {id: priv.canvas_id, class: `GRAPH_CANVAS graph-container`, style: 'height:100%; min-height:0;border: 1px solid var(--bulma-border-weak);border-radius:0.2rem;'}, [
|
|
359
367
|
]]
|
|
@@ -496,6 +504,23 @@ function make_toolbar(gobj)
|
|
|
496
504
|
}
|
|
497
505
|
}],
|
|
498
506
|
|
|
507
|
+
/* Which colour is which topic. The port colour of a node encodes
|
|
508
|
+
* the topic it links to, which is the whole point of the graph and
|
|
509
|
+
* which nothing on screen explained. Clicking an entry focuses that
|
|
510
|
+
* topic, so the legend is also the way to say "show me these" —
|
|
511
|
+
* and clicking the focused one again clears it. */
|
|
512
|
+
['button', {class: 'GRAPH_LEGEND_BTN button ml-2',
|
|
513
|
+
title: t('legend'), 'aria-label': t('legend'),
|
|
514
|
+
'data-i18n-title': 'legend', 'data-i18n-aria-label': 'legend'}, [
|
|
515
|
+
['i', {class: 'yi-square'}],
|
|
516
|
+
['span', {class: 'is-hidden-mobile', style: 'padding-left:5px;', i18n: 'legend'}, 'legend']
|
|
517
|
+
], {
|
|
518
|
+
click: (evt) => {
|
|
519
|
+
evt.stopPropagation();
|
|
520
|
+
gobj_send_event(gobj, "EV_TOGGLE_LEGEND", {}, gobj);
|
|
521
|
+
}
|
|
522
|
+
}],
|
|
523
|
+
|
|
499
524
|
/* Inspect the treedb's raw tranger json in the lazy tree viewer
|
|
500
525
|
* (print-tranger on the C_NODE service). A treedb can be huge, so
|
|
501
526
|
* the viewer drills in on demand — see open_json_viewer. */
|
|
@@ -2194,6 +2219,115 @@ function ac_set_operation_mode(gobj, event, kw, src)
|
|
|
2194
2219
|
* tree, which centres + highlights that topic's nodes (deferring
|
|
2195
2220
|
* until its data is loaded).
|
|
2196
2221
|
************************************************************/
|
|
2222
|
+
/************************************************************
|
|
2223
|
+
* Fill the legend strip with one entry per topic: a swatch in the
|
|
2224
|
+
* topic's colour and its name. The colours belong to the graph child —
|
|
2225
|
+
* it assigns them from its palette when the schema arrives — so they
|
|
2226
|
+
* are read from there and never guessed twice.
|
|
2227
|
+
*
|
|
2228
|
+
* An entry is a BUTTON: clicking it focuses that topic, clicking the
|
|
2229
|
+
* focused one clears the focus. The focus travels the same way a topic
|
|
2230
|
+
* card's graph icon travels — as EV_TOPIC_SELECTED, which the host turns
|
|
2231
|
+
* into the URL, so what you are looking at stays linkable.
|
|
2232
|
+
************************************************************/
|
|
2233
|
+
function refresh_legend(gobj)
|
|
2234
|
+
{
|
|
2235
|
+
let priv = gobj.priv;
|
|
2236
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
2237
|
+
if(!$container) {
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
let $legend = $container.querySelector(".GRAPH_LEGEND");
|
|
2241
|
+
if(!$legend) {
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
let descs = priv.gobj_nodes_tree?
|
|
2246
|
+
gobj_read_attr(priv.gobj_nodes_tree, "descs") : null;
|
|
2247
|
+
$legend.textContent = "";
|
|
2248
|
+
if(!descs) {
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
for(const [topic_name, desc] of Object.entries(descs)) {
|
|
2253
|
+
if(topic_name.substring(0, 2) === "__") {
|
|
2254
|
+
continue; /* the internal topics are not drawn */
|
|
2255
|
+
}
|
|
2256
|
+
if(!desc || !desc.color) {
|
|
2257
|
+
continue;
|
|
2258
|
+
}
|
|
2259
|
+
let focused = (priv.focus_topic === topic_name);
|
|
2260
|
+
let $item = createElement2(
|
|
2261
|
+
['button', {
|
|
2262
|
+
class: 'GRAPH_LEGEND_ITEM button is-small' +
|
|
2263
|
+
(focused? ' is-primary' : ''),
|
|
2264
|
+
title: topic_name,
|
|
2265
|
+
'aria-label': topic_name,
|
|
2266
|
+
'aria-pressed': focused? 'true' : 'false'
|
|
2267
|
+
}, [
|
|
2268
|
+
['span', {class: 'GRAPH_LEGEND_SWATCH',
|
|
2269
|
+
style: `display:inline-block; width:.8rem; height:.8rem; ` +
|
|
2270
|
+
`border-radius:3px; margin-right:.4rem; ` +
|
|
2271
|
+
`background:${desc.color}; ` +
|
|
2272
|
+
`border:1px solid rgba(0,0,0,.25);`}],
|
|
2273
|
+
/* The topic name is DATA: it carries no i18n key and is
|
|
2274
|
+
* never translated. */
|
|
2275
|
+
['span', {}, topic_name]
|
|
2276
|
+
], {
|
|
2277
|
+
click: (evt) => {
|
|
2278
|
+
evt.stopPropagation();
|
|
2279
|
+
gobj_send_event(gobj, "EV_LEGEND_TOPIC", {topic: topic_name}, gobj);
|
|
2280
|
+
}
|
|
2281
|
+
}]
|
|
2282
|
+
);
|
|
2283
|
+
$legend.appendChild($item);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
/************************************************************
|
|
2288
|
+
*
|
|
2289
|
+
************************************************************/
|
|
2290
|
+
function ac_toggle_legend(gobj, event, kw, src)
|
|
2291
|
+
{
|
|
2292
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
2293
|
+
if(!$container) {
|
|
2294
|
+
return 0;
|
|
2295
|
+
}
|
|
2296
|
+
let $legend = $container.querySelector(".GRAPH_LEGEND");
|
|
2297
|
+
if(!$legend) {
|
|
2298
|
+
log_error(`${gobj_short_name(gobj)}: no legend strip to toggle`);
|
|
2299
|
+
return -1;
|
|
2300
|
+
}
|
|
2301
|
+
if($legend.classList.contains("is-hidden")) {
|
|
2302
|
+
refresh_legend(gobj);
|
|
2303
|
+
$legend.classList.remove("is-hidden");
|
|
2304
|
+
} else {
|
|
2305
|
+
$legend.classList.add("is-hidden");
|
|
2306
|
+
}
|
|
2307
|
+
return 0;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
/************************************************************
|
|
2311
|
+
* A legend entry was clicked: focus that topic, or clear the focus if
|
|
2312
|
+
* it was already the focused one. Published UP rather than applied
|
|
2313
|
+
* here, so the URL follows — the host owns the route.
|
|
2314
|
+
************************************************************/
|
|
2315
|
+
function ac_legend_topic(gobj, event, kw, src)
|
|
2316
|
+
{
|
|
2317
|
+
let priv = gobj.priv;
|
|
2318
|
+
let topic = (kw && kw.topic) || "";
|
|
2319
|
+
if(!topic) {
|
|
2320
|
+
log_error(`${gobj_short_name(gobj)}: legend click with no topic`);
|
|
2321
|
+
return -1;
|
|
2322
|
+
}
|
|
2323
|
+
gobj_publish_event(
|
|
2324
|
+
gobj,
|
|
2325
|
+
"EV_TOPIC_SELECTED",
|
|
2326
|
+
{topic: (priv.focus_topic === topic)? "" : topic}
|
|
2327
|
+
);
|
|
2328
|
+
return 0;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2197
2331
|
/************************************************************
|
|
2198
2332
|
* Forward the find down to the graph child.
|
|
2199
2333
|
************************************************************/
|
|
@@ -2242,14 +2376,22 @@ function ac_find_result(gobj, event, kw, src)
|
|
|
2242
2376
|
function ac_set_focus_topic(gobj, event, kw, src)
|
|
2243
2377
|
{
|
|
2244
2378
|
let priv = gobj.priv;
|
|
2379
|
+
priv.focus_topic = (kw && kw.topic) || "";
|
|
2245
2380
|
if(priv.gobj_nodes_tree) {
|
|
2246
2381
|
gobj_send_event(
|
|
2247
2382
|
priv.gobj_nodes_tree,
|
|
2248
2383
|
"EV_FOCUS_TOPIC",
|
|
2249
|
-
{topic:
|
|
2384
|
+
{topic: priv.focus_topic},
|
|
2250
2385
|
gobj
|
|
2251
2386
|
);
|
|
2252
2387
|
}
|
|
2388
|
+
/* Keep the legend's mark on the topic that is actually focused —
|
|
2389
|
+
* including when the focus arrives from the URL and not from a click. */
|
|
2390
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
2391
|
+
let $legend = $container? $container.querySelector(".GRAPH_LEGEND") : null;
|
|
2392
|
+
if($legend && !$legend.classList.contains("is-hidden")) {
|
|
2393
|
+
refresh_legend(gobj);
|
|
2394
|
+
}
|
|
2253
2395
|
return 0;
|
|
2254
2396
|
}
|
|
2255
2397
|
|
|
@@ -2339,6 +2481,8 @@ function create_gclass(gclass_name)
|
|
|
2339
2481
|
["EV_SET_OPERATION_MODE", ac_set_operation_mode, null],
|
|
2340
2482
|
["EV_SET_FOCUS_TOPIC", ac_set_focus_topic, null],
|
|
2341
2483
|
["EV_FIND_NODES", ac_find_nodes, null],
|
|
2484
|
+
["EV_TOGGLE_LEGEND", ac_toggle_legend, null],
|
|
2485
|
+
["EV_LEGEND_TOPIC", ac_legend_topic, null],
|
|
2342
2486
|
["EV_FIND_RESULT", ac_find_result, null],
|
|
2343
2487
|
["EV_SHOW", ac_show, null],
|
|
2344
2488
|
["EV_HIDE", ac_hide, null],
|
|
@@ -2376,6 +2520,10 @@ function create_gclass(gclass_name)
|
|
|
2376
2520
|
["EV_SET_FOCUS_TOPIC", 0],
|
|
2377
2521
|
["EV_FIND_NODES", 0],
|
|
2378
2522
|
["EV_FIND_RESULT", 0],
|
|
2523
|
+
["EV_TOGGLE_LEGEND", 0],
|
|
2524
|
+
["EV_LEGEND_TOPIC", 0],
|
|
2525
|
+
["EV_TOPIC_SELECTED",
|
|
2526
|
+
event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
|
2379
2527
|
["EV_OPERATION_MODE_CHANGED",
|
|
2380
2528
|
event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
|
2381
2529
|
["EV_RECORD_WRITTEN",
|