@yuneta/gobj-ui 7.17.0 → 7.18.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.
- package/README.md +14 -2
- package/dist/gobj-ui.cjs.js +101 -0
- package/dist/gobj-ui.es.js +101 -0
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +131 -0
- package/src/lib_icons.js +9 -0
package/README.md
CHANGED
|
@@ -680,6 +680,8 @@ Two implementation notes worth keeping:
|
|
|
680
680
|
| fit | `fitView()`: the whole graph in the viewport |
|
|
681
681
|
| **`1:1`** | `zoomTo(1)`: actual size |
|
|
682
682
|
| ── | |
|
|
683
|
+
| **fit to selection** | `fit`, for the part that is selected (edition only, disabled while nothing is) |
|
|
684
|
+
| ── | |
|
|
683
685
|
| full screen | the container, not the camera |
|
|
684
686
|
|
|
685
687
|
Two of those rows are the answer to a real complaint, and the reasoning
|
|
@@ -695,6 +697,14 @@ generalises:
|
|
|
695
697
|
that offers it, because there is no glyph anybody recognises for it.
|
|
696
698
|
- **the zoom level is shown** because `1:1` is a jump to a number, and a jump
|
|
697
699
|
to a number is only meaningful next to the number you are on.
|
|
700
|
+
- **fit to selection** wears the `fit` icon with a marked object inside it,
|
|
701
|
+
drawn rather than borrowed: the two sit next to each other and are the same
|
|
702
|
+
action at two scopes, so they have to read as a family. It appears only in
|
|
703
|
+
edition — a button that can never be enabled is furniture, not a control —
|
|
704
|
+
and is disabled while nothing is selected. `fitView()` has no subset form,
|
|
705
|
+
so the bounds are measured off the elements and the zoom is clamped to the
|
|
706
|
+
graph's own `zoomRange`, the only limit that is not invented here. One card
|
|
707
|
+
filling the view is not a bug: that is what zooming to it means.
|
|
698
708
|
|
|
699
709
|
The **separators are gaps, not lines**: every item already carries a hairline
|
|
700
710
|
against its neighbour, so one more line would not group anything. Full screen
|
|
@@ -705,8 +715,9 @@ Both toolbars (this one and the edit one) **follow the theme**. They used to be
|
|
|
705
715
|
pinned to a light background in both themes, with the icon colour pinned dark
|
|
706
716
|
so it survived that — two light islands over a dark canvas.
|
|
707
717
|
|
|
708
|
-
**New keys for consumers: `actual size`, `zoom level
|
|
709
|
-
host that has not defined them shows the key on hover and
|
|
718
|
+
**New keys for consumers: `actual size`, `zoom level`, `zoom to selection`**
|
|
719
|
+
(all tooltips, so a host that has not defined them shows the key on hover and
|
|
720
|
+
nothing else breaks).
|
|
710
721
|
|
|
711
722
|
### Selecting several nodes, and moving them together
|
|
712
723
|
|
|
@@ -719,6 +730,7 @@ clicked":
|
|
|
719
730
|
| **shift + click** | adds that node to the selection, or takes it out |
|
|
720
731
|
| **shift + drag on the canvas** | rubber band: the selection becomes what it enclosed |
|
|
721
732
|
| **ctrl/cmd + A** | every node |
|
|
733
|
+
| the **fit-to-selection** button | puts the viewport on what is selected |
|
|
722
734
|
| drag any selected node | **moves the whole selection**, as one undo |
|
|
723
735
|
| **Delete** / **Backspace** | deletes the selection, after a confirmation that counts what it takes |
|
|
724
736
|
| **Esc**, or a click on the canvas | clears it |
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -53617,6 +53617,15 @@ var ICONS = `<svg xmlns="http://www.w3.org/2000/svg">
|
|
|
53617
53617
|
<symbol id="g6-icon-fit" viewBox="0 0 448 512">
|
|
53618
53618
|
<path fill="currentColor" 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"/>
|
|
53619
53619
|
</symbol>
|
|
53620
|
+
<!-- fit to selection: the same corner brackets as the fit icon,
|
|
53621
|
+
around a marked object. The two sit side by side in the
|
|
53622
|
+
toolbar and have to read as a family, which a borrowed glyph
|
|
53623
|
+
would not. (No backticks in here: the sprite lives inside a
|
|
53624
|
+
template literal.) -->
|
|
53625
|
+
<symbol id="g6-icon-fit-selection" viewBox="0 0 448 512">
|
|
53626
|
+
<path fill="currentColor" 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"/>
|
|
53627
|
+
<rect fill="currentColor" x="144" y="176" width="160" height="160" rx="24"/>
|
|
53628
|
+
</symbol>
|
|
53620
53629
|
<!-- crosshairs (center) -->
|
|
53621
53630
|
<symbol id="g6-icon-center" viewBox="0 0 576 512">
|
|
53622
53631
|
<path fill="currentColor" d="M288-16c17.7 0 32 14.3 32 32l0 18.3c98.1 14 175.7 91.6 189.7 189.7l18.3 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-18.3 0c-14 98.1-91.6 175.7-189.7 189.7l0 18.3c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-18.3C157.9 463.7 80.3 386.1 66.3 288L48 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l18.3 0C80.3 125.9 157.9 48.3 256 34.3L256 16c0-17.7 14.3-32 32-32zM131.2 288c12.7 62.7 62.1 112.1 124.8 124.8l0-12.8c0-17.7 14.3-32 32-32s32 14.3 32 32l0 12.8c62.7-12.7 112.1-62.1 124.8-124.8L432 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l12.8 0C432.1 161.3 382.7 111.9 320 99.2l0 12.8c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-12.8C193.3 111.9 143.9 161.3 131.2 224l12.8 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-12.8 0zM288 208a48 48 0 1 1 0 96 48 48 0 1 1 0-96z"/>
|
|
@@ -54150,6 +54159,7 @@ function update_toolbar(gobj) {
|
|
|
54150
54159
|
if (graph_get_plugin(gobj, "toolbar")) graph.updatePlugin({ key: "toolbar" });
|
|
54151
54160
|
configure_toolbar_edit(gobj);
|
|
54152
54161
|
if (graph_get_plugin(gobj, "toolbar-edit")) graph.updatePlugin({ key: "toolbar-edit" });
|
|
54162
|
+
update_zoom_selection_button(gobj);
|
|
54153
54163
|
}
|
|
54154
54164
|
/************************************************************
|
|
54155
54165
|
* The floating toolbars follow the theme.
|
|
@@ -54250,6 +54260,13 @@ function configure_toolbar(gobj) {
|
|
|
54250
54260
|
title: (0, i18next.t)("actual size")
|
|
54251
54261
|
}
|
|
54252
54262
|
];
|
|
54263
|
+
if (priv.edit_mode) items.push({
|
|
54264
|
+
id: "g6-icon-fit-selection",
|
|
54265
|
+
value: "fit-selection",
|
|
54266
|
+
className: "EV_ZOOM_SELECTION",
|
|
54267
|
+
title: (0, i18next.t)("zoom to selection"),
|
|
54268
|
+
disabled: true
|
|
54269
|
+
});
|
|
54253
54270
|
if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_fullscreen")) {
|
|
54254
54271
|
items.push({ separator: true });
|
|
54255
54272
|
if (priv.is_fullscreen) items.push({
|
|
@@ -54281,6 +54298,9 @@ function configure_toolbar(gobj) {
|
|
|
54281
54298
|
case "auto-fit":
|
|
54282
54299
|
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_AUTO_FIT", {}, gobj);
|
|
54283
54300
|
break;
|
|
54301
|
+
case "fit-selection":
|
|
54302
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_ZOOM_SELECTION", {}, gobj);
|
|
54303
|
+
break;
|
|
54284
54304
|
case "center":
|
|
54285
54305
|
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CENTER", {}, gobj);
|
|
54286
54306
|
break;
|
|
@@ -55086,6 +55106,52 @@ async function graph_fit_readable(gobj) {
|
|
|
55086
55106
|
}
|
|
55087
55107
|
}
|
|
55088
55108
|
/************************************************************
|
|
55109
|
+
* Put the viewport on the selection: what `fit` does, for a part.
|
|
55110
|
+
*
|
|
55111
|
+
* Computed rather than delegated because `fitView()` fits the whole
|
|
55112
|
+
* graph and there is no subset form of it. The bounds come from the
|
|
55113
|
+
* elements themselves, so a node's real card size is what is
|
|
55114
|
+
* measured, not an assumed one; the zoom is clamped to the graph's
|
|
55115
|
+
* own `zoomRange`, which is the only limit that is not invented
|
|
55116
|
+
* here. A selection of one card filling the view is not a bug --
|
|
55117
|
+
* that is what zooming to it means, and it is what every editor
|
|
55118
|
+
* that offers the action does.
|
|
55119
|
+
************************************************************/
|
|
55120
|
+
async function graph_fit_selection(gobj, ids) {
|
|
55121
|
+
let graph = gobj.priv.graph;
|
|
55122
|
+
if (!graph || !ids || !ids.length) return;
|
|
55123
|
+
let minx = Infinity, miny = Infinity, maxx = -Infinity, maxy = -Infinity;
|
|
55124
|
+
let found = 0;
|
|
55125
|
+
for (let id of ids) {
|
|
55126
|
+
let bounds;
|
|
55127
|
+
try {
|
|
55128
|
+
bounds = graph.getElementRenderBounds(id);
|
|
55129
|
+
} catch (e) {
|
|
55130
|
+
continue;
|
|
55131
|
+
}
|
|
55132
|
+
if (!bounds || !bounds.min || !bounds.max) continue;
|
|
55133
|
+
minx = Math.min(minx, bounds.min[0]);
|
|
55134
|
+
miny = Math.min(miny, bounds.min[1]);
|
|
55135
|
+
maxx = Math.max(maxx, bounds.max[0]);
|
|
55136
|
+
maxy = Math.max(maxy, bounds.max[1]);
|
|
55137
|
+
found++;
|
|
55138
|
+
}
|
|
55139
|
+
if (!found) {
|
|
55140
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: the selection has no bounds to fit`);
|
|
55141
|
+
return;
|
|
55142
|
+
}
|
|
55143
|
+
let size = graph.getSize();
|
|
55144
|
+
let vw = Math.max(1, size[0] - 96);
|
|
55145
|
+
let vh = Math.max(1, size[1] - 96);
|
|
55146
|
+
let bw = Math.max(1, maxx - minx);
|
|
55147
|
+
let bh = Math.max(1, maxy - miny);
|
|
55148
|
+
let zoom = Math.min(vw / bw, vh / bh);
|
|
55149
|
+
let range = graph.getZoomRange() || [.2, 4];
|
|
55150
|
+
zoom = Math.max(range[0], Math.min(range[1], zoom));
|
|
55151
|
+
await graph.zoomTo(zoom);
|
|
55152
|
+
await graph.focusElement(ids);
|
|
55153
|
+
}
|
|
55154
|
+
/************************************************************
|
|
55089
55155
|
* Focus the graph on one topic: highlight (amber 'active' state)
|
|
55090
55156
|
* every node of that topic and centre the viewport on them. An
|
|
55091
55157
|
* empty topic clears the highlight. If the graph is not rendered
|
|
@@ -55554,6 +55620,23 @@ function paint_selection(gobj, ids) {
|
|
|
55554
55620
|
let next = ids || [];
|
|
55555
55621
|
priv._selected_paint_ids = next;
|
|
55556
55622
|
repaint_cards(gobj, /* @__PURE__ */ new Set([...prev, ...next]));
|
|
55623
|
+
update_zoom_selection_button(gobj);
|
|
55624
|
+
}
|
|
55625
|
+
/************************************************************
|
|
55626
|
+
* Zoom-to-selection can only act on a selection.
|
|
55627
|
+
*
|
|
55628
|
+
* The attribute is toggled on the element instead of re-rendering
|
|
55629
|
+
* the toolbar, which would rebuild its innerHTML -- and with it
|
|
55630
|
+
* drop the disabled state of every OTHER button -- each time a
|
|
55631
|
+
* card is ticked.
|
|
55632
|
+
************************************************************/
|
|
55633
|
+
function update_zoom_selection_button(gobj) {
|
|
55634
|
+
let priv = gobj.priv;
|
|
55635
|
+
let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
|
|
55636
|
+
if (!$container) return;
|
|
55637
|
+
if (!$container.querySelector(".EV_ZOOM_SELECTION")) return;
|
|
55638
|
+
if ((priv._selected_paint_ids || []).length > 0) enableElements($container, ".EV_ZOOM_SELECTION");
|
|
55639
|
+
else disableElements($container, ".EV_ZOOM_SELECTION");
|
|
55557
55640
|
}
|
|
55558
55641
|
/************************************************************
|
|
55559
55642
|
* Select a SET of nodes: what a marquee and a shift-click do.
|
|
@@ -58058,6 +58141,18 @@ function ac_auto_fit(gobj, event, kw, src) {
|
|
|
58058
58141
|
graph_fitview(gobj);
|
|
58059
58142
|
return 0;
|
|
58060
58143
|
}
|
|
58144
|
+
/************************************************************
|
|
58145
|
+
* Zoom to what is selected.
|
|
58146
|
+
************************************************************/
|
|
58147
|
+
function ac_zoom_selection(gobj, event, kw, src) {
|
|
58148
|
+
let ids = gobj.priv._selected_paint_ids || [];
|
|
58149
|
+
if (!ids.length) {
|
|
58150
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: zoom to selection with nothing selected`);
|
|
58151
|
+
return -1;
|
|
58152
|
+
}
|
|
58153
|
+
graph_fit_selection(gobj, ids);
|
|
58154
|
+
return 0;
|
|
58155
|
+
}
|
|
58061
58156
|
function ac_focus_topic(gobj, event, kw, src) {
|
|
58062
58157
|
graph_focus_topic(gobj, kw && kw.topic);
|
|
58063
58158
|
return 0;
|
|
@@ -58431,6 +58526,11 @@ function create_gclass$1(gclass_name) {
|
|
|
58431
58526
|
ac_auto_fit,
|
|
58432
58527
|
null
|
|
58433
58528
|
],
|
|
58529
|
+
[
|
|
58530
|
+
"EV_ZOOM_SELECTION",
|
|
58531
|
+
ac_zoom_selection,
|
|
58532
|
+
null
|
|
58533
|
+
],
|
|
58434
58534
|
[
|
|
58435
58535
|
"EV_FOCUS_TOPIC",
|
|
58436
58536
|
ac_focus_topic,
|
|
@@ -58525,6 +58625,7 @@ function create_gclass$1(gclass_name) {
|
|
|
58525
58625
|
["EV_ZOOM_OUT", 0],
|
|
58526
58626
|
["EV_ZOOM_RESET", 0],
|
|
58527
58627
|
["EV_AUTO_FIT", 0],
|
|
58628
|
+
["EV_ZOOM_SELECTION", 0],
|
|
58528
58629
|
["EV_FOCUS_TOPIC", 0],
|
|
58529
58630
|
["EV_FIND_NODES", 0],
|
|
58530
58631
|
["EV_FIND_RESULT", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -53604,6 +53604,15 @@ var ICONS = `<svg xmlns="http://www.w3.org/2000/svg">
|
|
|
53604
53604
|
<symbol id="g6-icon-fit" viewBox="0 0 448 512">
|
|
53605
53605
|
<path fill="currentColor" 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"/>
|
|
53606
53606
|
</symbol>
|
|
53607
|
+
<!-- fit to selection: the same corner brackets as the fit icon,
|
|
53608
|
+
around a marked object. The two sit side by side in the
|
|
53609
|
+
toolbar and have to read as a family, which a borrowed glyph
|
|
53610
|
+
would not. (No backticks in here: the sprite lives inside a
|
|
53611
|
+
template literal.) -->
|
|
53612
|
+
<symbol id="g6-icon-fit-selection" viewBox="0 0 448 512">
|
|
53613
|
+
<path fill="currentColor" 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"/>
|
|
53614
|
+
<rect fill="currentColor" x="144" y="176" width="160" height="160" rx="24"/>
|
|
53615
|
+
</symbol>
|
|
53607
53616
|
<!-- crosshairs (center) -->
|
|
53608
53617
|
<symbol id="g6-icon-center" viewBox="0 0 576 512">
|
|
53609
53618
|
<path fill="currentColor" d="M288-16c17.7 0 32 14.3 32 32l0 18.3c98.1 14 175.7 91.6 189.7 189.7l18.3 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-18.3 0c-14 98.1-91.6 175.7-189.7 189.7l0 18.3c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-18.3C157.9 463.7 80.3 386.1 66.3 288L48 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l18.3 0C80.3 125.9 157.9 48.3 256 34.3L256 16c0-17.7 14.3-32 32-32zM131.2 288c12.7 62.7 62.1 112.1 124.8 124.8l0-12.8c0-17.7 14.3-32 32-32s32 14.3 32 32l0 12.8c62.7-12.7 112.1-62.1 124.8-124.8L432 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l12.8 0C432.1 161.3 382.7 111.9 320 99.2l0 12.8c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-12.8C193.3 111.9 143.9 161.3 131.2 224l12.8 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-12.8 0zM288 208a48 48 0 1 1 0 96 48 48 0 1 1 0-96z"/>
|
|
@@ -54137,6 +54146,7 @@ function update_toolbar(gobj) {
|
|
|
54137
54146
|
if (graph_get_plugin(gobj, "toolbar")) graph.updatePlugin({ key: "toolbar" });
|
|
54138
54147
|
configure_toolbar_edit(gobj);
|
|
54139
54148
|
if (graph_get_plugin(gobj, "toolbar-edit")) graph.updatePlugin({ key: "toolbar-edit" });
|
|
54149
|
+
update_zoom_selection_button(gobj);
|
|
54140
54150
|
}
|
|
54141
54151
|
/************************************************************
|
|
54142
54152
|
* The floating toolbars follow the theme.
|
|
@@ -54237,6 +54247,13 @@ function configure_toolbar(gobj) {
|
|
|
54237
54247
|
title: t("actual size")
|
|
54238
54248
|
}
|
|
54239
54249
|
];
|
|
54250
|
+
if (priv.edit_mode) items.push({
|
|
54251
|
+
id: "g6-icon-fit-selection",
|
|
54252
|
+
value: "fit-selection",
|
|
54253
|
+
className: "EV_ZOOM_SELECTION",
|
|
54254
|
+
title: t("zoom to selection"),
|
|
54255
|
+
disabled: true
|
|
54256
|
+
});
|
|
54240
54257
|
if (gobj_read_bool_attr(gobj, "with_fullscreen")) {
|
|
54241
54258
|
items.push({ separator: true });
|
|
54242
54259
|
if (priv.is_fullscreen) items.push({
|
|
@@ -54268,6 +54285,9 @@ function configure_toolbar(gobj) {
|
|
|
54268
54285
|
case "auto-fit":
|
|
54269
54286
|
gobj_send_event(gobj, "EV_AUTO_FIT", {}, gobj);
|
|
54270
54287
|
break;
|
|
54288
|
+
case "fit-selection":
|
|
54289
|
+
gobj_send_event(gobj, "EV_ZOOM_SELECTION", {}, gobj);
|
|
54290
|
+
break;
|
|
54271
54291
|
case "center":
|
|
54272
54292
|
gobj_send_event(gobj, "EV_CENTER", {}, gobj);
|
|
54273
54293
|
break;
|
|
@@ -55073,6 +55093,52 @@ async function graph_fit_readable(gobj) {
|
|
|
55073
55093
|
}
|
|
55074
55094
|
}
|
|
55075
55095
|
/************************************************************
|
|
55096
|
+
* Put the viewport on the selection: what `fit` does, for a part.
|
|
55097
|
+
*
|
|
55098
|
+
* Computed rather than delegated because `fitView()` fits the whole
|
|
55099
|
+
* graph and there is no subset form of it. The bounds come from the
|
|
55100
|
+
* elements themselves, so a node's real card size is what is
|
|
55101
|
+
* measured, not an assumed one; the zoom is clamped to the graph's
|
|
55102
|
+
* own `zoomRange`, which is the only limit that is not invented
|
|
55103
|
+
* here. A selection of one card filling the view is not a bug --
|
|
55104
|
+
* that is what zooming to it means, and it is what every editor
|
|
55105
|
+
* that offers the action does.
|
|
55106
|
+
************************************************************/
|
|
55107
|
+
async function graph_fit_selection(gobj, ids) {
|
|
55108
|
+
let graph = gobj.priv.graph;
|
|
55109
|
+
if (!graph || !ids || !ids.length) return;
|
|
55110
|
+
let minx = Infinity, miny = Infinity, maxx = -Infinity, maxy = -Infinity;
|
|
55111
|
+
let found = 0;
|
|
55112
|
+
for (let id of ids) {
|
|
55113
|
+
let bounds;
|
|
55114
|
+
try {
|
|
55115
|
+
bounds = graph.getElementRenderBounds(id);
|
|
55116
|
+
} catch (e) {
|
|
55117
|
+
continue;
|
|
55118
|
+
}
|
|
55119
|
+
if (!bounds || !bounds.min || !bounds.max) continue;
|
|
55120
|
+
minx = Math.min(minx, bounds.min[0]);
|
|
55121
|
+
miny = Math.min(miny, bounds.min[1]);
|
|
55122
|
+
maxx = Math.max(maxx, bounds.max[0]);
|
|
55123
|
+
maxy = Math.max(maxy, bounds.max[1]);
|
|
55124
|
+
found++;
|
|
55125
|
+
}
|
|
55126
|
+
if (!found) {
|
|
55127
|
+
log_error(`${gobj_short_name(gobj)}: the selection has no bounds to fit`);
|
|
55128
|
+
return;
|
|
55129
|
+
}
|
|
55130
|
+
let size = graph.getSize();
|
|
55131
|
+
let vw = Math.max(1, size[0] - 96);
|
|
55132
|
+
let vh = Math.max(1, size[1] - 96);
|
|
55133
|
+
let bw = Math.max(1, maxx - minx);
|
|
55134
|
+
let bh = Math.max(1, maxy - miny);
|
|
55135
|
+
let zoom = Math.min(vw / bw, vh / bh);
|
|
55136
|
+
let range = graph.getZoomRange() || [.2, 4];
|
|
55137
|
+
zoom = Math.max(range[0], Math.min(range[1], zoom));
|
|
55138
|
+
await graph.zoomTo(zoom);
|
|
55139
|
+
await graph.focusElement(ids);
|
|
55140
|
+
}
|
|
55141
|
+
/************************************************************
|
|
55076
55142
|
* Focus the graph on one topic: highlight (amber 'active' state)
|
|
55077
55143
|
* every node of that topic and centre the viewport on them. An
|
|
55078
55144
|
* empty topic clears the highlight. If the graph is not rendered
|
|
@@ -55541,6 +55607,23 @@ function paint_selection(gobj, ids) {
|
|
|
55541
55607
|
let next = ids || [];
|
|
55542
55608
|
priv._selected_paint_ids = next;
|
|
55543
55609
|
repaint_cards(gobj, /* @__PURE__ */ new Set([...prev, ...next]));
|
|
55610
|
+
update_zoom_selection_button(gobj);
|
|
55611
|
+
}
|
|
55612
|
+
/************************************************************
|
|
55613
|
+
* Zoom-to-selection can only act on a selection.
|
|
55614
|
+
*
|
|
55615
|
+
* The attribute is toggled on the element instead of re-rendering
|
|
55616
|
+
* the toolbar, which would rebuild its innerHTML -- and with it
|
|
55617
|
+
* drop the disabled state of every OTHER button -- each time a
|
|
55618
|
+
* card is ticked.
|
|
55619
|
+
************************************************************/
|
|
55620
|
+
function update_zoom_selection_button(gobj) {
|
|
55621
|
+
let priv = gobj.priv;
|
|
55622
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
55623
|
+
if (!$container) return;
|
|
55624
|
+
if (!$container.querySelector(".EV_ZOOM_SELECTION")) return;
|
|
55625
|
+
if ((priv._selected_paint_ids || []).length > 0) enableElements($container, ".EV_ZOOM_SELECTION");
|
|
55626
|
+
else disableElements($container, ".EV_ZOOM_SELECTION");
|
|
55544
55627
|
}
|
|
55545
55628
|
/************************************************************
|
|
55546
55629
|
* Select a SET of nodes: what a marquee and a shift-click do.
|
|
@@ -58045,6 +58128,18 @@ function ac_auto_fit(gobj, event, kw, src) {
|
|
|
58045
58128
|
graph_fitview(gobj);
|
|
58046
58129
|
return 0;
|
|
58047
58130
|
}
|
|
58131
|
+
/************************************************************
|
|
58132
|
+
* Zoom to what is selected.
|
|
58133
|
+
************************************************************/
|
|
58134
|
+
function ac_zoom_selection(gobj, event, kw, src) {
|
|
58135
|
+
let ids = gobj.priv._selected_paint_ids || [];
|
|
58136
|
+
if (!ids.length) {
|
|
58137
|
+
log_error(`${gobj_short_name(gobj)}: zoom to selection with nothing selected`);
|
|
58138
|
+
return -1;
|
|
58139
|
+
}
|
|
58140
|
+
graph_fit_selection(gobj, ids);
|
|
58141
|
+
return 0;
|
|
58142
|
+
}
|
|
58048
58143
|
function ac_focus_topic(gobj, event, kw, src) {
|
|
58049
58144
|
graph_focus_topic(gobj, kw && kw.topic);
|
|
58050
58145
|
return 0;
|
|
@@ -58418,6 +58513,11 @@ function create_gclass$1(gclass_name) {
|
|
|
58418
58513
|
ac_auto_fit,
|
|
58419
58514
|
null
|
|
58420
58515
|
],
|
|
58516
|
+
[
|
|
58517
|
+
"EV_ZOOM_SELECTION",
|
|
58518
|
+
ac_zoom_selection,
|
|
58519
|
+
null
|
|
58520
|
+
],
|
|
58421
58521
|
[
|
|
58422
58522
|
"EV_FOCUS_TOPIC",
|
|
58423
58523
|
ac_focus_topic,
|
|
@@ -58512,6 +58612,7 @@ function create_gclass$1(gclass_name) {
|
|
|
58512
58612
|
["EV_ZOOM_OUT", 0],
|
|
58513
58613
|
["EV_ZOOM_RESET", 0],
|
|
58514
58614
|
["EV_AUTO_FIT", 0],
|
|
58615
|
+
["EV_ZOOM_SELECTION", 0],
|
|
58515
58616
|
["EV_FOCUS_TOPIC", 0],
|
|
58516
58617
|
["EV_FIND_NODES", 0],
|
|
58517
58618
|
["EV_FIND_RESULT", event_flag_t.EVF_OUTPUT_EVENT],
|
package/package.json
CHANGED
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -912,6 +912,10 @@ function update_toolbar(gobj)
|
|
|
912
912
|
key: 'toolbar-edit',
|
|
913
913
|
});
|
|
914
914
|
}
|
|
915
|
+
|
|
916
|
+
/* A re-render rebuilds the innerHTML, so a button whose enabled
|
|
917
|
+
* state is not part of getItems() has to be told again. */
|
|
918
|
+
update_zoom_selection_button(gobj);
|
|
915
919
|
}
|
|
916
920
|
|
|
917
921
|
/************************************************************
|
|
@@ -1031,6 +1035,20 @@ function configure_toolbar(gobj)
|
|
|
1031
1035
|
{ text: '1:1', value: 'reset', className: 'EV_ZOOM_RESET', title: t('actual size') },
|
|
1032
1036
|
];
|
|
1033
1037
|
|
|
1038
|
+
/* Only in edition, because that is where a selection
|
|
1039
|
+
* can exist: a button that is permanently disabled is
|
|
1040
|
+
* not a control, it is furniture. Disabled while the
|
|
1041
|
+
* selection is empty -- `paint_selection` is the one
|
|
1042
|
+
* funnel every change goes through, so it is the one
|
|
1043
|
+
* place that has to say so. */
|
|
1044
|
+
if(priv.edit_mode) {
|
|
1045
|
+
items.push({
|
|
1046
|
+
id: 'g6-icon-fit-selection', value: 'fit-selection',
|
|
1047
|
+
className: 'EV_ZOOM_SELECTION',
|
|
1048
|
+
title: t('zoom to selection'), disabled: true
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1034
1052
|
if(gobj_read_bool_attr(gobj, "with_fullscreen")) {
|
|
1035
1053
|
/* Full screen is a WINDOW control, not a camera one:
|
|
1036
1054
|
* it goes in its own group. */
|
|
@@ -1062,6 +1080,9 @@ function configure_toolbar(gobj)
|
|
|
1062
1080
|
case 'auto-fit':
|
|
1063
1081
|
gobj_send_event(gobj, "EV_AUTO_FIT", {}, gobj);
|
|
1064
1082
|
break;
|
|
1083
|
+
case 'fit-selection':
|
|
1084
|
+
gobj_send_event(gobj, "EV_ZOOM_SELECTION", {}, gobj);
|
|
1085
|
+
break;
|
|
1065
1086
|
case 'center':
|
|
1066
1087
|
gobj_send_event(gobj, "EV_CENTER", {}, gobj);
|
|
1067
1088
|
break;
|
|
@@ -2290,6 +2311,65 @@ async function graph_fit_readable(gobj)
|
|
|
2290
2311
|
}
|
|
2291
2312
|
}
|
|
2292
2313
|
|
|
2314
|
+
/************************************************************
|
|
2315
|
+
* Put the viewport on the selection: what `fit` does, for a part.
|
|
2316
|
+
*
|
|
2317
|
+
* Computed rather than delegated because `fitView()` fits the whole
|
|
2318
|
+
* graph and there is no subset form of it. The bounds come from the
|
|
2319
|
+
* elements themselves, so a node's real card size is what is
|
|
2320
|
+
* measured, not an assumed one; the zoom is clamped to the graph's
|
|
2321
|
+
* own `zoomRange`, which is the only limit that is not invented
|
|
2322
|
+
* here. A selection of one card filling the view is not a bug --
|
|
2323
|
+
* that is what zooming to it means, and it is what every editor
|
|
2324
|
+
* that offers the action does.
|
|
2325
|
+
************************************************************/
|
|
2326
|
+
async function graph_fit_selection(gobj, ids)
|
|
2327
|
+
{
|
|
2328
|
+
let priv = gobj.priv;
|
|
2329
|
+
let graph = priv.graph;
|
|
2330
|
+
|
|
2331
|
+
if(!graph || !ids || !ids.length) {
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
let minx = Infinity, miny = Infinity, maxx = -Infinity, maxy = -Infinity;
|
|
2336
|
+
let found = 0;
|
|
2337
|
+
for(let id of ids) {
|
|
2338
|
+
let bounds;
|
|
2339
|
+
try {
|
|
2340
|
+
bounds = graph.getElementRenderBounds(id);
|
|
2341
|
+
} catch(e) {
|
|
2342
|
+
continue; /* gone since it was selected */
|
|
2343
|
+
}
|
|
2344
|
+
if(!bounds || !bounds.min || !bounds.max) {
|
|
2345
|
+
continue;
|
|
2346
|
+
}
|
|
2347
|
+
minx = Math.min(minx, bounds.min[0]);
|
|
2348
|
+
miny = Math.min(miny, bounds.min[1]);
|
|
2349
|
+
maxx = Math.max(maxx, bounds.max[0]);
|
|
2350
|
+
maxy = Math.max(maxy, bounds.max[1]);
|
|
2351
|
+
found++;
|
|
2352
|
+
}
|
|
2353
|
+
if(!found) {
|
|
2354
|
+
log_error(`${gobj_short_name(gobj)}: the selection has no bounds to fit`);
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
const MARGIN = 48;
|
|
2359
|
+
let size = graph.getSize();
|
|
2360
|
+
let vw = Math.max(1, size[0] - MARGIN * 2);
|
|
2361
|
+
let vh = Math.max(1, size[1] - MARGIN * 2);
|
|
2362
|
+
let bw = Math.max(1, maxx - minx);
|
|
2363
|
+
let bh = Math.max(1, maxy - miny);
|
|
2364
|
+
|
|
2365
|
+
let zoom = Math.min(vw / bw, vh / bh);
|
|
2366
|
+
let range = graph.getZoomRange() || [0.2, 4];
|
|
2367
|
+
zoom = Math.max(range[0], Math.min(range[1], zoom));
|
|
2368
|
+
|
|
2369
|
+
await graph.zoomTo(zoom);
|
|
2370
|
+
await graph.focusElement(ids);
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2293
2373
|
/************************************************************
|
|
2294
2374
|
* Focus the graph on one topic: highlight (amber 'active' state)
|
|
2295
2375
|
* every node of that topic and centre the viewport on them. An
|
|
@@ -3049,6 +3129,36 @@ function paint_selection(gobj, ids)
|
|
|
3049
3129
|
priv._selected_paint_ids = next;
|
|
3050
3130
|
|
|
3051
3131
|
repaint_cards(gobj, new Set([...prev, ...next]));
|
|
3132
|
+
update_zoom_selection_button(gobj);
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
/************************************************************
|
|
3136
|
+
* Zoom-to-selection can only act on a selection.
|
|
3137
|
+
*
|
|
3138
|
+
* The attribute is toggled on the element instead of re-rendering
|
|
3139
|
+
* the toolbar, which would rebuild its innerHTML -- and with it
|
|
3140
|
+
* drop the disabled state of every OTHER button -- each time a
|
|
3141
|
+
* card is ticked.
|
|
3142
|
+
************************************************************/
|
|
3143
|
+
function update_zoom_selection_button(gobj)
|
|
3144
|
+
{
|
|
3145
|
+
let priv = gobj.priv;
|
|
3146
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
3147
|
+
|
|
3148
|
+
if(!$container) {
|
|
3149
|
+
return;
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
let $btn = $container.querySelector(".EV_ZOOM_SELECTION");
|
|
3153
|
+
if(!$btn) {
|
|
3154
|
+
return; /* no toolbar, or not in edition: nothing to enable */
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
if((priv._selected_paint_ids || []).length > 0) {
|
|
3158
|
+
enableElements($container, ".EV_ZOOM_SELECTION");
|
|
3159
|
+
} else {
|
|
3160
|
+
disableElements($container, ".EV_ZOOM_SELECTION");
|
|
3161
|
+
}
|
|
3052
3162
|
}
|
|
3053
3163
|
|
|
3054
3164
|
/************************************************************
|
|
@@ -6774,6 +6884,25 @@ function ac_auto_fit(gobj, event, kw, src)
|
|
|
6774
6884
|
return 0;
|
|
6775
6885
|
}
|
|
6776
6886
|
|
|
6887
|
+
/************************************************************
|
|
6888
|
+
* Zoom to what is selected.
|
|
6889
|
+
************************************************************/
|
|
6890
|
+
function ac_zoom_selection(gobj, event, kw, src)
|
|
6891
|
+
{
|
|
6892
|
+
let ids = gobj.priv._selected_paint_ids || [];
|
|
6893
|
+
|
|
6894
|
+
if(!ids.length) {
|
|
6895
|
+
/* The button is disabled without a selection, so getting here
|
|
6896
|
+
* means the event came from somewhere else. */
|
|
6897
|
+
log_error(`${gobj_short_name(gobj)}: zoom to selection with nothing selected`);
|
|
6898
|
+
return -1;
|
|
6899
|
+
}
|
|
6900
|
+
|
|
6901
|
+
graph_fit_selection(gobj, ids);
|
|
6902
|
+
|
|
6903
|
+
return 0;
|
|
6904
|
+
}
|
|
6905
|
+
|
|
6777
6906
|
function ac_focus_topic(gobj, event, kw, src)
|
|
6778
6907
|
{
|
|
6779
6908
|
graph_focus_topic(gobj, kw && kw.topic);
|
|
@@ -7232,6 +7361,7 @@ function create_gclass(gclass_name)
|
|
|
7232
7361
|
["EV_ZOOM_OUT", ac_zoom_out, null],
|
|
7233
7362
|
["EV_ZOOM_RESET", ac_zoom_reset, null],
|
|
7234
7363
|
["EV_AUTO_FIT", ac_auto_fit, null],
|
|
7364
|
+
["EV_ZOOM_SELECTION", ac_zoom_selection, null],
|
|
7235
7365
|
["EV_FOCUS_TOPIC", ac_focus_topic, null],
|
|
7236
7366
|
["EV_FIND_NODES", ac_find_nodes, null],
|
|
7237
7367
|
["EV_CENTER", ac_center, null],
|
|
@@ -7278,6 +7408,7 @@ function create_gclass(gclass_name)
|
|
|
7278
7408
|
["EV_ZOOM_OUT", 0],
|
|
7279
7409
|
["EV_ZOOM_RESET", 0],
|
|
7280
7410
|
["EV_AUTO_FIT", 0],
|
|
7411
|
+
["EV_ZOOM_SELECTION", 0],
|
|
7281
7412
|
["EV_FOCUS_TOPIC", 0],
|
|
7282
7413
|
["EV_FIND_NODES", 0],
|
|
7283
7414
|
["EV_FIND_RESULT", event_flag_t.EVF_OUTPUT_EVENT],
|
package/src/lib_icons.js
CHANGED
|
@@ -52,6 +52,15 @@ const ICONS = `<svg xmlns="http://www.w3.org/2000/svg">
|
|
|
52
52
|
<symbol id="g6-icon-fit" viewBox="0 0 448 512">
|
|
53
53
|
<path fill="currentColor" 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"/>
|
|
54
54
|
</symbol>
|
|
55
|
+
<!-- fit to selection: the same corner brackets as the fit icon,
|
|
56
|
+
around a marked object. The two sit side by side in the
|
|
57
|
+
toolbar and have to read as a family, which a borrowed glyph
|
|
58
|
+
would not. (No backticks in here: the sprite lives inside a
|
|
59
|
+
template literal.) -->
|
|
60
|
+
<symbol id="g6-icon-fit-selection" viewBox="0 0 448 512">
|
|
61
|
+
<path fill="currentColor" 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"/>
|
|
62
|
+
<rect fill="currentColor" x="144" y="176" width="160" height="160" rx="24"/>
|
|
63
|
+
</symbol>
|
|
55
64
|
<!-- crosshairs (center) -->
|
|
56
65
|
<symbol id="g6-icon-center" viewBox="0 0 576 512">
|
|
57
66
|
<path fill="currentColor" d="M288-16c17.7 0 32 14.3 32 32l0 18.3c98.1 14 175.7 91.6 189.7 189.7l18.3 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-18.3 0c-14 98.1-91.6 175.7-189.7 189.7l0 18.3c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-18.3C157.9 463.7 80.3 386.1 66.3 288L48 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l18.3 0C80.3 125.9 157.9 48.3 256 34.3L256 16c0-17.7 14.3-32 32-32zM131.2 288c12.7 62.7 62.1 112.1 124.8 124.8l0-12.8c0-17.7 14.3-32 32-32s32 14.3 32 32l0 12.8c62.7-12.7 112.1-62.1 124.8-124.8L432 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l12.8 0C432.1 161.3 382.7 111.9 320 99.2l0 12.8c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-12.8C193.3 111.9 143.9 161.3 131.2 224l12.8 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-12.8 0zM288 208a48 48 0 1 1 0 96 48 48 0 1 1 0-96z"/>
|