@yuneta/gobj-ui 7.23.2 → 7.23.3

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
@@ -448,6 +448,13 @@ stays. Note the close contract: `C_YUI_WINDOW.close_window()` calls `on_close`
448
448
  and THEN destroys itself, so a host must drop its reference rather than destroy
449
449
  the window too.
450
450
 
451
+ Its camera speaks the **same vocabulary as the treedb graph** (`7.23.3`): zoom
452
+ in / zoom out, a live **zoom readout**, `yi-fit` for fit — the same corner
453
+ brackets the SVG sprite draws for `g6-icon-fit` — and the WRITTEN `1:1` for
454
+ actual size, which is written in every editor that offers it and never drawn.
455
+ The readout hangs off G6's `aftertransform`, the one hook that also covers the
456
+ wheel.
457
+
451
458
  The graph carries **its own** toolbar, a **layout picker**
452
459
  (`EV_CHANGE_LAYOUT {layout}`: `vertical tree`, `dagre top-down`,
453
460
  `dagre left-right` — the layout and the edge type move together), and since
@@ -9556,6 +9556,7 @@ var PRIVATE_DATA$11 = {
9556
9556
  find_timer: null,
9557
9557
  fold_listener: null,
9558
9558
  $layout_select: null,
9559
+ $zoom_readout: null,
9559
9560
  $find_input: null,
9560
9561
  $find_result: null,
9561
9562
  $find_count: null
@@ -9753,6 +9754,54 @@ function destroy_ui$6(gobj) {
9753
9754
  }
9754
9755
  }
9755
9756
  /************************************************************
9757
+ * One camera button.
9758
+ ************************************************************/
9759
+ function camera_button(gobj, icon, event_name, label_key, wide) {
9760
+ return [
9761
+ "button",
9762
+ {
9763
+ class: `button ${event_name}`,
9764
+ type: "button",
9765
+ style: {
9766
+ height: wide,
9767
+ width: "2.5em"
9768
+ },
9769
+ title: (0, i18next.t)(label_key),
9770
+ "data-i18n-title": label_key,
9771
+ "aria-label": (0, i18next.t)(label_key),
9772
+ "data-i18n-aria-label": label_key
9773
+ },
9774
+ ["i", {
9775
+ style: "font-size:1.5em; color:inherit;",
9776
+ class: icon
9777
+ }],
9778
+ { click: (evt) => {
9779
+ evt.stopPropagation();
9780
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, event_name, { evt }, gobj);
9781
+ } }
9782
+ ];
9783
+ }
9784
+ /************************************************************
9785
+ * The zoom, as a percentage. What the two magnifiers
9786
+ * change: an editor that offers a zoom shows the number.
9787
+ ************************************************************/
9788
+ function zoom_percent_text$1(gobj) {
9789
+ let graph = gobj.priv.graph;
9790
+ if (!graph) return "100%";
9791
+ try {
9792
+ return Math.round(graph.getZoom() * 100) + "%";
9793
+ } catch (e) {
9794
+ return "100%";
9795
+ }
9796
+ }
9797
+ /************************************************************
9798
+ * Repaint the readout after anything that moves the camera.
9799
+ ************************************************************/
9800
+ function update_zoom_readout$1(gobj) {
9801
+ let priv = gobj.priv;
9802
+ if (priv.$zoom_readout) priv.$zoom_readout.textContent = zoom_percent_text$1(gobj);
9803
+ }
9804
+ /************************************************************
9756
9805
  * One fold button. `open` rotates the chevron to the open
9757
9806
  * state, mirroring the per-node toggle of the lazy tree.
9758
9807
  ************************************************************/
@@ -9832,62 +9881,52 @@ function make_toolbar$3(gobj) {
9832
9881
  ]]
9833
9882
  ]];
9834
9883
  let center_items = [];
9835
- for (let item of [
9836
- [
9837
- "yi-magnifying-glass-plus",
9838
- "EV_ZOOM_IN",
9839
- false,
9840
- "i"
9841
- ],
9842
- [
9843
- "yi-magnifying-glass",
9844
- "EV_ZOOM_RESET",
9845
- false,
9846
- "i"
9847
- ],
9848
- [
9849
- "yi-magnifying-glass-minus",
9850
- "EV_ZOOM_OUT",
9851
- false,
9852
- "i"
9853
- ],
9854
- [
9855
- "yi-arrows-to-eye",
9856
- "EV_CENTER",
9857
- false,
9858
- "i"
9859
- ],
9860
- [
9861
- "yi-arrows-rotate",
9862
- "EV_REFRESH",
9863
- false,
9864
- "i"
9865
- ]
9866
- ]) {
9867
- let icon_name = item[0];
9868
- let event_name = item[1];
9869
- let disabled = item[2];
9870
- let button = {
9871
- class: `button ${event_name}`,
9884
+ for (let item of [[
9885
+ "yi-magnifying-glass-plus",
9886
+ "EV_ZOOM_IN",
9887
+ "zoom in"
9888
+ ], [
9889
+ "yi-magnifying-glass-minus",
9890
+ "EV_ZOOM_OUT",
9891
+ "zoom out"
9892
+ ]]) center_items.push(camera_button(gobj, item[0], item[1], item[2], toolbar_wide));
9893
+ priv.$zoom_readout = (0, _yuneta_gobj_js.createElement2)([
9894
+ "span",
9895
+ {
9896
+ class: "JSON_GRAPH_ZOOM_LEVEL is-flex is-align-items-center px-2 has-text-grey",
9897
+ style: "font-size:.85rem; min-width:3.5em; justify-content:center;",
9898
+ title: (0, i18next.t)("zoom level"),
9899
+ "data-i18n-title": "zoom level"
9900
+ },
9901
+ zoom_percent_text$1(gobj)
9902
+ ]);
9903
+ center_items.push(priv.$zoom_readout);
9904
+ center_items.push(camera_button(gobj, "yi-fit", "EV_CENTER", "auto fit", toolbar_wide));
9905
+ center_items.push([
9906
+ "button",
9907
+ {
9908
+ class: "button EV_ZOOM_RESET",
9909
+ type: "button",
9872
9910
  style: {
9873
9911
  height: toolbar_wide,
9874
9912
  width: "2.5em"
9875
- }
9876
- };
9877
- if (disabled) button.disabled = true;
9878
- center_items.push([
9879
- "button",
9880
- button,
9881
- ["i", {
9882
- style: "font-size:1.5em; color:inherit;",
9883
- class: icon_name
9884
- }],
9885
- { click: (evt) => {
9886
- evt.stopPropagation();
9887
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, event_name, { evt }, gobj);
9888
- } }
9889
- ]);
9890
- }
9913
+ },
9914
+ title: (0, i18next.t)("actual size"),
9915
+ "data-i18n-title": "actual size",
9916
+ "aria-label": (0, i18next.t)("actual size"),
9917
+ "data-i18n-aria-label": "actual size"
9918
+ },
9919
+ [
9920
+ "span",
9921
+ { style: "font-weight:700;" },
9922
+ "1:1"
9923
+ ],
9924
+ { click: (evt) => {
9925
+ evt.stopPropagation();
9926
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_ZOOM_RESET", { evt }, gobj);
9927
+ } }
9928
+ ]);
9929
+ center_items.push(camera_button(gobj, "yi-arrows-rotate", "EV_REFRESH", "refresh", toolbar_wide));
9891
9930
  let current_layout = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "layout") || DEFAULT_LAYOUT;
9892
9931
  let options = Object.keys(LAYOUTS$1).map(function(key) {
9893
9932
  let attrs = {
@@ -9971,6 +10010,9 @@ function build_graph$3(gobj) {
9971
10010
  graph.on(_antv_g6.NodeEvent.CLICK, (evt) => {
9972
10011
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_NODE_CLICK", { evt }, gobj);
9973
10012
  });
10013
+ graph.on("aftertransform", () => {
10014
+ update_zoom_readout$1(gobj);
10015
+ });
9974
10016
  graph.on(_antv_g6.CanvasEvent.CLICK, (evt) => {});
9975
10017
  if (typeof ResizeObserver !== "undefined") {
9976
10018
  let $canvas = document.getElementById(priv.canvas_id);
@@ -10230,6 +10272,7 @@ function load_json(gobj) {
10230
10272
  });
10231
10273
  graph.render().then(() => {
10232
10274
  if (!preserve_view) graph.fitView();
10275
+ update_zoom_readout$1(gobj);
10233
10276
  });
10234
10277
  }
10235
10278
  }
@@ -9551,6 +9551,7 @@ var PRIVATE_DATA$11 = {
9551
9551
  find_timer: null,
9552
9552
  fold_listener: null,
9553
9553
  $layout_select: null,
9554
+ $zoom_readout: null,
9554
9555
  $find_input: null,
9555
9556
  $find_result: null,
9556
9557
  $find_count: null
@@ -9748,6 +9749,54 @@ function destroy_ui$6(gobj) {
9748
9749
  }
9749
9750
  }
9750
9751
  /************************************************************
9752
+ * One camera button.
9753
+ ************************************************************/
9754
+ function camera_button(gobj, icon, event_name, label_key, wide) {
9755
+ return [
9756
+ "button",
9757
+ {
9758
+ class: `button ${event_name}`,
9759
+ type: "button",
9760
+ style: {
9761
+ height: wide,
9762
+ width: "2.5em"
9763
+ },
9764
+ title: t(label_key),
9765
+ "data-i18n-title": label_key,
9766
+ "aria-label": t(label_key),
9767
+ "data-i18n-aria-label": label_key
9768
+ },
9769
+ ["i", {
9770
+ style: "font-size:1.5em; color:inherit;",
9771
+ class: icon
9772
+ }],
9773
+ { click: (evt) => {
9774
+ evt.stopPropagation();
9775
+ gobj_send_event(gobj, event_name, { evt }, gobj);
9776
+ } }
9777
+ ];
9778
+ }
9779
+ /************************************************************
9780
+ * The zoom, as a percentage. What the two magnifiers
9781
+ * change: an editor that offers a zoom shows the number.
9782
+ ************************************************************/
9783
+ function zoom_percent_text$1(gobj) {
9784
+ let graph = gobj.priv.graph;
9785
+ if (!graph) return "100%";
9786
+ try {
9787
+ return Math.round(graph.getZoom() * 100) + "%";
9788
+ } catch (e) {
9789
+ return "100%";
9790
+ }
9791
+ }
9792
+ /************************************************************
9793
+ * Repaint the readout after anything that moves the camera.
9794
+ ************************************************************/
9795
+ function update_zoom_readout$1(gobj) {
9796
+ let priv = gobj.priv;
9797
+ if (priv.$zoom_readout) priv.$zoom_readout.textContent = zoom_percent_text$1(gobj);
9798
+ }
9799
+ /************************************************************
9751
9800
  * One fold button. `open` rotates the chevron to the open
9752
9801
  * state, mirroring the per-node toggle of the lazy tree.
9753
9802
  ************************************************************/
@@ -9827,62 +9876,52 @@ function make_toolbar$3(gobj) {
9827
9876
  ]]
9828
9877
  ]];
9829
9878
  let center_items = [];
9830
- for (let item of [
9831
- [
9832
- "yi-magnifying-glass-plus",
9833
- "EV_ZOOM_IN",
9834
- false,
9835
- "i"
9836
- ],
9837
- [
9838
- "yi-magnifying-glass",
9839
- "EV_ZOOM_RESET",
9840
- false,
9841
- "i"
9842
- ],
9843
- [
9844
- "yi-magnifying-glass-minus",
9845
- "EV_ZOOM_OUT",
9846
- false,
9847
- "i"
9848
- ],
9849
- [
9850
- "yi-arrows-to-eye",
9851
- "EV_CENTER",
9852
- false,
9853
- "i"
9854
- ],
9855
- [
9856
- "yi-arrows-rotate",
9857
- "EV_REFRESH",
9858
- false,
9859
- "i"
9860
- ]
9861
- ]) {
9862
- let icon_name = item[0];
9863
- let event_name = item[1];
9864
- let disabled = item[2];
9865
- let button = {
9866
- class: `button ${event_name}`,
9879
+ for (let item of [[
9880
+ "yi-magnifying-glass-plus",
9881
+ "EV_ZOOM_IN",
9882
+ "zoom in"
9883
+ ], [
9884
+ "yi-magnifying-glass-minus",
9885
+ "EV_ZOOM_OUT",
9886
+ "zoom out"
9887
+ ]]) center_items.push(camera_button(gobj, item[0], item[1], item[2], toolbar_wide));
9888
+ priv.$zoom_readout = createElement2([
9889
+ "span",
9890
+ {
9891
+ class: "JSON_GRAPH_ZOOM_LEVEL is-flex is-align-items-center px-2 has-text-grey",
9892
+ style: "font-size:.85rem; min-width:3.5em; justify-content:center;",
9893
+ title: t("zoom level"),
9894
+ "data-i18n-title": "zoom level"
9895
+ },
9896
+ zoom_percent_text$1(gobj)
9897
+ ]);
9898
+ center_items.push(priv.$zoom_readout);
9899
+ center_items.push(camera_button(gobj, "yi-fit", "EV_CENTER", "auto fit", toolbar_wide));
9900
+ center_items.push([
9901
+ "button",
9902
+ {
9903
+ class: "button EV_ZOOM_RESET",
9904
+ type: "button",
9867
9905
  style: {
9868
9906
  height: toolbar_wide,
9869
9907
  width: "2.5em"
9870
- }
9871
- };
9872
- if (disabled) button.disabled = true;
9873
- center_items.push([
9874
- "button",
9875
- button,
9876
- ["i", {
9877
- style: "font-size:1.5em; color:inherit;",
9878
- class: icon_name
9879
- }],
9880
- { click: (evt) => {
9881
- evt.stopPropagation();
9882
- gobj_send_event(gobj, event_name, { evt }, gobj);
9883
- } }
9884
- ]);
9885
- }
9908
+ },
9909
+ title: t("actual size"),
9910
+ "data-i18n-title": "actual size",
9911
+ "aria-label": t("actual size"),
9912
+ "data-i18n-aria-label": "actual size"
9913
+ },
9914
+ [
9915
+ "span",
9916
+ { style: "font-weight:700;" },
9917
+ "1:1"
9918
+ ],
9919
+ { click: (evt) => {
9920
+ evt.stopPropagation();
9921
+ gobj_send_event(gobj, "EV_ZOOM_RESET", { evt }, gobj);
9922
+ } }
9923
+ ]);
9924
+ center_items.push(camera_button(gobj, "yi-arrows-rotate", "EV_REFRESH", "refresh", toolbar_wide));
9886
9925
  let current_layout = gobj_read_str_attr(gobj, "layout") || DEFAULT_LAYOUT;
9887
9926
  let options = Object.keys(LAYOUTS$1).map(function(key) {
9888
9927
  let attrs = {
@@ -9966,6 +10005,9 @@ function build_graph$3(gobj) {
9966
10005
  graph.on(NodeEvent.CLICK, (evt) => {
9967
10006
  gobj_send_event(gobj, "EV_NODE_CLICK", { evt }, gobj);
9968
10007
  });
10008
+ graph.on("aftertransform", () => {
10009
+ update_zoom_readout$1(gobj);
10010
+ });
9969
10011
  graph.on(CanvasEvent.CLICK, (evt) => {});
9970
10012
  if (typeof ResizeObserver !== "undefined") {
9971
10013
  let $canvas = document.getElementById(priv.canvas_id);
@@ -10225,6 +10267,7 @@ function load_json(gobj) {
10225
10267
  });
10226
10268
  graph.render().then(() => {
10227
10269
  if (!preserve_view) graph.fitView();
10270
+ update_zoom_readout$1(gobj);
10228
10271
  });
10229
10272
  }
10230
10273
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.2",
3
+ "version": "7.23.3",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -179,6 +179,7 @@ let PRIVATE_DATA = {
179
179
  find_timer: null, // rate-limits the find box
180
180
  fold_listener: null, // delegated click on the card fold handles
181
181
  $layout_select: null, // the layout picker
182
+ $zoom_readout: null, // the zoom percentage
182
183
  $find_input: null,
183
184
  $find_result: null,
184
185
  $find_count: null,
@@ -454,6 +455,53 @@ function destroy_ui(gobj)
454
455
  }
455
456
  }
456
457
 
458
+ /************************************************************
459
+ * One camera button.
460
+ ************************************************************/
461
+ function camera_button(gobj, icon, event_name, label_key, wide)
462
+ {
463
+ return ['button', {class: `button ${event_name}`, type: 'button',
464
+ style: {height: wide, width: '2.5em'},
465
+ title: t(label_key), 'data-i18n-title': label_key,
466
+ 'aria-label': t(label_key), 'data-i18n-aria-label': label_key},
467
+ ['i', {style: 'font-size:1.5em; color:inherit;', class: icon}],
468
+ {
469
+ click: (evt) => {
470
+ evt.stopPropagation();
471
+ gobj_send_event(gobj, event_name, {evt: evt}, gobj);
472
+ }
473
+ }
474
+ ];
475
+ }
476
+
477
+ /************************************************************
478
+ * The zoom, as a percentage. What the two magnifiers
479
+ * change: an editor that offers a zoom shows the number.
480
+ ************************************************************/
481
+ function zoom_percent_text(gobj)
482
+ {
483
+ let graph = gobj.priv.graph;
484
+ if(!graph) {
485
+ return "100%";
486
+ }
487
+ try {
488
+ return Math.round(graph.getZoom() * 100) + "%";
489
+ } catch(e) {
490
+ return "100%";
491
+ }
492
+ }
493
+
494
+ /************************************************************
495
+ * Repaint the readout after anything that moves the camera.
496
+ ************************************************************/
497
+ function update_zoom_readout(gobj)
498
+ {
499
+ let priv = gobj.priv;
500
+ if(priv.$zoom_readout) {
501
+ priv.$zoom_readout.textContent = zoom_percent_text(gobj);
502
+ }
503
+ }
504
+
457
505
  /************************************************************
458
506
  * One fold button. `open` rotates the chevron to the open
459
507
  * state, mirroring the per-node toggle of the lazy tree.
@@ -544,39 +592,56 @@ function make_toolbar(gobj)
544
592
  ];
545
593
  let center_items = [];
546
594
 
595
+ /*
596
+ * The camera, in the SAME vocabulary the treedb graph uses
597
+ * (`c_g6_nodes_tree.js`): zoom in / zoom out, then what those two
598
+ * change — the zoom READOUT, because without it "1:1" is a jump to
599
+ * a value nobody was told — then fit, then actual size.
600
+ *
601
+ * Two of these used to be a different picture for the same action
602
+ * in a sibling view, which is the worst thing an icon can be:
603
+ * `EV_ZOOM_RESET` was a bare magnifier and `EV_CENTER` was
604
+ * `yi-arrows-to-eye`. Now fit is the same bracket glyph as the
605
+ * sprite's `g6-icon-fit`, and actual size is WRITTEN — `1:1` is
606
+ * written in every editor that offers it, never drawn, because
607
+ * there is no glyph for it.
608
+ */
547
609
  let c_icons = [
548
- ["yi-magnifying-glass-plus", "EV_ZOOM_IN", false, 'i'],
549
- ["yi-magnifying-glass", "EV_ZOOM_RESET", false, 'i'],
550
- ["yi-magnifying-glass-minus","EV_ZOOM_OUT", false, 'i'],
551
- ["yi-arrows-to-eye", "EV_CENTER", false, 'i'],
552
- ["yi-arrows-rotate", "EV_REFRESH", false, 'i'],
610
+ ["yi-magnifying-glass-plus", "EV_ZOOM_IN", "zoom in"],
611
+ ["yi-magnifying-glass-minus", "EV_ZOOM_OUT", "zoom out"],
553
612
  ];
554
613
 
555
614
  for(let item of c_icons) {
556
- let icon_name = item[0];
557
- let event_name = item[1];
558
- let disabled = item[2];
559
-
560
- let button = {
561
- class: `button ${event_name}`,
562
- style: {height: toolbar_wide, width: '2.5em'}
563
- };
564
- if(disabled) {
565
- button.disabled = true;
566
- }
567
- center_items.push(
568
- ['button', button,
569
- ['i', {style: 'font-size:1.5em; color:inherit;', class: icon_name}],
570
- {
571
- click: (evt) => {
572
- evt.stopPropagation();
573
- gobj_send_event(gobj, event_name, {evt: evt}, gobj);
574
- }
575
- }
576
- ]
577
- );
615
+ center_items.push(camera_button(gobj, item[0], item[1], item[2], toolbar_wide));
578
616
  }
579
617
 
618
+ priv.$zoom_readout = createElement2(
619
+ ['span', {class: 'JSON_GRAPH_ZOOM_LEVEL is-flex is-align-items-center px-2 has-text-grey',
620
+ style: 'font-size:.85rem; min-width:3.5em; justify-content:center;',
621
+ title: t('zoom level'), 'data-i18n-title': 'zoom level'},
622
+ zoom_percent_text(gobj)]
623
+ );
624
+ center_items.push(priv.$zoom_readout);
625
+
626
+ center_items.push(camera_button(gobj, "yi-fit", "EV_CENTER", "auto fit", toolbar_wide));
627
+
628
+ /* Written, not drawn — see above. */
629
+ center_items.push(
630
+ ['button', {class: 'button EV_ZOOM_RESET', type: 'button',
631
+ style: {height: toolbar_wide, width: '2.5em'},
632
+ title: t('actual size'), 'data-i18n-title': 'actual size',
633
+ 'aria-label': t('actual size'), 'data-i18n-aria-label': 'actual size'},
634
+ ['span', {style: 'font-weight:700;'}, '1:1'],
635
+ {
636
+ click: (evt) => {
637
+ evt.stopPropagation();
638
+ gobj_send_event(gobj, "EV_ZOOM_RESET", {evt: evt}, gobj);
639
+ }
640
+ }]
641
+ );
642
+
643
+ center_items.push(camera_button(gobj, "yi-arrows-rotate", "EV_REFRESH", "refresh", toolbar_wide));
644
+
580
645
  /*
581
646
  * Right: fold the tree.
582
647
  *
@@ -677,6 +742,15 @@ function build_graph(gobj)
677
742
  gobj_send_event(gobj, "EV_NODE_CLICK", {evt: evt}, gobj);
678
743
  });
679
744
 
745
+ /* ONE hook for the readout, the same one the treedb graph uses:
746
+ * G6 fires it on any camera change, and the WHEEL is a camera
747
+ * change that passes through no action of ours — patching the text
748
+ * node from each zoom action would have left the readout lying
749
+ * after every notch. */
750
+ graph.on('aftertransform', () => {
751
+ update_zoom_readout(gobj);
752
+ });
753
+
680
754
  graph.on(CanvasEvent.CLICK, (evt) => {
681
755
  // Canvas click
682
756
  });
@@ -1071,6 +1145,7 @@ function load_json(gobj)
1071
1145
  if(!preserve_view) {
1072
1146
  graph.fitView();
1073
1147
  }
1148
+ update_zoom_readout(gobj);
1074
1149
  });
1075
1150
  }
1076
1151
  }
package/src/yui_icons.css CHANGED
@@ -376,3 +376,13 @@
376
376
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M208 80c0-26.5 21.5-48 48-48l64 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-8 0 0 40 152 0c30.9 0 56 25.1 56 56l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-4.4-3.6-8-8-8l-152 0 0 40 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-40-152 0c-4.4 0-8 3.6-8 8l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-30.9 25.1-56 56-56l152 0 0-40-8 0c-26.5 0-48-21.5-48-48l0-64z'/%3E%3C/svg%3E");
377
377
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M208 80c0-26.5 21.5-48 48-48l64 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-8 0 0 40 152 0c30.9 0 56 25.1 56 56l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-4.4-3.6-8-8-8l-152 0 0 40 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-40-152 0c-4.4 0-8 3.6-8 8l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-30.9 25.1-56 56-56l152 0 0-40-8 0c-26.5 0-48-21.5-48-48l0-64z'/%3E%3C/svg%3E");
378
378
  }
379
+
380
+ /* The camera's "fit" bracket, the SAME glyph the treedb graph's SVG
381
+ * sprite uses for the same action (`g6-icon-fit` in lib_icons.js).
382
+ * One action, one drawing: the JSON graph used to say it with
383
+ * `yi-arrows-to-eye`, which is a different picture for the same
384
+ * button in a sibling view. */
385
+ .yi-fit::before {
386
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M32 32C14.3 32 0 46.3 0 64l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64 64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 32zM64 352c0-17.7-14.3-32-32-32S0 334.3 0 352l0 96c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0 0-64zM320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32l-96 0zM448 352c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 64-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-96z'/%3E%3C/svg%3E");
387
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M32 32C14.3 32 0 46.3 0 64l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64 64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 32zM64 352c0-17.7-14.3-32-32-32S0 334.3 0 352l0 96c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0 0-64zM320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32l-96 0zM448 352c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 64-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-96z'/%3E%3C/svg%3E");
388
+ }