@yuneta/gobj-ui 7.0.1 → 7.2.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.
@@ -11012,7 +11012,7 @@ function ac_expand_collapsed(gobj, event, kw, src) {
11012
11012
  /************************************************************
11013
11013
  * EV_SEARCH { text }
11014
11014
  ************************************************************/
11015
- function ac_search(gobj, event, kw, src) {
11015
+ function ac_search$1(gobj, event, kw, src) {
11016
11016
  let priv = gobj.priv;
11017
11017
  priv.search = (kw.text || "").trim().toLowerCase();
11018
11018
  render_tree(gobj);
@@ -11161,7 +11161,7 @@ function create_gclass$10(gclass_name) {
11161
11161
  ],
11162
11162
  [
11163
11163
  "EV_SEARCH",
11164
- ac_search,
11164
+ ac_search$1,
11165
11165
  null
11166
11166
  ],
11167
11167
  [
@@ -16217,6 +16217,9 @@ var attrs_table$5 = [
16217
16217
  SDATA(data_type_t.DTP_BOOLEAN, "with_refresh_button", 0, true, "Button toolbar REFRESH"),
16218
16218
  SDATA(data_type_t.DTP_BOOLEAN, "with_search_button", 0, true, "Button toolbar SEARCH"),
16219
16219
  SDATA(data_type_t.DTP_BOOLEAN, "with_schema_button", 0, true, "Button toolbar SCHEMA (show the topic's desc)"),
16220
+ SDATA(data_type_t.DTP_BOOLEAN, "with_columns_button", 0, true, "Button toolbar COLUMNS (choose which columns the table shows)"),
16221
+ SDATA(data_type_t.DTP_BOOLEAN, "with_export_button", 0, true, "Button toolbar EXPORT (download as CSV what the table holds)"),
16222
+ SDATA(data_type_t.DTP_BOOLEAN, "with_header_filters", 0, true, "Per-column filter box in the table header, on the columns a text/number match means something (not hooks, fkeys or json)"),
16220
16223
  SDATA(data_type_t.DTP_BOOLEAN, "with_in_row_edit_icons", 0, true, "Add a last column with internal EDIT/DELETE icon"),
16221
16224
  SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"),
16222
16225
  SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, true, "Auxiliary first column to select rows"),
@@ -16509,6 +16512,8 @@ function build_ui$5(gobj) {
16509
16512
  let with_refresh_button = gobj_read_bool_attr(gobj, "with_refresh_button");
16510
16513
  let with_search_button = gobj_read_bool_attr(gobj, "with_search_button");
16511
16514
  let with_schema_button = gobj_read_bool_attr(gobj, "with_schema_button");
16515
+ let with_columns_button = gobj_read_bool_attr(gobj, "with_columns_button");
16516
+ let with_export_button = gobj_read_bool_attr(gobj, "with_export_button");
16512
16517
  if (with_search_button) {
16513
16518
  let search_id = `${toolbar_id}_search`;
16514
16519
  let $search_box = createElement2([
@@ -16529,17 +16534,7 @@ function build_ui$5(gobj) {
16529
16534
  let $search_input = $search_box.querySelector(`#${search_id}`);
16530
16535
  attach_clear($search_box, $search_input);
16531
16536
  if ($search_input) $search_input.addEventListener("input", (event) => {
16532
- let searchText = event.target.value.trim().toLowerCase();
16533
- let tabulator = gobj_read_attr(gobj, "tabulator");
16534
- if (!tabulator) return;
16535
- if (searchText) tabulator.setFilter(function(data) {
16536
- return Object.entries(data).some(([key, val]) => {
16537
- if (key.startsWith("_")) return false;
16538
- if (val === null || val === void 0) return false;
16539
- return String(val).toLowerCase().includes(searchText);
16540
- });
16541
- });
16542
- else tabulator.clearFilter();
16537
+ gobj_send_event(gobj, "EV_SEARCH", { text: event.target.value.trim() }, gobj);
16543
16538
  });
16544
16539
  $view_toolbar.appendChild($search_box);
16545
16540
  }
@@ -16593,6 +16588,58 @@ function build_ui$5(gobj) {
16593
16588
  ]);
16594
16589
  $view_toolbar.appendChild($schema);
16595
16590
  }
16591
+ if (with_columns_button) {
16592
+ let $columns = createElement2([
16593
+ "button",
16594
+ {
16595
+ class: "TREEDB_TABLE_COLUMNS button mr-1",
16596
+ title: t("choose the columns to show"),
16597
+ "data-i18n-title": "choose the columns to show",
16598
+ "aria-label": t("columns"),
16599
+ "data-i18n-aria-label": "columns"
16600
+ },
16601
+ [["i", { class: "yi-table" }], [
16602
+ "span",
16603
+ {
16604
+ class: "is-hidden-mobile",
16605
+ i18n: "columns",
16606
+ style: "padding-left:5px;"
16607
+ },
16608
+ "columns"
16609
+ ]],
16610
+ { "click": (event) => {
16611
+ event.stopPropagation();
16612
+ gobj_send_event(gobj, "EV_OPEN_COLUMNS", {}, gobj);
16613
+ } }
16614
+ ]);
16615
+ $view_toolbar.appendChild($columns);
16616
+ }
16617
+ if (with_export_button) {
16618
+ let $export = createElement2([
16619
+ "button",
16620
+ {
16621
+ class: "TREEDB_TABLE_EXPORT button mr-1",
16622
+ title: t("download the rows loaded in this table as csv"),
16623
+ "data-i18n-title": "download the rows loaded in this table as csv",
16624
+ "aria-label": t("export"),
16625
+ "data-i18n-aria-label": "export"
16626
+ },
16627
+ [["i", { class: "yi-download" }], [
16628
+ "span",
16629
+ {
16630
+ class: "is-hidden-mobile",
16631
+ i18n: "export",
16632
+ style: "padding-left:5px;"
16633
+ },
16634
+ "export"
16635
+ ]],
16636
+ { "click": (event) => {
16637
+ event.stopPropagation();
16638
+ gobj_send_event(gobj, "EV_EXPORT_TABLE", {}, gobj);
16639
+ } }
16640
+ ]);
16641
+ $view_toolbar.appendChild($export);
16642
+ }
16596
16643
  let table_id = gobj_read_str_attr(gobj, "table_id");
16597
16644
  let $container = createElement2([
16598
16645
  "div",
@@ -16681,6 +16728,7 @@ function col_title_formatter(gobj, col) {
16681
16728
  function create_tabulator(gobj) {
16682
16729
  let table_id = gobj_read_str_attr(gobj, "table_id");
16683
16730
  let desc = gobj_read_attr(gobj, "desc");
16731
+ let with_header_filters = gobj_read_bool_attr(gobj, "with_header_filters");
16684
16732
  let columns = [];
16685
16733
  let with_checkbox = gobj_read_bool_attr(gobj, "with_checkbox");
16686
16734
  let with_radio = gobj_read_bool_attr(gobj, "with_radio");
@@ -16724,6 +16772,46 @@ function create_tabulator(gobj) {
16724
16772
  col_id: cell.getField()
16725
16773
  }, gobj);
16726
16774
  }
16775
+ const FILTERABLE = [
16776
+ "string",
16777
+ "integer",
16778
+ "real",
16779
+ "rowid",
16780
+ "uuid",
16781
+ "qualified",
16782
+ "email",
16783
+ "url",
16784
+ "tel",
16785
+ "id",
16786
+ "hex",
16787
+ "currency",
16788
+ "percent"
16789
+ ];
16790
+ function fkey_matches(term, value) {
16791
+ if (term === "" || term === null || term === void 0) return true;
16792
+ if (value === null || value === void 0) return false;
16793
+ return (typeof value === "string" ? value : JSON.stringify(value)).toLowerCase().includes(String(term).toLowerCase());
16794
+ }
16795
+ function apply_header_filter(colDef, field_desc) {
16796
+ switch (field_desc.type) {
16797
+ case "boolean":
16798
+ colDef.headerFilter = "tickCross";
16799
+ colDef.headerFilterParams = { tristate: true };
16800
+ break;
16801
+ case "enum":
16802
+ colDef.headerFilter = "list";
16803
+ colDef.headerFilterParams = {
16804
+ values: field_desc.enum_list || [],
16805
+ clearable: true
16806
+ };
16807
+ break;
16808
+ case "fkey":
16809
+ colDef.headerFilter = "input";
16810
+ colDef.headerFilterFunc = fkey_matches;
16811
+ break;
16812
+ default: if (FILTERABLE.includes(field_desc.type)) colDef.headerFilter = "input";
16813
+ }
16814
+ }
16727
16815
  for (let i = 0; i < desc.cols.length; i++) {
16728
16816
  let col = desc.cols[i];
16729
16817
  if (!col.id || col.id[0] === "_") continue;
@@ -16804,6 +16892,7 @@ function create_tabulator(gobj) {
16804
16892
  hozAlign,
16805
16893
  formatter: colFormatter
16806
16894
  };
16895
+ if (with_header_filters) apply_header_filter(colDef, field_desc);
16807
16896
  if (vertAlign) colDef.vertAlign = vertAlign;
16808
16897
  if (formatterParams) colDef.formatterParams = formatterParams;
16809
16898
  if (cellClick) colDef.cellClick = cellClick;
@@ -18013,6 +18102,125 @@ function ac_show_schema(gobj, event, kw, src) {
18013
18102
  return 0;
18014
18103
  }
18015
18104
  /************************************************************
18105
+ * Filter the loaded rows by a term matched against every non-internal
18106
+ * field. `clearFilter()` with no argument drops only THIS filter: the
18107
+ * per-column header filters are a separate layer and survive, so a
18108
+ * cleared search box does not silently undo them.
18109
+ ************************************************************/
18110
+ function ac_search(gobj, event, kw, src) {
18111
+ let tabulator = gobj_read_attr(gobj, "tabulator");
18112
+ if (!tabulator) {
18113
+ log_error(`${gobj_short_name(gobj)}: no table to search`);
18114
+ return -1;
18115
+ }
18116
+ let term = (kw && kw.text || "").toLowerCase();
18117
+ if (!term) {
18118
+ tabulator.clearFilter();
18119
+ return 0;
18120
+ }
18121
+ tabulator.setFilter(function(data) {
18122
+ return Object.entries(data).some(([key, val]) => {
18123
+ if (key.startsWith("_")) return false;
18124
+ if (val === null || val === void 0) return false;
18125
+ return String(val).toLowerCase().includes(term);
18126
+ });
18127
+ });
18128
+ return 0;
18129
+ }
18130
+ /************************************************************
18131
+ * Which columns the table shows: one checkbox per column, the current
18132
+ * visibility ticked. The internal columns (`_check_box_state_`,
18133
+ * `_operation`) are not offered — they are chrome, not data.
18134
+ ************************************************************/
18135
+ function ac_open_columns(gobj, event, kw, src) {
18136
+ let tabulator = gobj_read_attr(gobj, "tabulator");
18137
+ if (!tabulator) {
18138
+ log_error(`${gobj_short_name(gobj)}: no table, no columns to choose`);
18139
+ return -1;
18140
+ }
18141
+ let shell = yui_shell_of(gobj);
18142
+ if (!shell) {
18143
+ log_error(`${gobj_short_name(gobj)}: no shell, cannot open the column chooser`);
18144
+ return -1;
18145
+ }
18146
+ let $list = createElement2(["div", { class: "TREEDB_COLUMNS_LIST" }]);
18147
+ let columns = tabulator.getColumns();
18148
+ for (let i = 0; i < columns.length; i++) {
18149
+ let column = columns[i];
18150
+ let field = column.getField();
18151
+ if (!field || field[0] === "_") continue;
18152
+ let $cb = createElement2(["input", {
18153
+ type: "checkbox",
18154
+ class: "mr-2"
18155
+ }]);
18156
+ $cb.checked = column.isVisible();
18157
+ $cb.addEventListener("change", () => {
18158
+ gobj_send_event(gobj, "EV_TOGGLE_COLUMN", {
18159
+ field,
18160
+ visible: $cb.checked
18161
+ }, gobj);
18162
+ });
18163
+ $list.appendChild(createElement2([
18164
+ "label",
18165
+ { class: "checkbox is-block mb-1 TREEDB_COLUMN_ITEM" },
18166
+ [$cb, [
18167
+ "span",
18168
+ { class: "ml-2" },
18169
+ column.getDefinition().title || field
18170
+ ]]
18171
+ ]));
18172
+ }
18173
+ yui_shell_show_modal(shell, $list, {
18174
+ dialog: true,
18175
+ logical_class: "TREEDB_COLUMNS_DIALOG",
18176
+ title_prefix: gobj_read_str_attr(gobj, "topic_name"),
18177
+ title: "columns",
18178
+ t
18179
+ });
18180
+ return 0;
18181
+ }
18182
+ /************************************************************
18183
+ * Show / hide one column.
18184
+ ************************************************************/
18185
+ function ac_toggle_column(gobj, event, kw, src) {
18186
+ let tabulator = gobj_read_attr(gobj, "tabulator");
18187
+ let field = kw && kw.field || "";
18188
+ if (!tabulator || !field) {
18189
+ log_error(`${gobj_short_name(gobj)}: no column '${field}' to toggle`);
18190
+ return -1;
18191
+ }
18192
+ try {
18193
+ if (kw.visible) tabulator.showColumn(field);
18194
+ else tabulator.hideColumn(field);
18195
+ } catch (e) {
18196
+ log_error(`${gobj_short_name(gobj)}: cannot toggle column '${field}': ${e}`);
18197
+ return -1;
18198
+ }
18199
+ return 0;
18200
+ }
18201
+ /************************************************************
18202
+ * Download what the table HOLDS as CSV: the loaded rows, the visible
18203
+ * columns, search and header filters applied — which is what the reader
18204
+ * is looking at. Not the topic: a server-side dump of every node is not
18205
+ * something this view can stream.
18206
+ ************************************************************/
18207
+ function ac_export_table(gobj, event, kw, src) {
18208
+ let tabulator = gobj_read_attr(gobj, "tabulator");
18209
+ if (!tabulator) {
18210
+ log_error(`${gobj_short_name(gobj)}: no table to export`);
18211
+ return -1;
18212
+ }
18213
+ let name = `${gobj_read_str_attr(gobj, "treedb_name")}-${gobj_read_str_attr(gobj, "topic_name")}.csv`;
18214
+ name = name.replace(/[^\w.\-]+/g, "_");
18215
+ try {
18216
+ tabulator.download("csv", name);
18217
+ } catch (e) {
18218
+ log_error(`${gobj_short_name(gobj)}: CSV export failed: ${e}`);
18219
+ return -1;
18220
+ }
18221
+ return 0;
18222
+ }
18223
+ /************************************************************
18016
18224
  * {
18017
18225
  * href: href
18018
18226
  * }
@@ -18136,6 +18344,26 @@ function create_gclass$5(gclass_name) {
18136
18344
  ac_show_schema,
18137
18345
  null
18138
18346
  ],
18347
+ [
18348
+ "EV_SEARCH",
18349
+ ac_search,
18350
+ null
18351
+ ],
18352
+ [
18353
+ "EV_OPEN_COLUMNS",
18354
+ ac_open_columns,
18355
+ null
18356
+ ],
18357
+ [
18358
+ "EV_TOGGLE_COLUMN",
18359
+ ac_toggle_column,
18360
+ null
18361
+ ],
18362
+ [
18363
+ "EV_EXPORT_TABLE",
18364
+ ac_export_table,
18365
+ null
18366
+ ],
18139
18367
  [
18140
18368
  "EV_SHOW",
18141
18369
  ac_show$4,
@@ -18166,6 +18394,10 @@ function create_gclass$5(gclass_name) {
18166
18394
  ["EV_CHANGE_LOCALE", 0],
18167
18395
  ["EV_REFRESH", 0],
18168
18396
  ["EV_SHOW_SCHEMA", 0],
18397
+ ["EV_SEARCH", 0],
18398
+ ["EV_OPEN_COLUMNS", 0],
18399
+ ["EV_TOGGLE_COLUMN", 0],
18400
+ ["EV_EXPORT_TABLE", 0],
18169
18401
  ["EV_CREATE_RECORD", event_flag_t.EVF_OUTPUT_EVENT],
18170
18402
  ["EV_UPDATE_RECORD", event_flag_t.EVF_OUTPUT_EVENT],
18171
18403
  ["EV_DELETE_RECORD", event_flag_t.EVF_OUTPUT_EVENT],
@@ -18527,101 +18759,140 @@ function make_toolbar(gobj) {
18527
18759
  gobj.priv;
18528
18760
  let modes = gobj_read_attr(gobj, "operation_modes");
18529
18761
  if (gobj_read_bool_attr(gobj, "readonly")) modes = modes.filter((mode) => mode !== "edition");
18762
+ let left_items = [
18763
+ [
18764
+ "span",
18765
+ {
18766
+ class: "GRAPH_LAYOUT_LABEL is-hidden-mobile",
18767
+ style: "padding-right:5px;",
18768
+ i18n: "layout"
18769
+ },
18770
+ "layout"
18771
+ ],
18772
+ [
18773
+ "div",
18774
+ { class: "select" },
18775
+ [["select", { class: "GRAPH_LAYOUT_SELECT" }]],
18776
+ { change: (evt) => {
18777
+ evt.stopPropagation();
18778
+ gobj_send_event(gobj, "EV_SET_LAYOUT", { layout: evt.target.value }, gobj);
18779
+ } }
18780
+ ],
18781
+ [
18782
+ "span",
18783
+ {
18784
+ class: "GRAPH_MODE_LABEL is-hidden-mobile",
18785
+ style: "padding-left:10px; padding-right:5px;",
18786
+ i18n: "operation mode"
18787
+ },
18788
+ "operation mode"
18789
+ ],
18790
+ [
18791
+ "div",
18792
+ { class: "select" },
18793
+ [[
18794
+ "select",
18795
+ { class: "GRAPH_MODE_SELECT" },
18796
+ modes.map((item) => [
18797
+ "option",
18798
+ {},
18799
+ item
18800
+ ])
18801
+ ]],
18802
+ { change: (evt) => {
18803
+ evt.stopPropagation();
18804
+ gobj_send_event(gobj, "EV_SET_OPERATION_MODE", { operation_mode: evt.target.value }, gobj);
18805
+ } }
18806
+ ],
18807
+ [
18808
+ "button",
18809
+ { class: "GRAPH_REFRESH button" },
18810
+ [["i", { class: "yi-arrows-rotate" }], [
18811
+ "span",
18812
+ {
18813
+ class: "is-hidden-mobile",
18814
+ style: "padding-left:5px;",
18815
+ i18n: "refresh"
18816
+ },
18817
+ "refresh"
18818
+ ]],
18819
+ { click: (evt) => {
18820
+ evt.stopPropagation();
18821
+ gobj_send_event(gobj, "EV_REFRESH_TREEDB", { evt }, gobj);
18822
+ } }
18823
+ ],
18824
+ [
18825
+ "button",
18826
+ {
18827
+ class: "button ml-2 TREEDB_JSON_BTN",
18828
+ title: t("raw json"),
18829
+ "aria-label": t("raw json"),
18830
+ "data-i18n-title": "raw json",
18831
+ "data-i18n-aria-label": "raw json"
18832
+ },
18833
+ [["i", { class: "yi-eye" }], [
18834
+ "span",
18835
+ {
18836
+ class: "is-hidden-mobile",
18837
+ style: "padding-left:5px;",
18838
+ i18n: "raw json"
18839
+ },
18840
+ "raw json"
18841
+ ]],
18842
+ { click: (evt) => {
18843
+ evt.stopPropagation();
18844
+ gobj_send_event(gobj, "EV_OPEN_JSON", {}, gobj);
18845
+ } }
18846
+ ]
18847
+ ];
18848
+ let center_items = [[
18849
+ "div",
18850
+ {
18851
+ class: "GRAPH_FIND control has-icons-left",
18852
+ style: "margin-right:.5rem; max-width:12rem; min-width:7rem;"
18853
+ },
18854
+ [["input", {
18855
+ class: "GRAPH_FIND_INPUT input",
18856
+ type: "text",
18857
+ placeholder: t("search"),
18858
+ "data-i18n-placeholder": "search",
18859
+ "aria-label": t("search"),
18860
+ "data-i18n-aria-label": "search"
18861
+ }], [
18862
+ "span",
18863
+ { class: "icon is-left" },
18864
+ [["i", { class: "yi-magnifying-glass" }]]
18865
+ ]],
18866
+ { input: (evt) => {
18867
+ evt.stopPropagation();
18868
+ gobj_send_event(gobj, "EV_FIND_NODES", { text: evt.target.value.trim() }, gobj);
18869
+ } }
18870
+ ], [
18871
+ "div",
18872
+ {
18873
+ class: "GRAPH_FIND_RESULT is-hidden is-flex is-align-items-center",
18874
+ style: "gap:.3rem; margin-right:.5rem; font-size:.85rem;"
18875
+ },
18876
+ [[
18877
+ "span",
18878
+ { class: "GRAPH_FIND_COUNT" },
18879
+ ""
18880
+ ], [
18881
+ "span",
18882
+ { i18n: "matches" },
18883
+ "matches"
18884
+ ]]
18885
+ ]];
18530
18886
  const $toolbar_header = yui_toolbar({}, [
18531
18887
  [
18532
18888
  "div",
18533
18889
  { class: "yui-horizontal-toolbar-section left" },
18534
- [
18535
- [
18536
- "span",
18537
- {
18538
- class: "GRAPH_LAYOUT_LABEL is-hidden-mobile",
18539
- style: "padding-right:5px;",
18540
- i18n: "layout"
18541
- },
18542
- "layout"
18543
- ],
18544
- [
18545
- "div",
18546
- { class: "select" },
18547
- [["select", { class: "GRAPH_LAYOUT_SELECT" }]],
18548
- { change: (evt) => {
18549
- evt.stopPropagation();
18550
- gobj_send_event(gobj, "EV_SET_LAYOUT", { layout: evt.target.value }, gobj);
18551
- } }
18552
- ],
18553
- [
18554
- "span",
18555
- {
18556
- class: "GRAPH_MODE_LABEL is-hidden-mobile",
18557
- style: "padding-left:10px; padding-right:5px;",
18558
- i18n: "operation mode"
18559
- },
18560
- "operation mode"
18561
- ],
18562
- [
18563
- "div",
18564
- { class: "select" },
18565
- [[
18566
- "select",
18567
- { class: "GRAPH_MODE_SELECT" },
18568
- modes.map((item) => [
18569
- "option",
18570
- {},
18571
- item
18572
- ])
18573
- ]],
18574
- { change: (evt) => {
18575
- evt.stopPropagation();
18576
- gobj_send_event(gobj, "EV_SET_OPERATION_MODE", { operation_mode: evt.target.value }, gobj);
18577
- } }
18578
- ],
18579
- [
18580
- "button",
18581
- { class: "GRAPH_REFRESH button" },
18582
- [["i", { class: "yi-arrows-rotate" }], [
18583
- "span",
18584
- {
18585
- class: "is-hidden-mobile",
18586
- style: "padding-left:5px;",
18587
- i18n: "refresh"
18588
- },
18589
- "refresh"
18590
- ]],
18591
- { click: (evt) => {
18592
- evt.stopPropagation();
18593
- gobj_send_event(gobj, "EV_REFRESH_TREEDB", { evt }, gobj);
18594
- } }
18595
- ],
18596
- [
18597
- "button",
18598
- {
18599
- class: "button ml-2 TREEDB_JSON_BTN",
18600
- title: t("raw json"),
18601
- "aria-label": t("raw json"),
18602
- "data-i18n-title": "raw json",
18603
- "data-i18n-aria-label": "raw json"
18604
- },
18605
- [["i", { class: "yi-eye" }], [
18606
- "span",
18607
- {
18608
- class: "is-hidden-mobile",
18609
- style: "padding-left:5px;",
18610
- i18n: "raw json"
18611
- },
18612
- "raw json"
18613
- ]],
18614
- { click: (evt) => {
18615
- evt.stopPropagation();
18616
- gobj_send_event(gobj, "EV_OPEN_JSON", {}, gobj);
18617
- } }
18618
- ]
18619
- ]
18890
+ left_items
18620
18891
  ],
18621
18892
  [
18622
18893
  "div",
18623
18894
  { class: "yui-horizontal-toolbar-section center" },
18624
- []
18895
+ center_items
18625
18896
  ],
18626
18897
  [
18627
18898
  "div",
@@ -19553,6 +19824,33 @@ function ac_set_operation_mode$1(gobj, event, kw, src) {
19553
19824
  * tree, which centres + highlights that topic's nodes (deferring
19554
19825
  * until its data is loaded).
19555
19826
  ************************************************************/
19827
+ /************************************************************
19828
+ * Forward the find down to the graph child.
19829
+ ************************************************************/
19830
+ function ac_find_nodes$1(gobj, event, kw, src) {
19831
+ let priv = gobj.priv;
19832
+ if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_FIND_NODES", { text: kw && kw.text || "" }, gobj);
19833
+ return 0;
19834
+ }
19835
+ /************************************************************
19836
+ * How many nodes the term matched, from the graph child. Zero with a
19837
+ * term typed is an ANSWER and is shown; an empty box shows nothing.
19838
+ ************************************************************/
19839
+ function ac_find_result(gobj, event, kw, src) {
19840
+ let $container = gobj_read_attr(gobj, "$container");
19841
+ if (!$container) return 0;
19842
+ let $result = $container.querySelector(".GRAPH_FIND_RESULT");
19843
+ let $count = $container.querySelector(".GRAPH_FIND_COUNT");
19844
+ if (!$result || !$count) return 0;
19845
+ if (!(kw && kw.term || "")) {
19846
+ $result.classList.add("is-hidden");
19847
+ $count.textContent = "";
19848
+ return 0;
19849
+ }
19850
+ $count.textContent = String(kw && kw.matches || 0);
19851
+ $result.classList.remove("is-hidden");
19852
+ return 0;
19853
+ }
19556
19854
  function ac_set_focus_topic(gobj, event, kw, src) {
19557
19855
  let priv = gobj.priv;
19558
19856
  if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_FOCUS_TOPIC", { topic: kw && kw.topic }, gobj);
@@ -19718,6 +20016,16 @@ function create_gclass$4(gclass_name) {
19718
20016
  ac_set_focus_topic,
19719
20017
  null
19720
20018
  ],
20019
+ [
20020
+ "EV_FIND_NODES",
20021
+ ac_find_nodes$1,
20022
+ null
20023
+ ],
20024
+ [
20025
+ "EV_FIND_RESULT",
20026
+ ac_find_result,
20027
+ null
20028
+ ],
19721
20029
  [
19722
20030
  "EV_SHOW",
19723
20031
  ac_show$3,
@@ -19759,6 +20067,8 @@ function create_gclass$4(gclass_name) {
19759
20067
  ["EV_SET_LAYOUT", 0],
19760
20068
  ["EV_SET_OPERATION_MODE", 0],
19761
20069
  ["EV_SET_FOCUS_TOPIC", 0],
20070
+ ["EV_FIND_NODES", 0],
20071
+ ["EV_FIND_RESULT", 0],
19762
20072
  ["EV_OPERATION_MODE_CHANGED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
19763
20073
  ["EV_RECORD_WRITTEN", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
19764
20074
  ["EV_SHOW", 0],
@@ -52424,7 +52734,8 @@ var PRIVATE_DATA$1 = {
52424
52734
  _link_saved_styles: [],
52425
52735
  _focus_topic: null,
52426
52736
  _focus_ids: [],
52427
- _pending_focus_topic: null
52737
+ _pending_focus_topic: null,
52738
+ _pending_find: null
52428
52739
  };
52429
52740
  var __gclass__$1 = null;
52430
52741
  /******************************
@@ -53598,6 +53909,67 @@ function graph_focus_topic(gobj, topic) {
53598
53909
  log_error(`${gobj_short_name(gobj)}: focus topic apply failed: ${e}`);
53599
53910
  }
53600
53911
  }
53912
+ /************************************************************
53913
+ * Find the nodes whose name, id or topic contains `term`, highlight
53914
+ * them with the same amber 'active' state the topic focus uses, and
53915
+ * centre the viewport on them. Returns how many matched, which is the
53916
+ * half the toolbar needs: a search that finds nothing must SAY so,
53917
+ * because a graph that did not move is also what a graph looks like
53918
+ * when the match is off-screen.
53919
+ *
53920
+ * It shares the highlight slot with graph_focus_topic on purpose —
53921
+ * two amber sets at once would say nothing about either. An empty
53922
+ * term clears it. Matching reads the node's LABEL and not only its
53923
+ * id: on a topic keyed by rowid or by a qualified path, the id is a
53924
+ * counter and the name a human knows is elsewhere (see node_label).
53925
+ ************************************************************/
53926
+ function graph_find_nodes(gobj, term) {
53927
+ let priv = gobj.priv;
53928
+ let graph = priv.graph;
53929
+ if (!graph || !priv.graph_rendered) {
53930
+ priv._pending_find = term;
53931
+ return 0;
53932
+ }
53933
+ let data = {};
53934
+ try {
53935
+ data = graph.getData() || {};
53936
+ } catch (e) {
53937
+ log_error(`${gobj_short_name(gobj)}: graph.getData() failed: ${e}`);
53938
+ return 0;
53939
+ }
53940
+ let nodes = is_array(data.nodes) ? data.nodes : [];
53941
+ let states = {};
53942
+ for (let id of priv._focus_ids || []) states[id] = [];
53943
+ let ids = [];
53944
+ if (!empty_string(term)) {
53945
+ let needle = String(term).toLowerCase();
53946
+ for (let n of nodes) {
53947
+ if (!n || !n.data) continue;
53948
+ let record = n.data.record || {};
53949
+ let label = "";
53950
+ try {
53951
+ label = node_label(n.data.desc || {}, record) || "";
53952
+ } catch (e) {
53953
+ label = "";
53954
+ }
53955
+ if ([
53956
+ label,
53957
+ record.id,
53958
+ n.data.topic_name
53959
+ ].filter((v) => typeof v === "string").join(" ").toLowerCase().includes(needle)) ids.push(n.id);
53960
+ }
53961
+ for (let id of ids) states[id] = ["active"];
53962
+ }
53963
+ priv._focus_ids = ids;
53964
+ priv._focus_topic = null;
53965
+ try {
53966
+ if (typeof graph.setElementState === "function") graph.setElementState(states);
53967
+ if (ids.length && typeof graph.focusElement === "function") graph.focusElement(ids);
53968
+ } catch (e) {
53969
+ log_error(`${gobj_short_name(gobj)}: find apply failed: ${e}`);
53970
+ }
53971
+ return ids.length;
53972
+ }
53601
53973
  async function graph_zoom_in(gobj) {
53602
53974
  let graph = gobj.priv.graph;
53603
53975
  const z = graph.getZoom();
@@ -55892,6 +56264,11 @@ function ac_load_data(gobj, event, kw, src) {
55892
56264
  priv._pending_focus_topic = null;
55893
56265
  graph_focus_topic(gobj, ft);
55894
56266
  }
56267
+ if (priv._pending_find !== null) {
56268
+ let term = priv._pending_find;
56269
+ priv._pending_find = null;
56270
+ publish_find_result(gobj, term, graph_find_nodes(gobj, term));
56271
+ }
55895
56272
  });
55896
56273
  });
55897
56274
  });
@@ -56108,6 +56485,25 @@ function ac_focus_topic(gobj, event, kw, src) {
56108
56485
  graph_focus_topic(gobj, kw && kw.topic);
56109
56486
  return 0;
56110
56487
  }
56488
+ /************************************************************
56489
+ * Tell whoever asked how many nodes the term matched. The count is
56490
+ * the answer to the question the box asks; without it "nothing moved"
56491
+ * and "nothing matched" look the same.
56492
+ ************************************************************/
56493
+ function publish_find_result(gobj, term, matches) {
56494
+ gobj_publish_event(gobj, "EV_FIND_RESULT", {
56495
+ term,
56496
+ matches
56497
+ });
56498
+ }
56499
+ /************************************************************
56500
+ *
56501
+ ************************************************************/
56502
+ function ac_find_nodes(gobj, event, kw, src) {
56503
+ let term = kw && kw.text || "";
56504
+ publish_find_result(gobj, term, graph_find_nodes(gobj, term));
56505
+ return 0;
56506
+ }
56111
56507
  function ac_center(gobj, event, kw, src) {
56112
56508
  graph_center(gobj);
56113
56509
  return 0;
@@ -56409,6 +56805,11 @@ function create_gclass$1(gclass_name) {
56409
56805
  ac_focus_topic,
56410
56806
  null
56411
56807
  ],
56808
+ [
56809
+ "EV_FIND_NODES",
56810
+ ac_find_nodes,
56811
+ null
56812
+ ],
56412
56813
  [
56413
56814
  "EV_CENTER",
56414
56815
  ac_center,
@@ -56492,6 +56893,8 @@ function create_gclass$1(gclass_name) {
56492
56893
  ["EV_ZOOM_RESET", 0],
56493
56894
  ["EV_AUTO_FIT", 0],
56494
56895
  ["EV_FOCUS_TOPIC", 0],
56896
+ ["EV_FIND_NODES", 0],
56897
+ ["EV_FIND_RESULT", event_flag_t.EVF_OUTPUT_EVENT],
56495
56898
  ["EV_CENTER", 0],
56496
56899
  ["EV_FULLSCREEN", 0],
56497
56900
  ["EV_SET_LAYOUT", 0],