@yuneta/gobj-ui 7.23.76 → 7.23.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1250,7 +1250,12 @@ a level or two, with a count on every cut.
1250
1250
  - **The toolbar's fold pair** (the same two chevrons as the JSON graph):
1251
1251
  *expand all* opens every hook on all its children — the reader asking for
1252
1252
  the pile on purpose — and *collapse all* leaves the roots. *Refresh* is the
1253
- way back to the default depth.
1253
+ way back to the default depth. **Neither moves the zoom** (since `7.23.78`):
1254
+ a fold is not a reason to change the scale the reader chose, and until then
1255
+ both fitted the whole graph — opening everything zoomed out to a strip and
1256
+ closing it zoomed in on the roots, every time. The camera holds the anchor
1257
+ still when there is one, else the first root: the top of the tree, which is
1258
+ where a JSON viewer keeps the eye when it opens or closes everything.
1254
1259
  - **`dagre` reads LEFT TO RIGHT** now, with explicit `nodesep`/`ranksep`: the
1255
1260
  children of a node are a column beside it and the graph reads like a file
1256
1261
  tree. The ports move to the sides with it (fkeys on the left edge, hooks
@@ -59312,6 +59312,42 @@ function copy_size_to_ports(gobj, same_topic_only) {
59312
59312
  ***************************/
59313
59313
  var PILL_ROW_HEIGHT = 24;
59314
59314
  var MORE_CHIP_SIZE = [76, 26];
59315
+ var MORE_CHIP_SIZE_COMPACT = [40, 22];
59316
+ function more_chip_is_compact(gobj, nd) {
59317
+ let parent_id = nd && nd.data ? nd.data.parent_node : "";
59318
+ if (parent_id) return node_is_compact(gobj, parent_id);
59319
+ return (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "node_mode") === "compact";
59320
+ }
59321
+ function more_chip_style(gobj, nd, color, rest) {
59322
+ let compact = more_chip_is_compact(gobj, nd);
59323
+ let size = compact ? MORE_CHIP_SIZE_COMPACT : MORE_CHIP_SIZE;
59324
+ return {
59325
+ size: [...size],
59326
+ dx: -size[0] / 2,
59327
+ dy: -size[1] / 2,
59328
+ innerHTML: build_more_innerHTML(color, gobj.priv.theme, rest, compact)
59329
+ };
59330
+ }
59331
+ function reshape_more_chips(gobj) {
59332
+ let priv = gobj.priv;
59333
+ let graph = priv.graph;
59334
+ if (!graph) return;
59335
+ let updates = [];
59336
+ for (let nd of graph.getNodeData() || []) {
59337
+ if (!is_more_node(nd)) continue;
59338
+ let child_desc = priv.descs ? priv.descs[nd.data.child_topic] : null;
59339
+ let color = child_desc && child_desc.color || "#94a3b8";
59340
+ updates.push({
59341
+ id: nd.id,
59342
+ style: more_chip_style(gobj, nd, color, nd.data.rest)
59343
+ });
59344
+ }
59345
+ if (updates.length) try {
59346
+ graph.updateNodeData(updates);
59347
+ } catch (e) {
59348
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: cannot reshape the +N chips: ${e}`);
59349
+ }
59350
+ }
59315
59351
  /************************************************************
59316
59352
  * The three card sizes, with room for the pill row when there
59317
59353
  * is one. A chip (pure child) never has pills: it has no hooks.
@@ -59405,25 +59441,26 @@ function pills_signature(gobj, node_key) {
59405
59441
  * the one WORD of the folding, and it is translated at build
59406
59442
  * time: the chips are rebuilt on a language change.
59407
59443
  ************************************************************/
59408
- function build_more_innerHTML(color, theme, rest) {
59444
+ function build_more_innerHTML(color, theme, rest, compact) {
59409
59445
  let c = pill_colors(color, theme);
59446
+ let tail = compact ? "" : `<span style="font-size: 10px; opacity: .8;">&#8230;</span>`;
59410
59447
  return `
59411
59448
  <div class="TREEDB_MORE" title="${(0, _yuneta_gobj_js.escapeHtml)((0, i18next.t)("show more"))}" style="
59412
59449
  box-sizing: border-box; width: 100%; height: 100%;
59413
59450
  display: flex; align-items: center; justify-content: center; gap: 4px;
59414
59451
  background: ${c.bg}; color: ${c.fg}; border: 1px dashed ${c.border};
59415
- border-radius: 13px;
59452
+ border-radius: ${compact ? "6px" : "13px"};
59416
59453
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
59417
- font-size: 12px; font-weight: 700; line-height: 1;
59454
+ font-size: ${compact ? "10px" : "12px"}; font-weight: 700; line-height: 1;
59418
59455
  cursor: pointer; user-select: none;
59419
- ">+${rest}<span style="font-size: 10px; opacity: .8;">&#8230;</span></div>
59456
+ ">+${rest}${tail}</div>
59420
59457
  `;
59421
59458
  }
59422
59459
  function more_innerHTML_of(gobj, nd, theme) {
59423
59460
  if (!is_more_node(nd)) return null;
59424
59461
  let priv = gobj.priv;
59425
59462
  let child_desc = priv.descs ? priv.descs[nd.data.child_topic] : null;
59426
- return build_more_innerHTML(child_desc && child_desc.color || "#94a3b8", theme, nd.data.rest);
59463
+ return build_more_innerHTML(child_desc && child_desc.color || "#94a3b8", theme, nd.data.rest, more_chip_is_compact(gobj, nd));
59427
59464
  }
59428
59465
  /************************************************************
59429
59466
  * The pills live inside an `innerHTML` string, so no handler can
@@ -59648,28 +59685,31 @@ async function reconcile_fold_apply(gobj, opts, visible) {
59648
59685
  nd.data.rest = p.rest;
59649
59686
  graph.updateNodeData([{
59650
59687
  id,
59651
- style: { innerHTML: build_more_innerHTML(color, priv.theme, p.rest) }
59688
+ style: { innerHTML: build_more_innerHTML(color, priv.theme, p.rest, more_chip_is_compact(gobj, nd)) }
59652
59689
  }]);
59653
59690
  }
59654
59691
  continue;
59655
59692
  }
59656
59693
  let xy = get_default_ne_xy(gobj);
59694
+ let parent_node = "";
59695
+ if (p.node_key) {
59696
+ let parent = model.nodes.get(p.node_key);
59697
+ parent_node = build_node_name(gobj, parent.topic_name, parent.id);
59698
+ }
59699
+ let chip_data = {
59700
+ more: p.group_key,
59701
+ child_topic,
59702
+ rest: p.rest,
59703
+ parent_node
59704
+ };
59657
59705
  graph.addNodeData([{
59658
59706
  id,
59659
59707
  type: "html",
59660
- style: {
59708
+ style: Object.assign({
59661
59709
  x: xy,
59662
- y: xy,
59663
- size: [...MORE_CHIP_SIZE],
59664
- dx: -MORE_CHIP_SIZE[0] / 2,
59665
- dy: -MORE_CHIP_SIZE[1] / 2,
59666
- innerHTML: build_more_innerHTML(color, priv.theme, p.rest)
59667
- },
59668
- data: {
59669
- more: p.group_key,
59670
- child_topic,
59671
- rest: p.rest
59672
- }
59710
+ y: xy
59711
+ }, more_chip_style(gobj, { data: chip_data }, color, p.rest)),
59712
+ data: chip_data
59673
59713
  }]);
59674
59714
  if (p.node_key) {
59675
59715
  let parent = model.nodes.get(p.node_key);
@@ -60235,11 +60275,36 @@ function ac_expand_all$1(gobj, event, kw, src) {
60235
60275
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: expand all with no tree loaded`);
60236
60276
  return -1;
60237
60277
  }
60278
+ let keep = fold_keep_node(gobj);
60279
+ let vp = yui_graph_viewport_of(priv.graph, keep);
60238
60280
  fold_expand_all(priv._fold_model, priv._fold_state);
60239
- reconcile_fold(gobj, { fit: true });
60281
+ reconcile_fold(gobj, {
60282
+ keep,
60283
+ keep_vp: vp
60284
+ });
60240
60285
  return 0;
60241
60286
  }
60242
60287
  /************************************************************
60288
+ * The node the camera holds still across a GLOBAL fold: the
60289
+ * anchor when there is one, else the first root -- the top of
60290
+ * the tree, which is where a JSON viewer keeps the eye when it
60291
+ * opens or closes everything. The zoom does not move either way:
60292
+ * a fold is not a reason to change the scale the reader chose,
60293
+ * and until 7.23.78 both buttons fitted the whole graph, so
60294
+ * opening everything zoomed out to a strip and closing it zoomed
60295
+ * in on the roots, every time.
60296
+ ************************************************************/
60297
+ function fold_keep_node(gobj) {
60298
+ let priv = gobj.priv;
60299
+ if (priv.anchor_state === "on" && priv.anchor_id) return priv.anchor_id;
60300
+ let model = priv._fold_model;
60301
+ for (let topic_name of model.topics) {
60302
+ let roots = model.groups.get(fold_root_group_key(topic_name)) || [];
60303
+ if (roots.length) return node_id_of_key(gobj, roots[0]);
60304
+ }
60305
+ return "";
60306
+ }
60307
+ /************************************************************
60243
60308
  * The roots alone.
60244
60309
  ************************************************************/
60245
60310
  function ac_collapse_all$1(gobj, event, kw, src) {
@@ -60248,8 +60313,13 @@ function ac_collapse_all$1(gobj, event, kw, src) {
60248
60313
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: collapse all with no tree loaded`);
60249
60314
  return -1;
60250
60315
  }
60316
+ let keep = fold_keep_node(gobj);
60317
+ let vp = yui_graph_viewport_of(priv.graph, keep);
60251
60318
  fold_collapse_all(priv._fold_model, priv._fold_state);
60252
- reconcile_fold(gobj, { fit: true });
60319
+ reconcile_fold(gobj, {
60320
+ keep,
60321
+ keep_vp: vp
60322
+ });
60253
60323
  return 0;
60254
60324
  }
60255
60325
  /************************************************************
@@ -60303,6 +60373,7 @@ function ac_set_node_mode(gobj, event, kw, src) {
60303
60373
  priv._open_nodes = /* @__PURE__ */ new Set();
60304
60374
  if (!priv.graph || !priv._fold_model) return 0;
60305
60375
  reshape_nodes(gobj, all_record_node_ids(gobj));
60376
+ reshape_more_chips(gobj);
60306
60377
  reconcile_fold(gobj, {
60307
60378
  relayout: true,
60308
60379
  fit: true
@@ -60338,6 +60409,7 @@ function ac_toggle_node_mode$1(gobj, event, kw, src) {
60338
60409
  else priv._open_nodes.add(node_id);
60339
60410
  let vp = yui_graph_viewport_of(priv.graph, node_id);
60340
60411
  reshape_nodes(gobj, [node_id]);
60412
+ reshape_more_chips(gobj);
60341
60413
  if (priv._fold_model) reconcile_fold(gobj, {
60342
60414
  relayout: true,
60343
60415
  keep: node_id,
@@ -59307,6 +59307,42 @@ function copy_size_to_ports(gobj, same_topic_only) {
59307
59307
  ***************************/
59308
59308
  var PILL_ROW_HEIGHT = 24;
59309
59309
  var MORE_CHIP_SIZE = [76, 26];
59310
+ var MORE_CHIP_SIZE_COMPACT = [40, 22];
59311
+ function more_chip_is_compact(gobj, nd) {
59312
+ let parent_id = nd && nd.data ? nd.data.parent_node : "";
59313
+ if (parent_id) return node_is_compact(gobj, parent_id);
59314
+ return gobj_read_str_attr(gobj, "node_mode") === "compact";
59315
+ }
59316
+ function more_chip_style(gobj, nd, color, rest) {
59317
+ let compact = more_chip_is_compact(gobj, nd);
59318
+ let size = compact ? MORE_CHIP_SIZE_COMPACT : MORE_CHIP_SIZE;
59319
+ return {
59320
+ size: [...size],
59321
+ dx: -size[0] / 2,
59322
+ dy: -size[1] / 2,
59323
+ innerHTML: build_more_innerHTML(color, gobj.priv.theme, rest, compact)
59324
+ };
59325
+ }
59326
+ function reshape_more_chips(gobj) {
59327
+ let priv = gobj.priv;
59328
+ let graph = priv.graph;
59329
+ if (!graph) return;
59330
+ let updates = [];
59331
+ for (let nd of graph.getNodeData() || []) {
59332
+ if (!is_more_node(nd)) continue;
59333
+ let child_desc = priv.descs ? priv.descs[nd.data.child_topic] : null;
59334
+ let color = child_desc && child_desc.color || "#94a3b8";
59335
+ updates.push({
59336
+ id: nd.id,
59337
+ style: more_chip_style(gobj, nd, color, nd.data.rest)
59338
+ });
59339
+ }
59340
+ if (updates.length) try {
59341
+ graph.updateNodeData(updates);
59342
+ } catch (e) {
59343
+ log_error(`${gobj_short_name(gobj)}: cannot reshape the +N chips: ${e}`);
59344
+ }
59345
+ }
59310
59346
  /************************************************************
59311
59347
  * The three card sizes, with room for the pill row when there
59312
59348
  * is one. A chip (pure child) never has pills: it has no hooks.
@@ -59400,25 +59436,26 @@ function pills_signature(gobj, node_key) {
59400
59436
  * the one WORD of the folding, and it is translated at build
59401
59437
  * time: the chips are rebuilt on a language change.
59402
59438
  ************************************************************/
59403
- function build_more_innerHTML(color, theme, rest) {
59439
+ function build_more_innerHTML(color, theme, rest, compact) {
59404
59440
  let c = pill_colors(color, theme);
59441
+ let tail = compact ? "" : `<span style="font-size: 10px; opacity: .8;">&#8230;</span>`;
59405
59442
  return `
59406
59443
  <div class="TREEDB_MORE" title="${escapeHtml(t("show more"))}" style="
59407
59444
  box-sizing: border-box; width: 100%; height: 100%;
59408
59445
  display: flex; align-items: center; justify-content: center; gap: 4px;
59409
59446
  background: ${c.bg}; color: ${c.fg}; border: 1px dashed ${c.border};
59410
- border-radius: 13px;
59447
+ border-radius: ${compact ? "6px" : "13px"};
59411
59448
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
59412
- font-size: 12px; font-weight: 700; line-height: 1;
59449
+ font-size: ${compact ? "10px" : "12px"}; font-weight: 700; line-height: 1;
59413
59450
  cursor: pointer; user-select: none;
59414
- ">+${rest}<span style="font-size: 10px; opacity: .8;">&#8230;</span></div>
59451
+ ">+${rest}${tail}</div>
59415
59452
  `;
59416
59453
  }
59417
59454
  function more_innerHTML_of(gobj, nd, theme) {
59418
59455
  if (!is_more_node(nd)) return null;
59419
59456
  let priv = gobj.priv;
59420
59457
  let child_desc = priv.descs ? priv.descs[nd.data.child_topic] : null;
59421
- return build_more_innerHTML(child_desc && child_desc.color || "#94a3b8", theme, nd.data.rest);
59458
+ return build_more_innerHTML(child_desc && child_desc.color || "#94a3b8", theme, nd.data.rest, more_chip_is_compact(gobj, nd));
59422
59459
  }
59423
59460
  /************************************************************
59424
59461
  * The pills live inside an `innerHTML` string, so no handler can
@@ -59643,28 +59680,31 @@ async function reconcile_fold_apply(gobj, opts, visible) {
59643
59680
  nd.data.rest = p.rest;
59644
59681
  graph.updateNodeData([{
59645
59682
  id,
59646
- style: { innerHTML: build_more_innerHTML(color, priv.theme, p.rest) }
59683
+ style: { innerHTML: build_more_innerHTML(color, priv.theme, p.rest, more_chip_is_compact(gobj, nd)) }
59647
59684
  }]);
59648
59685
  }
59649
59686
  continue;
59650
59687
  }
59651
59688
  let xy = get_default_ne_xy(gobj);
59689
+ let parent_node = "";
59690
+ if (p.node_key) {
59691
+ let parent = model.nodes.get(p.node_key);
59692
+ parent_node = build_node_name(gobj, parent.topic_name, parent.id);
59693
+ }
59694
+ let chip_data = {
59695
+ more: p.group_key,
59696
+ child_topic,
59697
+ rest: p.rest,
59698
+ parent_node
59699
+ };
59652
59700
  graph.addNodeData([{
59653
59701
  id,
59654
59702
  type: "html",
59655
- style: {
59703
+ style: Object.assign({
59656
59704
  x: xy,
59657
- y: xy,
59658
- size: [...MORE_CHIP_SIZE],
59659
- dx: -MORE_CHIP_SIZE[0] / 2,
59660
- dy: -MORE_CHIP_SIZE[1] / 2,
59661
- innerHTML: build_more_innerHTML(color, priv.theme, p.rest)
59662
- },
59663
- data: {
59664
- more: p.group_key,
59665
- child_topic,
59666
- rest: p.rest
59667
- }
59705
+ y: xy
59706
+ }, more_chip_style(gobj, { data: chip_data }, color, p.rest)),
59707
+ data: chip_data
59668
59708
  }]);
59669
59709
  if (p.node_key) {
59670
59710
  let parent = model.nodes.get(p.node_key);
@@ -60230,11 +60270,36 @@ function ac_expand_all$1(gobj, event, kw, src) {
60230
60270
  log_error(`${gobj_short_name(gobj)}: expand all with no tree loaded`);
60231
60271
  return -1;
60232
60272
  }
60273
+ let keep = fold_keep_node(gobj);
60274
+ let vp = yui_graph_viewport_of(priv.graph, keep);
60233
60275
  fold_expand_all(priv._fold_model, priv._fold_state);
60234
- reconcile_fold(gobj, { fit: true });
60276
+ reconcile_fold(gobj, {
60277
+ keep,
60278
+ keep_vp: vp
60279
+ });
60235
60280
  return 0;
60236
60281
  }
60237
60282
  /************************************************************
60283
+ * The node the camera holds still across a GLOBAL fold: the
60284
+ * anchor when there is one, else the first root -- the top of
60285
+ * the tree, which is where a JSON viewer keeps the eye when it
60286
+ * opens or closes everything. The zoom does not move either way:
60287
+ * a fold is not a reason to change the scale the reader chose,
60288
+ * and until 7.23.78 both buttons fitted the whole graph, so
60289
+ * opening everything zoomed out to a strip and closing it zoomed
60290
+ * in on the roots, every time.
60291
+ ************************************************************/
60292
+ function fold_keep_node(gobj) {
60293
+ let priv = gobj.priv;
60294
+ if (priv.anchor_state === "on" && priv.anchor_id) return priv.anchor_id;
60295
+ let model = priv._fold_model;
60296
+ for (let topic_name of model.topics) {
60297
+ let roots = model.groups.get(fold_root_group_key(topic_name)) || [];
60298
+ if (roots.length) return node_id_of_key(gobj, roots[0]);
60299
+ }
60300
+ return "";
60301
+ }
60302
+ /************************************************************
60238
60303
  * The roots alone.
60239
60304
  ************************************************************/
60240
60305
  function ac_collapse_all$1(gobj, event, kw, src) {
@@ -60243,8 +60308,13 @@ function ac_collapse_all$1(gobj, event, kw, src) {
60243
60308
  log_error(`${gobj_short_name(gobj)}: collapse all with no tree loaded`);
60244
60309
  return -1;
60245
60310
  }
60311
+ let keep = fold_keep_node(gobj);
60312
+ let vp = yui_graph_viewport_of(priv.graph, keep);
60246
60313
  fold_collapse_all(priv._fold_model, priv._fold_state);
60247
- reconcile_fold(gobj, { fit: true });
60314
+ reconcile_fold(gobj, {
60315
+ keep,
60316
+ keep_vp: vp
60317
+ });
60248
60318
  return 0;
60249
60319
  }
60250
60320
  /************************************************************
@@ -60298,6 +60368,7 @@ function ac_set_node_mode(gobj, event, kw, src) {
60298
60368
  priv._open_nodes = /* @__PURE__ */ new Set();
60299
60369
  if (!priv.graph || !priv._fold_model) return 0;
60300
60370
  reshape_nodes(gobj, all_record_node_ids(gobj));
60371
+ reshape_more_chips(gobj);
60301
60372
  reconcile_fold(gobj, {
60302
60373
  relayout: true,
60303
60374
  fit: true
@@ -60333,6 +60404,7 @@ function ac_toggle_node_mode$1(gobj, event, kw, src) {
60333
60404
  else priv._open_nodes.add(node_id);
60334
60405
  let vp = yui_graph_viewport_of(priv.graph, node_id);
60335
60406
  reshape_nodes(gobj, [node_id]);
60407
+ reshape_more_chips(gobj);
60336
60408
  if (priv._fold_model) reconcile_fold(gobj, {
60337
60409
  relayout: true,
60338
60410
  keep: node_id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.76",
3
+ "version": "7.23.78",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -124,6 +124,7 @@ import {
124
124
  fold_pending_groups,
125
125
  fold_node_key,
126
126
  fold_split_group_key,
127
+ fold_root_group_key,
127
128
  } from "./treedb_fold_model.js";
128
129
  import {layout_tree, layout_radial} from "./treedb_layout.js";
129
130
 
@@ -7666,6 +7667,61 @@ function copy_size_to_ports(gobj, same_topic_only)
7666
7667
  /* A card with pills is this much taller; the row sits under the subtitle. */
7667
7668
  const PILL_ROW_HEIGHT = 24;
7668
7669
  const MORE_CHIP_SIZE = [76, 26];
7670
+ /* The chip beside CLOSED nodes: a square of 22 next to a chip of 76
7671
+ * read as the biggest thing in the row, so it takes the closed
7672
+ * scale -- the count and nothing else. */
7673
+ const MORE_CHIP_SIZE_COMPACT = [40, 22];
7674
+
7675
+ /* A `+N` chip follows the shape of the card it continues: closed
7676
+ * beside a square, open beside a card. A root group's chip has no
7677
+ * parent and follows the mode. */
7678
+ function more_chip_is_compact(gobj, nd)
7679
+ {
7680
+ let parent_id = nd && nd.data? nd.data.parent_node : "";
7681
+ if(parent_id) {
7682
+ return node_is_compact(gobj, parent_id);
7683
+ }
7684
+ return gobj_read_str_attr(gobj, "node_mode") === "compact";
7685
+ }
7686
+
7687
+ function more_chip_style(gobj, nd, color, rest)
7688
+ {
7689
+ let compact = more_chip_is_compact(gobj, nd);
7690
+ let size = compact? MORE_CHIP_SIZE_COMPACT : MORE_CHIP_SIZE;
7691
+ return {
7692
+ size: [...size],
7693
+ dx: -size[0] / 2,
7694
+ dy: -size[1] / 2,
7695
+ innerHTML: build_more_innerHTML(color, gobj.priv.theme, rest, compact),
7696
+ };
7697
+ }
7698
+
7699
+ /* Every `+N` chip re-sized to the shape of its parent: after a
7700
+ * change of mode, or of one node. */
7701
+ function reshape_more_chips(gobj)
7702
+ {
7703
+ let priv = gobj.priv;
7704
+ let graph = priv.graph;
7705
+ if(!graph) {
7706
+ return;
7707
+ }
7708
+ let updates = [];
7709
+ for(let nd of (graph.getNodeData() || [])) {
7710
+ if(!is_more_node(nd)) {
7711
+ continue;
7712
+ }
7713
+ let child_desc = priv.descs? priv.descs[nd.data.child_topic] : null;
7714
+ let color = (child_desc && child_desc.color) || "#94a3b8";
7715
+ updates.push({id: nd.id, style: more_chip_style(gobj, nd, color, nd.data.rest)});
7716
+ }
7717
+ if(updates.length) {
7718
+ try {
7719
+ graph.updateNodeData(updates);
7720
+ } catch(e) {
7721
+ log_error(`${gobj_short_name(gobj)}: cannot reshape the +N chips: ${e}`);
7722
+ }
7723
+ }
7724
+ }
7669
7725
 
7670
7726
  /************************************************************
7671
7727
  * The three card sizes, with room for the pill row when there
@@ -7805,19 +7861,20 @@ function pills_signature(gobj, node_key)
7805
7861
  * the one WORD of the folding, and it is translated at build
7806
7862
  * time: the chips are rebuilt on a language change.
7807
7863
  ************************************************************/
7808
- function build_more_innerHTML(color, theme, rest)
7864
+ function build_more_innerHTML(color, theme, rest, compact)
7809
7865
  {
7810
7866
  let c = pill_colors(color, theme);
7867
+ let tail = compact? "" : `<span style="font-size: 10px; opacity: .8;">&#8230;</span>`;
7811
7868
  return `
7812
7869
  <div class="TREEDB_MORE" title="${escapeHtml(t('show more'))}" style="
7813
7870
  box-sizing: border-box; width: 100%; height: 100%;
7814
7871
  display: flex; align-items: center; justify-content: center; gap: 4px;
7815
7872
  background: ${c.bg}; color: ${c.fg}; border: 1px dashed ${c.border};
7816
- border-radius: 13px;
7873
+ border-radius: ${compact? "6px" : "13px"};
7817
7874
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
7818
- font-size: 12px; font-weight: 700; line-height: 1;
7875
+ font-size: ${compact? "10px" : "12px"}; font-weight: 700; line-height: 1;
7819
7876
  cursor: pointer; user-select: none;
7820
- ">+${rest}<span style="font-size: 10px; opacity: .8;">&#8230;</span></div>
7877
+ ">+${rest}${tail}</div>
7821
7878
  `;
7822
7879
  }
7823
7880
 
@@ -7829,7 +7886,7 @@ function more_innerHTML_of(gobj, nd, theme)
7829
7886
  let priv = gobj.priv;
7830
7887
  let child_desc = priv.descs? priv.descs[nd.data.child_topic] : null;
7831
7888
  let color = (child_desc && child_desc.color) || "#94a3b8";
7832
- return build_more_innerHTML(color, theme, nd.data.rest);
7889
+ return build_more_innerHTML(color, theme, nd.data.rest, more_chip_is_compact(gobj, nd));
7833
7890
  }
7834
7891
 
7835
7892
  /************************************************************
@@ -8147,27 +8204,30 @@ async function reconcile_fold_apply(gobj, opts, visible)
8147
8204
  nd.data.rest = p.rest;
8148
8205
  graph.updateNodeData([{
8149
8206
  id: id,
8150
- style: {innerHTML: build_more_innerHTML(color, priv.theme, p.rest)},
8207
+ style: {innerHTML: build_more_innerHTML(
8208
+ color, priv.theme, p.rest, more_chip_is_compact(gobj, nd)
8209
+ )},
8151
8210
  }]);
8152
8211
  }
8153
8212
  continue;
8154
8213
  }
8155
8214
  let xy = get_default_ne_xy(gobj);
8215
+ let parent_node = "";
8216
+ if(p.node_key) {
8217
+ let parent = model.nodes.get(p.node_key);
8218
+ parent_node = build_node_name(gobj, parent.topic_name, parent.id);
8219
+ }
8220
+ let chip_data = {
8221
+ more: p.group_key,
8222
+ child_topic: child_topic,
8223
+ rest: p.rest,
8224
+ parent_node: parent_node,
8225
+ };
8156
8226
  graph.addNodeData([{
8157
8227
  id: id,
8158
8228
  type: 'html',
8159
- style: {
8160
- x: xy, y: xy,
8161
- size: [...MORE_CHIP_SIZE],
8162
- dx: -MORE_CHIP_SIZE[0] / 2,
8163
- dy: -MORE_CHIP_SIZE[1] / 2,
8164
- innerHTML: build_more_innerHTML(color, priv.theme, p.rest),
8165
- },
8166
- data: {
8167
- more: p.group_key,
8168
- child_topic: child_topic,
8169
- rest: p.rest,
8170
- },
8229
+ style: Object.assign({x: xy, y: xy}, more_chip_style(gobj, {data: chip_data}, color, p.rest)),
8230
+ data: chip_data,
8171
8231
  }]);
8172
8232
  if(p.node_key) {
8173
8233
  let parent = model.nodes.get(p.node_key);
@@ -9020,12 +9080,41 @@ function ac_expand_all(gobj, event, kw, src)
9020
9080
  log_error(`${gobj_short_name(gobj)}: expand all with no tree loaded`);
9021
9081
  return -1;
9022
9082
  }
9083
+ let keep = fold_keep_node(gobj);
9084
+ let vp = yui_graph_viewport_of(priv.graph, keep);
9023
9085
  fold_expand_all(priv._fold_model, priv._fold_state);
9024
- reconcile_fold(gobj, {fit: true});
9086
+ reconcile_fold(gobj, {keep: keep, keep_vp: vp});
9025
9087
 
9026
9088
  return 0;
9027
9089
  }
9028
9090
 
9091
+ /************************************************************
9092
+ * The node the camera holds still across a GLOBAL fold: the
9093
+ * anchor when there is one, else the first root -- the top of
9094
+ * the tree, which is where a JSON viewer keeps the eye when it
9095
+ * opens or closes everything. The zoom does not move either way:
9096
+ * a fold is not a reason to change the scale the reader chose,
9097
+ * and until 7.23.78 both buttons fitted the whole graph, so
9098
+ * opening everything zoomed out to a strip and closing it zoomed
9099
+ * in on the roots, every time.
9100
+ ************************************************************/
9101
+ function fold_keep_node(gobj)
9102
+ {
9103
+ let priv = gobj.priv;
9104
+
9105
+ if(priv.anchor_state === "on" && priv.anchor_id) {
9106
+ return priv.anchor_id;
9107
+ }
9108
+ let model = priv._fold_model;
9109
+ for(let topic_name of model.topics) {
9110
+ let roots = model.groups.get(fold_root_group_key(topic_name)) || [];
9111
+ if(roots.length) {
9112
+ return node_id_of_key(gobj, roots[0]);
9113
+ }
9114
+ }
9115
+ return "";
9116
+ }
9117
+
9029
9118
  /************************************************************
9030
9119
  * The roots alone.
9031
9120
  ************************************************************/
@@ -9037,8 +9126,10 @@ function ac_collapse_all(gobj, event, kw, src)
9037
9126
  log_error(`${gobj_short_name(gobj)}: collapse all with no tree loaded`);
9038
9127
  return -1;
9039
9128
  }
9129
+ let keep = fold_keep_node(gobj);
9130
+ let vp = yui_graph_viewport_of(priv.graph, keep);
9040
9131
  fold_collapse_all(priv._fold_model, priv._fold_state);
9041
- reconcile_fold(gobj, {fit: true});
9132
+ reconcile_fold(gobj, {keep: keep, keep_vp: vp});
9042
9133
 
9043
9134
  return 0;
9044
9135
  }
@@ -9113,6 +9204,7 @@ function ac_set_node_mode(gobj, event, kw, src)
9113
9204
  return 0; /* applied when the records arrive */
9114
9205
  }
9115
9206
  reshape_nodes(gobj, all_record_node_ids(gobj));
9207
+ reshape_more_chips(gobj);
9116
9208
  reconcile_fold(gobj, {relayout: true, fit: true});
9117
9209
  return 0;
9118
9210
  }
@@ -9159,6 +9251,7 @@ function ac_toggle_node_mode(gobj, event, kw, src)
9159
9251
  }
9160
9252
  let vp = yui_graph_viewport_of(priv.graph, node_id);
9161
9253
  reshape_nodes(gobj, [node_id]);
9254
+ reshape_more_chips(gobj);
9162
9255
  if(priv._fold_model) {
9163
9256
  reconcile_fold(gobj, {relayout: true, keep: node_id, keep_vp: vp});
9164
9257
  }