@elyra/canvas 12.36.0 → 12.37.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.
Files changed (105) hide show
  1. package/dist/canvas-constants-766c12a9.js +2 -0
  2. package/dist/{canvas-constants-85883d4c.js.map → canvas-constants-766c12a9.js.map} +1 -1
  3. package/dist/canvas-constants-f4219d26.js +2 -0
  4. package/dist/{canvas-constants-d8652829.js.map → canvas-constants-f4219d26.js.map} +1 -1
  5. package/dist/canvas-controller-62b66fc8.js +2 -0
  6. package/dist/canvas-controller-62b66fc8.js.map +1 -0
  7. package/dist/canvas-controller-76f68572.js +2 -0
  8. package/dist/canvas-controller-76f68572.js.map +1 -0
  9. package/dist/common-canvas-339584b8.js +2 -0
  10. package/dist/common-canvas-339584b8.js.map +1 -0
  11. package/dist/common-canvas-c728f092.js +2 -0
  12. package/dist/common-canvas-c728f092.js.map +1 -0
  13. package/dist/common-canvas.es.js +1 -1
  14. package/dist/common-canvas.es.js.map +1 -1
  15. package/dist/common-canvas.js +1 -1
  16. package/dist/common-canvas.js.map +1 -1
  17. package/dist/common-properties-009d29d6.js +2 -0
  18. package/dist/{common-properties-a34905c3.js.map → common-properties-009d29d6.js.map} +1 -1
  19. package/dist/common-properties-99d34523.js +2 -0
  20. package/dist/{common-properties-d5775a12.js.map → common-properties-99d34523.js.map} +1 -1
  21. package/dist/context-menu-wrapper-624a1e7c.js +2 -0
  22. package/dist/context-menu-wrapper-624a1e7c.js.map +1 -0
  23. package/dist/context-menu-wrapper-ab018d6e.js +2 -0
  24. package/dist/context-menu-wrapper-ab018d6e.js.map +1 -0
  25. package/dist/{datarecord-metadata-v3-schema-531c7b07.js → datarecord-metadata-v3-schema-1f21696a.js} +2 -2
  26. package/dist/{datarecord-metadata-v3-schema-531c7b07.js.map → datarecord-metadata-v3-schema-1f21696a.js.map} +1 -1
  27. package/dist/{datarecord-metadata-v3-schema-28d4d7bb.js → datarecord-metadata-v3-schema-c2ad8862.js} +2 -2
  28. package/dist/{datarecord-metadata-v3-schema-28d4d7bb.js.map → datarecord-metadata-v3-schema-c2ad8862.js.map} +1 -1
  29. package/dist/flexible-table-4cf19e2e.js +2 -0
  30. package/dist/flexible-table-4cf19e2e.js.map +1 -0
  31. package/dist/flexible-table-8d10f5c9.js +2 -0
  32. package/dist/flexible-table-8d10f5c9.js.map +1 -0
  33. package/dist/{icon-909437d7.js → icon-5e06bfe1.js} +2 -2
  34. package/dist/{icon-909437d7.js.map → icon-5e06bfe1.js.map} +1 -1
  35. package/dist/{icon-de9c6b33.js → icon-8433d369.js} +2 -2
  36. package/dist/{icon-de9c6b33.js.map → icon-8433d369.js.map} +1 -1
  37. package/dist/{index-9960d3bf.js → index-2a61be58.js} +2 -2
  38. package/dist/{index-9960d3bf.js.map → index-2a61be58.js.map} +1 -1
  39. package/dist/{index-61e4a113.js → index-9a355ed6.js} +2 -2
  40. package/dist/{index-61e4a113.js.map → index-9a355ed6.js.map} +1 -1
  41. package/dist/lib/canvas-controller.es.js +1 -1
  42. package/dist/lib/canvas-controller.js +1 -1
  43. package/dist/lib/canvas.es.js +1 -1
  44. package/dist/lib/canvas.js +1 -1
  45. package/dist/lib/context-menu.es.js +1 -1
  46. package/dist/lib/context-menu.js +1 -1
  47. package/dist/lib/properties/field-picker.es.js +1 -1
  48. package/dist/lib/properties/field-picker.js +1 -1
  49. package/dist/lib/properties/flexible-table.es.js +1 -1
  50. package/dist/lib/properties/flexible-table.js +1 -1
  51. package/dist/lib/properties.es.js +1 -1
  52. package/dist/lib/properties.js +1 -1
  53. package/dist/styles/common-canvas.min.css +1 -1
  54. package/dist/styles/common-canvas.min.css.map +1 -1
  55. package/dist/{toolbar-cdb38f4a.js → toolbar-76733735.js} +2 -2
  56. package/dist/{toolbar-cdb38f4a.js.map → toolbar-76733735.js.map} +1 -1
  57. package/dist/{toolbar-3b5a592c.js → toolbar-85e1e463.js} +2 -2
  58. package/dist/{toolbar-3b5a592c.js.map → toolbar-85e1e463.js.map} +1 -1
  59. package/locales/common-canvas/locales/en.json +1 -1
  60. package/locales/common-canvas/locales/eo.json +1 -1
  61. package/package.json +2 -3
  62. package/src/common-canvas/canvas-controller.js +7 -0
  63. package/src/common-canvas/cc-contents.jsx +42 -4
  64. package/src/common-canvas/common-canvas-utils.js +9 -2
  65. package/src/common-canvas/common-canvas.scss +30 -3
  66. package/src/common-canvas/constants/canvas-constants.js +0 -6
  67. package/src/common-canvas/svg-canvas-d3.js +1 -3
  68. package/src/common-canvas/svg-canvas-d3.scss +0 -26
  69. package/src/common-canvas/svg-canvas-pipeline.js +6 -0
  70. package/src/common-canvas/svg-canvas-renderer.js +488 -2815
  71. package/src/common-canvas/svg-canvas-utils-drag-det-link.js +491 -0
  72. package/src/common-canvas/svg-canvas-utils-drag-new-link.js +595 -0
  73. package/src/common-canvas/svg-canvas-utils-drag-objects.js +832 -0
  74. package/src/common-canvas/svg-canvas-utils-external.js +82 -16
  75. package/src/common-canvas/svg-canvas-utils-zoom.js +780 -0
  76. package/src/context-menu/common-context-menu.jsx +57 -26
  77. package/src/context-menu/context-menu.scss +33 -53
  78. package/src/notification-panel/notification-panel.jsx +6 -1
  79. package/src/notification-panel/notification-panel.scss +4 -2
  80. package/src/palette/palette-content-list-item.jsx +23 -7
  81. package/stats.html +1 -1
  82. package/dist/canvas-constants-85883d4c.js +0 -2
  83. package/dist/canvas-constants-d8652829.js +0 -2
  84. package/dist/canvas-controller-7beb80ec.js +0 -2
  85. package/dist/canvas-controller-7beb80ec.js.map +0 -1
  86. package/dist/canvas-controller-b70d0f98.js +0 -2
  87. package/dist/canvas-controller-b70d0f98.js.map +0 -1
  88. package/dist/common-canvas-86fb7d21.js +0 -2
  89. package/dist/common-canvas-86fb7d21.js.map +0 -1
  90. package/dist/common-canvas-a6bfce5e.js +0 -2
  91. package/dist/common-canvas-a6bfce5e.js.map +0 -1
  92. package/dist/common-properties-a34905c3.js +0 -2
  93. package/dist/common-properties-d5775a12.js +0 -2
  94. package/dist/context-menu-wrapper-19a1cf72.js +0 -2
  95. package/dist/context-menu-wrapper-19a1cf72.js.map +0 -1
  96. package/dist/context-menu-wrapper-c3a98c63.js +0 -2
  97. package/dist/context-menu-wrapper-c3a98c63.js.map +0 -1
  98. package/dist/extends-093996c9.js +0 -2
  99. package/dist/extends-093996c9.js.map +0 -1
  100. package/dist/extends-1b35a664.js +0 -2
  101. package/dist/extends-1b35a664.js.map +0 -1
  102. package/dist/flexible-table-b9c08069.js +0 -2
  103. package/dist/flexible-table-b9c08069.js.map +0 -1
  104. package/dist/flexible-table-ddd6132b.js +0 -2
  105. package/dist/flexible-table-ddd6132b.js.map +0 -1
@@ -0,0 +1,780 @@
1
+ /*
2
+ * Copyright 2017-2023 Elyra Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /* eslint no-lonely-if: "off" */
18
+
19
+ import * as d3Selection from "d3-selection";
20
+ import * as d3Zoom from "./d3-zoom-extension/src";
21
+ const d3 = Object.assign({}, d3Selection, d3Zoom);
22
+
23
+ import Logger from "../logging/canvas-logger.js";
24
+ import CanvasUtils from "./common-canvas-utils.js";
25
+ import { INTERACTION_CARBON, INTERACTION_MOUSE, INTERACTION_TRACKPAD }
26
+ from "./constants/canvas-constants.js";
27
+
28
+ // This utility file provides a d3-zoom handler which manages zoom operations
29
+ // on the canvas as well as various utility functions to handle zoom behavior.
30
+
31
+ export default class SVGCanvasUtilsZoom {
32
+
33
+ constructor(renderer) {
34
+ this.ren = renderer;
35
+
36
+ this.logger = new Logger("SVGCanvasUtilsZoom");
37
+
38
+ // Dimensions for extent of canvas scaling
39
+ this.minScaleExtent = 0.2;
40
+ this.maxScaleExtent = 1.8;
41
+
42
+ // Keep track of when the context menu has been closed, so we don't remove
43
+ // selections when a context menu is closed during a zoom gesture.
44
+ this.contextMenuClosedOnZoom = false;
45
+
46
+ // Keep track of when text editing has been closed, so we don't remove
47
+ // selections when that happens during a zoom gesture.
48
+ this.textEditingClosedOnZoom = false;
49
+
50
+ // Used to monitor the region selection rectangle.
51
+ this.regionSelect = false;
52
+
53
+ // Used to track the start of the zoom.
54
+ this.zoomStartPoint = { x: 0, y: 0, k: 0, startX: 0, startY: 0 };
55
+
56
+ // Stores the previous events from D3 so we can calculate zoom increment amounts.
57
+ this.previousD3Event = {};
58
+
59
+ // Stores the dimensions of the canvas to save recalculating the size on
60
+ // each zoom increment.
61
+ this.zoomCanvasDimensions = {};
62
+
63
+ // I was not able to figure out how to use the zoom filter method to
64
+ // allow mousedown and mousemove messages to go through to the canvas to
65
+ // do region selection. Therefore I had to implement region selection in
66
+ // the zoom methods. This has the side effect that, when a region is
67
+ // selected, d3Event.transform.x and d3Event.transform.y are incremented
68
+ // even though the objects in the canvas have not moved. The values below
69
+ // are used to store the current transform x and y amounts at the beginning
70
+ // of the region selection and then restore those amounts at the end of
71
+ // the region selection.
72
+ this.regionStartTransformX = 0;
73
+ this.regionStartTransformY = 0;
74
+
75
+ // Stores the current zoom transform amounts.
76
+ this.zoomTransform = d3.zoomIdentity.translate(0, 0).scale(1);
77
+
78
+ // Flag to indicate when a zoom handled by zoomHandler is hapening.
79
+ this.zooming = false;
80
+
81
+ // Flag to indicate when a zoom is invoked programmatically.
82
+ this.zoomingAction = false;
83
+
84
+ // Flag to indicate when the space key is down (used when dragging).
85
+ this.spaceKeyPressed = false;
86
+
87
+ // Create a zoom handler for use with the canvas.
88
+ this.zoomHandler =
89
+ d3.zoom()
90
+ .trackpad(this.ren.config.enableInteractionType === INTERACTION_TRACKPAD)
91
+ .preventBackGesture(true)
92
+ .wheelDelta((d3Event) => -d3Event.deltaY * (this.ren.config.enableInteractionType === INTERACTION_TRACKPAD ? 0.02 : 0.002))
93
+ .scaleExtent([this.minScaleExtent, this.maxScaleExtent])
94
+ .on("start", this.zoomStart.bind(this))
95
+ .on("zoom", this.zoomAction.bind(this))
96
+ .on("end", this.zoomEnd.bind(this));
97
+
98
+ }
99
+
100
+ // Saves the state when the user presses and holds the space bar. This
101
+ // can be used for gestures that require the space bar to be held down.
102
+ setSpaceKeyPressed(state) {
103
+ this.spaceKeyPressed = state;
104
+ }
105
+
106
+ // Returns true if the space bar is pressed and held down.
107
+ isSpaceKeyPressed() {
108
+ return this.spaceKeyPressed;
109
+ }
110
+
111
+ // Returns the dragObjectsHandler
112
+ getZoomHandler() {
113
+ return this.zoomHandler;
114
+ }
115
+
116
+ // Returns the zoom transform object.
117
+ getZoomTransform() {
118
+ return this.zoomTransform;
119
+ }
120
+
121
+ // Returns a copy of the zoom transform object.
122
+ getZoom() {
123
+ return { ...this.zoomTransform };
124
+ }
125
+
126
+ getZoomScale() {
127
+ return this.zoomTransform.k;
128
+ }
129
+
130
+ // Resets the local zoom transform object to the default (identity) zoom.
131
+ resetZoomTransform() {
132
+ this.zoomTransform = d3.zoomIdentity.translate(0, 0).scale(1);
133
+ }
134
+
135
+ // Zooms the canvas to the extent specified in the zoom object.
136
+ zoomTo(zoomObject) {
137
+ const animateTime = 500;
138
+ this.zoomCanvasInvokeZoomBehavior(zoomObject, animateTime);
139
+ }
140
+
141
+ // Pans the canvas by the x and y amount specified in the time specified.
142
+ translateBy(x, y, animateTime) {
143
+ const z = this.getZoomTransform();
144
+ const zoomObject = d3.zoomIdentity.translate(z.x + x, z.y + y).scale(z.k);
145
+ this.zoomCanvasInvokeZoomBehavior(zoomObject, animateTime);
146
+ }
147
+
148
+ // Zooms in the canvas by an increment amount.
149
+ zoomIn() {
150
+ if (this.zoomTransform.k < this.maxScaleExtent) {
151
+ const newScale = Math.min(this.zoomTransform.k * 1.1, this.maxScaleExtent);
152
+ this.ren.canvasSVG.call(this.zoomHandler.scaleTo, newScale);
153
+ }
154
+ }
155
+
156
+ // Zooms out the canvas by an increment amount.
157
+ zoomOut() {
158
+ if (this.zoomTransform.k > this.minScaleExtent) {
159
+ const newScale = Math.max(this.zoomTransform.k / 1.1, this.minScaleExtent);
160
+ this.ren.canvasSVG.call(this.zoomHandler.scaleTo, newScale);
161
+ }
162
+ }
163
+
164
+ // Returns true if the canvas is currently zoomed to the maximum amount.
165
+ isZoomedToMax() {
166
+ return this.zoomTransform ? this.zoomTransform.k === this.maxScaleExtent : false;
167
+ }
168
+
169
+ // Returns true if the canvas is currently zoomed to the minimum amount.
170
+ isZoomedToMin() {
171
+ return this.zoomTransform ? this.zoomTransform.k === this.minScaleExtent : false;
172
+ }
173
+
174
+ // Sets the maximum zoom extent by multiplying the current extent by
175
+ // the factor passed in.
176
+ setMaxZoomExtent(factor) {
177
+ // Don't allow the scale extent to be changed while in the middle of a
178
+ // zoom operation.
179
+ if (this.zooming) {
180
+ return;
181
+ }
182
+ const newMaxExtent = this.maxScaleExtent * factor;
183
+ this.zoomHandler = this.zoomHandler.scaleExtent([this.minScaleExtent, newMaxExtent]);
184
+ this.ren.resetCanvasSVGBehaviors();
185
+ }
186
+
187
+ // Transforms the x and y fields passed in by the current zoom
188
+ // transformation amounts to convert a coordinate position in screen pixels
189
+ // to a canvas coordinate position.
190
+ transformPos(pos) {
191
+ return {
192
+ x: (pos.x - this.zoomTransform.x) / this.zoomTransform.k,
193
+ y: (pos.y - this.zoomTransform.y) / this.zoomTransform.k
194
+ };
195
+ }
196
+
197
+ // Transforms the x and y fields passed in by the current zoom
198
+ // transformation amounts to convert a canvas coordinate position
199
+ // to a coordinate position in screen pixels.
200
+ unTransformPos(pos) {
201
+ return {
202
+ x: (pos.x * this.zoomTransform.k) + this.zoomTransform.x,
203
+ y: (pos.y * this.zoomTransform.k) + this.zoomTransform.y
204
+ };
205
+ }
206
+
207
+ // Transforms the x, y, height and width fields of the object passed in by the
208
+ // current zoom transformation amounts to convert coordinate positions and
209
+ // dimensions in screen pixels to coordinate positions and dimensions in
210
+ // zoomed pixels.
211
+ getTransformedRect(svgRect, pad) {
212
+ const transPad = (pad / this.zoomTransform.k);
213
+ return {
214
+ x: (-this.zoomTransform.x / this.zoomTransform.k) + transPad,
215
+ y: (-this.zoomTransform.y / this.zoomTransform.k) + transPad,
216
+ height: (svgRect.height / this.zoomTransform.k) - (2 * transPad),
217
+ width: (svgRect.width / this.zoomTransform.k) - (2 * transPad)
218
+ };
219
+ }
220
+
221
+ // Handles the beginning of a zoom action
222
+ zoomStart(d3Event) {
223
+ this.logger.log("zoomStart - " + JSON.stringify(d3Event.transform));
224
+
225
+ // Ensure any open tip is closed before starting a zoom operation.
226
+ this.ren.canvasController.closeTip();
227
+
228
+ this.zooming = true;
229
+
230
+ // Close the context menu, if it's open, before panning or zooming.
231
+ // If the context menu is opened inside the expanded supernode (in-place
232
+ // subflow), when the user zooms the canvas, the full page flow is handling
233
+ // that zoom, which causes a refresh in the subflow, so the full page flow
234
+ // will take care of closing the context menu. This means the in-place
235
+ // subflow doesn’t need to do anything on zoom,
236
+ // hence: !this.ren.dispUtils.isDisplayingSubFlowInPlace()
237
+ if (this.ren.canvasController.isContextMenuDisplayed() &&
238
+ !this.ren.dispUtils.isDisplayingSubFlowInPlace()) {
239
+ this.ren.canvasController.closeContextMenu();
240
+ this.contextMenuClosedOnZoom = true;
241
+ }
242
+
243
+ // Any text editing in progress will be closed by the textarea's blur event
244
+ // if the user clicks on the canvas background. So we set this flag to
245
+ // prevent the selection being lost in the zoomEnd (mouseup) event.
246
+ if (this.ren.svgCanvasTextArea.isEditingText()) {
247
+ this.textEditingClosedOnZoom = true;
248
+ }
249
+
250
+ this.regionSelect = this.isRegionSelectActivated(d3Event);
251
+
252
+ if (this.regionSelect) {
253
+ // Add a delay so, if the user just clicks, they don't see the crosshair.
254
+ // This will be cleared in zoomEnd if the user's click takes less than 200 ms.
255
+ this.addingCursorOverlay = setTimeout(() => this.ren.addTempCursorOverlay("crosshair"), 200);
256
+ this.regionStartTransformX = d3Event.transform.x;
257
+ this.regionStartTransformY = d3Event.transform.y;
258
+
259
+ } else {
260
+ if (this.isDragActivated(d3Event)) {
261
+ this.addingCursorOverlay = setTimeout(() => this.ren.addTempCursorOverlay("grabbing"), 200);
262
+ } else {
263
+ this.addingCursorOverlay = setTimeout(() => this.ren.addTempCursorOverlay("default"), 200);
264
+ }
265
+ }
266
+
267
+ const transPos = this.ren.getTransformedMousePos(d3Event);
268
+ this.zoomStartPoint = { x: d3Event.transform.x, y: d3Event.transform.y, k: d3Event.transform.k, startX: transPos.x, startY: transPos.y };
269
+ this.previousD3Event = { ...d3Event.transform };
270
+
271
+ // Store the canvas dimensions so we don't have to recalculate
272
+ // them for every zoom action event.
273
+ this.zoomCanvasDimensions = this.getCanvasDimensions();
274
+ }
275
+
276
+ // Handles each increment of a zoom action
277
+ zoomAction(d3Event) {
278
+ this.logger.log("zoomAction - " + JSON.stringify(d3Event.transform));
279
+
280
+ // If the scale amount is the same we are not zooming, so we must be panning.
281
+ if (d3Event.transform.k === this.zoomStartPoint.k) {
282
+ if (this.regionSelect) {
283
+ this.drawRegionSelector(d3Event);
284
+
285
+ } else {
286
+ this.zoomCanvasBackground(d3Event);
287
+ }
288
+ } else {
289
+ this.ren.addTempCursorOverlay("default");
290
+ this.zoomCanvasBackground(d3Event);
291
+ this.ren.repositionCommentToolbar();
292
+ }
293
+ }
294
+
295
+ // Handles the end of a zoom action
296
+ zoomEnd(d3Event) {
297
+ this.logger.log("zoomEnd - " + JSON.stringify(d3Event.transform));
298
+
299
+ // Clears the display of the cursor overlay if the user clicks within 200 ms
300
+ clearTimeout(this.addingCursorOverlay);
301
+
302
+ const transPos = this.ren.getTransformedMousePos(d3Event);
303
+
304
+ // The user just clicked -- with no drag.
305
+ if (transPos.x === this.zoomStartPoint.startX &&
306
+ transPos.y === this.zoomStartPoint.startY &&
307
+ !this.zoomChanged()) {
308
+ this.zoomClick(d3Event);
309
+
310
+ } else if (this.regionSelect) {
311
+ this.zoomEndRegionSelect(d3Event);
312
+
313
+ } else if (this.ren.dispUtils.isDisplayingFullPage() && this.zoomChanged()) {
314
+ this.zoomSave();
315
+ }
316
+
317
+ // Remove the cursor overlay and reset the SVG background rectangle
318
+ // cursor style, which was set in the zoom start method.
319
+ this.ren.resetCanvasCursor(d3Event);
320
+ this.ren.removeTempCursorOverlay();
321
+ this.contextMenuClosedOnZoom = false;
322
+ this.textEditingClosedOnZoom = false;
323
+ this.regionSelect = false;
324
+ this.zooming = false;
325
+ }
326
+
327
+ // Returns true if the current zoom transform is different from the
328
+ // zoom values at the beginning of the zoom action.
329
+ zoomChanged() {
330
+ return (this.zoomTransform.k !== this.zoomStartPoint.k ||
331
+ this.zoomTransform.x !== this.zoomStartPoint.x ||
332
+ this.zoomTransform.y !== this.zoomStartPoint.y);
333
+ }
334
+
335
+ // Returns true if the event indicates that a drag (rather than a region
336
+ // select) is in action. This means that, with the Carbon interation
337
+ // option the space bar is pressed or with legacy interation the
338
+ // shift key is NOT pressed.
339
+ isDragActivated(d3Event) {
340
+ if (this.ren.config.enableInteractionType === INTERACTION_CARBON) {
341
+ return this.isSpaceKeyPressed();
342
+ }
343
+ return (d3Event && d3Event.sourceEvent && !d3Event.sourceEvent.shiftKey);
344
+ }
345
+
346
+ // Returns true if the region select gesture is requested by the user.
347
+ isRegionSelectActivated(d3Event) {
348
+ // The this.zoomingAction flag indicates zooming is being invoked
349
+ // programmatically.
350
+ if (this.zoomingAction) {
351
+ return false;
352
+
353
+ } else if (this.ren.config.enableInteractionType === INTERACTION_MOUSE &&
354
+ (d3Event && d3Event.sourceEvent && d3Event.sourceEvent.shiftKey)) {
355
+ return true;
356
+
357
+ } else if (this.ren.config.enableInteractionType === INTERACTION_CARBON &&
358
+ !this.isSpaceKeyPressed()) {
359
+ return true;
360
+
361
+ } else if (this.ren.config.enableInteractionType === INTERACTION_TRACKPAD &&
362
+ (d3Event.sourceEvent && d3Event.sourceEvent.buttons === 1) && // Main button is pressed
363
+ !this.isSpaceKeyPressed()) {
364
+ return true;
365
+ }
366
+
367
+ return false;
368
+ }
369
+
370
+ drawRegionSelector(d3Event) {
371
+ this.removeRegionSelector();
372
+ const { x, y, width, height } = this.getRegionDimensions(d3Event);
373
+
374
+ this.ren.canvasGrp
375
+ .append("rect")
376
+ .attr("width", width)
377
+ .attr("height", height)
378
+ .attr("x", x)
379
+ .attr("y", y)
380
+ .attr("class", "d3-region-selector");
381
+ }
382
+
383
+ // Handles the behavior when the user stops doing a region select.
384
+ zoomEndRegionSelect(d3Event) {
385
+ this.removeRegionSelector();
386
+
387
+ // Reset the transform x and y to what they were before the region
388
+ // selection action was started. This directly sets the x and y values
389
+ // in the __zoom property of the svgCanvas DOM object.
390
+ d3Event.transform.x = this.regionStartTransformX;
391
+ d3Event.transform.y = this.regionStartTransformY;
392
+
393
+ this.ren.selectObjsInRegion(
394
+ this.getRegionDimensions(d3Event));
395
+ }
396
+
397
+ // Removes the region selection graphic rectangle.
398
+ removeRegionSelector() {
399
+ this.ren.canvasGrp.selectAll(".d3-region-selector").remove();
400
+ }
401
+
402
+ // Returns the x, y, width and height of the selection region
403
+ // where x and y are always the top left corner of the region
404
+ // and width and height are therefore always positive.
405
+ getRegionDimensions(d3Event) {
406
+ const transPos = this.ren.getTransformedMousePos(d3Event);
407
+ let x = this.zoomStartPoint.startX;
408
+ let y = this.zoomStartPoint.startY;
409
+ let width = transPos.x - x;
410
+ let height = transPos.y - y;
411
+
412
+ if (width < 0) {
413
+ width = Math.abs(width);
414
+ x -= width;
415
+ }
416
+ if (height < 0) {
417
+ height = Math.abs(height);
418
+ y -= height;
419
+ }
420
+
421
+ return { x, y, width, height };
422
+ }
423
+
424
+ // Performs zoom behaviors for each incremental zoom action.
425
+ zoomCanvasBackground(d3Event) {
426
+ this.regionSelect = false;
427
+
428
+ if (this.ren.dispUtils.isDisplayingPrimaryFlowFullPage()) {
429
+ const incTransform = this.getTransformIncrement(d3Event);
430
+ this.zoomTransform = this.zoomConstrainRegular(incTransform, this.getViewportDimensions(), this.zoomCanvasDimensions);
431
+ } else {
432
+ this.zoomTransform = d3.zoomIdentity.translate(d3Event.transform.x, d3Event.transform.y).scale(d3Event.transform.k);
433
+ }
434
+
435
+ this.ren.canvasGrp.attr("transform", this.zoomTransform);
436
+
437
+ this.ren.displayCanvasAccoutrements();
438
+ }
439
+
440
+ // Handles a zoom operation that is just a click on the canvas background.
441
+ zoomClick(d3Event) {
442
+ // Only clear selections under these conditions:
443
+ // * if the click was on the canvas of the current active pipeline. (This
444
+ // is because clicking on the canvas background of an expanded supernode
445
+ // should select that node.)
446
+ // * if we have not just closed a context menu
447
+ // * if we have not just closed text editing
448
+ // * if editing actions are enabled OR the target is the canvas background.
449
+ // (This condition is necessary because when editing actions are disabled,
450
+ // for a read-only canvas, the mouse-up over a node can cause this method
451
+ // to be called.)
452
+ if (this.ren.dispUtils.isDisplayingCurrentPipeline() &&
453
+ !this.contextMenuClosedOnZoom &&
454
+ !this.textEditingClosedOnZoom &&
455
+ (this.ren.config.enableEditingActions ||
456
+ (d3Event.sourceEvent?.target?.classList?.contains("d3-svg-background")))) {
457
+ this.ren.canvasController.clearSelections();
458
+ }
459
+ }
460
+
461
+ // Save the zoom amount. The canvas controller/object model will decide
462
+ // how this info is saved.
463
+ zoomSave() {
464
+ // Set the internal zoom value for canvasSVG used by D3. This will be
465
+ // used by d3Event next time a zoom action is initiated.
466
+ this.ren.canvasSVG.property("__zoom", this.zoomTransform);
467
+
468
+ const data = {
469
+ editType: "setZoom",
470
+ editSource: "canvas",
471
+ zoom: this.zoomTransform,
472
+ pipelineId: this.ren.activePipeline.id
473
+ };
474
+ this.ren.canvasController.editActionHandler(data);
475
+
476
+ }
477
+
478
+ // Returns a new zoom which is the result of incrementing the current zoom
479
+ // by the amount since the previous d3Event event.
480
+ // We calculate increments because d3Event.transform is not based on
481
+ // the constrained zoom position (which is very annoying) so we keep track
482
+ // of the current constraind zoom amount in this.zoomTransform.
483
+ getTransformIncrement(d3Event) {
484
+ const xInc = d3Event.transform.x - this.previousD3Event.x;
485
+ const yInc = d3Event.transform.y - this.previousD3Event.y;
486
+
487
+ const newTransform = { x: this.zoomTransform.x + xInc, y: this.zoomTransform.y + yInc, k: d3Event.transform.k };
488
+ this.previousD3Event = { ...d3Event.transform };
489
+ return newTransform;
490
+ }
491
+
492
+ // Returns a modifed transform object so that the canvas area (the area
493
+ // containing nodes and comments) is constrained such that it never totally
494
+ // disappears from the view port.
495
+ zoomConstrainRegular(transform, viewPort, canvasDimensions) {
496
+ if (!canvasDimensions) {
497
+ return this.zoomTransform;
498
+ }
499
+
500
+ const k = transform.k;
501
+ let x = transform.x;
502
+ let y = transform.y;
503
+
504
+ const canv =
505
+ this.convertRectAdjustedForScaleWithPadding(canvasDimensions, k, this.getZoomToFitPadding());
506
+
507
+ const rightOffsetLimit = viewPort.width - Math.min((viewPort.width * 0.25), (canv.width * 0.25));
508
+ const leftOffsetLimit = -(Math.max((canv.width - (viewPort.width * 0.25)), (canv.width * 0.75)));
509
+
510
+ const bottomOffsetLimit = viewPort.height - Math.min((viewPort.height * 0.25), (canv.height * 0.25));
511
+ const topOffsetLimit = -(Math.max((canv.height - (viewPort.height * 0.25)), (canv.height * 0.75)));
512
+
513
+ if (x > -canv.left + rightOffsetLimit) {
514
+ x = -canv.left + rightOffsetLimit;
515
+
516
+ } else if (x < -canv.left + leftOffsetLimit) {
517
+ x = -canv.left + leftOffsetLimit;
518
+ }
519
+
520
+ if (y > -canv.top + bottomOffsetLimit) {
521
+ y = -canv.top + bottomOffsetLimit;
522
+
523
+ } else if (y < -canv.top + topOffsetLimit) {
524
+ y = -canv.top + topOffsetLimit;
525
+ }
526
+
527
+ return d3.zoomIdentity.translate(x, y).scale(k);
528
+ }
529
+
530
+ // Restores the zoom of the canvas, if it has changed, based on the type
531
+ // of 'save zoom' specified in the configuration and, if no saved zoom, was
532
+ // provided pans the canvas area so it is always visible.
533
+ restoreZoom() {
534
+ let newZoom = this.ren.canvasController.getSavedZoom(this.ren.activePipeline.id);
535
+
536
+ // If there's no saved zoom, and enablePanIntoViewOnOpen is set, pan so
537
+ // the canvas area (containing nodes and comments) is visible in the viewport.
538
+ if (!newZoom && this.ren.config.enablePanIntoViewOnOpen) {
539
+ const canvWithPadding = this.getCanvasDimensionsWithPadding();
540
+ if (canvWithPadding) {
541
+ newZoom = { x: -canvWithPadding.left, y: -canvWithPadding.top, k: 1 };
542
+ }
543
+ }
544
+
545
+ // If there's no saved zoom and we have some initial pan amounts provided use them.
546
+ if (!newZoom && this.ren.canvasLayout.initialPanX && this.ren.canvasLayout.initialPanY) {
547
+ newZoom = { x: this.ren.canvasLayout.initialPanX, y: this.ren.canvasLayout.initialPanY, k: 1 };
548
+ }
549
+
550
+ // If new zoom is different to the current zoom amount, apply it.
551
+ if (newZoom &&
552
+ (newZoom.k !== this.zoomTransform.k ||
553
+ newZoom.x !== this.zoomTransform.x ||
554
+ newZoom.y !== this.zoomTransform.y)) {
555
+ this.zoomCanvasInvokeZoomBehavior(newZoom);
556
+ }
557
+ }
558
+
559
+ // Zooms the canvas to the amount specified in newZoomTransform. Zooming the
560
+ // canvas in this way will invoke the zoom behavior methods: zoomStart,
561
+ // zoomAction and zoomEnd. It does not perform a zoom if newZoomTransform
562
+ // is the same as the current zoom transform.
563
+ zoomCanvasInvokeZoomBehavior(newZoomTransform, animateTime) {
564
+ if (isFinite(newZoomTransform.x) &&
565
+ isFinite(newZoomTransform.y) &&
566
+ isFinite(newZoomTransform.k) &&
567
+ this.zoomHasChanged(newZoomTransform)) {
568
+ this.zoomingAction = true;
569
+ const zoomTransform = d3.zoomIdentity.translate(newZoomTransform.x, newZoomTransform.y).scale(newZoomTransform.k);
570
+ if (animateTime) {
571
+ this.ren.canvasSVG.call(this.zoomHandler).transition()
572
+ .duration(animateTime)
573
+ .call(this.zoomHandler.transform, zoomTransform);
574
+ } else {
575
+ this.ren.canvasSVG.call(this.zoomHandler.transform, zoomTransform);
576
+ }
577
+ this.zoomingAction = false;
578
+ }
579
+ }
580
+
581
+ // Return true if the new zoom transform passed in is different from the
582
+ // current zoom transform.
583
+ zoomHasChanged(newZoomTransform) {
584
+ return newZoomTransform.k !== this.zoomTransform.k ||
585
+ newZoomTransform.x !== this.zoomTransform.x ||
586
+ newZoomTransform.y !== this.zoomTransform.y;
587
+ }
588
+
589
+ // Zooms the canvas to fit in the current viewport.
590
+ zoomToFit() {
591
+ const canvasDimensions = this.getCanvasDimensionsWithPadding();
592
+ const viewPortDimensions = this.getViewportDimensions();
593
+
594
+ if (canvasDimensions) {
595
+ const xRatio = viewPortDimensions.width / canvasDimensions.width;
596
+ const yRatio = viewPortDimensions.height / canvasDimensions.height;
597
+ const newScale = Math.min(xRatio, yRatio, 1); // Don't let the canvas be scaled more than 1 in either direction
598
+
599
+ let x = (viewPortDimensions.width - (canvasDimensions.width * newScale)) / 2;
600
+ let y = (viewPortDimensions.height - (canvasDimensions.height * newScale)) / 2;
601
+
602
+ x -= newScale * canvasDimensions.left;
603
+ y -= newScale * canvasDimensions.top;
604
+
605
+ this.zoomCanvasInvokeZoomBehavior({ x: x, y: y, k: newScale });
606
+ }
607
+ }
608
+
609
+ // Returns a zoom object that will, if applied to the canvas, zoom the objects
610
+ // dentified in the objectIDs array so their center is at the xPos, yPos
611
+ // position in the viewport.
612
+ getZoomToReveal(objectIDs, xPos, yPos) {
613
+ const transformedSVGRect = this.getTransformedViewportDimensions();
614
+ const nodes = this.ren.activePipeline.getNodes(objectIDs);
615
+ const comments = this.ren.activePipeline.getComments(objectIDs);
616
+ const links = this.ren.activePipeline.getLinks(objectIDs);
617
+
618
+ if (nodes.length > 0 || comments.length > 0 || links.length > 0) {
619
+ const canvasDimensions = CanvasUtils.getCanvasDimensions(nodes, comments, links, 0, 0, true);
620
+ const canv = this.convertRectAdjustedForScaleWithPadding(canvasDimensions, 1, 10);
621
+ const xPosInt = parseInt(xPos, 10);
622
+ const yPosInt = typeof yPos === "undefined" ? xPosInt : parseInt(yPos, 10);
623
+
624
+ if (canv) {
625
+ let xOffset;
626
+ let yOffset;
627
+
628
+ if (!Number.isNaN(xPosInt) && !Number.isNaN(yPosInt)) {
629
+ xOffset = transformedSVGRect.x + (transformedSVGRect.width * (xPosInt / 100)) - (canv.left + (canv.width / 2));
630
+ yOffset = transformedSVGRect.y + (transformedSVGRect.height * (yPosInt / 100)) - (canv.top + (canv.height / 2));
631
+
632
+ } else {
633
+ if (canv.right > transformedSVGRect.x + transformedSVGRect.width) {
634
+ xOffset = transformedSVGRect.x + transformedSVGRect.width - canv.right;
635
+ }
636
+ if (canv.left < transformedSVGRect.x) {
637
+ xOffset = transformedSVGRect.x - canv.left;
638
+ }
639
+ if (canv.bottom > transformedSVGRect.y + transformedSVGRect.height) {
640
+ yOffset = transformedSVGRect.y + transformedSVGRect.height - canv.bottom;
641
+ }
642
+ if (canv.top < transformedSVGRect.y) {
643
+ yOffset = transformedSVGRect.y - canv.top;
644
+ }
645
+ }
646
+
647
+ if (typeof xOffset !== "undefined" || typeof yOffset !== "undefined") {
648
+ const x = this.zoomTransform.x + ((xOffset || 0)) * this.zoomTransform.k;
649
+ const y = this.zoomTransform.y + ((yOffset || 0)) * this.zoomTransform.k;
650
+ return { x: x || 0, y: y || 0, k: this.zoomTransform.k };
651
+ }
652
+ }
653
+ }
654
+
655
+ return null;
656
+ }
657
+
658
+ // Returns the maximum amount for padding, when zooming to fit the canvas
659
+ // objects within a subflow, to allow the connection lines to be displayed
660
+ // without them doubling back on themselves.
661
+ getMaxZoomToFitPaddingForConnections() {
662
+ const paddingForInputBinding = this.getMaxPaddingForConnectionsFromInputBindingNodes();
663
+ const paddingForOutputBinding = this.getMaxPaddingForConnectionsToOutputBindingNodes();
664
+ const padding = Math.max(paddingForInputBinding, paddingForOutputBinding);
665
+ return padding;
666
+ }
667
+
668
+ // Returns the maximum amount for padding, when zooming to fit the canvas
669
+ // objects within a subflow, to allow the connection lines (from input binding
670
+ // nodes to other sub-flow nodes) to be displayed without them doubling back
671
+ // on themselves.
672
+ getMaxPaddingForConnectionsFromInputBindingNodes() {
673
+ let maxPadding = 0;
674
+ const inputBindingNodes = this.ren.activePipeline.nodes.filter((n) => n.isSupernodeInputBinding);
675
+
676
+ inputBindingNodes.forEach((n) => {
677
+ const nodePadding = CanvasUtils.getNodePaddingToTargetNodes(n, this.ren.activePipeline.nodes,
678
+ this.ren.activePipeline.links, this.ren.canvasLayout.linkType);
679
+ maxPadding = Math.max(maxPadding, nodePadding);
680
+ });
681
+
682
+ return maxPadding;
683
+ }
684
+
685
+ // Returns the maximum amount for padding, when zooming to fit the canvas
686
+ // objects within a subflow, to allow the connection lines (from sub-flow nodes
687
+ // to output binding nodes) to be displayed without them doubling back
688
+ // on themselves.
689
+ getMaxPaddingForConnectionsToOutputBindingNodes() {
690
+ let maxPadding = 0;
691
+ const outputBindingNodes = this.ren.activePipeline.nodes.filter((n) => n.isSupernodeOutputBinding);
692
+
693
+ this.ren.activePipeline.nodes.forEach((n) => {
694
+ const nodePadding = CanvasUtils.getNodePaddingToTargetNodes(n, outputBindingNodes,
695
+ this.ren.activePipeline.links, this.ren.canvasLayout.linkType);
696
+ maxPadding = Math.max(maxPadding, nodePadding);
697
+ });
698
+
699
+ return maxPadding;
700
+ }
701
+
702
+ // Returns an object representing the viewport dimensions which have been
703
+ // transformed for the current zoom amount.
704
+ getTransformedViewportDimensions() {
705
+ const svgRect = this.getViewportDimensions();
706
+ return this.getTransformedRect(svgRect, 0);
707
+ }
708
+
709
+ // Returns the dimensions of the SVG area. When we are displaying a sub-flow
710
+ // we can use the supernode's dimensions. If not we are displaying
711
+ // full-page so we can use getBoundingClientRect() to get the dimensions
712
+ // (for some reason that method doesn't return correct values with embedded SVG areas).
713
+ getViewportDimensions() {
714
+ let viewportDimensions = {};
715
+
716
+ if (this.ren.dispUtils.isDisplayingSubFlowInPlace()) {
717
+ const dims = this.ren.getParentSupernodeSVGDimensions();
718
+ viewportDimensions.width = dims.width;
719
+ viewportDimensions.height = dims.height;
720
+
721
+ } else {
722
+ if (this.ren.canvasSVG && this.ren.canvasSVG.node()) {
723
+ viewportDimensions = this.ren.canvasSVG.node().getBoundingClientRect();
724
+ } else {
725
+ viewportDimensions = { x: 0, y: 0, width: 1100, height: 640 }; // Return a sensible default (for Jest tests)
726
+ }
727
+ }
728
+ return viewportDimensions;
729
+ }
730
+
731
+ // Returns the dimensions in SVG coordinates of the canvas area. This is
732
+ // based on the position and width and height of the nodes and comments. It
733
+ // does not include the 'super binding nodes' which are the binding nodes in
734
+ // a sub-flow that map to a port in the containing supernode. The dimensions
735
+ // include an appropriate padding amount.
736
+ getCanvasDimensionsWithPadding() {
737
+ return this.getCanvasDimensions(this.getZoomToFitPadding());
738
+ }
739
+
740
+ // Returns the dimensions in SVG coordinates of the canvas area. This is
741
+ // based on the position and width and height of the nodes and comments. It
742
+ // does not include the 'super binding nodes' which are the binding nodes in
743
+ // a sub-flow that map to a port in the containing supernode. If a pad is
744
+ // provided, it is also added in to the dimensions.
745
+ getCanvasDimensions(pad) {
746
+ const gap = this.ren.canvasLayout.commentHighlightGap;
747
+ const canvasDimensions = this.ren.activePipeline.getCanvasDimensions(gap);
748
+ return this.convertRectAdjustedForScaleWithPadding(canvasDimensions, 1, pad);
749
+ }
750
+
751
+ // Returns a rect object describing the rect passed in but
752
+ // scaled by k and with padding added.
753
+ convertRectAdjustedForScaleWithPadding(rect, k, pad = 0) {
754
+ if (rect) {
755
+ return {
756
+ left: (rect.left * k) - pad,
757
+ top: (rect.top * k) - pad,
758
+ right: (rect.right * k) + pad,
759
+ bottom: (rect.bottom * k) + pad,
760
+ width: (rect.width * k) + (2 * pad),
761
+ height: (rect.height * k) + (2 * pad)
762
+ };
763
+ }
764
+ return null;
765
+ }
766
+
767
+ // Returns the padding space for the canvas objects to be zoomed which takes
768
+ // into account any connections that need to be made to/from any sub-flow
769
+ // binding nodes plus any space needed for the binding nodes ports.
770
+ getZoomToFitPadding() {
771
+ let padding = this.ren.canvasLayout.zoomToFitPadding;
772
+
773
+ if (this.ren.dispUtils.isDisplayingSubFlow()) {
774
+ // Allocate some space for connecting lines and the binding node ports
775
+ const newPadding = this.getMaxZoomToFitPaddingForConnections() + (2 * this.ren.canvasLayout.supernodeBindingPortRadius);
776
+ padding = Math.max(padding, newPadding);
777
+ }
778
+ return padding;
779
+ }
780
+ }