@yuneta/gobj-ui 7.3.0 → 7.4.1

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.
@@ -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
- "div",
18698
- { class: "GRAPH_TOOLBAR_ROW is-flex-grow-0 is-flex is-align-items-center" },
18699
- $back ? [$back, $toolbar] : [$toolbar]
18700
- ], [
18701
- "div",
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
- id: priv.canvas_id,
18710
- class: `GRAPH_CANVAS graph-container`,
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,88 @@ 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
+ let next = priv.focus_topic === topic ? "" : topic;
19969
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SET_FOCUS_TOPIC", { topic: next }, gobj);
19970
+ (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_TOPIC_SELECTED", { topic: next });
19971
+ return 0;
19972
+ }
19973
+ /************************************************************
19857
19974
  * Forward the find down to the graph child.
19858
19975
  ************************************************************/
19859
19976
  function ac_find_nodes$1(gobj, event, kw, src) {
@@ -19882,7 +19999,11 @@ function ac_find_result(gobj, event, kw, src) {
19882
19999
  }
19883
20000
  function ac_set_focus_topic(gobj, event, kw, src) {
19884
20001
  let priv = gobj.priv;
19885
- if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_FOCUS_TOPIC", { topic: kw && kw.topic }, gobj);
20002
+ priv.focus_topic = kw && kw.topic || "";
20003
+ if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_FOCUS_TOPIC", { topic: priv.focus_topic }, gobj);
20004
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
20005
+ let $legend = $container ? $container.querySelector(".GRAPH_LEGEND") : null;
20006
+ if ($legend && !$legend.classList.contains("is-hidden")) refresh_legend(gobj);
19886
20007
  return 0;
19887
20008
  }
19888
20009
  /************************************************************
@@ -20050,6 +20171,16 @@ function create_gclass$4(gclass_name) {
20050
20171
  ac_find_nodes$1,
20051
20172
  null
20052
20173
  ],
20174
+ [
20175
+ "EV_TOGGLE_LEGEND",
20176
+ ac_toggle_legend,
20177
+ null
20178
+ ],
20179
+ [
20180
+ "EV_LEGEND_TOPIC",
20181
+ ac_legend_topic,
20182
+ null
20183
+ ],
20053
20184
  [
20054
20185
  "EV_FIND_RESULT",
20055
20186
  ac_find_result,
@@ -20098,6 +20229,9 @@ function create_gclass$4(gclass_name) {
20098
20229
  ["EV_SET_FOCUS_TOPIC", 0],
20099
20230
  ["EV_FIND_NODES", 0],
20100
20231
  ["EV_FIND_RESULT", 0],
20232
+ ["EV_TOGGLE_LEGEND", 0],
20233
+ ["EV_LEGEND_TOPIC", 0],
20234
+ ["EV_TOPIC_SELECTED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
20101
20235
  ["EV_OPERATION_MODE_CHANGED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
20102
20236
  ["EV_RECORD_WRITTEN", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
20103
20237
  ["EV_SHOW", 0],
@@ -52704,6 +52838,7 @@ var attrs_table$1 = [
52704
52838
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_gridline", 0, true, "Use gridline plugin"),
52705
52839
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_fullscreen", 0, true, "Use fullscreen plugin"),
52706
52840
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_toolbar", 0, true, "Use toolbar plugin"),
52841
+ (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
52842
  (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
52843
  (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
52844
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "hook_port_position", 0, "bottom", "Hook port position"),
@@ -55543,6 +55678,61 @@ function show_node_detail_popover(gobj, node_id) {
55543
55678
  * junction diamonds get their neutral fill/stroke re-themed.
55544
55679
  ************************************************************/
55545
55680
  /************************************************************
55681
+ * Show or hide the minimap, deciding by the SIZE of the graph.
55682
+ *
55683
+ * A minimap of a graph that already fits on screen is decoration; one
55684
+ * of two hundred cards is the only way to know where you are. So it is
55685
+ * not a preference the reader has to find and set — it appears when
55686
+ * there is something to be lost in, from `minimap_min_nodes` on.
55687
+ *
55688
+ * Its shapes are drawn by hand. G6's minimap clones each element's key
55689
+ * shape into its own canvas, and every node here is an `html` node
55690
+ * whose key shape is a DOM element — the same reason the `active`
55691
+ * state never painted (see HIGHLIGHT_COLOR). A block in the topic's
55692
+ * colour is also what a minimap of cards SHOULD show: at that scale a
55693
+ * card is a rectangle anyway.
55694
+ ************************************************************/
55695
+ function refresh_minimap(gobj) {
55696
+ let graph = gobj.priv.graph;
55697
+ if (!graph) return;
55698
+ let threshold = (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "minimap_min_nodes");
55699
+ let n = 0;
55700
+ try {
55701
+ let data = graph.getData() || {};
55702
+ n = (0, _yuneta_gobj_js.is_array)(data.nodes) ? data.nodes.length : 0;
55703
+ } catch (e) {
55704
+ n = 0;
55705
+ }
55706
+ if (!threshold || n < threshold) {
55707
+ graph_remove_plugin(gobj, "minimap");
55708
+ return;
55709
+ }
55710
+ if (graph_get_plugin(gobj, "minimap")) return;
55711
+ graph_add_plugin(gobj, "minimap", {
55712
+ size: [200, 140],
55713
+ position: "bottom-left",
55714
+ shape: (id, element_type, element) => {
55715
+ if (element_type !== "node") return element;
55716
+ try {
55717
+ let nd = graph.getNodeData(id);
55718
+ let size = nd && nd.style && nd.style.size || [120, 60];
55719
+ let color = nd && nd.data && nd.data.desc && nd.data.desc.color || "#94a3b8";
55720
+ return new Rect({ style: {
55721
+ x: -size[0] / 2,
55722
+ y: -size[1] / 2,
55723
+ width: size[0],
55724
+ height: size[1],
55725
+ fill: color,
55726
+ radius: 4
55727
+ } });
55728
+ } catch (e) {
55729
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: minimap shape failed: ${e}`);
55730
+ return element;
55731
+ }
55732
+ }
55733
+ });
55734
+ }
55735
+ /************************************************************
55546
55736
  * The innerHTML of ONE node, in the given theme and highlight state.
55547
55737
  * The three treedb tiers (hierarchical / extended / child) each have
55548
55738
  * their own card, and both the theme refresh and the highlight need the
@@ -56339,6 +56529,7 @@ function ac_load_data(gobj, event, kw, src) {
56339
56529
  graph_layout(gobj).then(() => {
56340
56530
  if (priv.edit_mode) graph_add_plugin(gobj, "history");
56341
56531
  else graph_remove_plugin(gobj, "history");
56532
+ refresh_minimap(gobj);
56342
56533
  if (priv._pending_focus_topic !== null) {
56343
56534
  let ft = priv._pending_focus_topic;
56344
56535
  priv._pending_focus_topic = null;
@@ -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
- "div",
18685
- { class: "GRAPH_TOOLBAR_ROW is-flex-grow-0 is-flex is-align-items-center" },
18686
- $back ? [$back, $toolbar] : [$toolbar]
18687
- ], [
18688
- "div",
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
- id: priv.canvas_id,
18697
- class: `GRAPH_CANVAS graph-container`,
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,88 @@ 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
+ let next = priv.focus_topic === topic ? "" : topic;
19956
+ gobj_send_event(gobj, "EV_SET_FOCUS_TOPIC", { topic: next }, gobj);
19957
+ gobj_publish_event(gobj, "EV_TOPIC_SELECTED", { topic: next });
19958
+ return 0;
19959
+ }
19960
+ /************************************************************
19844
19961
  * Forward the find down to the graph child.
19845
19962
  ************************************************************/
19846
19963
  function ac_find_nodes$1(gobj, event, kw, src) {
@@ -19869,7 +19986,11 @@ function ac_find_result(gobj, event, kw, src) {
19869
19986
  }
19870
19987
  function ac_set_focus_topic(gobj, event, kw, src) {
19871
19988
  let priv = gobj.priv;
19872
- if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_FOCUS_TOPIC", { topic: kw && kw.topic }, gobj);
19989
+ priv.focus_topic = kw && kw.topic || "";
19990
+ if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_FOCUS_TOPIC", { topic: priv.focus_topic }, gobj);
19991
+ let $container = gobj_read_attr(gobj, "$container");
19992
+ let $legend = $container ? $container.querySelector(".GRAPH_LEGEND") : null;
19993
+ if ($legend && !$legend.classList.contains("is-hidden")) refresh_legend(gobj);
19873
19994
  return 0;
19874
19995
  }
19875
19996
  /************************************************************
@@ -20037,6 +20158,16 @@ function create_gclass$4(gclass_name) {
20037
20158
  ac_find_nodes$1,
20038
20159
  null
20039
20160
  ],
20161
+ [
20162
+ "EV_TOGGLE_LEGEND",
20163
+ ac_toggle_legend,
20164
+ null
20165
+ ],
20166
+ [
20167
+ "EV_LEGEND_TOPIC",
20168
+ ac_legend_topic,
20169
+ null
20170
+ ],
20040
20171
  [
20041
20172
  "EV_FIND_RESULT",
20042
20173
  ac_find_result,
@@ -20085,6 +20216,9 @@ function create_gclass$4(gclass_name) {
20085
20216
  ["EV_SET_FOCUS_TOPIC", 0],
20086
20217
  ["EV_FIND_NODES", 0],
20087
20218
  ["EV_FIND_RESULT", 0],
20219
+ ["EV_TOGGLE_LEGEND", 0],
20220
+ ["EV_LEGEND_TOPIC", 0],
20221
+ ["EV_TOPIC_SELECTED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
20088
20222
  ["EV_OPERATION_MODE_CHANGED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
20089
20223
  ["EV_RECORD_WRITTEN", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
20090
20224
  ["EV_SHOW", 0],
@@ -52691,6 +52825,7 @@ var attrs_table$1 = [
52691
52825
  SDATA(data_type_t.DTP_BOOLEAN, "with_gridline", 0, true, "Use gridline plugin"),
52692
52826
  SDATA(data_type_t.DTP_BOOLEAN, "with_fullscreen", 0, true, "Use fullscreen plugin"),
52693
52827
  SDATA(data_type_t.DTP_BOOLEAN, "with_toolbar", 0, true, "Use toolbar plugin"),
52828
+ 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
52829
  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
52830
  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
52831
  SDATA(data_type_t.DTP_STRING, "hook_port_position", 0, "bottom", "Hook port position"),
@@ -55530,6 +55665,61 @@ function show_node_detail_popover(gobj, node_id) {
55530
55665
  * junction diamonds get their neutral fill/stroke re-themed.
55531
55666
  ************************************************************/
55532
55667
  /************************************************************
55668
+ * Show or hide the minimap, deciding by the SIZE of the graph.
55669
+ *
55670
+ * A minimap of a graph that already fits on screen is decoration; one
55671
+ * of two hundred cards is the only way to know where you are. So it is
55672
+ * not a preference the reader has to find and set — it appears when
55673
+ * there is something to be lost in, from `minimap_min_nodes` on.
55674
+ *
55675
+ * Its shapes are drawn by hand. G6's minimap clones each element's key
55676
+ * shape into its own canvas, and every node here is an `html` node
55677
+ * whose key shape is a DOM element — the same reason the `active`
55678
+ * state never painted (see HIGHLIGHT_COLOR). A block in the topic's
55679
+ * colour is also what a minimap of cards SHOULD show: at that scale a
55680
+ * card is a rectangle anyway.
55681
+ ************************************************************/
55682
+ function refresh_minimap(gobj) {
55683
+ let graph = gobj.priv.graph;
55684
+ if (!graph) return;
55685
+ let threshold = gobj_read_integer_attr(gobj, "minimap_min_nodes");
55686
+ let n = 0;
55687
+ try {
55688
+ let data = graph.getData() || {};
55689
+ n = is_array(data.nodes) ? data.nodes.length : 0;
55690
+ } catch (e) {
55691
+ n = 0;
55692
+ }
55693
+ if (!threshold || n < threshold) {
55694
+ graph_remove_plugin(gobj, "minimap");
55695
+ return;
55696
+ }
55697
+ if (graph_get_plugin(gobj, "minimap")) return;
55698
+ graph_add_plugin(gobj, "minimap", {
55699
+ size: [200, 140],
55700
+ position: "bottom-left",
55701
+ shape: (id, element_type, element) => {
55702
+ if (element_type !== "node") return element;
55703
+ try {
55704
+ let nd = graph.getNodeData(id);
55705
+ let size = nd && nd.style && nd.style.size || [120, 60];
55706
+ let color = nd && nd.data && nd.data.desc && nd.data.desc.color || "#94a3b8";
55707
+ return new Rect({ style: {
55708
+ x: -size[0] / 2,
55709
+ y: -size[1] / 2,
55710
+ width: size[0],
55711
+ height: size[1],
55712
+ fill: color,
55713
+ radius: 4
55714
+ } });
55715
+ } catch (e) {
55716
+ log_error(`${gobj_short_name(gobj)}: minimap shape failed: ${e}`);
55717
+ return element;
55718
+ }
55719
+ }
55720
+ });
55721
+ }
55722
+ /************************************************************
55533
55723
  * The innerHTML of ONE node, in the given theme and highlight state.
55534
55724
  * The three treedb tiers (hierarchical / extended / child) each have
55535
55725
  * their own card, and both the theme refresh and the highlight need the
@@ -56326,6 +56516,7 @@ function ac_load_data(gobj, event, kw, src) {
56326
56516
  graph_layout(gobj).then(() => {
56327
56517
  if (priv.edit_mode) graph_add_plugin(gobj, "history");
56328
56518
  else graph_remove_plugin(gobj, "history");
56519
+ refresh_minimap(gobj);
56329
56520
  if (priv._pending_focus_topic !== null) {
56330
56521
  let ft = priv._pending_focus_topic;
56331
56522
  priv._pending_focus_topic = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.3.0",
3
+ "version": "7.4.1",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -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,121 @@ 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
+
2324
+ let next = (priv.focus_topic === topic)? "" : topic;
2325
+
2326
+ /* APPLY, then announce — in that order, and both halves are needed.
2327
+ * The host mirrors the announcement into the URL and remembers the
2328
+ * segment it just wrote, so the route change it causes comes back
2329
+ * DEDUPED: a legend that only announced moved the URL and focused
2330
+ * nothing. This is the same contract the topics view keeps, where
2331
+ * clicking a topic shows it and says so. */
2332
+ gobj_send_event(gobj, "EV_SET_FOCUS_TOPIC", {topic: next}, gobj);
2333
+ gobj_publish_event(gobj, "EV_TOPIC_SELECTED", {topic: next});
2334
+ return 0;
2335
+ }
2336
+
2197
2337
  /************************************************************
2198
2338
  * Forward the find down to the graph child.
2199
2339
  ************************************************************/
@@ -2242,14 +2382,22 @@ function ac_find_result(gobj, event, kw, src)
2242
2382
  function ac_set_focus_topic(gobj, event, kw, src)
2243
2383
  {
2244
2384
  let priv = gobj.priv;
2385
+ priv.focus_topic = (kw && kw.topic) || "";
2245
2386
  if(priv.gobj_nodes_tree) {
2246
2387
  gobj_send_event(
2247
2388
  priv.gobj_nodes_tree,
2248
2389
  "EV_FOCUS_TOPIC",
2249
- {topic: kw && kw.topic},
2390
+ {topic: priv.focus_topic},
2250
2391
  gobj
2251
2392
  );
2252
2393
  }
2394
+ /* Keep the legend's mark on the topic that is actually focused —
2395
+ * including when the focus arrives from the URL and not from a click. */
2396
+ let $container = gobj_read_attr(gobj, "$container");
2397
+ let $legend = $container? $container.querySelector(".GRAPH_LEGEND") : null;
2398
+ if($legend && !$legend.classList.contains("is-hidden")) {
2399
+ refresh_legend(gobj);
2400
+ }
2253
2401
  return 0;
2254
2402
  }
2255
2403
 
@@ -2339,6 +2487,8 @@ function create_gclass(gclass_name)
2339
2487
  ["EV_SET_OPERATION_MODE", ac_set_operation_mode, null],
2340
2488
  ["EV_SET_FOCUS_TOPIC", ac_set_focus_topic, null],
2341
2489
  ["EV_FIND_NODES", ac_find_nodes, null],
2490
+ ["EV_TOGGLE_LEGEND", ac_toggle_legend, null],
2491
+ ["EV_LEGEND_TOPIC", ac_legend_topic, null],
2342
2492
  ["EV_FIND_RESULT", ac_find_result, null],
2343
2493
  ["EV_SHOW", ac_show, null],
2344
2494
  ["EV_HIDE", ac_hide, null],
@@ -2376,6 +2526,10 @@ function create_gclass(gclass_name)
2376
2526
  ["EV_SET_FOCUS_TOPIC", 0],
2377
2527
  ["EV_FIND_NODES", 0],
2378
2528
  ["EV_FIND_RESULT", 0],
2529
+ ["EV_TOGGLE_LEGEND", 0],
2530
+ ["EV_LEGEND_TOPIC", 0],
2531
+ ["EV_TOPIC_SELECTED",
2532
+ event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
2379
2533
  ["EV_OPERATION_MODE_CHANGED",
2380
2534
  event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
2381
2535
  ["EV_RECORD_WRITTEN",