@yuneta/gobj-ui 7.16.0 → 7.17.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 +20 -1
- package/dist/gobj-ui.cjs.js +136 -16
- package/dist/gobj-ui.es.js +136 -16
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +214 -36
package/README.md
CHANGED
|
@@ -718,8 +718,16 @@ clicked":
|
|
|
718
718
|
| click a node | selects it **and opens it**: resize handles, ports, popovers |
|
|
719
719
|
| **shift + click** | adds that node to the selection, or takes it out |
|
|
720
720
|
| **shift + drag on the canvas** | rubber band: the selection becomes what it enclosed |
|
|
721
|
+
| **ctrl/cmd + A** | every node |
|
|
721
722
|
| drag any selected node | **moves the whole selection**, as one undo |
|
|
722
|
-
|
|
|
723
|
+
| **Delete** / **Backspace** | deletes the selection, after a confirmation that counts what it takes |
|
|
724
|
+
| **Esc**, or a click on the canvas | clears it |
|
|
725
|
+
|
|
726
|
+
The keys reach the graph only while the **graph has focus** — G6 gives its
|
|
727
|
+
canvas a `tabIndex` of its own — which is what keeps `ctrl+A` inside the find
|
|
728
|
+
box a selection of the TEXT and not of every node: the focus is in the input,
|
|
729
|
+
and the input is not inside the canvas. They arrive as `EV_KEY_DOWN` and the
|
|
730
|
+
action decides, so a key is as visible in the `machine` trace as a click.
|
|
723
731
|
|
|
724
732
|
Three decisions are worth knowing, because each one is where this could have
|
|
725
733
|
gone wrong:
|
|
@@ -752,6 +760,17 @@ predicate), or the canvas would pan under the rubber band — G6 binds
|
|
|
752
760
|
`drag-canvas` straight to the drag events, and its own docs warn that the two
|
|
753
761
|
gestures cannot both be a plain drag.
|
|
754
762
|
|
|
763
|
+
**A delete says what it takes, whether it is one or twenty.** The Delete key
|
|
764
|
+
asks the same question the per-node delete icon asks, built by the same
|
|
765
|
+
function: the record's key when there is one, the count when there are more,
|
|
766
|
+
and then the two lines that are actually at stake — *N children will be
|
|
767
|
+
UNLINKED, not deleted* and *it will be detached from M parents* — where over a
|
|
768
|
+
set the numbers are the sums. These views delete with `force`, so an operator
|
|
769
|
+
pressing Delete over twelve cards has to read eleven detached children, not
|
|
770
|
+
"are you sure". It needs no new keys: the sentence is the one `7.10.0` already
|
|
771
|
+
defined. The question has no icon to hang off, so it is asked in the middle of
|
|
772
|
+
the graph it is about.
|
|
773
|
+
|
|
755
774
|
**A marquee selects, it does not open.** Even when it encloses exactly one
|
|
756
775
|
node, the handles and ports stay away: `_selected_node_id` means *the node
|
|
757
776
|
opened for editing*, and only a click sets it. Everything that hangs off a
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -54088,12 +54088,15 @@ function configure_events(gobj) {
|
|
|
54088
54088
|
update_toolbar(gobj);
|
|
54089
54089
|
};
|
|
54090
54090
|
i18next.default.on("languageChanged", priv._on_language_changed);
|
|
54091
|
-
|
|
54092
|
-
|
|
54093
|
-
|
|
54094
|
-
|
|
54095
|
-
|
|
54096
|
-
|
|
54091
|
+
graph.on("keydown", (evt) => {
|
|
54092
|
+
let ctrl = !!(evt.ctrlKey || evt.metaKey);
|
|
54093
|
+
if (ctrl && (evt.key === "a" || evt.key === "A")) {
|
|
54094
|
+
if (typeof evt.preventDefault === "function") evt.preventDefault();
|
|
54095
|
+
}
|
|
54096
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_KEY_DOWN", {
|
|
54097
|
+
key: evt.key,
|
|
54098
|
+
ctrl
|
|
54099
|
+
}, gobj);
|
|
54097
54100
|
});
|
|
54098
54101
|
}
|
|
54099
54102
|
/************************************************************
|
|
@@ -55470,10 +55473,17 @@ function create_form_button_row(parent, buttons) {
|
|
|
55470
55473
|
function show_confirm_popover(gobj, target_el, message, confirm_text, confirm_color, onConfirm, priv_key) {
|
|
55471
55474
|
hide_overlay(gobj, priv_key);
|
|
55472
55475
|
let priv = gobj.priv;
|
|
55473
|
-
if (!target_el) return;
|
|
55474
|
-
let iconRect = target_el.getBoundingClientRect();
|
|
55475
55476
|
let containerRect = priv.$container.getBoundingClientRect();
|
|
55476
|
-
|
|
55477
|
+
let left, top;
|
|
55478
|
+
if (target_el) {
|
|
55479
|
+
let iconRect = target_el.getBoundingClientRect();
|
|
55480
|
+
left = iconRect.right - containerRect.left + 6;
|
|
55481
|
+
top = iconRect.top - containerRect.top - 4;
|
|
55482
|
+
} else {
|
|
55483
|
+
left = Math.round(containerRect.width / 2) - 110;
|
|
55484
|
+
top = Math.round(containerRect.height / 4);
|
|
55485
|
+
}
|
|
55486
|
+
const popover = create_popover_base(left, top, "g6-confirm-popover", "#ff4d4f", 160);
|
|
55477
55487
|
let msg = document.createElement("div");
|
|
55478
55488
|
msg.style.cssText = "margin-bottom:10px;font-weight:500;white-space:pre-line;";
|
|
55479
55489
|
msg.textContent = message;
|
|
@@ -57157,17 +57167,89 @@ function execute_delete_node(gobj, nodeData) {
|
|
|
57157
57167
|
deselect_node(gobj);
|
|
57158
57168
|
}
|
|
57159
57169
|
/************************************************************
|
|
57170
|
+
* Every node in the graph.
|
|
57171
|
+
************************************************************/
|
|
57172
|
+
function select_all_nodes(gobj) {
|
|
57173
|
+
let graph = gobj.priv.graph;
|
|
57174
|
+
if (!graph) return;
|
|
57175
|
+
set_selection(gobj, ((graph.getData() || {}).nodes || []).map((nd) => nd.id));
|
|
57176
|
+
}
|
|
57177
|
+
/************************************************************
|
|
57178
|
+
* The nodes currently selected, as their node data.
|
|
57179
|
+
************************************************************/
|
|
57180
|
+
function selected_nodes_data(gobj) {
|
|
57181
|
+
let priv = gobj.priv;
|
|
57182
|
+
let graph = priv.graph;
|
|
57183
|
+
let ids = new Set(selected_node_ids(gobj));
|
|
57184
|
+
for (let id of priv._selected_paint_ids || []) ids.add(id);
|
|
57185
|
+
if (priv._selected_node_id) ids.add(priv._selected_node_id);
|
|
57186
|
+
let out = [];
|
|
57187
|
+
for (let id of ids) {
|
|
57188
|
+
let nd;
|
|
57189
|
+
try {
|
|
57190
|
+
nd = graph.getNodeData(id);
|
|
57191
|
+
} catch (e) {
|
|
57192
|
+
continue;
|
|
57193
|
+
}
|
|
57194
|
+
if (nd && nd.data && nd.data.desc) out.push(nd);
|
|
57195
|
+
}
|
|
57196
|
+
return out;
|
|
57197
|
+
}
|
|
57198
|
+
/************************************************************
|
|
57199
|
+
* What a delete takes with it, for ONE node or for a set.
|
|
57200
|
+
*
|
|
57201
|
+
* Same sentence in both cases and the same keys, because it is the
|
|
57202
|
+
* same warning: these views delete with `force`, which UNLINKS the
|
|
57203
|
+
* children (they survive, loose) and cleans the node off its
|
|
57204
|
+
* parents. Over a set the two numbers are the sums -- an operator
|
|
57205
|
+
* about to detach eleven records has to read eleven, not "are you
|
|
57206
|
+
* sure".
|
|
57207
|
+
************************************************************/
|
|
57208
|
+
function delete_question(nodes) {
|
|
57209
|
+
let children = 0;
|
|
57210
|
+
let parents = 0;
|
|
57211
|
+
for (let nd of nodes) {
|
|
57212
|
+
let impact = delete_impact(nd.data.desc, nd.data.record || {});
|
|
57213
|
+
children += impact.children;
|
|
57214
|
+
parents += impact.parents;
|
|
57215
|
+
}
|
|
57216
|
+
let question;
|
|
57217
|
+
if (nodes.length === 1) {
|
|
57218
|
+
let nd = nodes[0];
|
|
57219
|
+
question = (0, i18next.t)("delete") + " " + nd.data.desc.topic_name + ": " + (nd.data.record || {}).id + "?";
|
|
57220
|
+
} else question = (0, i18next.t)("delete") + " " + nodes.length + " " + (0, i18next.t)("records") + "?";
|
|
57221
|
+
if (children > 0) question += "\n" + children + " " + (0, i18next.t)("children will be unlinked, not deleted", { count: children });
|
|
57222
|
+
if (parents > 0) question += "\n" + (0, i18next.t)("it will be detached from") + " " + parents + " " + (0, i18next.t)("parents", { count: parents });
|
|
57223
|
+
return question;
|
|
57224
|
+
}
|
|
57225
|
+
/************************************************************
|
|
57226
|
+
* Delete every selected node (the Delete key).
|
|
57227
|
+
************************************************************/
|
|
57228
|
+
function request_delete_selection(gobj) {
|
|
57229
|
+
let nodes = selected_nodes_data(gobj);
|
|
57230
|
+
if (!nodes.length) return;
|
|
57231
|
+
if (!(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "confirm_delete_node")) {
|
|
57232
|
+
execute_delete_selection(gobj, nodes);
|
|
57233
|
+
return;
|
|
57234
|
+
}
|
|
57235
|
+
show_confirm_popover(gobj, null, delete_question(nodes), "delete", "#ff4d4f", () => execute_delete_selection(gobj, nodes), "_delete_confirm_el");
|
|
57236
|
+
}
|
|
57237
|
+
function execute_delete_selection(gobj, nodes) {
|
|
57238
|
+
let priv = gobj.priv;
|
|
57239
|
+
hide_delete_confirm(gobj);
|
|
57240
|
+
for (let nd of nodes) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_DELETE_NODE", {
|
|
57241
|
+
treedb_name: priv.treedb_name,
|
|
57242
|
+
topic_name: nd.data.desc.topic_name,
|
|
57243
|
+
record: nd.data.record
|
|
57244
|
+
});
|
|
57245
|
+
deselect_node(gobj);
|
|
57246
|
+
}
|
|
57247
|
+
/************************************************************
|
|
57160
57248
|
* Show a confirmation popover next to the delete icon.
|
|
57161
57249
|
************************************************************/
|
|
57162
57250
|
function show_delete_confirm(gobj, nodeData) {
|
|
57163
57251
|
let priv = gobj.priv;
|
|
57164
|
-
|
|
57165
|
-
let topic_name = nodeData.data.desc.topic_name;
|
|
57166
|
-
let impact = delete_impact(nodeData.data.desc, record);
|
|
57167
|
-
let question = (0, i18next.t)("delete") + " " + topic_name + ": " + record.id + "?";
|
|
57168
|
-
if (impact.children > 0) question += "\n" + impact.children + " " + (0, i18next.t)("children will be unlinked, not deleted", { count: impact.children });
|
|
57169
|
-
if (impact.parents > 0) question += "\n" + (0, i18next.t)("it will be detached from") + " " + impact.parents + " " + (0, i18next.t)("parents", { count: impact.parents });
|
|
57170
|
-
show_confirm_popover(gobj, priv._node_delete_el, question, "delete", "#ff4d4f", () => execute_delete_node(gobj, nodeData), "_delete_confirm_el");
|
|
57252
|
+
show_confirm_popover(gobj, priv._node_delete_el, delete_question([nodeData]), "delete", "#ff4d4f", () => execute_delete_node(gobj, nodeData), "_delete_confirm_el");
|
|
57171
57253
|
}
|
|
57172
57254
|
function hide_delete_confirm(gobj) {
|
|
57173
57255
|
hide_overlay(gobj, "_delete_confirm_el");
|
|
@@ -58034,6 +58116,38 @@ function ac_node_drag_end(gobj, event, kw, src) {
|
|
|
58034
58116
|
return 0;
|
|
58035
58117
|
}
|
|
58036
58118
|
/************************************************************
|
|
58119
|
+
* A key, while the graph has focus.
|
|
58120
|
+
*
|
|
58121
|
+
* Full screen keeps the two keys it always had. The rest belong to
|
|
58122
|
+
* the selection, so they only mean anything in edition -- outside
|
|
58123
|
+
* it there is nothing selected to clear, select or delete.
|
|
58124
|
+
************************************************************/
|
|
58125
|
+
function ac_key_down(gobj, event, kw, src) {
|
|
58126
|
+
let priv = gobj.priv;
|
|
58127
|
+
let key = kw && kw.key || "";
|
|
58128
|
+
if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_fullscreen")) {
|
|
58129
|
+
let fullscreen = graph_get_plugin(gobj, "fullscreen");
|
|
58130
|
+
if (fullscreen) {
|
|
58131
|
+
if (key === "F" || key === "f") fullscreen.request();
|
|
58132
|
+
else if (key === "Escape") fullscreen.exit();
|
|
58133
|
+
}
|
|
58134
|
+
}
|
|
58135
|
+
if (!priv.edit_mode) return 0;
|
|
58136
|
+
if (key === "Escape") {
|
|
58137
|
+
deselect_node(gobj);
|
|
58138
|
+
return 0;
|
|
58139
|
+
}
|
|
58140
|
+
if (kw.ctrl && (key === "a" || key === "A")) {
|
|
58141
|
+
select_all_nodes(gobj);
|
|
58142
|
+
return 0;
|
|
58143
|
+
}
|
|
58144
|
+
if (key === "Delete" || key === "Backspace") {
|
|
58145
|
+
request_delete_selection(gobj);
|
|
58146
|
+
return 0;
|
|
58147
|
+
}
|
|
58148
|
+
return 0;
|
|
58149
|
+
}
|
|
58150
|
+
/************************************************************
|
|
58037
58151
|
* The rubber band let go: these are the nodes it enclosed.
|
|
58038
58152
|
*
|
|
58039
58153
|
* They arrive in the kw because `onSelect` runs BEFORE G6 writes
|
|
@@ -58292,6 +58406,11 @@ function create_gclass$1(gclass_name) {
|
|
|
58292
58406
|
ac_brush_select,
|
|
58293
58407
|
null
|
|
58294
58408
|
],
|
|
58409
|
+
[
|
|
58410
|
+
"EV_KEY_DOWN",
|
|
58411
|
+
ac_key_down,
|
|
58412
|
+
null
|
|
58413
|
+
],
|
|
58295
58414
|
[
|
|
58296
58415
|
"EV_ZOOM_IN",
|
|
58297
58416
|
ac_zoom_in,
|
|
@@ -58401,6 +58520,7 @@ function create_gclass$1(gclass_name) {
|
|
|
58401
58520
|
["EV_CANVAS_CLICK", 0],
|
|
58402
58521
|
["EV_NODE_DRAG_END", 0],
|
|
58403
58522
|
["EV_BRUSH_SELECT", 0],
|
|
58523
|
+
["EV_KEY_DOWN", 0],
|
|
58404
58524
|
["EV_ZOOM_IN", 0],
|
|
58405
58525
|
["EV_ZOOM_OUT", 0],
|
|
58406
58526
|
["EV_ZOOM_RESET", 0],
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -54075,12 +54075,15 @@ function configure_events(gobj) {
|
|
|
54075
54075
|
update_toolbar(gobj);
|
|
54076
54076
|
};
|
|
54077
54077
|
i18next.on("languageChanged", priv._on_language_changed);
|
|
54078
|
-
|
|
54079
|
-
|
|
54080
|
-
|
|
54081
|
-
|
|
54082
|
-
|
|
54083
|
-
|
|
54078
|
+
graph.on("keydown", (evt) => {
|
|
54079
|
+
let ctrl = !!(evt.ctrlKey || evt.metaKey);
|
|
54080
|
+
if (ctrl && (evt.key === "a" || evt.key === "A")) {
|
|
54081
|
+
if (typeof evt.preventDefault === "function") evt.preventDefault();
|
|
54082
|
+
}
|
|
54083
|
+
gobj_send_event(gobj, "EV_KEY_DOWN", {
|
|
54084
|
+
key: evt.key,
|
|
54085
|
+
ctrl
|
|
54086
|
+
}, gobj);
|
|
54084
54087
|
});
|
|
54085
54088
|
}
|
|
54086
54089
|
/************************************************************
|
|
@@ -55457,10 +55460,17 @@ function create_form_button_row(parent, buttons) {
|
|
|
55457
55460
|
function show_confirm_popover(gobj, target_el, message, confirm_text, confirm_color, onConfirm, priv_key) {
|
|
55458
55461
|
hide_overlay(gobj, priv_key);
|
|
55459
55462
|
let priv = gobj.priv;
|
|
55460
|
-
if (!target_el) return;
|
|
55461
|
-
let iconRect = target_el.getBoundingClientRect();
|
|
55462
55463
|
let containerRect = priv.$container.getBoundingClientRect();
|
|
55463
|
-
|
|
55464
|
+
let left, top;
|
|
55465
|
+
if (target_el) {
|
|
55466
|
+
let iconRect = target_el.getBoundingClientRect();
|
|
55467
|
+
left = iconRect.right - containerRect.left + 6;
|
|
55468
|
+
top = iconRect.top - containerRect.top - 4;
|
|
55469
|
+
} else {
|
|
55470
|
+
left = Math.round(containerRect.width / 2) - 110;
|
|
55471
|
+
top = Math.round(containerRect.height / 4);
|
|
55472
|
+
}
|
|
55473
|
+
const popover = create_popover_base(left, top, "g6-confirm-popover", "#ff4d4f", 160);
|
|
55464
55474
|
let msg = document.createElement("div");
|
|
55465
55475
|
msg.style.cssText = "margin-bottom:10px;font-weight:500;white-space:pre-line;";
|
|
55466
55476
|
msg.textContent = message;
|
|
@@ -57144,17 +57154,89 @@ function execute_delete_node(gobj, nodeData) {
|
|
|
57144
57154
|
deselect_node(gobj);
|
|
57145
57155
|
}
|
|
57146
57156
|
/************************************************************
|
|
57157
|
+
* Every node in the graph.
|
|
57158
|
+
************************************************************/
|
|
57159
|
+
function select_all_nodes(gobj) {
|
|
57160
|
+
let graph = gobj.priv.graph;
|
|
57161
|
+
if (!graph) return;
|
|
57162
|
+
set_selection(gobj, ((graph.getData() || {}).nodes || []).map((nd) => nd.id));
|
|
57163
|
+
}
|
|
57164
|
+
/************************************************************
|
|
57165
|
+
* The nodes currently selected, as their node data.
|
|
57166
|
+
************************************************************/
|
|
57167
|
+
function selected_nodes_data(gobj) {
|
|
57168
|
+
let priv = gobj.priv;
|
|
57169
|
+
let graph = priv.graph;
|
|
57170
|
+
let ids = new Set(selected_node_ids(gobj));
|
|
57171
|
+
for (let id of priv._selected_paint_ids || []) ids.add(id);
|
|
57172
|
+
if (priv._selected_node_id) ids.add(priv._selected_node_id);
|
|
57173
|
+
let out = [];
|
|
57174
|
+
for (let id of ids) {
|
|
57175
|
+
let nd;
|
|
57176
|
+
try {
|
|
57177
|
+
nd = graph.getNodeData(id);
|
|
57178
|
+
} catch (e) {
|
|
57179
|
+
continue;
|
|
57180
|
+
}
|
|
57181
|
+
if (nd && nd.data && nd.data.desc) out.push(nd);
|
|
57182
|
+
}
|
|
57183
|
+
return out;
|
|
57184
|
+
}
|
|
57185
|
+
/************************************************************
|
|
57186
|
+
* What a delete takes with it, for ONE node or for a set.
|
|
57187
|
+
*
|
|
57188
|
+
* Same sentence in both cases and the same keys, because it is the
|
|
57189
|
+
* same warning: these views delete with `force`, which UNLINKS the
|
|
57190
|
+
* children (they survive, loose) and cleans the node off its
|
|
57191
|
+
* parents. Over a set the two numbers are the sums -- an operator
|
|
57192
|
+
* about to detach eleven records has to read eleven, not "are you
|
|
57193
|
+
* sure".
|
|
57194
|
+
************************************************************/
|
|
57195
|
+
function delete_question(nodes) {
|
|
57196
|
+
let children = 0;
|
|
57197
|
+
let parents = 0;
|
|
57198
|
+
for (let nd of nodes) {
|
|
57199
|
+
let impact = delete_impact(nd.data.desc, nd.data.record || {});
|
|
57200
|
+
children += impact.children;
|
|
57201
|
+
parents += impact.parents;
|
|
57202
|
+
}
|
|
57203
|
+
let question;
|
|
57204
|
+
if (nodes.length === 1) {
|
|
57205
|
+
let nd = nodes[0];
|
|
57206
|
+
question = t("delete") + " " + nd.data.desc.topic_name + ": " + (nd.data.record || {}).id + "?";
|
|
57207
|
+
} else question = t("delete") + " " + nodes.length + " " + t("records") + "?";
|
|
57208
|
+
if (children > 0) question += "\n" + children + " " + t("children will be unlinked, not deleted", { count: children });
|
|
57209
|
+
if (parents > 0) question += "\n" + t("it will be detached from") + " " + parents + " " + t("parents", { count: parents });
|
|
57210
|
+
return question;
|
|
57211
|
+
}
|
|
57212
|
+
/************************************************************
|
|
57213
|
+
* Delete every selected node (the Delete key).
|
|
57214
|
+
************************************************************/
|
|
57215
|
+
function request_delete_selection(gobj) {
|
|
57216
|
+
let nodes = selected_nodes_data(gobj);
|
|
57217
|
+
if (!nodes.length) return;
|
|
57218
|
+
if (!gobj_read_bool_attr(gobj, "confirm_delete_node")) {
|
|
57219
|
+
execute_delete_selection(gobj, nodes);
|
|
57220
|
+
return;
|
|
57221
|
+
}
|
|
57222
|
+
show_confirm_popover(gobj, null, delete_question(nodes), "delete", "#ff4d4f", () => execute_delete_selection(gobj, nodes), "_delete_confirm_el");
|
|
57223
|
+
}
|
|
57224
|
+
function execute_delete_selection(gobj, nodes) {
|
|
57225
|
+
let priv = gobj.priv;
|
|
57226
|
+
hide_delete_confirm(gobj);
|
|
57227
|
+
for (let nd of nodes) gobj_publish_event(gobj, "EV_DELETE_NODE", {
|
|
57228
|
+
treedb_name: priv.treedb_name,
|
|
57229
|
+
topic_name: nd.data.desc.topic_name,
|
|
57230
|
+
record: nd.data.record
|
|
57231
|
+
});
|
|
57232
|
+
deselect_node(gobj);
|
|
57233
|
+
}
|
|
57234
|
+
/************************************************************
|
|
57147
57235
|
* Show a confirmation popover next to the delete icon.
|
|
57148
57236
|
************************************************************/
|
|
57149
57237
|
function show_delete_confirm(gobj, nodeData) {
|
|
57150
57238
|
let priv = gobj.priv;
|
|
57151
|
-
|
|
57152
|
-
let topic_name = nodeData.data.desc.topic_name;
|
|
57153
|
-
let impact = delete_impact(nodeData.data.desc, record);
|
|
57154
|
-
let question = t("delete") + " " + topic_name + ": " + record.id + "?";
|
|
57155
|
-
if (impact.children > 0) question += "\n" + impact.children + " " + t("children will be unlinked, not deleted", { count: impact.children });
|
|
57156
|
-
if (impact.parents > 0) question += "\n" + t("it will be detached from") + " " + impact.parents + " " + t("parents", { count: impact.parents });
|
|
57157
|
-
show_confirm_popover(gobj, priv._node_delete_el, question, "delete", "#ff4d4f", () => execute_delete_node(gobj, nodeData), "_delete_confirm_el");
|
|
57239
|
+
show_confirm_popover(gobj, priv._node_delete_el, delete_question([nodeData]), "delete", "#ff4d4f", () => execute_delete_node(gobj, nodeData), "_delete_confirm_el");
|
|
57158
57240
|
}
|
|
57159
57241
|
function hide_delete_confirm(gobj) {
|
|
57160
57242
|
hide_overlay(gobj, "_delete_confirm_el");
|
|
@@ -58021,6 +58103,38 @@ function ac_node_drag_end(gobj, event, kw, src) {
|
|
|
58021
58103
|
return 0;
|
|
58022
58104
|
}
|
|
58023
58105
|
/************************************************************
|
|
58106
|
+
* A key, while the graph has focus.
|
|
58107
|
+
*
|
|
58108
|
+
* Full screen keeps the two keys it always had. The rest belong to
|
|
58109
|
+
* the selection, so they only mean anything in edition -- outside
|
|
58110
|
+
* it there is nothing selected to clear, select or delete.
|
|
58111
|
+
************************************************************/
|
|
58112
|
+
function ac_key_down(gobj, event, kw, src) {
|
|
58113
|
+
let priv = gobj.priv;
|
|
58114
|
+
let key = kw && kw.key || "";
|
|
58115
|
+
if (gobj_read_bool_attr(gobj, "with_fullscreen")) {
|
|
58116
|
+
let fullscreen = graph_get_plugin(gobj, "fullscreen");
|
|
58117
|
+
if (fullscreen) {
|
|
58118
|
+
if (key === "F" || key === "f") fullscreen.request();
|
|
58119
|
+
else if (key === "Escape") fullscreen.exit();
|
|
58120
|
+
}
|
|
58121
|
+
}
|
|
58122
|
+
if (!priv.edit_mode) return 0;
|
|
58123
|
+
if (key === "Escape") {
|
|
58124
|
+
deselect_node(gobj);
|
|
58125
|
+
return 0;
|
|
58126
|
+
}
|
|
58127
|
+
if (kw.ctrl && (key === "a" || key === "A")) {
|
|
58128
|
+
select_all_nodes(gobj);
|
|
58129
|
+
return 0;
|
|
58130
|
+
}
|
|
58131
|
+
if (key === "Delete" || key === "Backspace") {
|
|
58132
|
+
request_delete_selection(gobj);
|
|
58133
|
+
return 0;
|
|
58134
|
+
}
|
|
58135
|
+
return 0;
|
|
58136
|
+
}
|
|
58137
|
+
/************************************************************
|
|
58024
58138
|
* The rubber band let go: these are the nodes it enclosed.
|
|
58025
58139
|
*
|
|
58026
58140
|
* They arrive in the kw because `onSelect` runs BEFORE G6 writes
|
|
@@ -58279,6 +58393,11 @@ function create_gclass$1(gclass_name) {
|
|
|
58279
58393
|
ac_brush_select,
|
|
58280
58394
|
null
|
|
58281
58395
|
],
|
|
58396
|
+
[
|
|
58397
|
+
"EV_KEY_DOWN",
|
|
58398
|
+
ac_key_down,
|
|
58399
|
+
null
|
|
58400
|
+
],
|
|
58282
58401
|
[
|
|
58283
58402
|
"EV_ZOOM_IN",
|
|
58284
58403
|
ac_zoom_in,
|
|
@@ -58388,6 +58507,7 @@ function create_gclass$1(gclass_name) {
|
|
|
58388
58507
|
["EV_CANVAS_CLICK", 0],
|
|
58389
58508
|
["EV_NODE_DRAG_END", 0],
|
|
58390
58509
|
["EV_BRUSH_SELECT", 0],
|
|
58510
|
+
["EV_KEY_DOWN", 0],
|
|
58391
58511
|
["EV_ZOOM_IN", 0],
|
|
58392
58512
|
["EV_ZOOM_OUT", 0],
|
|
58393
58513
|
["EV_ZOOM_RESET", 0],
|
package/package.json
CHANGED
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -787,20 +787,24 @@ function configure_events(gobj)
|
|
|
787
787
|
};
|
|
788
788
|
i18next.on('languageChanged', priv._on_language_changed);
|
|
789
789
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
790
|
+
/* The canvas carries a `tabIndex` of its own, so a keydown reaches
|
|
791
|
+
* us only while the GRAPH has focus. That is what keeps Ctrl+A in
|
|
792
|
+
* the find box a text selection and not a selection of every node:
|
|
793
|
+
* the focus is in the input, and the input is not inside the
|
|
794
|
+
* canvas. The callback only translates -- the work is in the
|
|
795
|
+
* action, like every other gesture here. */
|
|
796
|
+
graph.on("keydown", (evt) => {
|
|
797
|
+
let ctrl = !!(evt.ctrlKey || evt.metaKey);
|
|
798
|
+
|
|
799
|
+
if(ctrl && (evt.key === "a" || evt.key === "A")) {
|
|
800
|
+
/* Or the browser selects the page's text underneath. */
|
|
801
|
+
if(typeof evt.preventDefault === "function") {
|
|
802
|
+
evt.preventDefault();
|
|
801
803
|
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
gobj_send_event(gobj, "EV_KEY_DOWN", {key: evt.key, ctrl: ctrl}, gobj);
|
|
807
|
+
});
|
|
804
808
|
}
|
|
805
809
|
|
|
806
810
|
/************************************************************
|
|
@@ -2925,14 +2929,19 @@ function show_confirm_popover(gobj, target_el, message, confirm_text, confirm_co
|
|
|
2925
2929
|
hide_overlay(gobj, priv_key);
|
|
2926
2930
|
|
|
2927
2931
|
let priv = gobj.priv;
|
|
2928
|
-
if(!target_el) {
|
|
2929
|
-
return;
|
|
2930
|
-
}
|
|
2931
|
-
|
|
2932
|
-
let iconRect = target_el.getBoundingClientRect();
|
|
2933
2932
|
let containerRect = priv.$container.getBoundingClientRect();
|
|
2934
|
-
let left
|
|
2935
|
-
|
|
2933
|
+
let left, top;
|
|
2934
|
+
|
|
2935
|
+
if(target_el) {
|
|
2936
|
+
let iconRect = target_el.getBoundingClientRect();
|
|
2937
|
+
left = iconRect.right - containerRect.left + 6;
|
|
2938
|
+
top = iconRect.top - containerRect.top - 4;
|
|
2939
|
+
} else {
|
|
2940
|
+
/* A question about a SET has no icon to hang off: it is asked
|
|
2941
|
+
* in the middle of the graph it is about. */
|
|
2942
|
+
left = Math.round(containerRect.width / 2) - 110;
|
|
2943
|
+
top = Math.round(containerRect.height / 4);
|
|
2944
|
+
}
|
|
2936
2945
|
|
|
2937
2946
|
const popover = create_popover_base(left, top, 'g6-confirm-popover', '#ff4d4f', 160);
|
|
2938
2947
|
|
|
@@ -5347,31 +5356,151 @@ function execute_delete_node(gobj, nodeData)
|
|
|
5347
5356
|
}
|
|
5348
5357
|
|
|
5349
5358
|
/************************************************************
|
|
5350
|
-
*
|
|
5359
|
+
* Every node in the graph.
|
|
5351
5360
|
************************************************************/
|
|
5352
|
-
function
|
|
5361
|
+
function select_all_nodes(gobj)
|
|
5362
|
+
{
|
|
5363
|
+
let graph = gobj.priv.graph;
|
|
5364
|
+
|
|
5365
|
+
if(!graph) {
|
|
5366
|
+
return;
|
|
5367
|
+
}
|
|
5368
|
+
|
|
5369
|
+
let nodes = (graph.getData() || {}).nodes || [];
|
|
5370
|
+
set_selection(gobj, nodes.map((nd) => nd.id));
|
|
5371
|
+
}
|
|
5372
|
+
|
|
5373
|
+
/************************************************************
|
|
5374
|
+
* The nodes currently selected, as their node data.
|
|
5375
|
+
************************************************************/
|
|
5376
|
+
function selected_nodes_data(gobj)
|
|
5353
5377
|
{
|
|
5354
5378
|
let priv = gobj.priv;
|
|
5355
|
-
let
|
|
5356
|
-
let topic_name = nodeData.data.desc.topic_name;
|
|
5379
|
+
let graph = priv.graph;
|
|
5357
5380
|
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5381
|
+
let ids = new Set(selected_node_ids(gobj));
|
|
5382
|
+
for(let id of (priv._selected_paint_ids || [])) {
|
|
5383
|
+
ids.add(id);
|
|
5384
|
+
}
|
|
5385
|
+
if(priv._selected_node_id) {
|
|
5386
|
+
ids.add(priv._selected_node_id);
|
|
5387
|
+
}
|
|
5388
|
+
|
|
5389
|
+
let out = [];
|
|
5390
|
+
for(let id of ids) {
|
|
5391
|
+
let nd;
|
|
5392
|
+
try {
|
|
5393
|
+
nd = graph.getNodeData(id);
|
|
5394
|
+
} catch(e) {
|
|
5395
|
+
continue; /* gone since it was selected */
|
|
5396
|
+
}
|
|
5397
|
+
if(nd && nd.data && nd.data.desc) {
|
|
5398
|
+
out.push(nd);
|
|
5399
|
+
}
|
|
5400
|
+
}
|
|
5401
|
+
|
|
5402
|
+
return out;
|
|
5403
|
+
}
|
|
5404
|
+
|
|
5405
|
+
/************************************************************
|
|
5406
|
+
* What a delete takes with it, for ONE node or for a set.
|
|
5407
|
+
*
|
|
5408
|
+
* Same sentence in both cases and the same keys, because it is the
|
|
5409
|
+
* same warning: these views delete with `force`, which UNLINKS the
|
|
5410
|
+
* children (they survive, loose) and cleans the node off its
|
|
5411
|
+
* parents. Over a set the two numbers are the sums -- an operator
|
|
5412
|
+
* about to detach eleven records has to read eleven, not "are you
|
|
5413
|
+
* sure".
|
|
5414
|
+
************************************************************/
|
|
5415
|
+
function delete_question(nodes)
|
|
5416
|
+
{
|
|
5417
|
+
let children = 0;
|
|
5418
|
+
let parents = 0;
|
|
5419
|
+
|
|
5420
|
+
for(let nd of nodes) {
|
|
5421
|
+
let impact = delete_impact(nd.data.desc, nd.data.record || {});
|
|
5422
|
+
children += impact.children;
|
|
5423
|
+
parents += impact.parents;
|
|
5367
5424
|
}
|
|
5368
|
-
|
|
5425
|
+
|
|
5426
|
+
let question;
|
|
5427
|
+
if(nodes.length === 1) {
|
|
5428
|
+
let nd = nodes[0];
|
|
5429
|
+
question = t('delete') + ' ' + nd.data.desc.topic_name + ': ' +
|
|
5430
|
+
(nd.data.record || {}).id + '?';
|
|
5431
|
+
} else {
|
|
5432
|
+
question = t('delete') + ' ' + nodes.length + ' ' + t('records') + '?';
|
|
5433
|
+
}
|
|
5434
|
+
|
|
5435
|
+
if(children > 0) {
|
|
5436
|
+
question += '\n' + children + ' ' +
|
|
5437
|
+
t('children will be unlinked, not deleted', {count: children});
|
|
5438
|
+
}
|
|
5439
|
+
if(parents > 0) {
|
|
5369
5440
|
question += '\n' + t('it will be detached from') + ' ' +
|
|
5370
|
-
|
|
5441
|
+
parents + ' ' + t('parents', {count: parents});
|
|
5371
5442
|
}
|
|
5372
5443
|
|
|
5444
|
+
return question;
|
|
5445
|
+
}
|
|
5446
|
+
|
|
5447
|
+
/************************************************************
|
|
5448
|
+
* Delete every selected node (the Delete key).
|
|
5449
|
+
************************************************************/
|
|
5450
|
+
function request_delete_selection(gobj)
|
|
5451
|
+
{
|
|
5452
|
+
let nodes = selected_nodes_data(gobj);
|
|
5453
|
+
|
|
5454
|
+
if(!nodes.length) {
|
|
5455
|
+
return; /* nothing selected: the key means nothing here */
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5458
|
+
if(!gobj_read_bool_attr(gobj, "confirm_delete_node")) {
|
|
5459
|
+
execute_delete_selection(gobj, nodes);
|
|
5460
|
+
return;
|
|
5461
|
+
}
|
|
5462
|
+
|
|
5463
|
+
/* No anchor: the set has no icon of its own, so the question is
|
|
5464
|
+
* asked in the middle of the graph. */
|
|
5465
|
+
show_confirm_popover(gobj, null,
|
|
5466
|
+
delete_question(nodes),
|
|
5467
|
+
'delete', '#ff4d4f',
|
|
5468
|
+
() => execute_delete_selection(gobj, nodes),
|
|
5469
|
+
'_delete_confirm_el'
|
|
5470
|
+
);
|
|
5471
|
+
}
|
|
5472
|
+
|
|
5473
|
+
function execute_delete_selection(gobj, nodes)
|
|
5474
|
+
{
|
|
5475
|
+
let priv = gobj.priv;
|
|
5476
|
+
|
|
5477
|
+
hide_delete_confirm(gobj);
|
|
5478
|
+
|
|
5479
|
+
/* One event per node: a treedb deletes records, it has no bulk
|
|
5480
|
+
* delete, and the host turns each of these into its own command
|
|
5481
|
+
* exactly as the topic table's bulk delete does. */
|
|
5482
|
+
for(let nd of nodes) {
|
|
5483
|
+
gobj_publish_event(gobj, "EV_DELETE_NODE", {
|
|
5484
|
+
treedb_name: priv.treedb_name,
|
|
5485
|
+
topic_name: nd.data.desc.topic_name,
|
|
5486
|
+
record: nd.data.record,
|
|
5487
|
+
});
|
|
5488
|
+
}
|
|
5489
|
+
|
|
5490
|
+
deselect_node(gobj);
|
|
5491
|
+
}
|
|
5492
|
+
|
|
5493
|
+
/************************************************************
|
|
5494
|
+
* Show a confirmation popover next to the delete icon.
|
|
5495
|
+
************************************************************/
|
|
5496
|
+
function show_delete_confirm(gobj, nodeData)
|
|
5497
|
+
{
|
|
5498
|
+
let priv = gobj.priv;
|
|
5499
|
+
|
|
5500
|
+
/* Same sentence as the Delete key's, from the same place: the
|
|
5501
|
+
* warning does not depend on how the delete was asked for. */
|
|
5373
5502
|
show_confirm_popover(gobj, priv._node_delete_el,
|
|
5374
|
-
|
|
5503
|
+
delete_question([nodeData]),
|
|
5375
5504
|
'delete', '#ff4d4f',
|
|
5376
5505
|
() => execute_delete_node(gobj, nodeData),
|
|
5377
5506
|
'_delete_confirm_el'
|
|
@@ -6723,6 +6852,53 @@ function ac_node_drag_end(gobj, event, kw, src)
|
|
|
6723
6852
|
return 0;
|
|
6724
6853
|
}
|
|
6725
6854
|
|
|
6855
|
+
/************************************************************
|
|
6856
|
+
* A key, while the graph has focus.
|
|
6857
|
+
*
|
|
6858
|
+
* Full screen keeps the two keys it always had. The rest belong to
|
|
6859
|
+
* the selection, so they only mean anything in edition -- outside
|
|
6860
|
+
* it there is nothing selected to clear, select or delete.
|
|
6861
|
+
************************************************************/
|
|
6862
|
+
function ac_key_down(gobj, event, kw, src)
|
|
6863
|
+
{
|
|
6864
|
+
let priv = gobj.priv;
|
|
6865
|
+
let key = (kw && kw.key) || "";
|
|
6866
|
+
|
|
6867
|
+
if(gobj_read_bool_attr(gobj, "with_fullscreen")) {
|
|
6868
|
+
let fullscreen = graph_get_plugin(gobj, "fullscreen");
|
|
6869
|
+
if(fullscreen) {
|
|
6870
|
+
if(key === "F" || key === "f") {
|
|
6871
|
+
fullscreen.request();
|
|
6872
|
+
} else if(key === "Escape") {
|
|
6873
|
+
/* Escape also clears the selection below, and both are
|
|
6874
|
+
* right: the browser exits full screen on Escape
|
|
6875
|
+
* whatever we do, so refusing to clear as well would
|
|
6876
|
+
* only make the key do less than it appears to. */
|
|
6877
|
+
fullscreen.exit();
|
|
6878
|
+
}
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
|
|
6882
|
+
if(!priv.edit_mode) {
|
|
6883
|
+
return 0;
|
|
6884
|
+
}
|
|
6885
|
+
|
|
6886
|
+
if(key === "Escape") {
|
|
6887
|
+
deselect_node(gobj);
|
|
6888
|
+
return 0;
|
|
6889
|
+
}
|
|
6890
|
+
if(kw.ctrl && (key === "a" || key === "A")) {
|
|
6891
|
+
select_all_nodes(gobj);
|
|
6892
|
+
return 0;
|
|
6893
|
+
}
|
|
6894
|
+
if(key === "Delete" || key === "Backspace") {
|
|
6895
|
+
request_delete_selection(gobj);
|
|
6896
|
+
return 0;
|
|
6897
|
+
}
|
|
6898
|
+
|
|
6899
|
+
return 0;
|
|
6900
|
+
}
|
|
6901
|
+
|
|
6726
6902
|
/************************************************************
|
|
6727
6903
|
* The rubber band let go: these are the nodes it enclosed.
|
|
6728
6904
|
*
|
|
@@ -7049,6 +7225,7 @@ function create_gclass(gclass_name)
|
|
|
7049
7225
|
["EV_CANVAS_CLICK", ac_canvas_click, null],
|
|
7050
7226
|
["EV_NODE_DRAG_END", ac_node_drag_end, null],
|
|
7051
7227
|
["EV_BRUSH_SELECT", ac_brush_select, null],
|
|
7228
|
+
["EV_KEY_DOWN", ac_key_down, null],
|
|
7052
7229
|
|
|
7053
7230
|
/*--- Toolbar events ---*/
|
|
7054
7231
|
["EV_ZOOM_IN", ac_zoom_in, null],
|
|
@@ -7094,6 +7271,7 @@ function create_gclass(gclass_name)
|
|
|
7094
7271
|
["EV_CANVAS_CLICK", 0],
|
|
7095
7272
|
["EV_NODE_DRAG_END", 0],
|
|
7096
7273
|
["EV_BRUSH_SELECT", 0],
|
|
7274
|
+
["EV_KEY_DOWN", 0],
|
|
7097
7275
|
|
|
7098
7276
|
/*--- Toolbar (internal) ---*/
|
|
7099
7277
|
["EV_ZOOM_IN", 0],
|