@yuneta/gobj-ui 7.23.135 → 7.23.137

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.
@@ -57017,12 +57017,41 @@ var MIN_READABLE_ZOOM = .5;
57017
57017
  * G6 reports them.
57018
57018
  ************************************************************/
57019
57019
  var CAMERA_SETTLE_MS = 700;
57020
+ function node_nearest_view_centre(gobj) {
57021
+ let graph = gobj.priv.graph;
57022
+ let size;
57023
+ try {
57024
+ size = graph.getSize();
57025
+ } catch (e) {
57026
+ return "";
57027
+ }
57028
+ if (!(0, _yuneta_gobj_js.is_array)(size) || !(size[0] > 0) || !(size[1] > 0)) return "";
57029
+ let [w, h] = size;
57030
+ let best = "";
57031
+ let best_d = Infinity;
57032
+ for (let nd of graph.getNodeData() || []) {
57033
+ let vp = yui_graph_viewport_of(graph, nd.id);
57034
+ if (!vp || !(0, _yuneta_gobj_js.is_number)(vp[0]) || !(0, _yuneta_gobj_js.is_number)(vp[1])) continue;
57035
+ if (vp[0] < 0 || vp[1] < 0 || vp[0] > w || vp[1] > h) continue;
57036
+ let d = (vp[0] - w / 2) ** 2 + (vp[1] - h / 2) ** 2;
57037
+ if (d < best_d) {
57038
+ best_d = d;
57039
+ best = nd.id;
57040
+ }
57041
+ }
57042
+ return best;
57043
+ }
57044
+ function camera_anchor_node(gobj) {
57045
+ let priv = gobj.priv;
57046
+ if (priv.anchor_state === "on" && priv.anchor_id) return priv.anchor_id;
57047
+ return node_nearest_view_centre(gobj) || fold_keep_node(gobj);
57048
+ }
57020
57049
  function read_camera(gobj) {
57021
57050
  let graph = gobj.priv.graph;
57022
57051
  if (!graph || typeof graph.getZoom !== "function") return null;
57023
57052
  try {
57024
57053
  let z = graph.getZoom();
57025
- let node = fold_keep_node(gobj);
57054
+ let node = camera_anchor_node(gobj);
57026
57055
  let vp = node ? yui_graph_viewport_of(graph, node) : null;
57027
57056
  if (!(0, _yuneta_gobj_js.is_number)(z) || !node || !vp || !(0, _yuneta_gobj_js.is_number)(vp[0]) || !(0, _yuneta_gobj_js.is_number)(vp[1])) return null;
57028
57057
  return {
@@ -57041,6 +57070,8 @@ async function restore_camera(gobj) {
57041
57070
  let cam = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "camera");
57042
57071
  if (!graph || (0, _yuneta_gobj_js.empty_json)(cam) || !(0, _yuneta_gobj_js.is_number)(cam.zoom) || !cam.node || !(0, _yuneta_gobj_js.is_number)(cam.x) || !(0, _yuneta_gobj_js.is_number)(cam.y)) return false;
57043
57072
  if (!yui_graph_viewport_of(graph, cam.node)) return false;
57073
+ let size = graph.getSize() || [0, 0];
57074
+ if (cam.x < 0 || cam.y < 0 || cam.x > size[0] || cam.y > size[1]) return false;
57044
57075
  priv._camera_restoring = true;
57045
57076
  try {
57046
57077
  await graph.zoomTo(cam.zoom);
@@ -61262,6 +61293,7 @@ async function reconcile_fold_apply(gobj, opts, visible) {
61262
61293
  if (opts.fit) await graph_fit_readable(gobj);
61263
61294
  else if (opts.keep && opts.keep_vp) await yui_graph_place_at(graph, opts.keep, opts.keep_vp);
61264
61295
  }
61296
+ if ((graph.getNodeData() || []).length > 0 && !node_nearest_view_centre(gobj)) await graph_fit_readable(gobj);
61265
61297
  priv._camera_placed = true;
61266
61298
  update_resize_handles_position(gobj);
61267
61299
  update_port_resize_handles_position(gobj);
@@ -61339,13 +61371,13 @@ function ac_descs(gobj, event, kw, src) {
61339
61371
  gobj.priv;
61340
61372
  let descs = kw;
61341
61373
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "descs", descs);
61342
- let idx = 0;
61343
- for (const [topic_name, desc] of Object.entries(descs)) {
61344
- calculate_hooks_fkeys_counter(desc);
61345
- if (topic_name.substring(0, 2) === "__") continue;
61346
- desc.color = node_colors[idx % node_colors.length];
61347
- idx++;
61348
- }
61374
+ for (const desc of Object.values(descs)) calculate_hooks_fkeys_counter(desc);
61375
+ let names = Object.keys(descs).filter((topic_name) => {
61376
+ return topic_name.substring(0, 2) !== "__";
61377
+ }).sort((a, b) => {
61378
+ return a.localeCompare(b);
61379
+ });
61380
+ for (let idx = 0; idx < names.length; idx++) descs[names[idx]].color = node_colors[idx % node_colors.length];
61349
61381
  return 0;
61350
61382
  }
61351
61383
  /************************************************************
@@ -63130,56 +63162,54 @@ function make_toolbar(gobj) {
63130
63162
  evt.stopPropagation();
63131
63163
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SET_OPERATION_MODE", { operation_mode: evt.target.value }, gobj);
63132
63164
  } }
63133
- ],
63134
- [
63135
- "button",
63165
+ ]
63166
+ ];
63167
+ let center_items = [...yui_graph_fold_items(gobj, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "wide")), ...node_shape_items(gobj, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "wide"))];
63168
+ let backend_items = [[
63169
+ "button",
63170
+ {
63171
+ class: "GRAPH_REFRESH button",
63172
+ title: (0, i18next.t)("refresh"),
63173
+ "data-i18n-title": "refresh",
63174
+ "aria-label": (0, i18next.t)("refresh"),
63175
+ "data-i18n-aria-label": "refresh"
63176
+ },
63177
+ [yui_toolbar_icon("yi-arrows-rotate"), [
63178
+ "span",
63136
63179
  {
63137
- class: "GRAPH_REFRESH button",
63138
- title: (0, i18next.t)("refresh"),
63139
- "data-i18n-title": "refresh",
63140
- "aria-label": (0, i18next.t)("refresh"),
63141
- "data-i18n-aria-label": "refresh"
63180
+ class: "is-hidden-mobile",
63181
+ style: "padding-left:5px;",
63182
+ i18n: "refresh"
63142
63183
  },
63143
- [yui_toolbar_icon("yi-arrows-rotate"), [
63144
- "span",
63145
- {
63146
- class: "is-hidden-mobile",
63147
- style: "padding-left:5px;",
63148
- i18n: "refresh"
63149
- },
63150
- "refresh"
63151
- ]],
63152
- { click: (evt) => {
63153
- evt.stopPropagation();
63154
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REFRESH_TREEDB", { evt }, gobj);
63155
- } }
63156
- ],
63157
- ...yui_graph_fold_items(gobj, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "wide")),
63158
- ...node_shape_items(gobj, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "wide")),
63159
- [
63160
- "button",
63184
+ "refresh"
63185
+ ]],
63186
+ { click: (evt) => {
63187
+ evt.stopPropagation();
63188
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REFRESH_TREEDB", { evt }, gobj);
63189
+ } }
63190
+ ], [
63191
+ "button",
63192
+ {
63193
+ class: "button ml-2 TREEDB_JSON_BTN",
63194
+ title: (0, i18next.t)("raw json"),
63195
+ "aria-label": (0, i18next.t)("raw json"),
63196
+ "data-i18n-title": "raw json",
63197
+ "data-i18n-aria-label": "raw json"
63198
+ },
63199
+ [yui_toolbar_icon("yi-eye"), [
63200
+ "span",
63161
63201
  {
63162
- class: "button ml-2 TREEDB_JSON_BTN",
63163
- title: (0, i18next.t)("raw json"),
63164
- "aria-label": (0, i18next.t)("raw json"),
63165
- "data-i18n-title": "raw json",
63166
- "data-i18n-aria-label": "raw json"
63202
+ class: "is-hidden-mobile",
63203
+ style: "padding-left:5px;",
63204
+ i18n: "raw json"
63167
63205
  },
63168
- [yui_toolbar_icon("yi-eye"), [
63169
- "span",
63170
- {
63171
- class: "is-hidden-mobile",
63172
- style: "padding-left:5px;",
63173
- i18n: "raw json"
63174
- },
63175
- "raw json"
63176
- ]],
63177
- { click: (evt) => {
63178
- evt.stopPropagation();
63179
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_OPEN_JSON", {}, gobj);
63180
- } }
63181
- ]
63182
- ];
63206
+ "raw json"
63207
+ ]],
63208
+ { click: (evt) => {
63209
+ evt.stopPropagation();
63210
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_OPEN_JSON", {}, gobj);
63211
+ } }
63212
+ ]];
63183
63213
  let $find_input = (0, _yuneta_gobj_js.createElement2)([
63184
63214
  "input",
63185
63215
  {
@@ -63214,6 +63244,18 @@ function make_toolbar(gobj) {
63214
63244
  ]]
63215
63245
  ]);
63216
63246
  attach_clear($find_control, $find_input);
63247
+ let right_items = [...[$find_control, [
63248
+ "div",
63249
+ {
63250
+ class: "GRAPH_FIND_RESULT is-hidden",
63251
+ style: "display:flex; align-items:center; gap:.3rem; margin-right:.5rem; font-size:.85rem;"
63252
+ },
63253
+ [[
63254
+ "span",
63255
+ { class: "GRAPH_FIND_COUNT" },
63256
+ ""
63257
+ ]]
63258
+ ]], ...backend_items];
63217
63259
  const $toolbar_header = yui_toolbar({}, [
63218
63260
  [
63219
63261
  "div",
@@ -63223,23 +63265,12 @@ function make_toolbar(gobj) {
63223
63265
  [
63224
63266
  "div",
63225
63267
  { class: "yui-horizontal-toolbar-section center" },
63226
- [$find_control, [
63227
- "div",
63228
- {
63229
- class: "GRAPH_FIND_RESULT is-hidden",
63230
- style: "display:flex; align-items:center; gap:.3rem; margin-right:.5rem; font-size:.85rem;"
63231
- },
63232
- [[
63233
- "span",
63234
- { class: "GRAPH_FIND_COUNT" },
63235
- ""
63236
- ]]
63237
- ]]
63268
+ center_items
63238
63269
  ],
63239
63270
  [
63240
63271
  "div",
63241
63272
  { class: "GRAPH_MODE_BUTTONS yui-horizontal-toolbar-section right" },
63242
- []
63273
+ right_items
63243
63274
  ]
63244
63275
  ]);
63245
63276
  (0, _yuneta_gobj_js.refresh_language)($toolbar_header, i18next.t);
@@ -65162,17 +65193,21 @@ function schema_to_graph(gobj) {
65162
65193
  nodes,
65163
65194
  edges: []
65164
65195
  };
65165
- let idx = 0;
65196
+ let rank = {};
65197
+ Object.keys(descs).filter((topic) => {
65198
+ return topic.substring(0, 2) !== "__";
65199
+ }).sort((a, b) => {
65200
+ return a.localeCompare(b);
65201
+ }).forEach((topic, i) => {
65202
+ rank[topic] = i;
65203
+ });
65166
65204
  for (let topic of Object.keys(descs)) {
65167
65205
  let is_system = topic.substring(0, 2) === "__";
65168
65206
  if (!system && is_system) continue;
65169
65207
  let rows = schema_rows(descs[topic]);
65170
65208
  let color;
65171
65209
  if (is_system) color = SYSTEM_TOPIC_COLOR;
65172
- else {
65173
- color = topic_colors[idx % topic_colors.length];
65174
- idx++;
65175
- }
65210
+ else color = topic_colors[rank[topic] % topic_colors.length];
65176
65211
  cards[topic] = {
65177
65212
  rows,
65178
65213
  size: card_size(rows),
@@ -57012,12 +57012,41 @@ var MIN_READABLE_ZOOM = .5;
57012
57012
  * G6 reports them.
57013
57013
  ************************************************************/
57014
57014
  var CAMERA_SETTLE_MS = 700;
57015
+ function node_nearest_view_centre(gobj) {
57016
+ let graph = gobj.priv.graph;
57017
+ let size;
57018
+ try {
57019
+ size = graph.getSize();
57020
+ } catch (e) {
57021
+ return "";
57022
+ }
57023
+ if (!is_array(size) || !(size[0] > 0) || !(size[1] > 0)) return "";
57024
+ let [w, h] = size;
57025
+ let best = "";
57026
+ let best_d = Infinity;
57027
+ for (let nd of graph.getNodeData() || []) {
57028
+ let vp = yui_graph_viewport_of(graph, nd.id);
57029
+ if (!vp || !is_number(vp[0]) || !is_number(vp[1])) continue;
57030
+ if (vp[0] < 0 || vp[1] < 0 || vp[0] > w || vp[1] > h) continue;
57031
+ let d = (vp[0] - w / 2) ** 2 + (vp[1] - h / 2) ** 2;
57032
+ if (d < best_d) {
57033
+ best_d = d;
57034
+ best = nd.id;
57035
+ }
57036
+ }
57037
+ return best;
57038
+ }
57039
+ function camera_anchor_node(gobj) {
57040
+ let priv = gobj.priv;
57041
+ if (priv.anchor_state === "on" && priv.anchor_id) return priv.anchor_id;
57042
+ return node_nearest_view_centre(gobj) || fold_keep_node(gobj);
57043
+ }
57015
57044
  function read_camera(gobj) {
57016
57045
  let graph = gobj.priv.graph;
57017
57046
  if (!graph || typeof graph.getZoom !== "function") return null;
57018
57047
  try {
57019
57048
  let z = graph.getZoom();
57020
- let node = fold_keep_node(gobj);
57049
+ let node = camera_anchor_node(gobj);
57021
57050
  let vp = node ? yui_graph_viewport_of(graph, node) : null;
57022
57051
  if (!is_number(z) || !node || !vp || !is_number(vp[0]) || !is_number(vp[1])) return null;
57023
57052
  return {
@@ -57036,6 +57065,8 @@ async function restore_camera(gobj) {
57036
57065
  let cam = gobj_read_attr(gobj, "camera");
57037
57066
  if (!graph || empty_json(cam) || !is_number(cam.zoom) || !cam.node || !is_number(cam.x) || !is_number(cam.y)) return false;
57038
57067
  if (!yui_graph_viewport_of(graph, cam.node)) return false;
57068
+ let size = graph.getSize() || [0, 0];
57069
+ if (cam.x < 0 || cam.y < 0 || cam.x > size[0] || cam.y > size[1]) return false;
57039
57070
  priv._camera_restoring = true;
57040
57071
  try {
57041
57072
  await graph.zoomTo(cam.zoom);
@@ -61257,6 +61288,7 @@ async function reconcile_fold_apply(gobj, opts, visible) {
61257
61288
  if (opts.fit) await graph_fit_readable(gobj);
61258
61289
  else if (opts.keep && opts.keep_vp) await yui_graph_place_at(graph, opts.keep, opts.keep_vp);
61259
61290
  }
61291
+ if ((graph.getNodeData() || []).length > 0 && !node_nearest_view_centre(gobj)) await graph_fit_readable(gobj);
61260
61292
  priv._camera_placed = true;
61261
61293
  update_resize_handles_position(gobj);
61262
61294
  update_port_resize_handles_position(gobj);
@@ -61334,13 +61366,13 @@ function ac_descs(gobj, event, kw, src) {
61334
61366
  gobj.priv;
61335
61367
  let descs = kw;
61336
61368
  gobj_write_attr(gobj, "descs", descs);
61337
- let idx = 0;
61338
- for (const [topic_name, desc] of Object.entries(descs)) {
61339
- calculate_hooks_fkeys_counter(desc);
61340
- if (topic_name.substring(0, 2) === "__") continue;
61341
- desc.color = node_colors[idx % node_colors.length];
61342
- idx++;
61343
- }
61369
+ for (const desc of Object.values(descs)) calculate_hooks_fkeys_counter(desc);
61370
+ let names = Object.keys(descs).filter((topic_name) => {
61371
+ return topic_name.substring(0, 2) !== "__";
61372
+ }).sort((a, b) => {
61373
+ return a.localeCompare(b);
61374
+ });
61375
+ for (let idx = 0; idx < names.length; idx++) descs[names[idx]].color = node_colors[idx % node_colors.length];
61344
61376
  return 0;
61345
61377
  }
61346
61378
  /************************************************************
@@ -63125,56 +63157,54 @@ function make_toolbar(gobj) {
63125
63157
  evt.stopPropagation();
63126
63158
  gobj_send_event(gobj, "EV_SET_OPERATION_MODE", { operation_mode: evt.target.value }, gobj);
63127
63159
  } }
63128
- ],
63129
- [
63130
- "button",
63160
+ ]
63161
+ ];
63162
+ let center_items = [...yui_graph_fold_items(gobj, gobj_read_str_attr(gobj, "wide")), ...node_shape_items(gobj, gobj_read_str_attr(gobj, "wide"))];
63163
+ let backend_items = [[
63164
+ "button",
63165
+ {
63166
+ class: "GRAPH_REFRESH button",
63167
+ title: t("refresh"),
63168
+ "data-i18n-title": "refresh",
63169
+ "aria-label": t("refresh"),
63170
+ "data-i18n-aria-label": "refresh"
63171
+ },
63172
+ [yui_toolbar_icon("yi-arrows-rotate"), [
63173
+ "span",
63131
63174
  {
63132
- class: "GRAPH_REFRESH button",
63133
- title: t("refresh"),
63134
- "data-i18n-title": "refresh",
63135
- "aria-label": t("refresh"),
63136
- "data-i18n-aria-label": "refresh"
63175
+ class: "is-hidden-mobile",
63176
+ style: "padding-left:5px;",
63177
+ i18n: "refresh"
63137
63178
  },
63138
- [yui_toolbar_icon("yi-arrows-rotate"), [
63139
- "span",
63140
- {
63141
- class: "is-hidden-mobile",
63142
- style: "padding-left:5px;",
63143
- i18n: "refresh"
63144
- },
63145
- "refresh"
63146
- ]],
63147
- { click: (evt) => {
63148
- evt.stopPropagation();
63149
- gobj_send_event(gobj, "EV_REFRESH_TREEDB", { evt }, gobj);
63150
- } }
63151
- ],
63152
- ...yui_graph_fold_items(gobj, gobj_read_str_attr(gobj, "wide")),
63153
- ...node_shape_items(gobj, gobj_read_str_attr(gobj, "wide")),
63154
- [
63155
- "button",
63179
+ "refresh"
63180
+ ]],
63181
+ { click: (evt) => {
63182
+ evt.stopPropagation();
63183
+ gobj_send_event(gobj, "EV_REFRESH_TREEDB", { evt }, gobj);
63184
+ } }
63185
+ ], [
63186
+ "button",
63187
+ {
63188
+ class: "button ml-2 TREEDB_JSON_BTN",
63189
+ title: t("raw json"),
63190
+ "aria-label": t("raw json"),
63191
+ "data-i18n-title": "raw json",
63192
+ "data-i18n-aria-label": "raw json"
63193
+ },
63194
+ [yui_toolbar_icon("yi-eye"), [
63195
+ "span",
63156
63196
  {
63157
- class: "button ml-2 TREEDB_JSON_BTN",
63158
- title: t("raw json"),
63159
- "aria-label": t("raw json"),
63160
- "data-i18n-title": "raw json",
63161
- "data-i18n-aria-label": "raw json"
63197
+ class: "is-hidden-mobile",
63198
+ style: "padding-left:5px;",
63199
+ i18n: "raw json"
63162
63200
  },
63163
- [yui_toolbar_icon("yi-eye"), [
63164
- "span",
63165
- {
63166
- class: "is-hidden-mobile",
63167
- style: "padding-left:5px;",
63168
- i18n: "raw json"
63169
- },
63170
- "raw json"
63171
- ]],
63172
- { click: (evt) => {
63173
- evt.stopPropagation();
63174
- gobj_send_event(gobj, "EV_OPEN_JSON", {}, gobj);
63175
- } }
63176
- ]
63177
- ];
63201
+ "raw json"
63202
+ ]],
63203
+ { click: (evt) => {
63204
+ evt.stopPropagation();
63205
+ gobj_send_event(gobj, "EV_OPEN_JSON", {}, gobj);
63206
+ } }
63207
+ ]];
63178
63208
  let $find_input = createElement2([
63179
63209
  "input",
63180
63210
  {
@@ -63209,6 +63239,18 @@ function make_toolbar(gobj) {
63209
63239
  ]]
63210
63240
  ]);
63211
63241
  attach_clear($find_control, $find_input);
63242
+ let right_items = [...[$find_control, [
63243
+ "div",
63244
+ {
63245
+ class: "GRAPH_FIND_RESULT is-hidden",
63246
+ style: "display:flex; align-items:center; gap:.3rem; margin-right:.5rem; font-size:.85rem;"
63247
+ },
63248
+ [[
63249
+ "span",
63250
+ { class: "GRAPH_FIND_COUNT" },
63251
+ ""
63252
+ ]]
63253
+ ]], ...backend_items];
63212
63254
  const $toolbar_header = yui_toolbar({}, [
63213
63255
  [
63214
63256
  "div",
@@ -63218,23 +63260,12 @@ function make_toolbar(gobj) {
63218
63260
  [
63219
63261
  "div",
63220
63262
  { class: "yui-horizontal-toolbar-section center" },
63221
- [$find_control, [
63222
- "div",
63223
- {
63224
- class: "GRAPH_FIND_RESULT is-hidden",
63225
- style: "display:flex; align-items:center; gap:.3rem; margin-right:.5rem; font-size:.85rem;"
63226
- },
63227
- [[
63228
- "span",
63229
- { class: "GRAPH_FIND_COUNT" },
63230
- ""
63231
- ]]
63232
- ]]
63263
+ center_items
63233
63264
  ],
63234
63265
  [
63235
63266
  "div",
63236
63267
  { class: "GRAPH_MODE_BUTTONS yui-horizontal-toolbar-section right" },
63237
- []
63268
+ right_items
63238
63269
  ]
63239
63270
  ]);
63240
63271
  refresh_language($toolbar_header, t);
@@ -65157,17 +65188,21 @@ function schema_to_graph(gobj) {
65157
65188
  nodes,
65158
65189
  edges: []
65159
65190
  };
65160
- let idx = 0;
65191
+ let rank = {};
65192
+ Object.keys(descs).filter((topic) => {
65193
+ return topic.substring(0, 2) !== "__";
65194
+ }).sort((a, b) => {
65195
+ return a.localeCompare(b);
65196
+ }).forEach((topic, i) => {
65197
+ rank[topic] = i;
65198
+ });
65161
65199
  for (let topic of Object.keys(descs)) {
65162
65200
  let is_system = topic.substring(0, 2) === "__";
65163
65201
  if (!system && is_system) continue;
65164
65202
  let rows = schema_rows(descs[topic]);
65165
65203
  let color;
65166
65204
  if (is_system) color = SYSTEM_TOPIC_COLOR;
65167
- else {
65168
- color = topic_colors[idx % topic_colors.length];
65169
- idx++;
65170
- }
65205
+ else color = topic_colors[rank[topic] % topic_colors.length];
65171
65206
  cards[topic] = {
65172
65207
  rows,
65173
65208
  size: card_size(rows),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.135",
3
+ "version": "7.23.137",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -3382,6 +3382,58 @@ const CAMERA_SETTLE_MS = 700;
3382
3382
  *
3383
3383
  * A node and a pixel is also the SAME thing the folds keep across a
3384
3384
  * rebuild. One idea, used twice. */
3385
+ /* The drawn node nearest the middle of the VIEWPORT, among the ones
3386
+ * inside it; "" when there is none -- which is a blank view. */
3387
+ function node_nearest_view_centre(gobj)
3388
+ {
3389
+ let graph = gobj.priv.graph;
3390
+ let size;
3391
+ try {
3392
+ size = graph.getSize();
3393
+ } catch(e) {
3394
+ return ""; /* between renders: nothing is drawn */
3395
+ }
3396
+ if(!is_array(size) || !(size[0] > 0) || !(size[1] > 0)) {
3397
+ return "";
3398
+ }
3399
+ let [w, h] = size;
3400
+ let best = "";
3401
+ let best_d = Infinity;
3402
+ for(let nd of (graph.getNodeData() || [])) {
3403
+ let vp = yui_graph_viewport_of(graph, nd.id);
3404
+ if(!vp || !is_number(vp[0]) || !is_number(vp[1])) {
3405
+ continue;
3406
+ }
3407
+ if(vp[0] < 0 || vp[1] < 0 || vp[0] > w || vp[1] > h) {
3408
+ continue;
3409
+ }
3410
+ let d = (vp[0] - w / 2) ** 2 + (vp[1] - h / 2) ** 2;
3411
+ if(d < best_d) {
3412
+ best_d = d;
3413
+ best = nd.id;
3414
+ }
3415
+ }
3416
+ return best;
3417
+ }
3418
+
3419
+ /* The node a camera is SAVED by: the anchor when one is set, else the
3420
+ * node nearest the middle of the viewport.
3421
+ *
3422
+ * It used to be fold_keep_node() -- the first root of the tree,
3423
+ * wherever it was. A reader who had panned away saved a node OFF
3424
+ * screen at a pixel off screen, and a reload whose layout had moved
3425
+ * by a hair put that one node back at that pixel and nothing else in
3426
+ * view: a blank graph, with the whole tree sitting in the minimap. */
3427
+ function camera_anchor_node(gobj)
3428
+ {
3429
+ let priv = gobj.priv;
3430
+
3431
+ if(priv.anchor_state === "on" && priv.anchor_id) {
3432
+ return priv.anchor_id;
3433
+ }
3434
+ return node_nearest_view_centre(gobj) || fold_keep_node(gobj);
3435
+ }
3436
+
3385
3437
  function read_camera(gobj)
3386
3438
  {
3387
3439
  let priv = gobj.priv;
@@ -3392,7 +3444,7 @@ function read_camera(gobj)
3392
3444
  }
3393
3445
  try {
3394
3446
  let z = graph.getZoom();
3395
- let node = fold_keep_node(gobj);
3447
+ let node = camera_anchor_node(gobj);
3396
3448
  let vp = node? yui_graph_viewport_of(graph, node) : null;
3397
3449
  if(!is_number(z) || !node || !vp || !is_number(vp[0]) || !is_number(vp[1])) {
3398
3450
  return null;
@@ -3422,6 +3474,13 @@ async function restore_camera(gobj)
3422
3474
  if(!yui_graph_viewport_of(graph, cam.node)) {
3423
3475
  return false;
3424
3476
  }
3477
+ /* A camera saved by a node OFF screen (before 7.23.137 the node was
3478
+ * the first root, wherever it was) frames nothing it can promise:
3479
+ * the opening fit instead. */
3480
+ let size = graph.getSize() || [0, 0];
3481
+ if(cam.x < 0 || cam.y < 0 || cam.x > size[0] || cam.y > size[1]) {
3482
+ return false;
3483
+ }
3425
3484
  priv._camera_restoring = true;
3426
3485
  try {
3427
3486
  await graph.zoomTo(cam.zoom);
@@ -9709,6 +9768,15 @@ async function reconcile_fold_apply(gobj, opts, visible)
9709
9768
  await yui_graph_place_at(graph, opts.keep, opts.keep_vp);
9710
9769
  }
9711
9770
  }
9771
+ /* And never a BLANK viewport. Every branch above can leave one: a
9772
+ * restore by a node whose surroundings moved, a refresh or a global
9773
+ * fold holding the first root still at a pixel off screen, a saved
9774
+ * arrangement opened with no camera (no fit: its owner's view is
9775
+ * part of it) far from where G6 starts looking. A view with nothing
9776
+ * in it is not a place anybody chose to be. */
9777
+ if((graph.getNodeData() || []).length > 0 && !node_nearest_view_centre(gobj)) {
9778
+ await graph_fit_readable(gobj);
9779
+ }
9712
9780
  /* Whichever branch ran -- including none of them, which is a
9713
9781
  * graph somebody had arranged and left where it was -- the
9714
9782
  * opening viewport is decided from here on, and every move after
@@ -9825,15 +9893,23 @@ function ac_descs(gobj, event, kw, src)
9825
9893
  /*
9826
9894
  * Assign colors and calculate counters
9827
9895
  * descs is a dict: { __snaps__: {…}, roles: {…}, users: {…} }
9896
+ *
9897
+ * The palette goes by ALPHABETICAL order of the topic names, not
9898
+ * by the order the backend lists them in: that one is not the same
9899
+ * from one load to the next, and two topics swapped colours with
9900
+ * it. The legend is alphabetical too, so the strip reads as the
9901
+ * palette in order.
9828
9902
  */
9829
- let idx = 0;
9830
- for(const [topic_name, desc] of Object.entries(descs)) {
9903
+ for(const desc of Object.values(descs)) {
9831
9904
  calculate_hooks_fkeys_counter(desc);
9832
- if(topic_name.substring(0, 2) === "__") {
9833
- continue;
9834
- }
9835
- desc.color = node_colors[idx % node_colors.length];
9836
- idx++;
9905
+ }
9906
+ let names = Object.keys(descs).filter((topic_name) => {
9907
+ return topic_name.substring(0, 2) !== "__";
9908
+ }).sort((a, b) => {
9909
+ return a.localeCompare(b);
9910
+ });
9911
+ for(let idx = 0; idx < names.length; idx++) {
9912
+ descs[names[idx]].color = node_colors[idx % node_colors.length];
9837
9913
  }
9838
9914
 
9839
9915
  return 0;
@@ -694,6 +694,11 @@ function make_toolbar(gobj)
694
694
  );
695
695
 
696
696
  /*
697
+ * Three sections, by what the control is ABOUT:
698
+ * left how the graph is built: layout and operation mode
699
+ * center how it is SHOWN: the fold pair and the node views
700
+ * right what it is asked: find, refresh, the raw json
701
+ *
697
702
  * Left: layout and mode selectors
698
703
  * Layout options are empty — populated after child creation
699
704
  * via populate_nodes_tree_options()
@@ -730,6 +735,28 @@ function make_toolbar(gobj)
730
735
  }
731
736
  }],
732
737
 
738
+ ];
739
+
740
+ /*
741
+ * Center: how the graph is SHOWN.
742
+ */
743
+ let center_items = [
744
+ /* The fold pair, the same two chevrons the JSON graph and the
745
+ * lazy tree use: the graph opens folded (see the G6 child), and
746
+ * these are the whole thing and the roots alone. Refresh is the
747
+ * way back to the default depth. */
748
+ ...yui_graph_fold_items(gobj, gobj_read_str_attr(gobj, "wide")),
749
+
750
+ /* The view of the nodes -- full cards, one-line pills or
751
+ * figures -- and the name under a figure. */
752
+ ...node_shape_items(gobj, gobj_read_str_attr(gobj, "wide")),
753
+ ];
754
+
755
+ /*
756
+ * Right, after the find box: ask the backend again, and read its
757
+ * raw json.
758
+ */
759
+ let backend_items = [
733
760
  /* Icon + label, and the label is `is-hidden-mobile`: on a phone
734
761
  * this is a bare icon, so the name has to be on the button. */
735
762
  ['button', {class: 'GRAPH_REFRESH button',
@@ -744,16 +771,6 @@ function make_toolbar(gobj)
744
771
  }
745
772
  }],
746
773
 
747
- /* The fold pair, the same two chevrons the JSON graph and the
748
- * lazy tree use: the graph opens folded (see the G6 child), and
749
- * these are the whole thing and the roots alone. Refresh is the
750
- * way back to the default depth. */
751
- ...yui_graph_fold_items(gobj, gobj_read_str_attr(gobj, "wide")),
752
-
753
- /* The view of the nodes -- full cards, one-line pills or
754
- * figures -- and the name under a figure. */
755
- ...node_shape_items(gobj, gobj_read_str_attr(gobj, "wide")),
756
-
757
774
  /* Inspect the treedb's raw tranger json in the lazy tree viewer
758
775
  * (print-tranger on the C_NODE service). A treedb can be huge, so
759
776
  * the viewer drills in on demand — see open_json_viewer. */
@@ -771,7 +788,7 @@ function make_toolbar(gobj)
771
788
  ];
772
789
 
773
790
  /*
774
- * Center: find a node.
791
+ * Right: find a node.
775
792
  *
776
793
  * A graph of a few hundred records has no other way in: the only way
777
794
  * to locate one was to read every card. The box highlights every
@@ -821,7 +838,7 @@ function make_toolbar(gobj)
821
838
  ]]);
822
839
  attach_clear($find_control, $find_input);
823
840
 
824
- let center_items = [
841
+ let find_items = [
825
842
  $find_control,
826
843
  /* ONE counted span, not a number beside a word: "1 matches" is
827
844
  * what two spans always produce, because nothing there can see
@@ -844,10 +861,9 @@ function make_toolbar(gobj)
844
861
  ]]
845
862
  ];
846
863
 
847
- /*
848
- * Right, fill in set_mode
849
- */
850
864
  let right_items = [
865
+ ...find_items,
866
+ ...backend_items,
851
867
  ];
852
868
 
853
869
  const $toolbar_header = yui_toolbar({}, [
@@ -406,7 +406,19 @@ function schema_to_graph(gobj)
406
406
  return {nodes: nodes, edges: []};
407
407
  }
408
408
 
409
- let idx = 0;
409
+ /* The palette by ALPHABETICAL rank, as the treedb graph does it:
410
+ * the backend's order is not the same from one load to the next,
411
+ * and a topic's colour must not change with it. The cards keep
412
+ * the backend's order; only the colour is ranked. */
413
+ let rank = {};
414
+ Object.keys(descs).filter((topic) => {
415
+ return topic.substring(0, 2) !== "__";
416
+ }).sort((a, b) => {
417
+ return a.localeCompare(b);
418
+ }).forEach((topic, i) => {
419
+ rank[topic] = i;
420
+ });
421
+
410
422
  for(let topic of Object.keys(descs)) {
411
423
  let is_system = (topic.substring(0, 2) === "__");
412
424
  if(!system && is_system) {
@@ -417,8 +429,7 @@ function schema_to_graph(gobj)
417
429
  if(is_system) {
418
430
  color = SYSTEM_TOPIC_COLOR;
419
431
  } else {
420
- color = topic_colors[idx % topic_colors.length];
421
- idx++;
432
+ color = topic_colors[rank[topic] % topic_colors.length];
422
433
  }
423
434
  cards[topic] = {rows: rows, size: card_size(rows), color: color};
424
435
  }