@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
@@ -19,12 +19,9 @@
19
19
  /* eslint no-lonely-if: "off" */
20
20
 
21
21
  // Import just the D3 modules that are needed and combine them as the 'd3' object.
22
- import * as d3Drag from "d3-drag";
23
- import * as d3Ease from "d3-ease";
24
22
  import * as d3Selection from "d3-selection";
25
23
  import * as d3Fetch from "d3-fetch";
26
- import * as d3Zoom from "./d3-zoom-extension/src";
27
- const d3 = Object.assign({}, d3Drag, d3Ease, d3Selection, d3Fetch, d3Zoom);
24
+ const d3 = Object.assign({}, d3Selection, d3Fetch);
28
25
 
29
26
  const markdownIt = require("markdown-it")({
30
27
  html: false, // Don't allow HTML to be executed in comments.
@@ -32,17 +29,15 @@ const markdownIt = require("markdown-it")({
32
29
  typographer: true
33
30
  });
34
31
 
35
- import { cloneDeep, escape as escapeText, forOwn, get } from "lodash";
36
- import { addNodeExternalObject, addDecExternalObject, removeExternalObject } from "./svg-canvas-utils-external.js";
32
+ import { escape as escapeText, forOwn, get } from "lodash";
37
33
  import { ASSOC_RIGHT_SIDE_CURVE, ASSOCIATION_LINK, NODE_LINK, COMMENT_LINK,
38
34
  ASSOC_VAR_CURVE_LEFT, ASSOC_VAR_CURVE_RIGHT, ASSOC_VAR_DOUBLE_BACK_RIGHT,
39
35
  LINK_TYPE_CURVE, LINK_TYPE_ELBOW, LINK_TYPE_STRAIGHT,
40
36
  LINK_DIR_LEFT_RIGHT, LINK_DIR_TOP_BOTTOM, LINK_DIR_BOTTOM_TOP,
41
37
  LINK_SELECTION_NONE, LINK_SELECTION_HANDLES, LINK_SELECTION_DETACHABLE,
42
- CONTEXT_MENU_BUTTON, DEC_LINK, DEC_NODE, LEFT_ARROW_ICON, EDIT_ICON,
43
- NODE_MENU_ICON, SUPER_NODE_EXPAND_ICON, PORT_OBJECT_CIRCLE, PORT_OBJECT_IMAGE,
38
+ CONTEXT_MENU_BUTTON, DEC_LINK, DEC_NODE, EDIT_ICON,
39
+ NODE_MENU_ICON, SUPER_NODE_EXPAND_ICON, PORT_OBJECT_IMAGE,
44
40
  TIP_TYPE_NODE, TIP_TYPE_PORT, TIP_TYPE_DEC, TIP_TYPE_LINK,
45
- INTERACTION_MOUSE, INTERACTION_TRACKPAD, INTERACTION_CARBON,
46
41
  USE_DEFAULT_ICON, USE_DEFAULT_EXT_ICON,
47
42
  SUPER_NODE, SNAP_TO_GRID_AFTER, SNAP_TO_GRID_DURING,
48
43
  NORTH, SOUTH, EAST, WEST }
@@ -56,15 +51,16 @@ import SvgCanvasNodes from "./svg-canvas-utils-nodes.js";
56
51
  import SvgCanvasComments from "./svg-canvas-utils-comments.js";
57
52
  import SvgCanvasLinks from "./svg-canvas-utils-links.js";
58
53
  import SvgCanvasDecs from "./svg-canvas-utils-decs.js";
54
+ import SvgCanvasExternal from "./svg-canvas-utils-external.js";
59
55
  import SvgCanvasTextArea from "./svg-canvas-utils-textarea.js";
56
+ import SvgCanvasDragObject from "./svg-canvas-utils-drag-objects.js";
57
+ import SvgCanvasDragNewLink from "./svg-canvas-utils-drag-new-link.js";
58
+ import SvgCanvasDragDetLink from "./svg-canvas-utils-drag-det-link.js";
59
+ import SvgCanvasZoom from "./svg-canvas-utils-zoom.js";
60
60
  import SVGCanvasPipeline from "./svg-canvas-pipeline";
61
61
 
62
62
  const NINETY_DEGREES = 90;
63
63
 
64
- const INPUT_TYPE = "input_type";
65
- const OUTPUT_TYPE = "output_type";
66
-
67
-
68
64
  export default class SVGCanvasRenderer {
69
65
  constructor(pipelineId, canvasDiv, canvasController, canvasInfo, selectionInfo, breadcrumbs, nodeLayout, canvasLayout, config, supernodeInfo = {}) {
70
66
  this.logger = new Logger(["SVGCanvasRenderer", "PipeId", pipelineId]);
@@ -92,6 +88,11 @@ export default class SVGCanvasRenderer {
92
88
  this.commentUtils = new SvgCanvasComments();
93
89
  this.linkUtils = new SvgCanvasLinks(this.config, this.canvasLayout, this.nodeUtils, this.commentUtils);
94
90
  this.decUtils = new SvgCanvasDecs(this.canvasLayout);
91
+ this.dragObjectUtils = new SvgCanvasDragObject(this);
92
+ this.dragNewLinkUtils = new SvgCanvasDragNewLink(this);
93
+ this.dragDetLinkUtils = new SvgCanvasDragDetLink(this);
94
+ this.zoomUtils = new SvgCanvasZoom(this);
95
+ this.externalUtils = new SvgCanvasExternal(this);
95
96
  this.svgCanvasTextArea = new SvgCanvasTextArea(
96
97
  this.config,
97
98
  this.dispUtils,
@@ -108,44 +109,6 @@ export default class SVGCanvasRenderer {
108
109
  this.dispUtils.setDisplayState();
109
110
  this.logger.log(this.dispUtils.getDisplayStateMsg());
110
111
 
111
- // Initialize zoom variables
112
- this.initializeZoomVariables();
113
-
114
- // Dimensions for extent of canvas scaling
115
- this.minScaleExtent = 0.2;
116
- this.maxScaleExtent = 1.8;
117
-
118
- // Allows us to track the sizing behavior of comments
119
- this.commentSizing = false;
120
- this.commentSizingDirection = "";
121
-
122
- // Allows us to track the sizing behavior of nodes
123
- this.nodeSizing = false;
124
- this.nodeSizingDirection = "";
125
- this.nodeSizingObjectsInfo = {};
126
- this.nodeSizingDetLinksInfo = {};
127
-
128
- // Keeps track of the size and position, at the start of the sizing event,
129
- // of the object (node or comment) being sized.
130
- this.resizeObjInitialInfo = null;
131
-
132
- // Keeps track of the size and position, during a sizing event, of the
133
- // object (node or comment) being sized, before it is snapped to grid.
134
- this.notSnappedXPos = 0;
135
- this.notSnappedYPos = 0;
136
- this.notSnappedWidth = 0;
137
- this.notSnappedHeight = 0;
138
-
139
- // Allows us to record the drag behavior or nodes and comments.
140
- this.dragging = false;
141
- this.dragOffsetX = 0;
142
- this.dragOffsetY = 0;
143
- this.dragRunningX = 0;
144
- this.dragRunningY = 0;
145
- this.dragObjects = [];
146
- this.dragStartX = 0;
147
- this.dragStartY = 0;
148
-
149
112
  // The data link a node is currently being dragged over. It will be null
150
113
  // when the node being dragged is not over a data link.
151
114
  this.dragOverLink = null;
@@ -165,101 +128,32 @@ export default class SVGCanvasRenderer {
165
128
  // option is switched on.
166
129
  this.dragNewLinkOverNode = null;
167
130
 
168
- // Flag to indicate if the current drag operation is for a node that can
169
- // be inserted into a link. Such a node would need input and output ports.
170
- this.existingNodeInsertableIntoLink = false;
171
-
172
- // Flag to indicate if the current drag operation is for a node that can
173
- // be attached to a detached link.
174
- this.existingNodeAttachableToDetachedLinks = false;
175
-
176
- // Flag to indicate when the space key is down (used when dragging).
177
- this.spaceKeyPressed = false;
178
-
179
- // Flag to indicate when a zoom is invoked programmatically.
180
- this.zoomingAction = false;
181
-
182
- // Keep track of when the context menu has been closed, so we don't remove
183
- // selections when a context menu is closed during a zoom gesture.
184
- this.contextMenuClosedOnZoom = false;
185
-
186
- // Keep track of when text editing has been closed, so we don't remove
187
- // selections when that happens during a zoom gesture.
188
- this.textEditingClosedOnZoom = false;
189
-
190
- // Used to monitor the region selection rectangle.
191
- this.regionSelect = false;
192
-
193
- // Used to track the start of the zoom.
194
- this.zoomStartPoint = { x: 0, y: 0, k: 0, startX: 0, startY: 0 };
195
-
196
- // I was not able to figure out how to use the zoom filter method to
197
- // allow mousedown and mousemove messages to go through to the canvas to
198
- // do region selection. Therefore I had to implement region selection in
199
- // the zoom methods. This has the side effect that, when a region is
200
- // selected, d3Event.transform.x and d3Event.transform.y are incremented
201
- // even though the objects in the canvas have not moved. The values below
202
- // are used to store the current transform x and y amounts at the beginning
203
- // of the region selection and then restore those amounts at the end of
204
- // the region selection.
205
- this.regionStartTransformX = 0;
206
- this.regionStartTransformY = 0;
207
-
208
- // Object to store variables for dynamically drawing a new link line. The
209
- // existence of this object means a new link is being drawn. A null means
210
- // no link is currently being drawn.
211
- this.drawingNewLinkData = null;
212
-
213
- // Create a drag handler for use with nodes and comments.
214
- this.dragHandler = d3.drag()
215
- .on("start", this.dragStart.bind(this))
216
- .on("drag", this.dragMove.bind(this))
217
- .on("end", this.dragEnd.bind(this));
218
-
219
- this.draggingLinkData = null;
220
-
221
- // Create a drag handler that can be used with draggable ends of
222
- // detached links.
223
- this.dragLinkHandler = d3.drag()
224
- .on("start", this.dragStartLinkHandle.bind(this))
225
- .on("drag", this.dragMoveLinkHandle.bind(this))
226
- .on("end", this.dragEndLinkHandle.bind(this));
227
-
228
- // Create a zoom object for use with the canvas.
229
- this.zoom =
230
- d3.zoom()
231
- .trackpad(this.config.enableInteractionType === INTERACTION_TRACKPAD)
232
- .preventBackGesture(true)
233
- .wheelDelta((d3Event) => -d3Event.deltaY * (this.config.enableInteractionType === INTERACTION_TRACKPAD ? 0.02 : 0.002))
234
- .scaleExtent([this.minScaleExtent, this.maxScaleExtent])
235
- .on("start", this.zoomStart.bind(this))
236
- .on("zoom", this.zoomAction.bind(this))
237
- .on("end", this.zoomEnd.bind(this));
238
-
239
131
  this.initializeGhostDiv();
240
132
 
241
133
  this.canvasSVG = this.createCanvasSVG();
242
134
  this.canvasDefs = this.canvasSVG.selectChildren("defs");
243
- this.canvasGrp = this.createCanvasGroup(this.canvasSVG, "d3-canvas-group"); // Group to contain all canvas objects
244
- this.canvasUnderlay = this.createCanvasUnderlay(this.canvasGrp, "d3-canvas-underlay"); // Put underlay rectangle under comments, nodes and links
245
- this.commentsGrp = this.createCanvasGroup(this.canvasGrp, "d3-comments-group"); // Group to always position comments under nodes and links
246
- this.nodesLinksGrp = this.createCanvasGroup(this.canvasGrp, "d3-nodes-links-group"); // Group to position nodes and links over comments
247
- this.boundingRectsGrp = this.createBoundingRectanglesGrp(this.canvasGrp, "d3-bounding-rect-group"); // Group to optionally add bounding rectangles over all objects
248
135
 
136
+ // Group to contain all canvas objects
137
+ this.canvasGrp = this.createCanvasGroup(this.canvasSVG, "d3-canvas-group");
138
+
139
+ // Put underlay rectangle under comments, nodes and links
140
+ this.canvasUnderlay = this.createCanvasUnderlay(this.canvasGrp, "d3-canvas-underlay");
141
+
142
+ // Group to always position comments under nodes and links
143
+ this.commentsGrp = this.createCanvasGroup(this.canvasGrp, "d3-comments-group");
144
+
145
+ // Group to position nodes and links over comments
146
+ this.nodesLinksGrp = this.createCanvasGroup(this.canvasGrp, "d3-nodes-links-group");
147
+
148
+ // Group to optionally add bounding rectangles over all objects
149
+ this.boundingRectsGrp = this.createBoundingRectanglesGrp(this.canvasGrp, "d3-bounding-rect-group");
249
150
 
250
151
  this.resetCanvasSVGBehaviors();
251
152
 
252
153
  this.displayCanvas();
253
154
 
254
155
  if (this.dispUtils.isDisplayingFullPage()) {
255
- this.restoreZoom();
256
- }
257
-
258
- // If we are showing a sub-flow in full screen mode, or the options is
259
- // switched on to always display it, show the 'back to parent' control.
260
- if (this.dispUtils.isDisplayingSubFlowFullPage() ||
261
- this.canvasLayout.alwaysDisplayBackToParentFlow) {
262
- this.addBackToParentFlowArrow(this.canvasSVG);
156
+ this.zoomUtils.restoreZoom();
263
157
  }
264
158
 
265
159
  // If we are showing a sub-flow in full screen mode and there
@@ -280,30 +174,62 @@ export default class SVGCanvasRenderer {
280
174
  this.logger.logEndTimer("constructor" + pipelineId.substring(0, 5));
281
175
  }
282
176
 
177
+ // Sets the pressed state of the space bar. This is called
178
+ // from outside canvas via svg-canvas-d3.
283
179
  setSpaceKeyPressed(state) {
284
- this.spaceKeyPressed = state;
180
+ this.zoomUtils.setSpaceKeyPressed(state);
285
181
  this.resetCanvasCursor();
286
182
  }
287
183
 
288
184
  // Returns true if the space bar is pressed and held down. This is called
289
- // from outside canvas via svg-canvas-d3 as well as internally.
185
+ // from outside canvas via svg-canvas-d3.
290
186
  isSpaceKeyPressed() {
291
- return this.spaceKeyPressed;
187
+ return this.zoomUtils.isSpaceKeyPressed();
292
188
  }
293
189
 
294
- // Returns true if the event indicates that a drag is in action. This means
295
- // with regular Mouse interation that the space bar is pressed or with
296
- // legacy interation it means the shift key is NOT pressed.
297
- isDragActivated(d3Event) {
298
- if (this.config.enableInteractionType === INTERACTION_CARBON) {
299
- return this.isSpaceKeyPressed();
300
- }
301
- return (d3Event && d3Event.sourceEvent && !d3Event.sourceEvent.shiftKey);
190
+ zoomTo(zoomObject) {
191
+ this.zoomUtils.zoomTo(zoomObject);
192
+ }
193
+
194
+ translateBy(x, y, animateTime) {
195
+ this.zoomUtils.translateBy(x, y, animateTime);
196
+ }
197
+
198
+ zoomIn() {
199
+ this.zoomUtils.zoomIn();
200
+ }
201
+
202
+ zoomOut() {
203
+ this.zoomUtils.zoomOut();
204
+ }
205
+
206
+ zoomToFit() {
207
+ this.zoomUtils.zoomToFit();
208
+ }
209
+
210
+ isZoomedToMax() {
211
+ return this.zoomUtils.isZoomedToMax();
212
+ }
213
+
214
+ isZoomedToMin() {
215
+ return this.zoomUtils.isZoomedToMin();
216
+ }
217
+
218
+ getZoomToReveal(objectIDs, xPos, yPos) {
219
+ return this.zoomUtils.getZoomToReveal(objectIDs, xPos, yPos);
220
+ }
221
+
222
+ getZoom() {
223
+ return this.zoomUtils.getZoom();
224
+ }
225
+
226
+ getTransformedViewportDimensions() {
227
+ return this.zoomUtils.getTransformedViewportDimensions();
302
228
  }
303
229
 
304
230
  // Returns the data object for the parent supernode that references the
305
231
  // active pipeline (managed by this renderer). We get the supernode by
306
- // looking through the overall canvas info objects.
232
+ // looking through the overall canvas info object.
307
233
  // Don't be tempted into thinking you can retrieve the supernode datum by
308
234
  // calling the parent renderer because there is no parent renderer when we
309
235
  // are showing a sub-flow in full page mode.
@@ -321,15 +247,8 @@ export default class SVGCanvasRenderer {
321
247
  return node;
322
248
  }
323
249
 
324
- getZoomTransform() {
325
- return this.zoomTransform;
326
- }
327
-
328
- initializeZoomVariables() {
329
- // Allows us to record the current zoom amounts.
330
- this.zoomTransform = d3.zoomIdentity.translate(0, 0).scale(1);
331
- }
332
-
250
+ // Provides new canvas info data to this renderer and other display and layout info
251
+ // so the canvas can be redisplayed.
333
252
  setCanvasInfoRenderer(canvasInfo, selectionInfo, breadcrumbs, nodeLayout, canvasLayout, config) {
334
253
  this.logger.logStartTimer("setCanvasInfoRenderer" + this.pipelineId.substring(0, 5));
335
254
  this.config = config;
@@ -422,7 +341,7 @@ export default class SVGCanvasRenderer {
422
341
  }
423
342
 
424
343
  clearCanvas() {
425
- this.initializeZoomVariables();
344
+ this.zoomUtils.resetZoomTransform();
426
345
  this.canvasSVG.remove();
427
346
  }
428
347
 
@@ -468,25 +387,27 @@ export default class SVGCanvasRenderer {
468
387
  this.displaySVGToFitSupernode();
469
388
  }
470
389
 
471
- // The supernode will not have any calculated port positions when the
472
- // subflow is being displayed full screen, so calculate them first.
473
- if (this.dispUtils.isDisplayingSubFlowFullPage()) {
474
- this.displayPortsForSubFlowFullPage();
475
- }
476
-
477
390
  this.displayCanvasAccoutrements();
478
391
 
479
392
  this.logger.logEndTimer("displayCanvas");
480
393
  }
481
394
 
395
+ // Displays zoom and size dependent canvas elements.
482
396
  displayCanvasAccoutrements() {
483
397
  if (this.config.enableBoundingRectangles) {
484
398
  this.displayBoundingRectangles();
485
399
  }
486
400
 
487
- if (this.config.enableCanvasUnderlay !== "None" && this.dispUtils.isDisplayingPrimaryFlowFullPage()) {
401
+ if (this.config.enableCanvasUnderlay !== "None" &&
402
+ this.dispUtils.isDisplayingPrimaryFlowFullPage()) {
488
403
  this.setCanvasUnderlaySize();
489
404
  }
405
+
406
+ // The supernode will not have any calculated port positions when the
407
+ // subflow is being displayed full screen, so calculate them first.
408
+ if (this.dispUtils.isDisplayingSubFlowFullPage()) {
409
+ this.displayPortsForSubFlowFullPage();
410
+ }
490
411
  }
491
412
 
492
413
  // Ensures the binding ports for a full-page sub-flow are calculated
@@ -544,7 +465,7 @@ export default class SVGCanvasRenderer {
544
465
  // Moves the binding nodes in a sub-flow, which map to nodes in the parent
545
466
  // supernode, to the edge of the SVG area.
546
467
  moveSuperBindingNodes() {
547
- const transformedSVGRect = this.getTransformedViewportDimensions();
468
+ const transformedSVGRect = this.zoomUtils.getTransformedViewportDimensions();
548
469
 
549
470
  // this.logger.log("transformedSVGRect" +
550
471
  // " x = " + transformedSVGRect.x +
@@ -611,10 +532,10 @@ export default class SVGCanvasRenderer {
611
532
  if (!this.activePipeline) {
612
533
  return;
613
534
  }
614
- const svgRect = this.getViewportDimensions();
615
- const transformedSVGRect = this.getTransformedRect(svgRect, 1);
616
- const canv = this.getCanvasDimensionsAdjustedForScale(1);
617
- const canvWithPadding = this.getCanvasDimensionsAdjustedForScale(1, this.getZoomToFitPadding());
535
+ const svgRect = this.zoomUtils.getViewportDimensions();
536
+ const transformedSVGRect = this.zoomUtils.getTransformedRect(svgRect, 1);
537
+ const canv = this.zoomUtils.getCanvasDimensions();
538
+ const canvWithPadding = this.zoomUtils.getCanvasDimensionsWithPadding();
618
539
 
619
540
  this.boundingRectsGrp.selectChildren(".d3-bounding").remove();
620
541
 
@@ -668,6 +589,16 @@ export default class SVGCanvasRenderer {
668
589
  }
669
590
  }
670
591
 
592
+ // Selects any objects in the region provided where region is { x, y, width, height }
593
+ selectObjsInRegion(region) {
594
+ const selections =
595
+ CanvasUtils.selectInRegion(region, this.activePipeline,
596
+ this.config.enableLinkSelection !== LINK_SELECTION_NONE,
597
+ this.config.enableLinkType,
598
+ this.config.enableAssocLinkType);
599
+ this.canvasController.setSelections(selections, this.activePipeline.id);
600
+ }
601
+
671
602
  // Returns true when we are editing text. Called by svg-canvas-d3.
672
603
  isEditingText() {
673
604
  if (this.svgCanvasTextArea.isEditingText()) {
@@ -684,20 +615,17 @@ export default class SVGCanvasRenderer {
684
615
 
685
616
  // Returns true when we are dragging objects. Called by svg-canvas-d3.
686
617
  isDragging() {
687
- return this.dragging;
618
+ return this.dragObjectUtils.isMoving() || this.dragNewLinkUtils.isDragging() || this.dragDetLinkUtils.isDragging();
688
619
  }
689
620
 
690
- // Returns true if the node should be resizeable. Expanded supernodes are
691
- // always resizabele and all other nodes, except collapsed supernodes, are
692
- // resizeable when enableResizableNodes is switched on.
693
- isNodeResizable(node) {
694
- if (!this.config.enableEditingActions ||
695
- CanvasUtils.isSuperBindingNode(node) ||
696
- CanvasUtils.isCollapsedSupernode(node) ||
697
- (!this.config.enableResizableNodes && !CanvasUtils.isExpandedSupernode(node))) {
698
- return false;
699
- }
700
- return true;
621
+ // Returns true whenever a node or comment is being resized.
622
+ isSizing() {
623
+ this.dragObjectUtils.isSizing();
624
+ }
625
+
626
+ // Returns true whenever a node or comment is being moved.
627
+ isMoving() {
628
+ this.dragObjectUtils.isMoving();
701
629
  }
702
630
 
703
631
  // Returns true if the node should have a resizing area. We should display
@@ -784,22 +712,11 @@ export default class SVGCanvasRenderer {
784
712
  return transPos;
785
713
  }
786
714
 
787
- // Returns the object passed in with its position and size snapped to
788
- // the current grid dimensions.
789
- snapToGridObject(inResizeObj) {
790
- const resizeObj = inResizeObj;
791
- resizeObj.x_pos = CanvasUtils.snapToGrid(resizeObj.x_pos, this.canvasLayout.snapToGridXPx);
792
- resizeObj.y_pos = CanvasUtils.snapToGrid(resizeObj.y_pos, this.canvasLayout.snapToGridYPx);
793
- resizeObj.width = CanvasUtils.snapToGrid(resizeObj.width, this.canvasLayout.snapToGridXPx);
794
- resizeObj.height = CanvasUtils.snapToGrid(resizeObj.height, this.canvasLayout.snapToGridYPx);
795
- return resizeObj;
796
- }
797
-
798
715
  // Returns the current mouse position transformed by the current zoom
799
716
  // transformation amounts based on the local SVG -- that is, if we're
800
717
  // displaying a sub-flow it is based on the SVG in the supernode.
801
718
  getTransformedMousePos(d3Event) {
802
- return this.transformPos(this.getMousePos(d3Event, this.canvasSVG));
719
+ return this.zoomUtils.transformPos(this.getMousePos(d3Event, this.canvasSVG));
803
720
  }
804
721
 
805
722
  // Returns the current mouse position based on the D3 SVG selection object
@@ -818,45 +735,19 @@ export default class SVGCanvasRenderer {
818
735
  return { x: 0, y: 0 };
819
736
  }
820
737
 
738
+ // Returns the page position passed in snapped to the grid in canvas
739
+ // coordinates. Called externally via svg-canvas-d3.
740
+ convertPageCoordsToSnappedCanvasCoords(pos) {
741
+ let positon = this.convertPageCoordsToCanvasCoords(pos.x, pos.y);
742
+ positon = this.getMousePosSnapToGrid(positon);
743
+ return positon;
744
+ }
745
+
821
746
  // Convert coordinates from the page (based on the page top left corner) to
822
747
  // canvas coordinates based on the canvas coordinate system.
823
748
  convertPageCoordsToCanvasCoords(x, y) {
824
749
  const svgRect = this.canvasSVG.node().getBoundingClientRect();
825
- return this.transformPos({ x: x - Math.round(svgRect.left), y: y - Math.round(svgRect.top) });
826
- }
827
-
828
- // Transforms the x and y fields passed in by the current zoom
829
- // transformation amounts to convert a coordinate position in screen pixels
830
- // to a canvas coordinate position.
831
- transformPos(pos) {
832
- return {
833
- x: (pos.x - this.zoomTransform.x) / this.zoomTransform.k,
834
- y: (pos.y - this.zoomTransform.y) / this.zoomTransform.k
835
- };
836
- }
837
-
838
- // Transforms the x and y fields passed in by the current zoom
839
- // transformation amounts to convert a canvas coordinate position
840
- // to a coordinate position in screen pixels.
841
- unTransformPos(pos) {
842
- return {
843
- x: (pos.x * this.zoomTransform.k) + this.zoomTransform.x,
844
- y: (pos.y * this.zoomTransform.k) + this.zoomTransform.y
845
- };
846
- }
847
-
848
- // Transforms the x, y, height and width fields of the object passed in by the
849
- // current zoom transformation amounts to convert coordinate positions and
850
- // dimensions in screen pixels to coordinate positions and dimensions in
851
- // zoomed pixels.
852
- getTransformedRect(svgRect, pad) {
853
- const transPad = (pad / this.zoomTransform.k);
854
- return {
855
- x: (-this.zoomTransform.x / this.zoomTransform.k) + transPad,
856
- y: (-this.zoomTransform.y / this.zoomTransform.k) + transPad,
857
- height: (svgRect.height / this.zoomTransform.k) - (2 * transPad),
858
- width: (svgRect.width / this.zoomTransform.k) - (2 * transPad)
859
- };
750
+ return this.zoomUtils.transformPos({ x: x - Math.round(svgRect.left), y: y - Math.round(svgRect.top) });
860
751
  }
861
752
 
862
753
  // Creates the div which contains the ghost node for drag and
@@ -893,6 +784,7 @@ export default class SVGCanvasRenderer {
893
784
  getGhostNode(node) {
894
785
  const that = this;
895
786
  const ghostDivSel = this.getGhostDivSel();
787
+ const zoomScale = this.zoomUtils.getZoomScale();
896
788
 
897
789
  // Calculate the ghost area width which is the maximum of either the node
898
790
  // label or the default node width.
@@ -908,8 +800,8 @@ export default class SVGCanvasRenderer {
908
800
  // Create a new SVG area for the ghost area.
909
801
  const ghostAreaSVG = ghostDivSel
910
802
  .append("svg")
911
- .attr("width", ghostAreaWidth * this.zoomTransform.k)
912
- .attr("height", (50 + node.height) * this.zoomTransform.k) // Add some extra pixels, in case label is below label bottom
803
+ .attr("width", ghostAreaWidth * zoomScale)
804
+ .attr("height", (50 + node.height) * zoomScale) // Add some extra pixels, in case label is below label bottom
913
805
  .attr("x", 0)
914
806
  .attr("y", 0)
915
807
  .attr("class", "d3-ghost-svg");
@@ -971,7 +863,7 @@ export default class SVGCanvasRenderer {
971
863
 
972
864
  // Next calculate the amount, if any, the label protrudes beyond the edge
973
865
  // of the node width and move the ghost group by that amount.
974
- xOffset = Math.max(0, (labelDisplayLength - node.width) / 2) * this.zoomTransform.k;
866
+ xOffset = Math.max(0, (labelDisplayLength - node.width) / 2) * zoomScale;
975
867
 
976
868
  // If the label is center justified, restrict the label width to the
977
869
  // display amount and adjust the x coordinate to compensate for the change
@@ -986,7 +878,7 @@ export default class SVGCanvasRenderer {
986
878
  }
987
879
  }
988
880
 
989
- ghostGrp.attr("transform", `translate(${xOffset}, 0) scale(${this.zoomTransform.k})`);
881
+ ghostGrp.attr("transform", `translate(${xOffset}, 0) scale(${zoomScale})`);
990
882
 
991
883
  // Get the amount the actual browser page is 'zoomed'. This is differet
992
884
  // to the zoom amount for the canvas objects.
@@ -994,8 +886,8 @@ export default class SVGCanvasRenderer {
994
886
 
995
887
  // Calculate the center of the node area for positioning the mouse pointer
996
888
  // on the image when it is being dragged.
997
- const centerX = (xOffset + ((node.width / 2) * this.zoomTransform.k)) * browserZoom;
998
- const centerY = ((node.height / 2) * this.zoomTransform.k) * browserZoom;
889
+ const centerX = (xOffset + ((node.width / 2) * zoomScale)) * browserZoom;
890
+ const centerY = ((node.height / 2) * zoomScale) * browserZoom;
999
891
 
1000
892
  return {
1001
893
  element: ghostDivSel.node(),
@@ -1141,62 +1033,33 @@ export default class SVGCanvasRenderer {
1141
1033
  .attr("data-drag-node-over", state ? true : null); // true will add the attr, null will remove it
1142
1034
  }
1143
1035
 
1144
- // Switches on or off node port highlighting depending on the node
1036
+ // Switches on or off node highlighting depending on the node
1145
1037
  // passed in and keeps track of the currently highlighted node. This is
1146
- // called as a new link is being drawn towards a target node to highlight
1147
- // the target node.
1148
- setNewLinkOverNode(d3Event) {
1149
- const nodeNearMouse = this.getNodeNearMousePos(d3Event, this.canvasLayout.nodeProximity);
1150
- if (nodeNearMouse && this.isConnectionAllowedToNearbyNode(d3Event, nodeNearMouse)) {
1038
+ // called as a new link or a detached link is being dragged towards or
1039
+ // away from a target node to highlight or unhighlight the target node.
1040
+ setHighlightingOverNode(state, nodeNearMouse) {
1041
+ if (state) {
1151
1042
  if (!this.dragNewLinkOverNode) {
1152
1043
  this.dragNewLinkOverNode = nodeNearMouse;
1153
- this.setNewLinkOverNodeHighlighting(this.dragNewLinkOverNode, true);
1044
+ this.setLinkOverNode(this.dragNewLinkOverNode, true);
1154
1045
 
1155
1046
  } else if (nodeNearMouse.id !== this.dragNewLinkOverNode.id) {
1156
- this.setNewLinkOverNodeHighlighting(this.dragNewLinkOverNode, false);
1047
+ this.setLinkOverNode(this.dragNewLinkOverNode, false);
1157
1048
  this.dragNewLinkOverNode = nodeNearMouse;
1158
- this.setNewLinkOverNodeHighlighting(this.dragNewLinkOverNode, true);
1049
+ this.setLinkOverNode(this.dragNewLinkOverNode, true);
1159
1050
  }
1160
1051
 
1161
1052
  } else {
1162
1053
  if (this.dragNewLinkOverNode) {
1163
- this.setNewLinkOverNodeHighlighting(this.dragNewLinkOverNode, false);
1054
+ this.setLinkOverNode(this.dragNewLinkOverNode, false);
1164
1055
  this.dragNewLinkOverNode = null;
1165
1056
  }
1166
1057
  }
1167
1058
  }
1168
1059
 
1169
- isConnectionAllowedToNearbyNode(d3Event, nodeNearMouse) {
1170
- if (this.drawingNewLinkData) {
1171
- if (this.drawingNewLinkData.action === NODE_LINK) {
1172
- const srcNode = this.drawingNewLinkData.srcNode;
1173
- const trgNode = nodeNearMouse;
1174
- const srcNodePortId = this.drawingNewLinkData.srcNodePortId;
1175
- const trgNodePortId = CanvasUtils.getDefaultInputPortId(trgNode); // TODO - make specific to nodes.
1176
- return CanvasUtils.isDataConnectionAllowed(srcNodePortId, trgNodePortId, srcNode, trgNode, this.activePipeline.links);
1177
-
1178
- } else if (this.drawingNewLinkData.action === ASSOCIATION_LINK) {
1179
- const srcNode = this.drawingNewLinkData.srcNode;
1180
- const trgNode = nodeNearMouse;
1181
- return CanvasUtils.isAssocConnectionAllowed(srcNode, trgNode, this.activePipeline.links);
1182
-
1183
- } else if (this.drawingNewLinkData.action === COMMENT_LINK) {
1184
- const srcObjId = this.drawingNewLinkData.srcObjId;
1185
- const trgNodeId = nodeNearMouse.id;
1186
- return CanvasUtils.isCommentLinkConnectionAllowed(srcObjId, trgNodeId, this.activePipeline.links);
1187
- }
1188
- } else if (this.draggingLinkData) {
1189
- const newLink = this.getNewLinkOnDrag(d3Event, this.canvasLayout.nodeProximity);
1190
- if (newLink) {
1191
- return true;
1192
- }
1193
- }
1194
- return false;
1195
- }
1196
-
1197
- // Switches on or off the input-port highlighting on the node passed in.
1198
- // This is called when the user drags a new link towards a target node.
1199
- setNewLinkOverNodeHighlighting(node, state) {
1060
+ // Switches on or off the highlighting on the node passed in.
1061
+ // This is called when the user drags a link towards a target node.
1062
+ setLinkOverNode(node, state) {
1200
1063
  if (node) {
1201
1064
  this.getNodeGroupSelectionById(node.id)
1202
1065
  .attr("data-new-link-over", state ? "yes" : "no");
@@ -1205,8 +1068,8 @@ export default class SVGCanvasRenderer {
1205
1068
 
1206
1069
  // Removes the data-new-link-over attribute used for highlighting a node
1207
1070
  // that a new link is being dragged towards or over.
1208
- setNewLinkOverNodeCancel() {
1209
- this.setNewLinkOverNodeHighlighting(this.dragNewLinkOverNode, false);
1071
+ setLinkOverNodeCancel() {
1072
+ this.setLinkOverNode(this.dragNewLinkOverNode, false);
1210
1073
  this.dragNewLinkOverNode = null;
1211
1074
  }
1212
1075
 
@@ -1275,25 +1138,6 @@ export default class SVGCanvasRenderer {
1275
1138
  !this.isPortMaxCardinalityZero(nodeTemplate.outputs[0]);
1276
1139
  }
1277
1140
 
1278
- // Returns true if the current drag objects array has a single node which
1279
- // is 'insertable' into a data link between nodes on the canvas. Returns
1280
- // false otherwise, including if a single comment is being dragged.
1281
- isExistingNodeInsertableIntoLink() {
1282
- return (this.config.enableInsertNodeDroppedOnLink &&
1283
- this.dragObjects.length === 1 &&
1284
- CanvasUtils.isNode(this.dragObjects[0]) &&
1285
- CanvasUtils.hasInputAndOutputPorts(this.dragObjects[0]) &&
1286
- !CanvasUtils.isNodeDefaultPortsCardinalityAtMax(this.dragObjects[0], this.activePipeline.links));
1287
- }
1288
-
1289
- // Returns true if the current drag objects array has a single node which
1290
- // is 'attachable' to any detached link on the canvas. Returns false otherwise,
1291
- // including if a single comment is being dragged.
1292
- isExistingNodeAttachableToDetachedLinks() {
1293
- return (this.config.enableLinkSelection === LINK_SELECTION_DETACHABLE &&
1294
- this.dragObjects.length === 1 &&
1295
- CanvasUtils.isNode(this.dragObjects[0]));
1296
- }
1297
1141
 
1298
1142
  // Returns true if the current node template being dragged from the palette
1299
1143
  // is 'attachable' to any detached link on the canvas. Returns false otherwise.
@@ -1328,7 +1172,6 @@ export default class SVGCanvasRenderer {
1328
1172
  });
1329
1173
 
1330
1174
  if (attachableLinks.length > 0) {
1331
-
1332
1175
  // Make sure the attachable links can be attached to the node based on
1333
1176
  // the availability of ports and whether they are maxed out or not.
1334
1177
  const linkArrays =
@@ -1354,15 +1197,15 @@ export default class SVGCanvasRenderer {
1354
1197
  // means the factors will multiply as they percolate up to the top flow.
1355
1198
  setMaxZoomExtent(factor) {
1356
1199
  if (this.dispUtils.isDisplayingFullPage()) {
1357
- const newMaxExtent = this.maxScaleExtent * factor;
1358
-
1359
- this.zoom.scaleExtent([this.minScaleExtent, newMaxExtent]);
1200
+ this.zoomUtils.setMaxZoomExtent(factor);
1360
1201
  } else {
1361
- const newFactor = Number(factor) * 1 / this.zoomTransform.k;
1202
+ const newFactor = Number(factor) * 1 / this.zoomUtils.getZoomScale();
1362
1203
  this.supernodeInfo.renderer.setMaxZoomExtent(newFactor);
1363
1204
  }
1364
1205
  }
1365
1206
 
1207
+ // Returns a new canvas SVG object with all the behavior expected of a great
1208
+ // SVG canvas object.
1366
1209
  createCanvasSVG() {
1367
1210
  this.logger.log("Create Canvas SVG.");
1368
1211
 
@@ -1393,14 +1236,14 @@ export default class SVGCanvasRenderer {
1393
1236
  .attr("x", dims.x)
1394
1237
  .attr("y", dims.y)
1395
1238
  .on("mouseenter", (d3Event, d) => {
1396
- // If we are a sub-flow (i.e we have a parent renderer) set the max
1239
+ // If we are a sub-flow (i.e. we have a parent renderer) set the max
1397
1240
  // zoom extent with a factor calculated from our zoom amount.
1398
1241
  if (this.supernodeInfo.renderer && this.config.enableZoomIntoSubFlows) {
1399
- this.supernodeInfo.renderer.setMaxZoomExtent(1 / this.zoomTransform.k);
1242
+ this.supernodeInfo.renderer.setMaxZoomExtent(1 / this.zoomUtils.getZoomScale());
1400
1243
  }
1401
1244
  })
1402
1245
  .on("mouseleave", (d3Event, d) => {
1403
- // If we are a sub-flow (i.e we have a parent renderer) set the max
1246
+ // If we are a sub-flow (i.e. we have a parent renderer) set the max
1404
1247
  // zoom extent with a factor of 1.
1405
1248
  if (this.supernodeInfo.renderer && this.config.enableZoomIntoSubFlows) {
1406
1249
  this.supernodeInfo.renderer.setMaxZoomExtent(1);
@@ -1458,21 +1301,12 @@ export default class SVGCanvasRenderer {
1458
1301
  if (!this.activePipeline.isEmptyOrBindingsOnly() &&
1459
1302
  this.dispUtils.isDisplayingFullPage()) {
1460
1303
  this.canvasSVG
1461
- .call(this.zoom);
1304
+ .call(this.zoomUtils.getZoomHandler());
1462
1305
  }
1463
1306
 
1464
1307
  // These behaviors will be applied to SVG areas at the top level and
1465
1308
  // SVG areas displaying an in-place subflow
1466
1309
  this.canvasSVG
1467
- .on("mousemove.zoom", (d3Event) => {
1468
- // this.logger.log("Zoom - mousemove - drawingNewLink = " + this.drawingNewLinkData ? "yes" : "no");
1469
- if (this.drawingNewLinkData) {
1470
- this.drawNewLink(d3Event);
1471
- }
1472
- if (this.draggingLinkData) {
1473
- this.dragLinkHandle(d3Event);
1474
- }
1475
- })
1476
1310
  // Don't use mousedown.zoom here as it will replace the zoom start behavior
1477
1311
  // and prevent panning of canvas background.
1478
1312
  .on("mousedown", (d3Event) => {
@@ -1485,16 +1319,8 @@ export default class SVGCanvasRenderer {
1485
1319
  d3Event.stopPropagation();
1486
1320
  }
1487
1321
  })
1488
- .on("mouseup.zoom", (d3Event) => {
1489
- this.logger.log("Canvas - mouseup-zoom");
1490
- if (this.drawingNewLinkData) {
1491
- this.completeNewLink(d3Event);
1492
- }
1493
- if (this.draggingLinkData) {
1494
- this.completeDraggedLink(d3Event);
1495
- }
1496
- })
1497
1322
  .on("click.zoom", (d3Event) => {
1323
+ // Control comes here after the zoomClick action has been perfoemd in zoomUtils.
1498
1324
  this.logger.log("Canvas - click-zoom");
1499
1325
 
1500
1326
  this.canvasController.clickActionHandler({
@@ -1519,7 +1345,7 @@ export default class SVGCanvasRenderer {
1519
1345
  // Resets the pointer cursor on the background rectangle in the Canvas SVG area.
1520
1346
  resetCanvasCursor() {
1521
1347
  const selector = ".d3-svg-background[data-pipeline-id='" + this.activePipeline.id + "']";
1522
- this.canvasSVG.select(selector).style("cursor", this.isDragActivated() && this.dispUtils.isDisplayingFullPage() ? "grab" : "default");
1348
+ this.canvasSVG.select(selector).style("cursor", this.zoomUtils.isDragActivated() && this.dispUtils.isDisplayingFullPage() ? "grab" : "default");
1523
1349
  }
1524
1350
 
1525
1351
  createCanvasGroup(canvasObj, className) {
@@ -1541,7 +1367,7 @@ export default class SVGCanvasRenderer {
1541
1367
  }
1542
1368
 
1543
1369
  setCanvasUnderlaySize(x = 0, y = 0) {
1544
- const canv = this.getCanvasDimensionsAdjustedForScale(1, this.getZoomToFitPadding());
1370
+ const canv = this.zoomUtils.getCanvasDimensionsWithPadding();
1545
1371
  if (canv) {
1546
1372
  this.canvasUnderlay
1547
1373
  .attr("x", canv.left - 50)
@@ -1551,50 +1377,6 @@ export default class SVGCanvasRenderer {
1551
1377
  }
1552
1378
  }
1553
1379
 
1554
- addBackToParentFlowArrow(canvasSVG) {
1555
- const g = canvasSVG
1556
- .append("g")
1557
- .attr("transform", "translate(15, 15)")
1558
- .on("mouseenter", function(d3Event, d) { // Use function keyword so 'this' pointer references the DOM text object
1559
- d3.select(this).select("rect")
1560
- .attr("data-pointer-hover", "yes");
1561
- })
1562
- .on("mouseleave", function(d3Event, d) { // Use function keyword so 'this' pointer references the DOM text object
1563
- d3.select(this).select("rect")
1564
- .attr("data-pointer-hover", "no");
1565
- })
1566
- .on("mousedown mouseup", (d3Event) => {
1567
- // Prevent mouse events going through to the canvas. This prevents
1568
- // a drag gesture on the button activating the canvas drag action.
1569
- CanvasUtils.stopPropagationAndPreventDefault(d3Event);
1570
- })
1571
- .on("click", (d3Event) => {
1572
- CanvasUtils.stopPropagationAndPreventDefault(d3Event);
1573
- this.canvasController.displayPreviousPipeline();
1574
- });
1575
-
1576
- g.append("rect")
1577
- .attr("x", 0)
1578
- .attr("y", 0)
1579
- .attr("width", 210)
1580
- .attr("height", 40)
1581
- .attr("class", "d3-back-to-previous-flow-box");
1582
-
1583
- g.append("svg")
1584
- .attr("x", 16)
1585
- .attr("y", 11)
1586
- .attr("width", 16)
1587
- .attr("height", 16)
1588
- .html(LEFT_ARROW_ICON)
1589
- .attr("class", "d3-back-to-previous-flow-text");
1590
-
1591
- g.append("text")
1592
- .attr("x", 40)
1593
- .attr("y", 24)
1594
- .attr("class", "d3-back-to-previous-flow-text")
1595
- .text("Return to previous flow");
1596
- }
1597
-
1598
1380
  createDropShadow(defs) {
1599
1381
  var dropShadowFilter = defs.append("filter")
1600
1382
  .attr("id", this.getId("node_drop_shadow"))
@@ -1643,6 +1425,7 @@ export default class SVGCanvasRenderer {
1643
1425
  });
1644
1426
  }
1645
1427
  }
1428
+
1646
1429
  setNodeLabelEditingMode(nodeId, pipelineId) {
1647
1430
  if (this.pipelineId === pipelineId) {
1648
1431
  const node = this.activePipeline.getNode(nodeId);
@@ -1694,1066 +1477,133 @@ export default class SVGCanvasRenderer {
1694
1477
  }
1695
1478
  }
1696
1479
 
1697
- // Restores the zoom of the canvas, if it has changed, based on the type
1698
- // of 'save zoom' specified in the configuration and, if no saved zoom, was
1699
- // provided pans the canvas area so it is always visible.
1700
- restoreZoom() {
1701
- let newZoom = this.canvasController.getSavedZoom(this.pipelineId);
1702
-
1703
- // If there's no saved zoom, and enablePanIntoViewOnOpen is set, pan so
1704
- // the canvas area (containing nodes and comments) is visible in the viewport.
1705
- if (!newZoom && this.config.enablePanIntoViewOnOpen) {
1706
- const canvWithPadding = this.getCanvasDimensionsAdjustedForScale(1, this.getZoomToFitPadding());
1707
- if (canvWithPadding) {
1708
- newZoom = { x: -canvWithPadding.left, y: -canvWithPadding.top, k: 1 };
1480
+ // Repositions the comment toolbar so it is always over the top of the
1481
+ // comment being edited.
1482
+ repositionCommentToolbar() {
1483
+ if (this.config.enableMarkdownInComments &&
1484
+ this.dispUtils.isDisplayingFullPage() &&
1485
+ this.svgCanvasTextArea.isEditingText()) {
1486
+ // If a node label or text decoration is being edited com will be undefined.
1487
+ const com = this.activePipeline.getComment(this.svgCanvasTextArea.getEditingTextId());
1488
+ if (com) {
1489
+ const pos = this.getCommentToolbarPos(com);
1490
+ this.canvasController.moveTextToolbar(pos.x, pos.y);
1709
1491
  }
1710
1492
  }
1711
-
1712
- // If there's no saved zoom and we have some initial pan amounts provided use them.
1713
- if (!newZoom && this.canvasLayout.initialPanX && this.canvasLayout.initialPanY) {
1714
- newZoom = { x: this.canvasLayout.initialPanX, y: this.canvasLayout.initialPanY, k: 1 };
1715
- }
1716
-
1717
- // If new zoom is different to the current zoom amount, apply it.
1718
- if (newZoom &&
1719
- (newZoom.k !== this.zoomTransform.k ||
1720
- newZoom.x !== this.zoomTransform.x ||
1721
- newZoom.y !== this.zoomTransform.y)) {
1722
- this.zoomCanvasInvokeZoomBehavior(newZoom);
1723
- }
1724
1493
  }
1725
1494
 
1726
- // Zooms the canvas to the amount specified in newZoomTransform. Zooming the
1727
- // canvas in this way will invoke the zoom behavior methods: zoomStart,
1728
- // zoomAction and zoomEnd. It does not perform a zoom if newZoomTransform
1729
- // is the same as the current zoom transform.
1730
- zoomCanvasInvokeZoomBehavior(newZoomTransform, animateTime) {
1731
- if (isFinite(newZoomTransform.x) &&
1732
- isFinite(newZoomTransform.y) &&
1733
- isFinite(newZoomTransform.k) &&
1734
- this.zoomHasChanged(newZoomTransform)) {
1735
- this.zoomingAction = true;
1736
- const zoomTransform = d3.zoomIdentity.translate(newZoomTransform.x, newZoomTransform.y).scale(newZoomTransform.k);
1737
- if (animateTime) {
1738
- this.canvasSVG.call(this.zoom).transition()
1739
- .duration(animateTime)
1740
- .call(this.zoom.transform, zoomTransform);
1741
- } else {
1742
- this.canvasSVG.call(this.zoom.transform, zoomTransform);
1743
- }
1744
- this.zoomingAction = false;
1745
- }
1495
+ // Returns a position object that describes the position in page coordinates
1496
+ // of the comment toolbar so that it is positioned above the comment being edited.
1497
+ getCommentToolbarPos(com) {
1498
+ const pos = this.zoomUtils.unTransformPos({ x: com.x_pos, y: com.y_pos });
1499
+ return {
1500
+ x: pos.x + this.canvasLayout.commentToolbarPosX,
1501
+ y: pos.y + this.canvasLayout.commentToolbarPosY
1502
+ };
1746
1503
  }
1747
1504
 
1748
- // Return true if the new zoom transform passed in is different from the
1749
- // current zoom transform.
1750
- zoomHasChanged(newZoomTransform) {
1751
- return newZoomTransform.k !== this.zoomTransform.k ||
1752
- newZoomTransform.x !== this.zoomTransform.x ||
1753
- newZoomTransform.y !== this.zoomTransform.y;
1754
- }
1505
+ // Returns the snap-to-grid position of the object positioned at objPos.x
1506
+ // and objPos.y. The grid that is snapped to is defined by this.snapToGridXPx
1507
+ // and this.snapToGridYPx values which are pixel values.
1508
+ snapToGridPosition(objPos) {
1509
+ const stgPosX = CanvasUtils.snapToGrid(objPos.x, this.canvasLayout.snapToGridXPx);
1510
+ const stgPosY = CanvasUtils.snapToGrid(objPos.y, this.canvasLayout.snapToGridYPx);
1755
1511
 
1756
- zoomToFit() {
1757
- const padding = this.getZoomToFitPadding();
1758
- const canvasDimensions = this.getCanvasDimensionsAdjustedForScale(1, padding);
1759
- const viewPortDimensions = this.getViewportDimensions();
1512
+ return { x: stgPosX, y: stgPosY };
1513
+ }
1760
1514
 
1761
- if (canvasDimensions) {
1762
- const xRatio = viewPortDimensions.width / canvasDimensions.width;
1763
- const yRatio = viewPortDimensions.height / canvasDimensions.height;
1764
- const newScale = Math.min(xRatio, yRatio, 1); // Don't let the canvas be scaled more than 1 in either direction
1515
+ // Displays all the nodes on the canvas either by creating new nodes,
1516
+ // updating existing nodes or removing unwanted nodes.
1517
+ displayNodes() {
1518
+ this.logger.logStartTimer("displayNodes " + this.getFlags());
1765
1519
 
1766
- let x = (viewPortDimensions.width - (canvasDimensions.width * newScale)) / 2;
1767
- let y = (viewPortDimensions.height - (canvasDimensions.height * newScale)) / 2;
1520
+ // Set the port positions for all ports - these will be needed when displaying
1521
+ // nodes and links. This needs to be done here because a resized supernode
1522
+ // will cause its ports to move and resizing comments causes links to be
1523
+ // redrawn which will need port positions to be set appropriately.
1524
+ this.setPortPositionsAllNodes();
1768
1525
 
1769
- x -= newScale * canvasDimensions.left;
1770
- y -= newScale * canvasDimensions.top;
1526
+ const sel = this.getAllNodeGroupsSelection();
1527
+ this.displayNodesSubset(sel, this.activePipeline.nodes);
1771
1528
 
1772
- this.zoomCanvasInvokeZoomBehavior({ x: x, y: y, k: newScale });
1773
- }
1529
+ this.logger.logEndTimer("displayNodes " + this.getFlags());
1774
1530
  }
1775
1531
 
1776
- // Returns the padding space for the canvas objects to be zoomed which takes
1777
- // into account any connections that need to be made to/from any sub-flow
1778
- // binding nodes plus any space needed for the binding nodes ports.
1779
- getZoomToFitPadding() {
1780
- let padding = this.canvasLayout.zoomToFitPadding;
1532
+ displayMovedNodes() {
1533
+ this.logger.logStartTimer("displayMovedNodes");
1534
+ const nodeGroupSel = this.getAllNodeGroupsSelection();
1535
+
1536
+ nodeGroupSel
1537
+ .datum((d) => this.activePipeline.getNode(d.id))
1538
+ .attr("transform", (d) => `translate(${d.x_pos}, ${d.y_pos})`);
1781
1539
 
1782
1540
  if (this.dispUtils.isDisplayingSubFlow()) {
1783
- // Allocate some space for connecting lines and the binding node ports
1784
- const newPadding = this.getMaxZoomToFitPaddingForConnections() + (2 * this.canvasLayout.supernodeBindingPortRadius);
1785
- padding = Math.max(padding, newPadding);
1541
+ nodeGroupSel
1542
+ .each((d, i, nodeGrps) => {
1543
+ if (d.isSupernodeInputBinding) {
1544
+ this.updatePortRadiusAndPos(nodeGrps[i], d, "d3-node-port-output-main");
1545
+ }
1546
+ if (d.isSupernodeOutputBinding) {
1547
+ this.updatePortRadiusAndPos(nodeGrps[i], d, "d3-node-port-input-main");
1548
+ this.updatePortArrowPath(nodeGrps[i], "d3-node-port-input-arrow");
1549
+ }
1550
+ });
1786
1551
  }
1787
- return padding;
1552
+ this.logger.logEndTimer("displayMovedNodes");
1788
1553
  }
1789
1554
 
1790
- zoomTo(zoomObject) {
1791
- const animateTime = 500;
1792
- this.zoomCanvasInvokeZoomBehavior(zoomObject, animateTime);
1793
- }
1555
+ displayNodesSelectionStatus(selectionInfo) {
1556
+ this.logger.logStartTimer("displayNodesSelectionStatus");
1794
1557
 
1795
- getZoom() {
1796
- return { x: this.zoomTransform.x, y: this.zoomTransform.y, k: this.zoomTransform.k };
1797
- }
1558
+ this.getAllNodeGroupsSelection()
1559
+ .selectChildren(".d3-node-selection-highlight")
1560
+ .attr("data-selected", (d) => (this.activePipeline.isSelected(d.id) ? "yes" : "no"));
1798
1561
 
1799
- translateBy(x, y, animateTime) {
1800
- const z = this.getZoomTransform();
1801
- const zoomObject = d3.zoomIdentity.translate(z.x + x, z.y + y).scale(z.k);
1802
- this.zoomCanvasInvokeZoomBehavior(zoomObject, animateTime);
1562
+ this.logger.logEndTimer("displayNodesSelectionStatus");
1803
1563
  }
1804
1564
 
1805
- zoomIn() {
1806
- if (this.zoomTransform.k < this.maxScaleExtent) {
1807
- const newScale = Math.min(this.zoomTransform.k * 1.1, this.maxScaleExtent);
1808
- this.canvasSVG.call(this.zoom.scaleTo, newScale);
1809
- }
1810
- }
1565
+ displaySingleNode(d) {
1566
+ this.logger.logStartTimer("displaySingleNode " + this.getFlags());
1811
1567
 
1812
- zoomOut() {
1813
- if (this.zoomTransform.k > this.minScaleExtent) {
1814
- const newScale = Math.max(this.zoomTransform.k / 1.1, this.minScaleExtent);
1815
- this.canvasSVG.call(this.zoom.scaleTo, newScale);
1816
- }
1817
- }
1568
+ this.setPortPositionsForNode(d);
1818
1569
 
1819
- isZoomedToMax() {
1820
- return this.zoomTransform ? this.zoomTransform.k === this.maxScaleExtent : false;
1821
- }
1570
+ const selection = this.getNodeGroupSelectionById(d.id);
1571
+ this.displayNodesSubset(selection, [d]);
1822
1572
 
1823
- isZoomedToMin() {
1824
- return this.zoomTransform ? this.zoomTransform.k === this.minScaleExtent : false;
1573
+ this.logger.logEndTimer("displaySingleNode " + this.getFlags());
1825
1574
  }
1826
1575
 
1827
- getZoomToReveal(objectIDs, xPos, yPos) {
1828
- const transformedSVGRect = this.getTransformedViewportDimensions();
1829
- const nodes = this.activePipeline.getNodes(objectIDs);
1830
- const comments = this.activePipeline.getComments(objectIDs);
1831
- const links = this.activePipeline.getLinks(objectIDs);
1832
-
1833
- if (nodes.length > 0 || comments.length > 0 || links.length > 0) {
1834
- const canvasDimensions = CanvasUtils.getCanvasDimensions(nodes, comments, links, 0, 0, true);
1835
- const canv = this.convertCanvasDimensionsAdjustedForScaleWithPadding(canvasDimensions, 1, 10);
1836
- const xPosInt = parseInt(xPos, 10);
1837
- const yPosInt = typeof yPos === "undefined" ? xPosInt : parseInt(yPos, 10);
1838
-
1839
- if (canv) {
1840
- let xOffset;
1841
- let yOffset;
1576
+ displayNodesSubset(selection, data) {
1577
+ selection
1578
+ .data(data, (d) => d.id)
1579
+ .join(
1580
+ (enter) => this.createNodes(enter),
1581
+ null,
1582
+ (remove) => this.removeNodes(remove)
1583
+ )
1584
+ .attr("transform", (d) => `translate(${d.x_pos}, ${d.y_pos})`)
1585
+ .attr("class", (d) => this.getNodeGroupClass(d))
1586
+ .attr("style", (d) => this.getNodeGrpStyle(d))
1587
+ .call((joinedNodeGrps) => this.updateNodes(joinedNodeGrps, data));
1588
+ }
1842
1589
 
1843
- if (!Number.isNaN(xPosInt) && !Number.isNaN(yPosInt)) {
1844
- xOffset = transformedSVGRect.x + (transformedSVGRect.width * (xPosInt / 100)) - (canv.left + (canv.width / 2));
1845
- yOffset = transformedSVGRect.y + (transformedSVGRect.height * (yPosInt / 100)) - (canv.top + (canv.height / 2));
1590
+ createNodes(enter) {
1591
+ this.logger.logStartTimer("createNodes");
1846
1592
 
1847
- } else {
1848
- if (canv.right > transformedSVGRect.x + transformedSVGRect.width) {
1849
- xOffset = transformedSVGRect.x + transformedSVGRect.width - canv.right;
1850
- }
1851
- if (canv.left < transformedSVGRect.x) {
1852
- xOffset = transformedSVGRect.x - canv.left;
1853
- }
1854
- if (canv.bottom > transformedSVGRect.y + transformedSVGRect.height) {
1855
- yOffset = transformedSVGRect.y + transformedSVGRect.height - canv.bottom;
1856
- }
1857
- if (canv.top < transformedSVGRect.y) {
1858
- yOffset = transformedSVGRect.y - canv.top;
1859
- }
1860
- }
1593
+ const newNodeGroups = enter
1594
+ .append("g")
1595
+ .attr("data-id", (d) => this.getId("node_grp", d.id))
1596
+ .call(this.attachNodeGroupListeners.bind(this));
1861
1597
 
1862
- if (typeof xOffset !== "undefined" || typeof yOffset !== "undefined") {
1863
- const x = this.zoomTransform.x + ((xOffset || 0)) * this.zoomTransform.k;
1864
- const y = this.zoomTransform.y + ((yOffset || 0)) * this.zoomTransform.k;
1865
- return { x: x || 0, y: y || 0, k: this.zoomTransform.k };
1866
- }
1867
- }
1868
- }
1598
+ this.logger.logEndTimer("createNodes");
1869
1599
 
1870
- return null;
1600
+ return newNodeGroups;
1871
1601
  }
1872
1602
 
1873
- // Returns an object representing the viewport dimensions which have been
1874
- // transformed for the current zoom amount.
1875
- getTransformedViewportDimensions() {
1876
- const svgRect = this.getViewportDimensions();
1877
- return this.getTransformedRect(svgRect, 0);
1878
- }
1603
+ updateNodes(joinedNodeGrps, data) {
1604
+ this.logger.logStartTimer("updateNodes");
1879
1605
 
1880
- // Returns the dimensions of the SVG area. When we are displaying a sub-flow
1881
- // we can use the supernode's dimensions. If not we are displaying
1882
- // full-page so we can use getBoundingClientRect() to get the dimensions
1883
- // (for some reason that method doesn't return correct values with embedded SVG areas).
1884
- getViewportDimensions() {
1885
- let viewportDimensions = {};
1886
-
1887
- if (this.dispUtils.isDisplayingSubFlowInPlace()) {
1888
- const dims = this.getParentSupernodeSVGDimensions();
1889
- viewportDimensions.width = dims.width;
1890
- viewportDimensions.height = dims.height;
1891
-
1892
- } else {
1893
- if (this.canvasSVG && this.canvasSVG.node()) {
1894
- viewportDimensions = this.canvasSVG.node().getBoundingClientRect();
1895
- } else {
1896
- viewportDimensions = { x: 0, y: 0, width: 1100, height: 640 }; // Return a sensible default (for Jest tests)
1897
- }
1898
- }
1899
- return viewportDimensions;
1900
- }
1901
-
1902
- zoomStart(d3Event) {
1903
- this.logger.log("zoomStart - " + JSON.stringify(d3Event.transform));
1904
-
1905
- // Ensure any open tip is closed before starting a zoom operation.
1906
- this.canvasController.closeTip();
1907
-
1908
- // Close the context menu, if it's open, before panning or zooming.
1909
- // If the context menu is opened inside the expanded supernode (in-place
1910
- // subflow), when the user zooms the canvas, the full page flow is handling
1911
- // that zoom, which causes a refresh in the subflow, so the full page flow
1912
- // will take care of closing the context menu. This means the in-place
1913
- // subflow doesn’t need to do anything on zoom,
1914
- // hence: !this.dispUtils.isDisplayingSubFlowInPlace()
1915
- if (this.canvasController.isContextMenuDisplayed() &&
1916
- !this.dispUtils.isDisplayingSubFlowInPlace()) {
1917
- this.canvasController.closeContextMenu();
1918
- this.contextMenuClosedOnZoom = true;
1919
- }
1920
-
1921
- // Any text editing in progress will be closed by the textarea's blur event
1922
- // if the user clicks on the canvas background. So we set this flag to
1923
- // prevent the selection being lost in the zoomEnd (mouseup) event.
1924
- if (this.svgCanvasTextArea.isEditingText()) {
1925
- this.textEditingClosedOnZoom = true;
1926
- }
1927
-
1928
- this.regionSelect = this.shouldDoRegionSelect(d3Event);
1929
-
1930
- if (this.regionSelect) {
1931
- // Add a delay so, if the user just clicks, they don't see the crosshair.
1932
- // This will be cleared in zoomEnd if the user's click takes less than 200 ms.
1933
- this.addingCursorOverlay = setTimeout(() => this.addTempCursorOverlay("crosshair"), 200);
1934
- this.regionStartTransformX = d3Event.transform.x;
1935
- this.regionStartTransformY = d3Event.transform.y;
1936
-
1937
- } else {
1938
- if (this.isDragActivated(d3Event)) {
1939
- this.addingCursorOverlay = setTimeout(() => this.addTempCursorOverlay("grabbing"), 200);
1940
- } else {
1941
- this.addingCursorOverlay = setTimeout(() => this.addTempCursorOverlay("default"), 200);
1942
- }
1943
- }
1944
-
1945
- const transPos = this.getTransformedMousePos(d3Event);
1946
- this.zoomStartPoint = { x: d3Event.transform.x, y: d3Event.transform.y, k: d3Event.transform.k, startX: transPos.x, startY: transPos.y };
1947
- this.previousD3Event = { x: d3Event.transform.x, y: d3Event.transform.y, k: d3Event.transform.k };
1948
- // Calculate the canvas dimensions here, so we don't have to recalculate
1949
- // them for every zoom action event.
1950
- this.zoomCanvasDimensions = CanvasUtils.getCanvasDimensions(
1951
- this.activePipeline.nodes, this.activePipeline.comments,
1952
- this.activePipeline.links, this.canvasLayout.commentHighlightGap);
1953
- }
1954
-
1955
- zoomAction(d3Event) {
1956
- this.logger.log("zoomAction - " + JSON.stringify(d3Event.transform));
1957
-
1958
- // If the scale amount is the same we are not zooming, so we must be panning.
1959
- if (d3Event.transform.k === this.zoomStartPoint.k) {
1960
- if (this.regionSelect) {
1961
- this.drawRegionSelector(d3Event);
1962
-
1963
- } else {
1964
- this.zoomCanvasBackground(d3Event);
1965
- }
1966
- } else {
1967
- this.addTempCursorOverlay("default");
1968
- this.zoomCanvasBackground(d3Event);
1969
- this.zoomCommentToolbar();
1970
- }
1971
- }
1972
-
1973
- zoomEnd(d3Event) {
1974
- this.logger.log("zoomEnd - " + JSON.stringify(d3Event.transform));
1975
-
1976
- // Clears the display of the cursor overlay if the user clicks within 200 ms
1977
- clearTimeout(this.addingCursorOverlay);
1978
-
1979
- const transPos = this.getTransformedMousePos(d3Event);
1980
-
1981
- if (this.drawingNewLinkData) {
1982
- this.stopDrawingNewLink();
1983
-
1984
- } else if (this.draggingLinkData) {
1985
- this.stopDraggingLink();
1986
-
1987
- // The user just clicked -- with no drag.
1988
- } else if (transPos.x === this.zoomStartPoint.startX &&
1989
- transPos.y === this.zoomStartPoint.startY &&
1990
- !this.zoomChanged()) {
1991
- this.zoomClick(d3Event);
1992
-
1993
- } else if (this.regionSelect) {
1994
- this.zoomEndRegionSelect(d3Event);
1995
-
1996
- } else if (this.dispUtils.isDisplayingFullPage() && this.zoomChanged()) {
1997
- this.zoomSave();
1998
- }
1999
-
2000
- // Remove the cursor overlay and reset the SVG background rectangle
2001
- // cursor style, which was set in the zoom start method.
2002
- this.resetCanvasCursor(d3Event);
2003
- this.removeTempCursorOverlay();
2004
- this.contextMenuClosedOnZoom = false;
2005
- this.textEditingClosedOnZoom = false;
2006
- this.regionSelect = false;
2007
- }
2008
-
2009
- // Returns true if the region select gesture is requested by the user.
2010
- shouldDoRegionSelect(d3Event) {
2011
- // The this.zoomingAction flag indicates zooming is being invoked
2012
- // programmatically.
2013
- if (this.zoomingAction) {
2014
- return false;
2015
-
2016
- } else if (this.config.enableInteractionType === INTERACTION_MOUSE &&
2017
- (d3Event && d3Event.sourceEvent && d3Event.sourceEvent.shiftKey)) {
2018
- return true;
2019
-
2020
- } else if (this.config.enableInteractionType === INTERACTION_CARBON &&
2021
- !this.isSpaceKeyPressed(d3Event)) {
2022
- return true;
2023
-
2024
- } else if (this.config.enableInteractionType === INTERACTION_TRACKPAD &&
2025
- (d3Event.sourceEvent && d3Event.sourceEvent.buttons === 1) && // Main button is pressed
2026
- !this.spaceKeyPressed) {
2027
- return true;
2028
- }
2029
-
2030
- return false;
2031
- }
2032
-
2033
- // Returns true if the current zoom transform is different from the
2034
- // zoom values at the beginning of the zoom action.
2035
- zoomChanged() {
2036
- return (this.zoomTransform.k !== this.zoomStartPoint.k ||
2037
- this.zoomTransform.x !== this.zoomStartPoint.x ||
2038
- this.zoomTransform.y !== this.zoomStartPoint.y);
2039
- }
2040
-
2041
- zoomCanvasBackground(d3Event) {
2042
- this.regionSelect = false;
2043
-
2044
- if (this.dispUtils.isDisplayingPrimaryFlowFullPage()) {
2045
- const incTransform = this.getTransformIncrement(d3Event);
2046
- this.zoomTransform = this.zoomConstrainRegular(incTransform, this.getViewportDimensions(), this.zoomCanvasDimensions);
2047
- } else {
2048
- this.zoomTransform = d3.zoomIdentity.translate(d3Event.transform.x, d3Event.transform.y).scale(d3Event.transform.k);
2049
- }
2050
-
2051
- this.canvasGrp.attr("transform", this.zoomTransform);
2052
-
2053
- if (this.config.enableBoundingRectangles) {
2054
- this.displayBoundingRectangles();
2055
- }
2056
-
2057
- if (this.config.enableCanvasUnderlay !== "None" &&
2058
- this.dispUtils.isDisplayingPrimaryFlowFullPage()) {
2059
- this.setCanvasUnderlaySize();
2060
- }
2061
-
2062
- // The supernode will not have any calculated port positions when the
2063
- // subflow is being displayed full screen, so calculate them first.
2064
- if (this.dispUtils.isDisplayingSubFlowFullPage()) {
2065
- this.displayPortsForSubFlowFullPage();
2066
- }
2067
- }
2068
-
2069
- // Handles a zoom operation that is just a click on the canvas background.
2070
- zoomClick(d3Event) {
2071
- // Only clear selections under these conditions:
2072
- // * if the click was on the canvas of the current active pipeline. (This
2073
- // is because clicking on the canvas background of an expanded supernode
2074
- // should select that node.)
2075
- // * if we have not just closed a context menu
2076
- // * if we have not just closed text editing
2077
- // * if editing actions are enabled OR the target is the canvas background.
2078
- // (This condition is necessary because when editing actions are disabled,
2079
- // for a read-only canvas, the mouse-up over a node can cause this method
2080
- // to be called.)
2081
- if (this.dispUtils.isDisplayingCurrentPipeline() &&
2082
- !this.contextMenuClosedOnZoom &&
2083
- !this.textEditingClosedOnZoom &&
2084
- (this.config.enableEditingActions ||
2085
- (d3Event.sourceEvent &&
2086
- d3Event.sourceEvent.target.classList.contains("d3-svg-background")))) {
2087
- this.canvasController.clearSelections();
2088
- }
2089
- }
2090
-
2091
- // Handles the behavior when the user stops doing a region select.
2092
- zoomEndRegionSelect(d3Event) {
2093
- this.removeRegionSelector();
2094
-
2095
- // Reset the transform x and y to what they were before the region
2096
- // selection action was started. This directly sets the x and y values
2097
- // in the __zoom property of the svgCanvas DOM object.
2098
- d3Event.transform.x = this.regionStartTransformX;
2099
- d3Event.transform.y = this.regionStartTransformY;
2100
-
2101
- const { startX, startY, width, height } = this.getRegionDimensions(d3Event);
2102
-
2103
- const region = { x1: startX, y1: startY, x2: startX + width, y2: startY + height };
2104
- const selections =
2105
- CanvasUtils.selectInRegion(region, this.activePipeline,
2106
- this.config.enableLinkSelection !== LINK_SELECTION_NONE,
2107
- this.config.enableLinkType,
2108
- this.config.enableAssocLinkType);
2109
- this.canvasController.setSelections(selections, this.activePipeline.id);
2110
- }
2111
-
2112
- // Save the zoom amount. The canvas controller/object model will decide
2113
- // how this info is saved.
2114
- zoomSave() {
2115
- // Set the internal zoom value for canvasSVG used by D3. This will be
2116
- // used by d3Event next time a zoom action is initiated.
2117
- this.canvasSVG.property("__zoom", this.zoomTransform);
2118
-
2119
- const data = {
2120
- editType: "setZoom",
2121
- editSource: "canvas",
2122
- zoom: this.zoomTransform,
2123
- pipelineId: this.activePipeline.id
2124
- };
2125
- this.canvasController.editActionHandler(data);
2126
-
2127
- }
2128
-
2129
- // Repositions the comment toolbar so it is always over the top of the
2130
- // comment being edited.
2131
- zoomCommentToolbar() {
2132
- if (this.config.enableMarkdownInComments &&
2133
- this.dispUtils.isDisplayingFullPage() &&
2134
- this.svgCanvasTextArea.isEditingText()) {
2135
- // If a node label or text decoration is being edited com will be undefined.
2136
- const com = this.activePipeline.getComment(this.svgCanvasTextArea.getEditingTextId());
2137
- if (com) {
2138
- const pos = this.getCommentToolbarPos(com);
2139
- this.canvasController.moveTextToolbar(pos.x, pos.y);
2140
- }
2141
- }
2142
- }
2143
-
2144
- // Returns a position object that describes the position in page coordinates
2145
- // of the comment toolbar so that it is positioned above the comment being edited.
2146
- getCommentToolbarPos(com) {
2147
- const pos = this.unTransformPos({ x: com.x_pos, y: com.y_pos });
2148
- return {
2149
- x: pos.x + this.canvasLayout.commentToolbarPosX,
2150
- y: pos.y + this.canvasLayout.commentToolbarPosY
2151
- };
2152
- }
2153
-
2154
- // Returns a new zoom which is the result of incrementing the current zoom
2155
- // by the amount since the previous d3Event transform amount.
2156
- // We calculate increments because d3Event.transform is not based on
2157
- // the constrained zoom position (which is very annoying) so we keep track
2158
- // of the current constraind zoom amount in this.zoomTransform.
2159
- getTransformIncrement(d3Event) {
2160
- const xInc = d3Event.transform.x - this.previousD3Event.x;
2161
- const yInc = d3Event.transform.y - this.previousD3Event.y;
2162
-
2163
- const newTransform = { x: this.zoomTransform.x + xInc, y: this.zoomTransform.y + yInc, k: d3Event.transform.k };
2164
- this.previousD3Event = { x: d3Event.transform.x, y: d3Event.transform.y, k: d3Event.transform.k };
2165
- return newTransform;
2166
- }
2167
-
2168
- // Returns a modifed transform object so that the canvas area (the area
2169
- // containing nodes and comments) is constrained such that it never totally
2170
- // disappears from the view port.
2171
- zoomConstrainRegular(transform, viewPort, canvasDimensions) {
2172
- if (!canvasDimensions) {
2173
- return this.zoomTransform;
2174
- }
2175
-
2176
- const k = transform.k;
2177
- let x = transform.x;
2178
- let y = transform.y;
2179
-
2180
- const canv =
2181
- this.convertCanvasDimensionsAdjustedForScaleWithPadding(canvasDimensions, k, this.getZoomToFitPadding());
2182
-
2183
- const rightOffsetLimit = viewPort.width - Math.min((viewPort.width * 0.25), (canv.width * 0.25));
2184
- const leftOffsetLimit = -(Math.max((canv.width - (viewPort.width * 0.25)), (canv.width * 0.75)));
2185
-
2186
- const bottomOffsetLimit = viewPort.height - Math.min((viewPort.height * 0.25), (canv.height * 0.25));
2187
- const topOffsetLimit = -(Math.max((canv.height - (viewPort.height * 0.25)), (canv.height * 0.75)));
2188
-
2189
- if (x > -canv.left + rightOffsetLimit) {
2190
- x = -canv.left + rightOffsetLimit;
2191
-
2192
- } else if (x < -canv.left + leftOffsetLimit) {
2193
- x = -canv.left + leftOffsetLimit;
2194
- }
2195
-
2196
- if (y > -canv.top + bottomOffsetLimit) {
2197
- y = -canv.top + bottomOffsetLimit;
2198
-
2199
- } else if (y < -canv.top + topOffsetLimit) {
2200
- y = -canv.top + topOffsetLimit;
2201
- }
2202
-
2203
- return d3.zoomIdentity.translate(x, y).scale(k);
2204
- }
2205
-
2206
- // Returns the dimensions in SVG coordinates of the canvas area. This is
2207
- // based on the position and width and height of the nodes and comments. It
2208
- // does not include the 'super binding nodes' which are the binding nodes in
2209
- // a sub-flow that map to a port in the containing supernode. The dimensions
2210
- // are scaled by k and padded by pad (if provided).
2211
- getCanvasDimensionsAdjustedForScale(k, pad) {
2212
- const gap = this.canvasLayout.commentHighlightGap;
2213
- const canvasDimensions = this.activePipeline.getCanvasDimensions(gap);
2214
- return this.convertCanvasDimensionsAdjustedForScaleWithPadding(canvasDimensions, k, pad);
2215
- }
2216
-
2217
- convertCanvasDimensionsAdjustedForScaleWithPadding(canv, k, pad) {
2218
- const padding = pad || 0;
2219
- if (canv) {
2220
- return {
2221
- left: (canv.left * k) - padding,
2222
- top: (canv.top * k) - padding,
2223
- right: (canv.right * k) + padding,
2224
- bottom: (canv.bottom * k) + padding,
2225
- width: (canv.width * k) + (2 * padding),
2226
- height: (canv.height * k) + (2 * padding)
2227
- };
2228
- }
2229
- return null;
2230
- }
2231
-
2232
- drawRegionSelector(d3Event) {
2233
- this.removeRegionSelector();
2234
- const { startX, startY, width, height } = this.getRegionDimensions(d3Event);
2235
-
2236
- this.canvasGrp
2237
- .append("rect")
2238
- .attr("width", width)
2239
- .attr("height", height)
2240
- .attr("x", startX)
2241
- .attr("y", startY)
2242
- .attr("class", "d3-region-selector");
2243
- }
2244
-
2245
- removeRegionSelector() {
2246
- this.canvasGrp.selectAll(".d3-region-selector").remove();
2247
- }
2248
-
2249
- // Returns the startX, startY, width and height of the selection region
2250
- // where startX and startY are always the top left corner of the region
2251
- // and width and height are therefore always positive.
2252
- getRegionDimensions(d3Event) {
2253
- const transPos = this.getTransformedMousePos(d3Event);
2254
- let startX = this.zoomStartPoint.startX;
2255
- let startY = this.zoomStartPoint.startY;
2256
- let width = transPos.x - startX;
2257
- let height = transPos.y - startY;
2258
-
2259
- if (width < 0) {
2260
- width = Math.abs(width);
2261
- startX -= width;
2262
- }
2263
- if (height < 0) {
2264
- height = Math.abs(height);
2265
- startY -= height;
2266
- }
2267
-
2268
- return { startX, startY, width, height };
2269
- }
2270
-
2271
- // Records the initial starting position of the object being sized so
2272
- // that drag increments can be added to the original starting
2273
- // position to aid calculating the snap-to-grid position.
2274
- initializeResizeVariables(resizeObj) {
2275
- this.resizeObjInitialInfo = {
2276
- x_pos: resizeObj.x_pos, y_pos: resizeObj.y_pos, width: resizeObj.width, height: resizeObj.height };
2277
- this.notSnappedXPos = resizeObj.x_pos;
2278
- this.notSnappedYPos = resizeObj.y_pos;
2279
- this.notSnappedWidth = resizeObj.width;
2280
- this.notSnappedHeight = resizeObj.height;
2281
- }
2282
-
2283
- // Returns an array of objects to drag. If enableDragWithoutSelect is true,
2284
- // and the object on which this drag start has initiated is not in the
2285
- // set of selected objects, then just that object is to be dragged. Otherwise,
2286
- // the selected objects are the objects to be dragged.
2287
- getDragObjects(d) {
2288
- const selectedObjects = this.activePipeline.getSelectedNodesAndComments();
2289
-
2290
- if (this.config.enableDragWithoutSelect &&
2291
- selectedObjects.findIndex((o) => o.id === d.id) === -1) {
2292
- return [d];
2293
- }
2294
-
2295
- return selectedObjects;
2296
- }
2297
-
2298
- dragStart(d3Event, d) {
2299
- this.logger.logStartTimer("dragStart");
2300
-
2301
- this.closeContextMenuIfOpen();
2302
-
2303
- if (this.config.enableContextToolbar) {
2304
- this.removeContextToolbar();
2305
- }
2306
-
2307
-
2308
- // Note: Comment and Node resizing is started by the comment/node highlight rectangle.
2309
- if (this.commentSizing) {
2310
- this.initializeResizeVariables(d);
2311
-
2312
- } else if (this.nodeSizing) {
2313
- this.initializeResizeVariables(d);
2314
-
2315
- } else {
2316
- this.dragObjectsStart(d3Event, d);
2317
- }
2318
- this.logger.logEndTimer("dragStart", true);
2319
- }
2320
-
2321
- dragMove(d3Event, d) {
2322
- this.logger.logStartTimer("dragMove");
2323
- if (this.commentSizing) {
2324
- this.resizeComment(d3Event, d);
2325
- } else if (this.nodeSizing) {
2326
- this.resizeNode(d3Event, d);
2327
- } else {
2328
- this.dragObjectsAction(d3Event);
2329
- }
2330
-
2331
- this.logger.logEndTimer("dragMove", true);
2332
- }
2333
-
2334
- dragEnd(d3Event, d) {
2335
- this.logger.logStartTimer("dragEnd");
2336
-
2337
- this.removeTempCursorOverlay();
2338
-
2339
- if (this.commentSizing) {
2340
- this.endCommentSizing(d);
2341
-
2342
- } else if (this.nodeSizing) {
2343
- this.endNodeSizing(d);
2344
-
2345
- } else if (this.dragging) {
2346
- this.dragObjectsEnd(d3Event, d);
2347
- }
2348
-
2349
- this.logger.logEndTimer("dragEnd", true);
2350
- }
2351
-
2352
- // Starts the dragging action for canvas objects (nodes and comments).
2353
- dragObjectsStart(d3Event, d) {
2354
- // Ensure flags are false before staring a new drag.
2355
- this.existingNodeInsertableIntoLink = false;
2356
- this.existingNodeAttachableToDetachedLinks = false;
2357
-
2358
- this.dragging = true;
2359
- this.dragOffsetX = 0;
2360
- this.dragOffsetY = 0;
2361
- this.dragRunningX = 0;
2362
- this.dragRunningY = 0;
2363
- this.dragObjects = this.getDragObjects(d);
2364
- if (this.dragObjects && this.dragObjects.length > 0) {
2365
- this.dragStartX = this.dragObjects[0].x_pos;
2366
- this.dragStartY = this.dragObjects[0].y_pos;
2367
- }
2368
-
2369
- // If we are dragging an 'insertable' node, set it to be translucent so
2370
- // that, when it is dragged over a link line, the highlightd line can be seen OK.
2371
- if (this.isExistingNodeInsertableIntoLink()) {
2372
- // Only style the node to be translucent if this action isn't cancelled
2373
- // by the user releasing the mouse button within 200 ms of pressing it.
2374
- // This stops the node flashing when the user is only selecting it.
2375
- this.startNodeInsertingInLink = setTimeout(() => {
2376
- this.existingNodeInsertableIntoLink = true;
2377
- this.setNodeTranslucentState(this.dragObjects[0].id, true);
2378
- this.setDataLinkSelectionAreaWider(true);
2379
- }, 200);
2380
- }
2381
-
2382
- // If we are dragging an 'attachable' node, set it to be translucent so
2383
- // that, when it is dragged over link lines, the highlightd lines can be seen OK.
2384
- if (this.isExistingNodeAttachableToDetachedLinks()) {
2385
- // Only style the node to be translucent if this action isn't cancelled
2386
- // by the user releasing the mouse button within 200 ms of pressing it.
2387
- // This stops the node from when the user is only selecting it.
2388
- this.startNodeAttachingToDetachedLinks = setTimeout(() => {
2389
- this.existingNodeAttachableToDetachedLinks = true;
2390
- const mousePos = this.getTransformedMousePos(d3Event);
2391
- this.dragPointerOffsetInNode = {
2392
- x: mousePos.x - this.dragObjects[0].x_pos,
2393
- y: mousePos.y - this.dragObjects[0].y_pos
2394
- };
2395
- this.setNodeTranslucentState(this.dragObjects[0].id, true);
2396
- }, 200);
2397
- }
2398
- }
2399
-
2400
- // Performs the dragging action for canvas objects (nodes and comments).
2401
- dragObjectsAction(d3Event) {
2402
- this.dragOffsetX += d3Event.dx;
2403
- this.dragOffsetY += d3Event.dy;
2404
-
2405
- // Limit the size a drag can be so, when the user is dragging objects in
2406
- // an in-place subflow they do not drag them too far.
2407
- // this.logger.log("Drag offset X = " + this.dragOffsetX + " y = " + this.dragOffsetY);
2408
- if (this.dispUtils.isDisplayingSubFlowInPlace() &&
2409
- (this.dragOffsetX > 1000 || this.dragOffsetX < -1000 ||
2410
- this.dragOffsetY > 1000 || this.dragOffsetY < -1000)) {
2411
- this.dragOffsetX -= d3Event.dx;
2412
- this.dragOffsetY -= d3Event.dy;
2413
-
2414
- } else {
2415
- let increment = { x: 0, y: 0 };
2416
-
2417
- if (this.config.enableSnapToGridType === SNAP_TO_GRID_DURING) {
2418
- const stgPos = this.snapToGridDraggedNode();
2419
-
2420
- increment = {
2421
- x: stgPos.x - this.dragObjects[0].x_pos,
2422
- y: stgPos.y - this.dragObjects[0].y_pos
2423
- };
2424
-
2425
- } else {
2426
- increment = {
2427
- x: d3Event.dx,
2428
- y: d3Event.dy
2429
- };
2430
- }
2431
-
2432
- this.dragRunningX += increment.x;
2433
- this.dragRunningY += increment.y;
2434
-
2435
- this.dragObjects.forEach((d) => {
2436
- d.x_pos += increment.x;
2437
- d.y_pos += increment.y;
2438
- });
2439
-
2440
- if (this.config.enableLinkSelection === LINK_SELECTION_DETACHABLE) {
2441
- this.activePipeline.getSelectedLinks().forEach((link) => {
2442
- if (link.srcPos) {
2443
- link.srcPos.x_pos += increment.x;
2444
- link.srcPos.y_pos += increment.y;
2445
- }
2446
- if (link.trgPos) {
2447
- link.trgPos.x_pos += increment.x;
2448
- link.trgPos.y_pos += increment.y;
2449
- }
2450
- });
2451
- }
2452
- }
2453
-
2454
- this.displayMovedComments();
2455
- this.displayMovedNodes();
2456
- this.displayMovedLinks();
2457
- this.displayCanvasAccoutrements();
2458
-
2459
- if (this.dispUtils.isDisplayingSubFlowInPlace()) {
2460
- this.displaySVGToFitSupernode();
2461
- }
2462
-
2463
-
2464
- if (this.existingNodeInsertableIntoLink) {
2465
- const link = this.getLinkAtMousePos(d3Event.sourceEvent.clientX, d3Event.sourceEvent.clientY);
2466
- // Set highlighting when there is no link because this will turn
2467
- // current highlighting off. And only switch on highlighting when we are
2468
- // over a fully attached link (not a detached link) and provided the
2469
- // link is not to/from the node being dragged (which is possible in
2470
- // some odd situations).
2471
- if (!link ||
2472
- (this.isLinkFullyAttached(link) &&
2473
- this.dragObjects[0].id !== link.srcNodeId &&
2474
- this.dragObjects[0].id !== link.trgNodeId)) {
2475
- this.setInsertNodeIntoLinkHighlighting(link);
2476
- }
2477
- }
2478
-
2479
- if (this.existingNodeAttachableToDetachedLinks) {
2480
- const mousePos = this.getTransformedMousePos(d3Event);
2481
- const node = this.dragObjects[0];
2482
- const ghostArea = {
2483
- x1: mousePos.x - this.dragPointerOffsetInNode.x,
2484
- y1: mousePos.y - this.dragPointerOffsetInNode.y,
2485
- x2: mousePos.x - this.dragPointerOffsetInNode.x + node.width,
2486
- y2: mousePos.y - this.dragPointerOffsetInNode.y + node.height
2487
- };
2488
- const links = this.getAttachableLinksForNodeAtPos(node, ghostArea);
2489
- this.setDetachedLinkHighlighting(links);
2490
- }
2491
- }
2492
-
2493
- // Ends the dragging action for canvas objects (nodes and comments).
2494
- dragObjectsEnd(d3Event, d) {
2495
- // Set to false before updating object model so main body of displayNodes is run.
2496
- this.dragging = false;
2497
-
2498
- // Cancels the styling of insertable/attachable nodes if the user releases
2499
- // the mouse button with 200 milliseconds of pressing it on the node. This
2500
- // stops the node flashing when the user just selects the node.
2501
- clearTimeout(this.startNodeInsertingInLink);
2502
- clearTimeout(this.startNodeAttachingToDetachedLinks);
2503
-
2504
- // If the pointer hasn't moved and enableDragWithoutSelect we interpret
2505
- // that as a select on the object.
2506
- if (this.dragOffsetX === 0 &&
2507
- this.dragOffsetY === 0 &&
2508
- this.config.enableDragWithoutSelect) {
2509
- this.selectObjectSourceEvent(d3Event, d);
2510
-
2511
- } else {
2512
- if (this.dragRunningX !== 0 ||
2513
- this.dragRunningY !== 0) {
2514
- let dragFinalOffset = null;
2515
- if (this.config.enableSnapToGridType === SNAP_TO_GRID_AFTER) {
2516
- const stgPos = this.snapToGridDraggedNode();
2517
- dragFinalOffset = {
2518
- x: stgPos.x - this.dragStartX,
2519
- y: stgPos.y - this.dragStartY
2520
- };
2521
- } else {
2522
- dragFinalOffset = { x: this.dragRunningX, y: this.dragRunningY };
2523
- }
2524
-
2525
- if (this.existingNodeInsertableIntoLink &&
2526
- this.dragOverLink) {
2527
- this.canvasController.editActionHandler({
2528
- editType: "insertNodeIntoLink",
2529
- editSource: "canvas",
2530
- node: this.dragObjects[0],
2531
- link: this.dragOverLink,
2532
- offsetX: dragFinalOffset.x,
2533
- offsetY: dragFinalOffset.y,
2534
- pipelineId: this.activePipeline.id });
2535
-
2536
- } else if (this.existingNodeAttachableToDetachedLinks &&
2537
- this.dragOverDetachedLinks.length > 0) {
2538
- this.canvasController.editActionHandler({
2539
- editType: "attachNodeToLinks",
2540
- editSource: "canvas",
2541
- node: this.dragObjects[0],
2542
- detachedLinks: this.dragOverDetachedLinks,
2543
- offsetX: dragFinalOffset.x,
2544
- offsetY: dragFinalOffset.y,
2545
- pipelineId: this.activePipeline.id });
2546
-
2547
- } else {
2548
- this.canvasController.editActionHandler({
2549
- editType: "moveObjects",
2550
- editSource: "canvas",
2551
- nodes: this.dragObjects.map((o) => o.id),
2552
- links: this.activePipeline.getSelectedDetachedLinks(),
2553
- offsetX: dragFinalOffset.x,
2554
- offsetY: dragFinalOffset.y,
2555
- pipelineId: this.activePipeline.id });
2556
- }
2557
- }
2558
- }
2559
-
2560
- // Switch off any drag highlighting
2561
- this.setDataLinkSelectionAreaWider(false);
2562
- this.unsetNodeTranslucentState();
2563
- this.unsetInsertNodeIntoLinkHighlighting();
2564
- this.unsetDetachedLinkHighlighting();
2565
- }
2566
-
2567
- dragStartLinkHandle(d3Event, d) {
2568
- this.logger.logStartTimer("dragStartLinkHandle");
2569
-
2570
- this.closeContextMenuIfOpen();
2571
-
2572
- this.draggingLinkHandle = true;
2573
-
2574
- const handleSelection = d3.select(d3Event.sourceEvent.currentTarget);
2575
- const link = this.activePipeline.getLink(d.id);
2576
- const oldLink = cloneDeep(link);
2577
-
2578
- const linkGrpSelector = this.getLinkGroupSelectionById(d.id);
2579
-
2580
- this.draggingLinkData = {
2581
- lineInfo: d,
2582
- link: link,
2583
- oldLink: oldLink,
2584
- linkGrpSelection: d3.select(linkGrpSelector)
2585
- };
2586
-
2587
- if (handleSelection.attr("class").includes("d3-link-handle-end")) {
2588
- this.draggingLinkData.endBeingDragged = "end";
2589
-
2590
- } else if (handleSelection.attr("class").includes("d3-link-handle-start")) {
2591
- this.draggingLinkData.endBeingDragged = "start";
2592
- }
2593
-
2594
- if (this.config.enableHighlightUnavailableNodes) {
2595
- if (this.draggingLinkData.endBeingDragged === "end") {
2596
- const links = this.activePipeline.links.filter((lnk) => lnk.id !== link.id);
2597
- this.setUnavailableTargetNodesHighlighting(
2598
- this.activePipeline.getNode(this.draggingLinkData.link.srcNodeId),
2599
- this.draggingLinkData.link.srcNodePortId,
2600
- links
2601
- );
2602
- } else if (this.draggingLinkData.endBeingDragged === "start") {
2603
- const links = this.activePipeline.links.filter((lnk) => lnk.id !== link.id);
2604
- this.setUnavailableSourceNodesHighlighting(
2605
- this.activePipeline.getNode(this.draggingLinkData.oldLink.trgNodeId),
2606
- this.draggingLinkData.link.trgNodePortId,
2607
- links
2608
- );
2609
- }
2610
- }
2611
-
2612
- this.dragLinkHandle(d3Event);
2613
- this.logger.logEndTimer("dragStartLinkHandle", true);
2614
- }
2615
-
2616
- dragMoveLinkHandle(d3Event) {
2617
- this.logger.logStartTimer("dragMoveLinkHandle");
2618
- this.dragLinkHandle(d3Event);
2619
- this.logger.logEndTimer("dragMoveLinkHandle", true);
2620
- }
2621
-
2622
- dragEndLinkHandle(d3Event) {
2623
- this.logger.logStartTimer("dragEndLinkHandle");
2624
- this.completeDraggedLink(d3Event);
2625
- this.draggingLinkHandle = false;
2626
- this.logger.logEndTimer("dragEndLinkHandle", true);
2627
- }
2628
-
2629
- // Switches on or off the translucent state of the node identified by the
2630
- // node ID passed in. This is used when an 'insertable' node is dragged on
2631
- // the canvas. It makes is easier for the user to see the highlighted link
2632
- // when the node is dragged over it.
2633
- setNodeTranslucentState(nodeId, state) {
2634
- this.getNodeGroupSelectionById(nodeId).classed("d3-node-group-translucent", state);
2635
- }
2636
-
2637
- // Switched off the translucent state of the objects being dragged (if
2638
- // there are any).
2639
- unsetNodeTranslucentState() {
2640
- if (this.dragObjects && this.dragObjects.length > 0) {
2641
- this.setNodeTranslucentState(this.dragObjects[0].id, false);
2642
- }
2643
- }
2644
-
2645
- // Returns the snap-to-grid position of the object positioned at
2646
- // this.dragStartX and this.dragStartY after applying the current offset of
2647
- // this.dragOffsetX and this.dragOffsetY.
2648
- snapToGridDraggedNode() {
2649
- const objPosX = this.dragStartX + this.dragOffsetX;
2650
- const objPosY = this.dragStartY + this.dragOffsetY;
2651
-
2652
- return this.snapToGridPosition({ x: objPosX, y: objPosY });
2653
- }
2654
-
2655
- // Returns the snap-to-grid position of the object positioned at objPos.x
2656
- // and objPos.y. The grid that is snapped to is defined by this.snapToGridXPx
2657
- // and this.snapToGridYPx values which are pixel values.
2658
- snapToGridPosition(objPos) {
2659
- const stgPosX = CanvasUtils.snapToGrid(objPos.x, this.canvasLayout.snapToGridXPx);
2660
- const stgPosY = CanvasUtils.snapToGrid(objPos.y, this.canvasLayout.snapToGridYPx);
2661
-
2662
- return { x: stgPosX, y: stgPosY };
2663
- }
2664
-
2665
- // Displays all the nodes on the canvas either by creating new nodes,
2666
- // updating existing nodes or removing unwanted nodes.
2667
- displayNodes() {
2668
- this.logger.logStartTimer("displayNodes " + this.getFlags());
2669
-
2670
- // Set the port positions for all ports - these will be needed when displaying
2671
- // nodes and links. This needs to be done here because a resized supernode
2672
- // will cause its ports to move and resizing comments causes links to be
2673
- // redrawn which will need port positions to be set appropriately.
2674
- this.setPortPositionsAllNodes();
2675
-
2676
- const sel = this.getAllNodeGroupsSelection();
2677
- this.displayNodesSubset(sel, this.activePipeline.nodes);
2678
-
2679
- this.logger.logEndTimer("displayNodes " + this.getFlags());
2680
- }
2681
-
2682
- displayMovedNodes() {
2683
- this.logger.logStartTimer("displayMovedNodes");
2684
- const nodeGroupSel = this.getAllNodeGroupsSelection();
2685
-
2686
- nodeGroupSel
2687
- .datum((d) => this.activePipeline.getNode(d.id))
2688
- .attr("transform", (d) => `translate(${d.x_pos}, ${d.y_pos})`);
2689
-
2690
- if (this.dispUtils.isDisplayingSubFlow()) {
2691
- nodeGroupSel
2692
- .each((d, i, nodeGrps) => {
2693
- if (d.isSupernodeInputBinding) {
2694
- this.updatePortRadiusAndPos(nodeGrps[i], d, "d3-node-port-output-main");
2695
- }
2696
- if (d.isSupernodeOutputBinding) {
2697
- this.updatePortRadiusAndPos(nodeGrps[i], d, "d3-node-port-input-main");
2698
- this.updatePortArrowPath(nodeGrps[i], "d3-node-port-input-arrow");
2699
- }
2700
- });
2701
- }
2702
- this.logger.logEndTimer("displayMovedNodes");
2703
- }
2704
-
2705
- displayNodesSelectionStatus(selectionInfo) {
2706
- this.logger.logStartTimer("displayNodesSelectionStatus");
2707
-
2708
- this.getAllNodeGroupsSelection()
2709
- .selectChildren(".d3-node-selection-highlight")
2710
- .attr("data-selected", (d) => (this.activePipeline.isSelected(d.id) ? "yes" : "no"));
2711
-
2712
- this.logger.logEndTimer("displayNodesSelectionStatus");
2713
- }
2714
-
2715
- displaySingleNode(d) {
2716
- this.logger.logStartTimer("displaySingleNode " + this.getFlags());
2717
-
2718
- this.setPortPositionsForNode(d);
2719
-
2720
- const selection = this.getNodeGroupSelectionById(d.id);
2721
- this.displayNodesSubset(selection, [d]);
2722
-
2723
- this.logger.logEndTimer("displaySingleNode " + this.getFlags());
2724
- }
2725
-
2726
- displayNodesSubset(selection, data) {
2727
- selection
2728
- .data(data, (d) => d.id)
2729
- .join(
2730
- (enter) => this.createNodes(enter),
2731
- null,
2732
- (remove) => this.removeNodes(remove)
2733
- )
2734
- .attr("transform", (d) => `translate(${d.x_pos}, ${d.y_pos})`)
2735
- .attr("class", (d) => this.getNodeGroupClass(d))
2736
- .attr("style", (d) => this.getNodeGrpStyle(d))
2737
- .call((joinedNodeGrps) => this.updateNodes(joinedNodeGrps, data));
2738
- }
2739
-
2740
- createNodes(enter) {
2741
- this.logger.logStartTimer("createNodes");
2742
-
2743
- const newNodeGroups = enter
2744
- .append("g")
2745
- .attr("data-id", (d) => this.getId("node_grp", d.id))
2746
- .call(this.attachNodeGroupListeners.bind(this));
2747
-
2748
- this.logger.logEndTimer("createNodes");
2749
-
2750
- return newNodeGroups;
2751
- }
2752
-
2753
- updateNodes(joinedNodeGrps, data) {
2754
- this.logger.logStartTimer("updateNodes");
2755
-
2756
- const nonBindingNodeGrps = joinedNodeGrps.filter((node) => !CanvasUtils.isSuperBindingNode(node));
1606
+ const nonBindingNodeGrps = joinedNodeGrps.filter((node) => !CanvasUtils.isSuperBindingNode(node));
2757
1607
 
2758
1608
  // Node Sizing Area
2759
1609
  nonBindingNodeGrps
@@ -2769,7 +1619,6 @@ export default class SVGCanvasRenderer {
2769
1619
  .datum((d) => this.activePipeline.getNode(d.id))
2770
1620
  .attr("d", (d) => this.getNodeShapePathSizing(d));
2771
1621
 
2772
-
2773
1622
  // Node Selection Highlighting Outline.
2774
1623
  nonBindingNodeGrps
2775
1624
  .selectChildren(".d3-node-selection-highlight")
@@ -2810,7 +1659,8 @@ export default class SVGCanvasRenderer {
2810
1659
  .attr("class", "d3-foreign-object-external-node"),
2811
1660
  null,
2812
1661
  (exit) => {
2813
- exit.each(removeExternalObject.bind(this));
1662
+ exit.each((d, idx, exts) =>
1663
+ this.externalUtils.removeExternalObject(d, idx, exts));
2814
1664
  exit.remove();
2815
1665
  }
2816
1666
  )
@@ -2819,7 +1669,8 @@ export default class SVGCanvasRenderer {
2819
1669
  .attr("height", (d) => d.height)
2820
1670
  .attr("x", 0)
2821
1671
  .attr("y", 0)
2822
- .each(addNodeExternalObject.bind(this));
1672
+ .each((d, idx, exts) =>
1673
+ this.externalUtils.addNodeExternalObject(d, idx, exts));
2823
1674
 
2824
1675
  // Node Image
2825
1676
  nonBindingNodeGrps
@@ -2898,8 +1749,9 @@ export default class SVGCanvasRenderer {
2898
1749
 
2899
1750
  // Add or remove drag behavior as appropriate
2900
1751
  if (this.config.enableEditingActions) {
1752
+ const handler = this.dragObjectUtils.getDragObjectHandler();
2901
1753
  nonBindingNodeGrps
2902
- .call(this.dragHandler);
1754
+ .call(handler);
2903
1755
  } else {
2904
1756
  nonBindingNodeGrps
2905
1757
  .on(".drag", null);
@@ -2912,7 +1764,8 @@ export default class SVGCanvasRenderer {
2912
1764
  // Remove any foreign objects for react nodes, if necessary.
2913
1765
  removeSel
2914
1766
  .selectChildren(".d3-foreign-object-external-node")
2915
- .each(removeExternalObject.bind(this));
1767
+ .each((d, idx, exts) =>
1768
+ this.externalUtils.removeExternalObject(d, idx, exts));
2916
1769
 
2917
1770
  // Remove all nodes in the selection.
2918
1771
  removeSel.remove();
@@ -3026,6 +1879,13 @@ export default class SVGCanvasRenderer {
3026
1879
  .attr("transform", this.getPortArrowPathTransform(port));
3027
1880
  }
3028
1881
  });
1882
+
1883
+ if (this.config.enableEditingActions) {
1884
+ const handler = this.dragNewLinkUtils.getDragNewLinkHandler();
1885
+ joinedInputPortGrps.call(handler);
1886
+ } else {
1887
+ joinedInputPortGrps.on(".drag", null);
1888
+ }
3029
1889
  }
3030
1890
 
3031
1891
  displayOutputPorts(nodeGrp, node) {
@@ -3068,8 +1928,8 @@ export default class SVGCanvasRenderer {
3068
1928
  return outputPortGroups;
3069
1929
  }
3070
1930
 
3071
- updateOutputPorts(joinedOutputPortGroups, node) {
3072
- joinedOutputPortGroups.selectChildren(".d3-node-port-output-main")
1931
+ updateOutputPorts(joinedOutputPortGrps, node) {
1932
+ joinedOutputPortGrps.selectChildren(".d3-node-port-output-main")
3073
1933
  .datum((port) => node.outputs.find((o) => port.id === o.id))
3074
1934
  .each((port, i, outputPorts) => {
3075
1935
  const obj = d3.select(outputPorts[i]);
@@ -3087,12 +1947,22 @@ export default class SVGCanvasRenderer {
3087
1947
  .attr("cy", port.cy);
3088
1948
  }
3089
1949
  });
1950
+
1951
+ if (this.config.enableEditingActions) {
1952
+ const handler = this.dragNewLinkUtils.getDragNewLinkHandler();
1953
+ joinedOutputPortGrps.call(handler);
1954
+ } else {
1955
+ joinedOutputPortGrps.on(".drag", null);
1956
+ }
3090
1957
  }
3091
1958
 
3092
1959
  // Attaches the appropriate listeners to the node groups.
3093
1960
  attachNodeGroupListeners(nodeGrps) {
3094
1961
  nodeGrps
3095
1962
  .on("mouseenter", (d3Event, d) => {
1963
+ if (this.isDragging()) {
1964
+ return;
1965
+ }
3096
1966
  const nodeGrp = d3.select(d3Event.currentTarget);
3097
1967
  this.raiseNodeToTop(nodeGrp);
3098
1968
  this.setNodeStyles(d, "hover", nodeGrp);
@@ -3125,7 +1995,7 @@ export default class SVGCanvasRenderer {
3125
1995
  // Use mouse down instead of click because it gets called before drag start.
3126
1996
  .on("mousedown", (d3Event, d) => {
3127
1997
  this.logger.logStartTimer("Node Group - mouse down");
3128
- d3Event.stopPropagation(); // Stop event going to canvas when enableEditingActions is false
1998
+ d3Event.stopPropagation();
3129
1999
  if (this.svgCanvasTextArea.isEditingText()) {
3130
2000
  this.svgCanvasTextArea.completeEditing();
3131
2001
  }
@@ -3139,16 +2009,6 @@ export default class SVGCanvasRenderer {
3139
2009
  // Don't stop propogation. Mouse move messages must be allowed to
3140
2010
  // propagate to canvas zoom operation.
3141
2011
  })
3142
- .on("mouseup", (d3Event, d) => {
3143
- this.logger.log("Node Group - mouse up");
3144
- d3Event.stopPropagation();
3145
- if (this.drawingNewLinkData) {
3146
- this.completeNewLink(d3Event);
3147
- }
3148
- if (this.draggingLinkData) {
3149
- this.completeDraggedLink(d3Event, d);
3150
- }
3151
- })
3152
2012
  .on("click", (d3Event, d) => {
3153
2013
  this.logger.log("Node Group - click");
3154
2014
  d3Event.stopPropagation();
@@ -3181,11 +2041,7 @@ export default class SVGCanvasRenderer {
3181
2041
  attachNodeSizingListeners(nodeGrps) {
3182
2042
  nodeGrps
3183
2043
  .on("mousedown", (d3Event, d) => {
3184
- if (this.isNodeResizable(d)) {
3185
- this.nodeSizing = true;
3186
- // Note - node resizing and finalization of size is handled by drag functions.
3187
- this.addTempCursorOverlay(this.nodeSizingCursor);
3188
- }
2044
+ this.dragObjectUtils.mouseDownNodeSizingArea(d);
3189
2045
  })
3190
2046
  // Use mousemove as well as mouseenter so the cursor will change
3191
2047
  // if the pointer moves from one area of the node outline to another
@@ -3194,19 +2050,10 @@ export default class SVGCanvasRenderer {
3194
2050
  // pointer leaves the temporary overlay (which is removed) and enters
3195
2051
  // the node outline.
3196
2052
  .on("mousemove mouseenter", (d3Event, d) => {
3197
- if (this.isNodeResizable(d) &&
3198
- !this.isRegionSelectOrSizingInProgress()) { // Don't switch sizing direction if we are already sizing
3199
- let cursorType = "default";
3200
- if (!this.isPointerCloseToBodyEdge(d3Event, d)) {
3201
- this.nodeSizingDirection = this.getSizingDirection(d3Event, d, d.layout.nodeCornerResizeArea);
3202
- this.nodeSizingCursor = this.getCursorBasedOnDirection(this.nodeSizingDirection);
3203
- cursorType = this.nodeSizingCursor;
3204
- }
3205
- d3.select(d3Event.currentTarget).style("cursor", cursorType);
3206
- }
2053
+ this.dragObjectUtils.mouseEnterNodeSizingArea(d3Event, d);
3207
2054
  })
3208
2055
  .on("mouseleave", (d3Event, d) => {
3209
- d3.select(d3Event.currentTarget).style("cursor", "default");
2056
+ this.dragObjectUtils.mouseLeaveNodeSizingArea(d3Event, d);
3210
2057
  });
3211
2058
  }
3212
2059
 
@@ -3219,7 +2066,7 @@ export default class SVGCanvasRenderer {
3219
2066
  labelSel
3220
2067
  .attr("x", this.nodeUtils.getNodeLabelHoverPosX(d))
3221
2068
  .attr("width", this.nodeUtils.getNodeLabelHoverWidth(d))
3222
- .attr("height", this.nodeUtils.getNodeLabelHoverHeight(d, spanSel.node(), this.zoomTransform.k));
2069
+ .attr("height", this.nodeUtils.getNodeLabelHoverHeight(d, spanSel.node(), this.zoomUtils.getZoomScale()));
3223
2070
  spanSel.classed("d3-label-full", true);
3224
2071
  }
3225
2072
  })
@@ -3259,37 +2106,6 @@ export default class SVGCanvasRenderer {
3259
2106
 
3260
2107
  attachInputPortListeners(inputPorts, node) {
3261
2108
  inputPorts
3262
- .on("mousedown", (d3Event, port) => {
3263
- if (!this.config.enableEditingActions) {
3264
- CanvasUtils.stopPropagationAndPreventDefault(d3Event);
3265
- return;
3266
- }
3267
- if (this.config.enableAssocLinkCreation) {
3268
- // Make sure this is just a left mouse button click - we don't want context menu click starting a line being drawn
3269
- if (d3Event.button === 0) {
3270
- CanvasUtils.stopPropagationAndPreventDefault(d3Event); // Stops the node drag behavior when clicking on the handle/circle
3271
- const srcNode = this.activePipeline.getNode(node.id);
3272
- this.drawingNewLinkData = {
3273
- srcObjId: node.id,
3274
- srcPortId: port.id,
3275
- action: this.config.enableAssocLinkCreation ? ASSOCIATION_LINK : NODE_LINK,
3276
- srcNode: srcNode,
3277
- startPos: { x: srcNode.x_pos + port.cx, y: srcNode.y_pos + port.cy },
3278
- portType: "input",
3279
- portObject: node.layout.inputPortObject,
3280
- portImage: node.layout.inputPortImage,
3281
- portWidth: node.layout.inputPortWidth,
3282
- portHeight: node.layout.inputPortHeight,
3283
- portRadius: this.getPortRadius(srcNode),
3284
- minInitialLine: srcNode.layout.minInitialLine,
3285
- guideObject: node.layout.inputPortGuideObject,
3286
- guideImage: node.layout.inputPortGuideImage,
3287
- linkArray: []
3288
- };
3289
- this.drawNewLink(d3Event);
3290
- }
3291
- }
3292
- })
3293
2109
  .on("mouseenter", (d3Event, port) => {
3294
2110
  CanvasUtils.stopPropagationAndPreventDefault(d3Event); // stop event propagation, otherwise node tip is shown
3295
2111
  if (this.canOpenTip(TIP_TYPE_PORT)) {
@@ -3322,40 +2138,6 @@ export default class SVGCanvasRenderer {
3322
2138
 
3323
2139
  attachOutputPortListeners(outputPorts, node) {
3324
2140
  outputPorts
3325
- .on("mousedown", (d3Event, port) => {
3326
- if (!this.config.enableEditingActions) {
3327
- CanvasUtils.stopPropagationAndPreventDefault(d3Event);
3328
- return;
3329
- }
3330
- // Make sure this is just a left mouse button click - we don't want context menu click starting a line being drawn
3331
- if (d3Event.button === 0) {
3332
- CanvasUtils.stopPropagationAndPreventDefault(d3Event); // Stops the node drag behavior when clicking on the handle/circle
3333
- const srcNode = this.activePipeline.getNode(node.id);
3334
- if (!CanvasUtils.isSrcCardinalityAtMax(port.id, srcNode, this.activePipeline.links)) {
3335
- this.drawingNewLinkData = {
3336
- srcObjId: node.id,
3337
- srcPortId: port.id,
3338
- action: this.config.enableAssocLinkCreation ? ASSOCIATION_LINK : NODE_LINK,
3339
- srcNode: srcNode,
3340
- startPos: { x: srcNode.x_pos + port.cx, y: srcNode.y_pos + port.cy },
3341
- portType: "output",
3342
- portObject: node.layout.outputPortObject,
3343
- portImage: node.layout.outputPortImage,
3344
- portWidth: node.layout.outputPortWidth,
3345
- portHeight: node.layout.outputPortHeight,
3346
- portRadius: this.getPortRadius(srcNode),
3347
- minInitialLine: srcNode.layout.minInitialLine,
3348
- guideObject: node.layout.outputPortGuideObject,
3349
- guideImage: node.layout.outputPortGuideImage,
3350
- linkArray: []
3351
- };
3352
- if (this.config.enableHighlightUnavailableNodes) {
3353
- this.setUnavailableTargetNodesHighlighting(srcNode, port.id, this.activePipeline.links);
3354
- }
3355
- this.drawNewLink(d3Event);
3356
- }
3357
- }
3358
- })
3359
2141
  .on("mouseenter", (d3Event, port) => {
3360
2142
  CanvasUtils.stopPropagationAndPreventDefault(d3Event); // stop event propagation, otherwise node tip is shown
3361
2143
  if (this.canOpenTip(TIP_TYPE_PORT)) {
@@ -3416,7 +2198,7 @@ export default class SVGCanvasRenderer {
3416
2198
  const nodeObj = foreignObj.parentElement;
3417
2199
  const nodeGrpSel = d3.select(nodeObj);
3418
2200
  const transform = this.nodeUtils.getNodeLabelEditIconTranslate(node, spanObj,
3419
- this.zoomTransform.k, this.config.enableDisplayFullLabelOnHover);
2201
+ this.zoomUtils.getZoomScale(), this.config.enableDisplayFullLabelOnHover);
3420
2202
 
3421
2203
  this.displayEditIcon(spanObj, nodeGrpSel, transform,
3422
2204
  (d3Event, d) => this.displayNodeLabelTextArea(d, d3Event.currentTarget.parentNode));
@@ -3430,7 +2212,7 @@ export default class SVGCanvasRenderer {
3430
2212
  const decObj = foreignObj.parentElement;
3431
2213
  const decGrpSel = d3.select(decObj);
3432
2214
  const transform = this.decUtils.getDecLabelEditIconTranslate(
3433
- dec, obj, objType, spanObj, this.zoomTransform.k);
2215
+ dec, obj, objType, spanObj, this.zoomUtils.getZoomScale());
3434
2216
 
3435
2217
  this.displayEditIcon(spanObj, decGrpSel, transform,
3436
2218
  (d3Event, d) => this.displayDecLabelTextArea(dec, obj, objType, d3Event.currentTarget.parentNode));
@@ -3683,9 +2465,11 @@ export default class SVGCanvasRenderer {
3683
2465
  extSel
3684
2466
  .attr("width", this.decUtils.getDecWidth(dec, d, objType))
3685
2467
  .attr("height", this.decUtils.getDecHeight(dec, d, objType))
3686
- .each(addDecExternalObject.bind(this));
2468
+ .each((decData, idx, exts) =>
2469
+ this.externalUtils.addDecExternalObject(decData, idx, exts));
3687
2470
  } else {
3688
- extSel.each(removeExternalObject.bind(this));
2471
+ extSel.each((decData, idx, exts) =>
2472
+ this.externalUtils.removeExternalObject(decData, idx, exts));
3689
2473
  extSel.remove();
3690
2474
  }
3691
2475
  }
@@ -3940,55 +2724,11 @@ export default class SVGCanvasRenderer {
3940
2724
  expandedSupernodeHaveStyledNodes = true;
3941
2725
  return;
3942
2726
  } else if (!expandedSupernodeHaveStyledNodes && CanvasUtils.isExpandedSupernode(node)) {
3943
- expandedSupernodeHaveStyledNodes = this.doesExpandedSupernodeHaveStyledNodes(node);
3944
- }
3945
- });
3946
- }
3947
- return expandedSupernodeHaveStyledNodes;
3948
- }
3949
-
3950
- // Returns the maximum amount for padding, when zooming to fit the canvas
3951
- // objects within a subflow, to allow the connection lines to be displayed
3952
- // without them doubling back on themselves.
3953
- getMaxZoomToFitPaddingForConnections() {
3954
- const paddingForInputBinding = this.getMaxPaddingForConnectionsFromInputBindingNodes();
3955
- const paddingForOutputBinding = this.getMaxPaddingForConnectionsToOutputBindingNodes();
3956
- const padding = Math.max(paddingForInputBinding, paddingForOutputBinding);
3957
- return padding;
3958
- }
3959
-
3960
- // Returns the maximum amount for padding, when zooming to fit the canvas
3961
- // objects within a subflow, to allow the connection lines (from input binding
3962
- // nodes to other sub-flow nodes) to be displayed without them doubling back
3963
- // on themselves.
3964
- getMaxPaddingForConnectionsFromInputBindingNodes() {
3965
- let maxPadding = 0;
3966
- const inputBindingNodes = this.activePipeline.nodes.filter((n) => n.isSupernodeInputBinding);
3967
-
3968
- inputBindingNodes.forEach((n) => {
3969
- const nodePadding = CanvasUtils.getNodePaddingToTargetNodes(n, this.activePipeline.nodes,
3970
- this.activePipeline.links, this.canvasLayout.linkType);
3971
- maxPadding = Math.max(maxPadding, nodePadding);
3972
- });
3973
-
3974
- return maxPadding;
3975
- }
3976
-
3977
- // Returns the maximum amount for padding, when zooming to fit the canvas
3978
- // objects within a subflow, to allow the connection lines (from sub-flow nodes
3979
- // to output binding nodes) to be displayed without them doubling back
3980
- // on themselves.
3981
- getMaxPaddingForConnectionsToOutputBindingNodes() {
3982
- let maxPadding = 0;
3983
- const outputBindingNodes = this.activePipeline.nodes.filter((n) => n.isSupernodeOutputBinding);
3984
-
3985
- this.activePipeline.nodes.forEach((n) => {
3986
- const nodePadding = CanvasUtils.getNodePaddingToTargetNodes(n, outputBindingNodes,
3987
- this.activePipeline.links, this.canvasLayout.linkType);
3988
- maxPadding = Math.max(maxPadding, nodePadding);
3989
- });
3990
-
3991
- return maxPadding;
2727
+ expandedSupernodeHaveStyledNodes = this.doesExpandedSupernodeHaveStyledNodes(node);
2728
+ }
2729
+ });
2730
+ }
2731
+ return expandedSupernodeHaveStyledNodes;
3992
2732
  }
3993
2733
 
3994
2734
  getPortRadius(d) {
@@ -3998,11 +2738,11 @@ export default class SVGCanvasRenderer {
3998
2738
  // Returns the radius size of the supernode binding ports scaled up by
3999
2739
  // the zoom scale amount to give the actual size.
4000
2740
  getBindingPortRadius() {
4001
- return this.canvasLayout.supernodeBindingPortRadius / this.zoomTransform.k;
2741
+ return this.canvasLayout.supernodeBindingPortRadius / this.zoomUtils.getZoomScale();
4002
2742
  }
4003
2743
 
4004
2744
  addDynamicNodeIcons(d3Event, d, nodeGrp) {
4005
- if (!this.nodeSizing && !CanvasUtils.isSuperBindingNode(d)) {
2745
+ if (!this.isSizing() && !CanvasUtils.isSuperBindingNode(d)) {
4006
2746
  // Add the ellipsis icon if requested by layout config.
4007
2747
  if (d.layout.ellipsisDisplay) {
4008
2748
  this.addEllipsisIcon(d, nodeGrp);
@@ -4027,11 +2767,11 @@ export default class SVGCanvasRenderer {
4027
2767
  }
4028
2768
 
4029
2769
  addContextToolbar(d3Event, d, objType) {
4030
- if (!this.nodeSizing && !this.dragging && !this.draggingLinkData &&
2770
+ if (!this.isSizing() && !this.isDragging() &&
4031
2771
  !this.svgCanvasTextArea.isEditingText() && !CanvasUtils.isSuperBindingNode(d)) {
4032
2772
  this.canvasController.setMouseInObject(true);
4033
2773
  let pos = this.getContextToolbarPos(objType, d);
4034
- pos = this.unTransformPos(pos);
2774
+ pos = this.zoomUtils.unTransformPos(pos);
4035
2775
  this.openContextMenu(d3Event, objType, d, null, pos);
4036
2776
  }
4037
2777
  }
@@ -4122,836 +2862,167 @@ export default class SVGCanvasRenderer {
4122
2862
  .attr("y", this.canvasLayout.supernodeExpansionIconHoverAreaPadding);
4123
2863
  }
4124
2864
 
4125
- // Returns an array of breadcrumbs for the DOM element passed in. The DOM
4126
- // element is expected to be an element within a node (like the expansion
4127
- // icon). The output array will contain one breadcrumb for each nested
4128
- // supernode down to the supernode of which the DOM element is a part. So if
4129
- // there are three nested supernodes and the DOM element is part of the third
4130
- // one, the breadcrumbs array will have three elements.
4131
- getSupernodeBreadcrumbs(domEl) {
4132
- const breadcrumbs = [];
4133
-
4134
- let nodeGroupEl = domEl.closest(".d3-node-group");
4135
-
4136
- while (nodeGroupEl) {
4137
- const svgAreaEl = nodeGroupEl.closest(".svg-area");
4138
- const supernodeDatum = this.getD3DatumFromDomEl(nodeGroupEl);
4139
- const parentPipelineId = svgAreaEl.getAttribute("data-pipeline-id");
4140
-
4141
- breadcrumbs.push(
4142
- this.canvasController.createBreadcrumb(supernodeDatum, parentPipelineId)
4143
- );
4144
-
4145
- nodeGroupEl = svgAreaEl.closest(".d3-node-group");
4146
- }
4147
-
4148
- // Reverse the order, so they appear in the nesting order of the supernodes.
4149
- return breadcrumbs.reverse();
4150
- }
4151
-
4152
- // Returns the datum object (managd by D3) for the DOM element passed in.
4153
- getD3DatumFromDomEl(el) {
4154
- const sel = d3.select(el);
4155
- if (sel) {
4156
- return sel.datum();
4157
- }
4158
- return null;
4159
- }
4160
-
4161
- updatePortRadiusAndPos(nodeObj, node, portObjClassName) {
4162
- const nodeGrp = d3.select(nodeObj);
4163
- nodeGrp.selectAll("." + portObjClassName)
4164
- .attr("r", () => this.getPortRadius(node))
4165
- .attr("cx", (port) => port.cx)
4166
- .attr("cy", (port) => port.cy); // Port position may change for binding nodes with multiple-ports.
4167
- }
4168
-
4169
- updatePortArrowPath(nodeObj, portArrowClassName) {
4170
- const nodeGrp = d3.select(nodeObj);
4171
- nodeGrp.selectAll("." + portArrowClassName)
4172
- .attr("d", (port) => this.getPortArrowPath(port))
4173
- .attr("transform", (port) => this.getPortArrowPathTransform(port));
4174
- }
4175
-
4176
- // Returns true if the port (from a node template) passed in has a max
4177
- // cardinaility of zero. If cardinality or cardinality.max is missing the
4178
- // max is considered to be non-zero.
4179
- isPortMaxCardinalityZero(port) {
4180
- return (get(port, "app_data.ui_data.cardinality.max", 1) === 0);
4181
- }
4182
-
4183
- isMouseOverContextToolbar(d3Event) {
4184
- return this.getElementWithClassAtMousePos(d3Event, "context-toolbar");
4185
- }
4186
-
4187
- removeDynamicNodeIcons(d3Event, d, nodeGrp) {
4188
- if (d.layout.ellipsisDisplay) {
4189
- nodeGrp.selectChildren(".d3-node-ellipsis-group").remove();
4190
- }
4191
- nodeGrp.selectChildren(".d3-node-super-expand-icon-group").remove();
4192
- }
4193
-
4194
- createSupernodeRenderer(d, supernodeD3Object) {
4195
- if (d.subflow_ref && d.subflow_ref.pipeline_id_ref) {
4196
- const superRenderer = new SVGCanvasRenderer(
4197
- d.subflow_ref.pipeline_id_ref,
4198
- this.canvasDiv,
4199
- this.canvasController,
4200
- this.canvasInfo,
4201
- this.selectionInfo,
4202
- this.breadcrumbs,
4203
- this.nodeLayout,
4204
- this.canvasLayout,
4205
- this.config,
4206
- { id: d.id,
4207
- pipelineId: this.activePipeline.id,
4208
- renderer: this, // Only provided for in-place sub-flow
4209
- d3Selection: supernodeD3Object // Only provided for in-place sub-flow
4210
- });
4211
- return superRenderer;
4212
- }
4213
- return null;
4214
- }
4215
-
4216
- // Returns the renderer for the supernode passed in. With external
4217
- // pipeline handling the pipeline referencd by the supernode can change
4218
- // over time so we have to make sure the renderer is for the supernode AND
4219
- // for the active pipeline.
4220
- getRendererForSupernode(d) {
4221
- return this.superRenderers.find((sr) =>
4222
- sr.supernodeInfo.id === d.id && sr.activePipeline.id === d.subflow_ref.pipeline_id_ref);
4223
- }
4224
-
4225
- // Returns an array containing any renderers that are for the supernode passed
4226
- // in but where the supernode does NOT reference the renderer's active pipeline.
4227
- getDiscardedRenderersForSupernode(d) {
4228
- return this.superRenderers.filter((sr) =>
4229
- sr.supernodeInfo.id === d.id && sr.activePipeline.id !== d.subflow_ref.pipeline_id_ref);
4230
-
4231
- }
4232
-
4233
- openContextMenu(d3Event, type, d, port, pos) {
4234
- CanvasUtils.stopPropagationAndPreventDefault(d3Event); // Stop the browser context menu appearing
4235
- this.canvasController.contextMenuHandler({
4236
- type: type,
4237
- targetObject: type === "canvas" ? null : d,
4238
- id: type === "canvas" ? null : d.id, // For historical puposes, we pass d.id as well as d as targetObject.
4239
- pipelineId: this.activePipeline.id,
4240
- cmPos: pos
4241
- ? pos
4242
- : this.getMousePos(d3Event, this.canvasDiv.selectAll("svg")), // Get mouse pos relative to top most SVG area even in a subflow.
4243
- mousePos: this.getMousePosSnapToGrid(this.getTransformedMousePos(d3Event)),
4244
- selectedObjectIds: this.canvasController.getSelectedObjectIds(),
4245
- addBreadcrumbs: (d && d.type === SUPER_NODE) ? this.getSupernodeBreadcrumbs(d3Event.currentTarget) : null,
4246
- port: port,
4247
- zoom: this.zoomTransform.k });
4248
- }
4249
-
4250
- closeContextMenuIfOpen() {
4251
- if (this.canvasController.isContextMenuDisplayed()) {
4252
- this.canvasController.closeContextMenu();
4253
- }
4254
- if (this.config.enableContextToolbar) {
4255
- this.removeContextToolbar();
4256
- }
4257
- }
4258
-
4259
- callDecoratorCallback(d3Event, node, dec) {
4260
- d3Event.stopPropagation();
4261
- if (this.canvasController.decorationActionHandler) {
4262
- this.canvasController.decorationActionHandler(node, dec.id, this.activePipeline.id);
4263
- }
4264
- }
4265
-
4266
- drawNewLink(d3Event) {
4267
- if (this.config.enableEditingActions === false) {
4268
- return;
4269
- }
4270
-
4271
- this.closeContextMenuIfOpen();
4272
-
4273
- const transPos = this.getTransformedMousePos(d3Event);
4274
-
4275
- if (this.drawingNewLinkData.action === COMMENT_LINK) {
4276
- this.drawNewCommentLinkForPorts(transPos);
4277
- } else {
4278
- this.drawNewNodeLinkForPorts(transPos);
4279
- }
4280
- // Switch on an attribute to indicate a new link is being dragged
4281
- // towards and over a target node.
4282
- if (this.config.enableHighlightNodeOnNewLinkDrag) {
4283
- this.setNewLinkOverNode(d3Event);
4284
- }
4285
- }
4286
-
4287
- drawNewNodeLinkForPorts(transPos) {
4288
- var that = this;
4289
- const linkType = this.config.enableAssocLinkCreation ? ASSOCIATION_LINK : NODE_LINK;
4290
-
4291
- let startPos;
4292
- if (this.canvasLayout.linkType === LINK_TYPE_STRAIGHT) {
4293
- startPos = this.linkUtils.getNewStraightNodeLinkStartPos(this.drawingNewLinkData.srcNode, transPos);
4294
- } else {
4295
- startPos = {
4296
- x: this.drawingNewLinkData.startPos.x,
4297
- y: this.drawingNewLinkData.startPos.y };
4298
- }
4299
-
4300
- this.drawingNewLinkData.linkArray = [{
4301
- "x1": startPos.x,
4302
- "y1": startPos.y,
4303
- "x2": transPos.x,
4304
- "y2": transPos.y,
4305
- "originX": startPos.originX,
4306
- "originY": startPos.originY,
4307
- "type": linkType }];
4308
-
4309
- if (this.config.enableAssocLinkCreation) {
4310
- this.drawingNewLinkData.linkArray[0].assocLinkVariation =
4311
- this.getNewLinkAssocVariation(
4312
- this.drawingNewLinkData.linkArray[0].x1,
4313
- this.drawingNewLinkData.linkArray[0].x2,
4314
- this.drawingNewLinkData.portType);
4315
- }
4316
-
4317
- const pathInfo = this.linkUtils.getConnectorPathInfo(
4318
- this.drawingNewLinkData.linkArray[0], this.drawingNewLinkData.minInitialLine);
4319
-
4320
- const connectionLineSel = this.nodesLinksGrp.selectAll(".d3-new-connection-line");
4321
- const connectionStartSel = this.nodesLinksGrp.selectAll(".d3-new-connection-start");
4322
- const connectionGuideSel = this.nodesLinksGrp.selectAll(".d3-new-connection-guide");
4323
-
4324
- // For a straight node line, don't draw the new link line when the guide
4325
- // icon or object is inside the node boundary.
4326
- if (linkType === NODE_LINK &&
4327
- this.canvasLayout.linkType === LINK_TYPE_STRAIGHT &&
4328
- this.nodeUtils.isPointInNodeBoundary(transPos, this.drawingNewLinkData.srcNode)) {
4329
- this.removeNewLinkLine();
4330
-
4331
- } else {
4332
- connectionLineSel
4333
- .data(this.drawingNewLinkData.linkArray)
4334
- .enter()
4335
- .append("path")
4336
- .attr("class", "d3-new-connection-line")
4337
- .attr("linkType", linkType)
4338
- .merge(connectionLineSel)
4339
- .attr("d", pathInfo.path)
4340
- .attr("transform", pathInfo.transform);
4341
- }
4342
-
4343
- if (this.canvasLayout.linkType !== LINK_TYPE_STRAIGHT) {
4344
- connectionStartSel
4345
- .data(this.drawingNewLinkData.linkArray)
4346
- .enter()
4347
- .append(this.drawingNewLinkData.portObject)
4348
- .attr("class", "d3-new-connection-start")
4349
- .attr("linkType", linkType)
4350
- .merge(connectionStartSel)
4351
- .each(function(d) {
4352
- // No need to draw the starting object of the new line if it is an image.
4353
- if (that.drawingNewLinkData.portObject === PORT_OBJECT_CIRCLE) {
4354
- d3.select(this)
4355
- .attr("cx", d.x1)
4356
- .attr("cy", d.y1)
4357
- .attr("r", that.drawingNewLinkData.portRadius);
4358
- }
4359
- });
4360
- }
4361
-
4362
- connectionGuideSel
4363
- .data(this.drawingNewLinkData.linkArray)
4364
- .enter()
4365
- .append(this.drawingNewLinkData.guideObject)
4366
- .attr("class", "d3-new-connection-guide")
4367
- .attr("linkType", linkType)
4368
- .on("mouseup", (d3Event) => {
4369
- CanvasUtils.stopPropagationAndPreventDefault(d3Event);
4370
- this.completeNewLink(d3Event);
4371
- })
4372
- .merge(connectionGuideSel)
4373
- .each(function(d) {
4374
- if (that.drawingNewLinkData.guideObject === PORT_OBJECT_IMAGE) {
4375
- d3.select(this)
4376
- .attr("xlink:href", that.drawingNewLinkData.guideImage)
4377
- .attr("x", d.x2 - (that.drawingNewLinkData.portWidth / 2))
4378
- .attr("y", d.y2 - (that.drawingNewLinkData.portHeight / 2))
4379
- .attr("width", that.drawingNewLinkData.portWidth)
4380
- .attr("height", that.drawingNewLinkData.portHeight)
4381
- .attr("transform", that.getLinkImageTransform(d));
4382
- } else {
4383
- d3.select(this)
4384
- .attr("cx", d.x2)
4385
- .attr("cy", d.y2)
4386
- .attr("r", that.drawingNewLinkData.portRadius);
4387
- }
4388
- });
4389
- }
4390
-
4391
- getLinkImageTransform(d) {
4392
- let angle = 0;
4393
- if (this.canvasLayout.linkType === LINK_TYPE_STRAIGHT) {
4394
- const adjacent = d.x2 - (d.originX || d.x1);
4395
- const opposite = d.y2 - (d.originY || d.y1);
4396
- if (adjacent === 0 && opposite === 0) {
4397
- angle = 0;
4398
- } else {
4399
- angle = Math.atan(opposite / adjacent) * (180 / Math.PI);
4400
- angle = adjacent >= 0 ? angle : angle + 180;
4401
- if (this.canvasLayout.linkDirection === LINK_DIR_TOP_BOTTOM) {
4402
- angle -= 90;
4403
- } else if (this.canvasLayout.linkDirection === LINK_DIR_BOTTOM_TOP) {
4404
- angle += 90;
4405
- }
4406
- }
4407
- return `rotate(${angle},${d.x2},${d.y2})`;
4408
- }
4409
- return null;
4410
- }
4411
-
4412
- drawNewCommentLinkForPorts(transPos) {
4413
- const that = this;
4414
- const srcComment = this.activePipeline.getComment(this.drawingNewLinkData.srcObjId);
4415
- const startPos = this.linkUtils.getNewStraightCommentLinkStartPos(srcComment, transPos);
4416
- const linkType = COMMENT_LINK;
4417
-
4418
- this.drawingNewLinkData.linkArray = [{
4419
- "x1": startPos.x,
4420
- "y1": startPos.y,
4421
- "x2": transPos.x,
4422
- "y2": transPos.y,
4423
- "type": linkType }];
4424
-
4425
- const connectionLineSel = this.nodesLinksGrp.selectAll(".d3-new-connection-line");
4426
- const connectionGuideSel = this.nodesLinksGrp.selectAll(".d3-new-connection-guide");
4427
-
4428
- connectionLineSel
4429
- .data(this.drawingNewLinkData.linkArray)
4430
- .enter()
4431
- .append("path")
4432
- .attr("class", "d3-new-connection-line")
4433
- .attr("linkType", linkType)
4434
- .merge(connectionLineSel)
4435
- .attr("d", (d) => that.linkUtils.getConnectorPathInfo(d).path);
4436
-
4437
- connectionGuideSel
4438
- .data(this.drawingNewLinkData.linkArray)
4439
- .enter()
4440
- .append("circle")
4441
- .attr("class", "d3-new-connection-guide")
4442
- .attr("linkType", linkType)
4443
- .on("mouseup", (d3Event) => {
4444
- CanvasUtils.stopPropagationAndPreventDefault(d3Event);
4445
- this.completeNewLink(d3Event);
4446
- })
4447
- .merge(connectionGuideSel)
4448
- .attr("cx", (d) => d.x2)
4449
- .attr("cy", (d) => d.y2)
4450
- .attr("r", this.canvasLayout.commentPortRadius);
4451
-
4452
- if (this.canvasLayout.commentLinkArrowHead) {
4453
- const connectionArrowHeadSel = this.nodesLinksGrp.selectAll(".d3-new-connection-arrow");
4454
-
4455
- connectionArrowHeadSel
4456
- .data(this.drawingNewLinkData.linkArray)
4457
- .enter()
4458
- .append("path")
4459
- .attr("class", "d3-new-connection-arrow")
4460
- .attr("linkType", linkType)
4461
- .on("mouseup", (d3Event) => {
4462
- CanvasUtils.stopPropagationAndPreventDefault(d3Event);
4463
- this.completeNewLink(d3Event);
4464
- })
4465
- .merge(connectionArrowHeadSel)
4466
- .attr("d", (d) => this.getArrowHead(d))
4467
- .attr("transform", (d) => this.getArrowHeadTransform(d));
4468
- }
4469
- }
4470
-
4471
- // Handles the completion of a new link being drawn from a source node.
4472
- completeNewLink(d3Event) {
4473
- if (this.config.enableEditingActions === false) {
4474
- return;
4475
- }
4476
-
4477
- if (this.config.enableHighlightUnavailableNodes) {
4478
- this.unsetUnavailableNodesHighlighting();
4479
- }
4480
- var trgNode = this.getNodeAtMousePos(d3Event);
4481
- if (trgNode !== null) {
4482
- this.completeNewLinkOnNode(d3Event, trgNode);
4483
- } else {
4484
- if (this.config.enableLinkSelection === LINK_SELECTION_DETACHABLE &&
4485
- this.drawingNewLinkData.action === NODE_LINK &&
4486
- !this.config.enableAssocLinkCreation) {
4487
- this.completeNewDetachedLink(d3Event);
4488
- } else {
4489
- this.stopDrawingNewLink();
4490
- }
4491
- }
4492
- }
4493
-
4494
- // Handles the completion of a new link when the end is dropped on a node.
4495
- completeNewLinkOnNode(d3Event, trgNode) {
4496
- // If we completed a connection remove the new line objects.
4497
- this.removeNewLink();
4498
-
4499
- // Switch 'new link over node' highlighting off
4500
- if (this.config.enableHighlightNodeOnNewLinkDrag) {
4501
- this.setNewLinkOverNodeCancel();
4502
- }
4503
-
4504
- if (trgNode !== null) {
4505
- const type = this.drawingNewLinkData.action;
4506
- if (type === NODE_LINK) {
4507
- const srcNode = this.activePipeline.getNode(this.drawingNewLinkData.srcObjId);
4508
- const srcPortId = this.drawingNewLinkData.srcPortId;
4509
- const trgPortId = this.getInputNodePortId(d3Event, trgNode);
4510
-
4511
- if (CanvasUtils.isDataConnectionAllowed(srcPortId, trgPortId, srcNode, trgNode, this.activePipeline.links)) {
4512
- this.canvasController.editActionHandler({
4513
- editType: "linkNodes",
4514
- editSource: "canvas",
4515
- nodes: [{ "id": this.drawingNewLinkData.srcObjId, "portId": this.drawingNewLinkData.srcPortId }],
4516
- targetNodes: [{ "id": trgNode.id, "portId": trgPortId }],
4517
- type: type,
4518
- linkType: "data", // Added for historical purposes - for WML Canvas support
4519
- pipelineId: this.pipelineId });
4520
-
4521
- } else if (this.config.enableLinkReplaceOnNewConnection &&
4522
- CanvasUtils.isDataLinkReplacementAllowed(srcPortId, trgPortId, srcNode, trgNode, this.activePipeline.links)) {
4523
- const linksToTrgPort = CanvasUtils.getDataLinksConnectedTo(trgPortId, trgNode, this.activePipeline.links);
4524
- // We only replace a link to a maxed out cardinality port if there
4525
- // is only one link. i.e. the input port cardinality is 0:1
4526
- if (linksToTrgPort.length === 1) {
4527
- this.canvasController.editActionHandler({
4528
- editType: "linkNodesAndReplace",
4529
- editSource: "canvas",
4530
- nodes: [{ "id": this.drawingNewLinkData.srcObjId, "portId": this.drawingNewLinkData.srcPortId }],
4531
- targetNodes: [{ "id": trgNode.id, "portId": trgPortId }],
4532
- type: type,
4533
- pipelineId: this.pipelineId,
4534
- replaceLink: linksToTrgPort[0]
4535
- });
4536
- }
4537
- }
4538
-
4539
- } else if (type === ASSOCIATION_LINK) {
4540
- const srcNode = this.activePipeline.getNode(this.drawingNewLinkData.srcObjId);
4541
-
4542
- if (CanvasUtils.isAssocConnectionAllowed(srcNode, trgNode, this.activePipeline.links)) {
4543
- this.canvasController.editActionHandler({
4544
- editType: "linkNodes",
4545
- editSource: "canvas",
4546
- nodes: [{ "id": this.drawingNewLinkData.srcObjId }],
4547
- targetNodes: [{ "id": trgNode.id }],
4548
- type: type,
4549
- pipelineId: this.pipelineId });
4550
- }
4551
-
4552
- } else {
4553
- if (CanvasUtils.isCommentLinkConnectionAllowed(this.drawingNewLinkData.srcObjId, trgNode.id, this.activePipeline.links)) {
4554
- this.canvasController.editActionHandler({
4555
- editType: "linkComment",
4556
- editSource: "canvas",
4557
- nodes: [this.drawingNewLinkData.srcObjId],
4558
- targetNodes: [trgNode.id],
4559
- type: COMMENT_LINK,
4560
- linkType: "comment", // Added for historical purposes - for WML Canvas support
4561
- pipelineId: this.pipelineId });
4562
- }
4563
- }
4564
- }
4565
-
4566
- this.drawingNewLinkData = null;
4567
- }
4568
-
4569
- // Handles the completion of a new link when the end is dropped away from
4570
- // a node (when enableLinkSelection is set to LINK_SELECTION_DETACHABLE)
4571
- // which creates a new detached link.
4572
- completeNewDetachedLink(d3Event) {
4573
- // If we completed a connection remove the new line objects.
4574
- this.removeNewLink();
4575
-
4576
- // Switch 'new link over node' highlighting off
4577
- if (this.config.enableHighlightNodeOnNewLinkDrag) {
4578
- this.setNewLinkOverNodeCancel();
4579
- }
4580
-
4581
- const endPoint = this.getTransformedMousePos(d3Event);
4582
- this.canvasController.editActionHandler({
4583
- editType: "createDetachedLink",
4584
- editSource: "canvas",
4585
- srcNodeId: this.drawingNewLinkData.srcObjId,
4586
- srcNodePortId: this.drawingNewLinkData.srcPortId,
4587
- trgPos: endPoint,
4588
- type: NODE_LINK,
4589
- pipelineId: this.pipelineId });
4590
-
4591
- this.drawingNewLinkData = null;
4592
- }
4593
-
4594
- stopDrawingNewLink() {
4595
- // Switch 'new link over node' highlighting off
4596
- if (this.config.enableHighlightNodeOnNewLinkDrag) {
4597
- this.setNewLinkOverNodeCancel();
4598
- }
4599
-
4600
- this.stopDrawingNewLinkForPorts();
4601
- this.drawingNewLinkData = null;
4602
- }
4603
-
4604
- stopDrawingNewLinkForPorts() {
4605
- const saveX1 = this.drawingNewLinkData.linkArray[0].x1;
4606
- const saveY1 = this.drawingNewLinkData.linkArray[0].y1;
4607
- const saveX2 = this.drawingNewLinkData.linkArray[0].x2;
4608
- const saveY2 = this.drawingNewLinkData.linkArray[0].y2;
4609
-
4610
- const saveNewLinkData = Object.assign({}, this.drawingNewLinkData);
4611
-
4612
- this.drawingNewLinkData = null;
4613
-
4614
- // If we completed a connection successfully just remove the new line
4615
- // objects.
4616
- let newPath = "";
4617
- let duration = 350;
4618
-
4619
- if (this.canvasLayout.linkType === LINK_TYPE_CURVE) {
4620
- newPath = "M " + saveX1 + " " + saveY1 +
4621
- "C " + saveX2 + " " + saveY2 +
4622
- " " + saveX2 + " " + saveY2 +
4623
- " " + saveX2 + " " + saveY2;
4624
-
4625
- } else if (this.canvasLayout.linkType === LINK_TYPE_STRAIGHT) {
4626
- if (saveX1 < saveX2) {
4627
- duration = 0;
4628
- }
4629
- newPath = "M " + saveX1 + " " + saveY1 +
4630
- "L " + saveX2 + " " + saveY2 +
4631
- " " + saveX2 + " " + saveY2 +
4632
- " " + saveX2 + " " + saveY2;
4633
-
4634
- } else {
4635
- newPath = "M " + saveX1 + " " + saveY1 +
4636
- "L " + saveX2 + " " + saveY2 +
4637
- "Q " + saveX2 + " " + saveY2 + " " + saveX2 + " " + saveY2 +
4638
- "L " + saveX2 + " " + saveY2 +
4639
- "Q " + saveX2 + " " + saveY2 + " " + saveX2 + " " + saveY2 +
4640
- "L " + saveX2 + " " + saveY2 +
4641
- "Q " + saveX2 + " " + saveY2 + " " + saveX2 + " " + saveY2 +
4642
- "L " + saveX2 + " " + saveY2 +
4643
- "Q " + saveX2 + " " + saveY2 + " " + saveX2 + " " + saveY2 +
4644
- "L " + saveX2 + " " + saveY2;
4645
- }
4646
-
4647
- this.nodesLinksGrp.selectAll(".d3-new-connection-line")
4648
- .transition()
4649
- .duration(duration)
4650
- .attr("d", newPath)
4651
- .on("end", () => {
4652
- this.nodesLinksGrp.selectAll(".d3-new-connection-arrow").remove();
4653
-
4654
- this.nodesLinksGrp.selectAll(".d3-new-connection-guide")
4655
- .transition()
4656
- .duration(1000)
4657
- .ease(d3.easeElastic)
4658
- // The lines below set all attributes for images AND circles even
4659
- // though some attributes will not be relevant. This is done
4660
- // because I could not get the .each() method to work here (which
4661
- // would be necessary to have an if statement based on guide object)
4662
- .attr("x", saveX1 - (saveNewLinkData.portWidth / 2))
4663
- .attr("y", saveY1 - (saveNewLinkData.portHeight / 2))
4664
- .attr("cx", saveX1)
4665
- .attr("cy", saveY1)
4666
- .attr("transform", null);
4667
- this.nodesLinksGrp.selectAll(".d3-new-connection-line")
4668
- .transition()
4669
- .duration(1000)
4670
- .ease(d3.easeElastic)
4671
- .attr("d", "M " + saveX1 + " " + saveY1 +
4672
- "L " + saveX1 + " " + saveY1)
4673
- .on("end", this.removeNewLink.bind(this));
4674
- });
4675
- }
4676
-
4677
- removeNewLink() {
4678
- this.nodesLinksGrp.selectAll(".d3-new-connection-line").remove();
4679
- this.nodesLinksGrp.selectAll(".d3-new-connection-start").remove();
4680
- this.nodesLinksGrp.selectAll(".d3-new-connection-guide").remove();
4681
- this.nodesLinksGrp.selectAll(".d3-new-connection-arrow").remove();
4682
- }
2865
+ // Returns an array of breadcrumbs for the DOM element passed in. The DOM
2866
+ // element is expected to be an element within a node (like the expansion
2867
+ // icon). The output array will contain one breadcrumb for each nested
2868
+ // supernode down to the supernode of which the DOM element is a part. So if
2869
+ // there are three nested supernodes and the DOM element is part of the third
2870
+ // one, the breadcrumbs array will have three elements.
2871
+ getSupernodeBreadcrumbs(domEl) {
2872
+ const breadcrumbs = [];
4683
2873
 
4684
- removeNewLinkLine() {
4685
- this.nodesLinksGrp.selectAll(".d3-new-connection-line").remove();
4686
- }
2874
+ let nodeGroupEl = domEl.closest(".d3-node-group");
4687
2875
 
4688
- dragLinkHandle(d3Event) {
4689
- const transPos = this.getTransformedMousePos(d3Event);
4690
- const link = this.draggingLinkData.link;
2876
+ while (nodeGroupEl) {
2877
+ const svgAreaEl = nodeGroupEl.closest(".svg-area");
2878
+ const supernodeDatum = this.getD3DatumFromDomEl(nodeGroupEl);
2879
+ const parentPipelineId = svgAreaEl.getAttribute("data-pipeline-id");
4691
2880
 
4692
- if (this.draggingLinkData.endBeingDragged === "start") {
4693
- link.srcPos = { x_pos: transPos.x, y_pos: transPos.y };
4694
- delete link.srcNodeId;
4695
- delete link.srcNodePortId;
4696
- delete link.srcObj;
2881
+ breadcrumbs.push(
2882
+ this.canvasController.createBreadcrumb(supernodeDatum, parentPipelineId)
2883
+ );
4697
2884
 
4698
- } else {
4699
- link.trgPos = { x_pos: transPos.x, y_pos: transPos.y };
4700
- delete link.trgNodeId;
4701
- delete link.trgNodePortId;
4702
- delete link.trgNode;
2885
+ nodeGroupEl = svgAreaEl.closest(".d3-node-group");
4703
2886
  }
4704
2887
 
4705
- this.displayLinks();
4706
-
4707
- // Switch on an attribute to indicate a new link is being dragged
4708
- // towards and over a target node.
4709
- if (this.config.enableHighlightNodeOnNewLinkDrag) {
4710
- this.setNewLinkOverNode(d3Event);
4711
- }
4712
- }
4713
-
4714
- completeDraggedLink(d3Event) {
4715
- const newLink = this.getNewLinkOnDrag(d3Event);
4716
-
4717
- if (newLink) {
4718
- const editSubType = this.getLinkEditSubType(newLink);
4719
- // If editSubType is set the user did a gesture that requires a change
4720
- // to the object model.
4721
- if (editSubType) {
4722
- this.canvasController.editActionHandler({
4723
- editType: "updateLink",
4724
- editSubType: editSubType,
4725
- editSource: "canvas",
4726
- newLink: newLink,
4727
- pipelineId: this.pipelineId });
4728
- // If editSubType is null, the user performed a gesture which should
4729
- // not be executed as an action so draw the link back in its old position.
4730
- } else {
4731
- this.snapBackOldLink();
4732
- }
4733
- // newLink might be null when we are dragging a link handle with
4734
- // enableLinkSelection not set to detachable. If that's the case the
4735
- // link needs to snap back (redrawn) to its original position.
4736
- } else {
4737
- this.snapBackOldLink();
4738
- }
2888
+ // Reverse the order, so they appear in the nesting order of the supernodes.
2889
+ return breadcrumbs.reverse();
2890
+ }
4739
2891
 
4740
- // Switch 'new link over node' highlighting off
4741
- if (this.config.enableHighlightNodeOnNewLinkDrag) {
4742
- this.setNewLinkOverNodeCancel();
2892
+ // Returns the datum object (managd by D3) for the DOM element passed in.
2893
+ getD3DatumFromDomEl(el) {
2894
+ const sel = d3.select(el);
2895
+ if (sel) {
2896
+ return sel.datum();
4743
2897
  }
4744
-
4745
- this.unsetUnavailableNodesHighlighting();
4746
- this.stopDraggingLink();
2898
+ return null;
4747
2899
  }
4748
2900
 
4749
- // Resets and redraws the link being dragged back to its original position.
4750
- // This is necessary when the user performs a link drag gesture which should
4751
- // NOT be executed as an action -- therefore the link need to be drawn back
4752
- // in its original position.
4753
- snapBackOldLink() {
4754
- this.activePipeline.replaceLink(this.draggingLinkData.oldLink);
4755
- this.displayLinks();
2901
+ updatePortRadiusAndPos(nodeObj, node, portObjClassName) {
2902
+ const nodeGrp = d3.select(nodeObj);
2903
+ nodeGrp.selectAll("." + portObjClassName)
2904
+ .attr("r", () => this.getPortRadius(node))
2905
+ .attr("cx", (port) => port.cx)
2906
+ .attr("cy", (port) => port.cy); // Port position may change for binding nodes with multiple-ports.
4756
2907
  }
4757
2908
 
4758
- // Returns the edit sub-type for the link action being performed to further
4759
- // explain the updateLink action.
4760
- getLinkEditSubType(newLink) {
4761
- const oldLink = this.draggingLinkData.oldLink;
4762
-
4763
- if (oldLink.srcNodeId && !newLink.srcNodeId) {
4764
- return "detachFromSrcNode";
4765
-
4766
- } else if (oldLink.trgNodeId && !newLink.trgNodeId) {
4767
- return "detachFromTrgNode";
4768
-
4769
- } else if (!oldLink.srcNodeId && newLink.srcNodeId) {
4770
- return "attachToSrcNode";
4771
-
4772
- } else if (!oldLink.trgNodeId && newLink.trgNodeId) {
4773
- return "attachToTrgNode";
4774
-
4775
- } else if (!oldLink.srcNodeId && !newLink.srcNodeId &&
4776
- (oldLink.srcPos.x_pos !== newLink.srcPos.x_pos ||
4777
- oldLink.srcPos.y_pos !== newLink.srcPos.y_pos)) {
4778
- return "moveSrcPosition";
4779
-
4780
- } else if (!oldLink.trgNodeId && !newLink.trgNodeId &&
4781
- (oldLink.trgPos.x_pos !== newLink.trgPos.x_pos ||
4782
- oldLink.trgPos.y_pos !== newLink.trgPos.y_pos)) {
4783
- return "moveTrgPosition";
2909
+ updatePortArrowPath(nodeObj, portArrowClassName) {
2910
+ const nodeGrp = d3.select(nodeObj);
2911
+ nodeGrp.selectAll("." + portArrowClassName)
2912
+ .attr("d", (port) => this.getPortArrowPath(port))
2913
+ .attr("transform", (port) => this.getPortArrowPathTransform(port));
2914
+ }
4784
2915
 
4785
- } else if (oldLink.srcNodeId && newLink.srcNodeId &&
4786
- oldLink.srcNodeId !== newLink.srcNodeId) {
4787
- return "switchSrcNode";
2916
+ // Returns true if the port (from a node template) passed in has a max
2917
+ // cardinaility of zero. If cardinality or cardinality.max is missing the
2918
+ // max is considered to be non-zero.
2919
+ isPortMaxCardinalityZero(port) {
2920
+ return (get(port, "app_data.ui_data.cardinality.max", 1) === 0);
2921
+ }
4788
2922
 
4789
- } else if (oldLink.trgNodeId && newLink.trgNodeId &&
4790
- oldLink.trgNodeId !== newLink.trgNodeId) {
4791
- return "switchTrgNode";
2923
+ isMouseOverContextToolbar(d3Event) {
2924
+ return this.getElementWithClassAtMousePos(d3Event, "context-toolbar");
2925
+ }
4792
2926
 
4793
- } else if (oldLink.srcNodeId && newLink.srcNodeId &&
4794
- oldLink.srcNodeId === newLink.srcNodeId &&
4795
- oldLink.srcNodePortId !== newLink.srcNodePortId) {
4796
- return "switchSrcNodePort";
2927
+ removeDynamicNodeIcons(d3Event, d, nodeGrp) {
2928
+ if (d.layout.ellipsisDisplay) {
2929
+ nodeGrp.selectChildren(".d3-node-ellipsis-group").remove();
2930
+ }
2931
+ nodeGrp.selectChildren(".d3-node-super-expand-icon-group").remove();
2932
+ }
4797
2933
 
4798
- } else if (oldLink.trgNodeId && newLink.trgNodeId &&
4799
- oldLink.trgNodeId === newLink.trgNodeId &&
4800
- oldLink.trgNodePortId !== newLink.trgNodePortId) {
4801
- return "switchTrgNodePort";
2934
+ createSupernodeRenderer(d, supernodeD3Object) {
2935
+ if (d.subflow_ref && d.subflow_ref.pipeline_id_ref) {
2936
+ const superRenderer = new SVGCanvasRenderer(
2937
+ d.subflow_ref.pipeline_id_ref,
2938
+ this.canvasDiv,
2939
+ this.canvasController,
2940
+ this.canvasInfo,
2941
+ this.selectionInfo,
2942
+ this.breadcrumbs,
2943
+ this.nodeLayout,
2944
+ this.canvasLayout,
2945
+ this.config,
2946
+ { id: d.id,
2947
+ pipelineId: this.activePipeline.id,
2948
+ renderer: this, // Only provided for in-place sub-flow
2949
+ d3Selection: supernodeD3Object // Only provided for in-place sub-flow
2950
+ });
2951
+ return superRenderer;
4802
2952
  }
4803
- // We arrive here, in two ways:
4804
- // 1. if the user dragged a link handle from a node/port and dropped it
4805
- // back on the same node/port.
4806
- // 2. If the user clicked on the unattached end of a detached link but did
4807
- // not move it anywhere
4808
- // In these cases, the updateLink action should NOT be performed and
4809
- // consequently NO command should be added to the command stack.
4810
2953
  return null;
4811
2954
  }
4812
2955
 
4813
- // Returns a new link if one can be created given the current data in the
4814
- // this.draggingLinkData object. Returns null if a link cannot be created.
4815
- getNewLinkOnDrag(d3Event, nodeProximity) {
4816
- const oldLink = this.draggingLinkData.oldLink;
4817
- const newLink = cloneDeep(oldLink);
4818
-
4819
- if (this.draggingLinkData.endBeingDragged === "start") {
4820
- delete newLink.srcObj;
4821
- delete newLink.srcNodeId;
4822
- delete newLink.srcNodePortId;
4823
- delete newLink.srcPos;
4824
-
4825
- const srcNode = nodeProximity
4826
- ? this.getNodeNearMousePos(d3Event, nodeProximity)
4827
- : this.getNodeAtMousePos(d3Event);
4828
-
4829
- if (srcNode) {
4830
- newLink.srcNodeId = srcNode.id;
4831
- newLink.srcObj = this.activePipeline.getNode(srcNode.id);
4832
- newLink.srcNodePortId = nodeProximity
4833
- ? this.getNodePortIdNearMousePos(d3Event, OUTPUT_TYPE, srcNode)
4834
- : this.getOutputNodePortId(d3Event, srcNode);
4835
- } else {
4836
- newLink.srcPos = this.draggingLinkData.link.srcPos;
4837
- }
4838
-
4839
- } else {
4840
- delete newLink.trgNode;
4841
- delete newLink.trgNodeId;
4842
- delete newLink.trgNodePortId;
4843
- delete newLink.trgPos;
4844
-
4845
- const trgNode = nodeProximity
4846
- ? this.getNodeNearMousePos(d3Event, nodeProximity)
4847
- : this.getNodeAtMousePos(d3Event);
4848
-
4849
- if (trgNode) {
4850
- newLink.trgNodeId = trgNode.id;
4851
- newLink.trgNode = this.activePipeline.getNode(trgNode.id);
4852
- newLink.trgNodePortId = nodeProximity
4853
- ? this.getNodePortIdNearMousePos(d3Event, INPUT_TYPE, trgNode)
4854
- : this.getInputNodePortId(d3Event, trgNode);
4855
- } else {
4856
- newLink.trgPos = this.draggingLinkData.link.trgPos;
4857
- }
4858
- }
2956
+ // Returns the renderer for the supernode passed in. With external
2957
+ // pipeline handling the pipeline referencd by the supernode can change
2958
+ // over time so we have to make sure the renderer is for the supernode AND
2959
+ // for the active pipeline.
2960
+ getRendererForSupernode(d) {
2961
+ return this.superRenderers.find((sr) =>
2962
+ sr.supernodeInfo.id === d.id && sr.activePipeline.id === d.subflow_ref.pipeline_id_ref);
2963
+ }
4859
2964
 
4860
- // If links are not detachable, we cannot create a link if srcPos
4861
- // or trgPos are set because that would create a detached link unconnected
4862
- // to either a source node or a target node or both.
4863
- if (this.config.enableLinkSelection !== LINK_SELECTION_DETACHABLE &&
4864
- (newLink.srcPos || newLink.trgPos)) {
4865
- return null;
4866
- }
2965
+ // Returns an array containing any renderers that are for the supernode passed
2966
+ // in but where the supernode does NOT reference the renderer's active pipeline.
2967
+ getDiscardedRenderersForSupernode(d) {
2968
+ return this.superRenderers.filter((sr) =>
2969
+ sr.supernodeInfo.id === d.id && sr.activePipeline.id !== d.subflow_ref.pipeline_id_ref);
4867
2970
 
4868
- if (this.canExecuteUpdateLinkCommand(newLink, oldLink)) {
4869
- return newLink;
4870
- }
4871
- return null;
4872
2971
  }
4873
2972
 
4874
- // Returns true if the update command for a dragged link can be executed.
4875
- // It might be prevented from executing if either the course
4876
- canExecuteUpdateLinkCommand(newLink, oldLink) {
4877
- const srcNode = this.activePipeline.getNode(newLink.srcNodeId);
4878
- const trgNode = this.activePipeline.getNode(newLink.trgNodeId);
4879
- const linkSrcChanged = this.hasLinkSrcChanged(newLink, oldLink);
4880
- const linkTrgChanged = this.hasLinkTrgChanged(newLink, oldLink);
4881
- const links = this.activePipeline.links;
4882
- let executeCommand = true;
2973
+ openContextMenu(d3Event, type, d, port, pos) {
2974
+ CanvasUtils.stopPropagationAndPreventDefault(d3Event); // Stop the browser context menu appearing
2975
+ this.canvasController.contextMenuHandler({
2976
+ type: type,
2977
+ targetObject: type === "canvas" ? null : d,
2978
+ id: type === "canvas" ? null : d.id, // For historical puposes, we pass d.id as well as d as targetObject.
2979
+ pipelineId: this.activePipeline.id,
2980
+ cmPos: pos
2981
+ ? pos
2982
+ : this.getMousePos(d3Event, this.canvasDiv.selectAll("svg")), // Get mouse pos relative to top most SVG area even in a subflow.
2983
+ mousePos: this.getMousePosSnapToGrid(this.getTransformedMousePos(d3Event)),
2984
+ selectedObjectIds: this.canvasController.getSelectedObjectIds(),
2985
+ addBreadcrumbs: (d && d.type === SUPER_NODE) ? this.getSupernodeBreadcrumbs(d3Event.currentTarget) : null,
2986
+ port: port,
2987
+ zoom: this.zoomUtils.getZoomScale() });
2988
+ }
4883
2989
 
4884
- if (linkSrcChanged && srcNode &&
4885
- !CanvasUtils.isSrcConnectionAllowedWithDetachedLinks(newLink.srcNodePortId, srcNode, links)) {
4886
- executeCommand = false;
4887
- }
4888
- if (linkTrgChanged && trgNode &&
4889
- !CanvasUtils.isTrgConnectionAllowedWithDetachedLinks(newLink.trgNodePortId, trgNode, links)) {
4890
- executeCommand = false;
2990
+ closeContextMenuIfOpen() {
2991
+ if (this.canvasController.isContextMenuDisplayed()) {
2992
+ this.canvasController.closeContextMenu();
4891
2993
  }
4892
- if (srcNode && trgNode &&
4893
- !CanvasUtils.isConnectionAllowedWithDetachedLinks(newLink.srcNodePortId, newLink.trgNodePortId, srcNode, trgNode, links)) {
4894
- executeCommand = false;
2994
+ if (this.config.enableContextToolbar) {
2995
+ this.removeContextToolbar();
4895
2996
  }
4896
- return executeCommand;
4897
2997
  }
4898
2998
 
4899
- // Returns true if the source information has changed between
4900
- // the two links.
4901
- hasLinkSrcChanged(newLink, oldLink) {
4902
- let linkUpdated = false;
4903
-
4904
- if (newLink.srcNodeId) {
4905
- if (newLink.srcNodeId !== oldLink.srcNodeId) {
4906
- linkUpdated = true;
4907
- }
4908
-
4909
- if (newLink.srcNodePortId !== oldLink.srcNodePortId) {
4910
- linkUpdated = true;
4911
- }
4912
-
4913
- } else {
4914
- if (oldLink.srcPos) {
4915
- if (newLink.srcPos.x_pos !== oldLink.srcPos.x_pos ||
4916
- newLink.srcPos.y_pos !== oldLink.srcPos.y_pos) {
4917
- linkUpdated = true;
4918
- }
4919
- } else {
4920
- linkUpdated = true;
4921
- }
2999
+ callDecoratorCallback(d3Event, node, dec) {
3000
+ d3Event.stopPropagation();
3001
+ if (this.canvasController.decorationActionHandler) {
3002
+ this.canvasController.decorationActionHandler(node, dec.id, this.activePipeline.id);
4922
3003
  }
4923
- return linkUpdated;
4924
3004
  }
4925
3005
 
4926
- // Returns true if the target information has changed between
4927
- // the two links.
4928
- hasLinkTrgChanged(newLink, oldLink) {
4929
- let linkUpdated = false;
4930
-
4931
- if (newLink.trgNodeId) {
4932
- if (newLink.trgNodeId !== oldLink.trgNodeId) {
4933
- linkUpdated = true;
4934
- }
4935
-
4936
- if (newLink.trgNodePortId !== oldLink.trgNodePortId) {
4937
- linkUpdated = true;
4938
- }
4939
3006
 
4940
- } else {
4941
- if (oldLink.trgPos) {
4942
- if (newLink.trgPos.x_pos !== oldLink.trgPos.x_pos ||
4943
- newLink.trgPos.y_pos !== oldLink.trgPos.y_pos) {
4944
- linkUpdated = true;
4945
- }
3007
+ getLinkImageTransform(d) {
3008
+ let angle = 0;
3009
+ if (this.canvasLayout.linkType === LINK_TYPE_STRAIGHT) {
3010
+ const adjacent = d.x2 - (d.originX || d.x1);
3011
+ const opposite = d.y2 - (d.originY || d.y1);
3012
+ if (adjacent === 0 && opposite === 0) {
3013
+ angle = 0;
4946
3014
  } else {
4947
- linkUpdated = true;
3015
+ angle = Math.atan(opposite / adjacent) * (180 / Math.PI);
3016
+ angle = adjacent >= 0 ? angle : angle + 180;
3017
+ if (this.canvasLayout.linkDirection === LINK_DIR_TOP_BOTTOM) {
3018
+ angle -= 90;
3019
+ } else if (this.canvasLayout.linkDirection === LINK_DIR_BOTTOM_TOP) {
3020
+ angle += 90;
3021
+ }
4948
3022
  }
3023
+ return `rotate(${angle},${d.x2},${d.y2})`;
4949
3024
  }
4950
- return linkUpdated;
4951
- }
4952
-
4953
- stopDraggingLink() {
4954
- this.draggingLinkData = null;
3025
+ return null;
4955
3026
  }
4956
3027
 
4957
3028
  // Returns a link, if one can be found, at the position indicated by x and y
@@ -5083,7 +3154,7 @@ export default class SVGCanvasRenderer {
5083
3154
  .each((d) => {
5084
3155
  let portRadius = d.layout.portRadius;
5085
3156
  if (CanvasUtils.isSuperBindingNode(d)) {
5086
- portRadius = this.canvasLayout.supernodeBindingPortRadius / this.zoomTransform.k;
3157
+ portRadius = this.canvasLayout.supernodeBindingPortRadius / this.zoomUtils.getZoomScale();
5087
3158
  }
5088
3159
 
5089
3160
  if (pos.x >= d.x_pos - portRadius - prox && // Target port sticks out by its radius so need to allow for it.
@@ -5096,75 +3167,6 @@ export default class SVGCanvasRenderer {
5096
3167
  return node;
5097
3168
  }
5098
3169
 
5099
- getNodePortIdNearMousePos(d3Event, portType, node) {
5100
- const pos = this.getTransformedMousePos(d3Event);
5101
- let portId = null;
5102
- let defaultPortId = null;
5103
-
5104
- if (node) {
5105
- if (portType === OUTPUT_TYPE) {
5106
- const portObjs = this.getAllNodeGroupsSelection()
5107
- .selectChildren("." + this.getNodeOutputPortClassName())
5108
- .selectChildren(".d3-node-port-output-main");
5109
-
5110
- portId = this.searchForPortNearMouse(
5111
- node, pos, portObjs,
5112
- node.layout.outputPortObject,
5113
- node.width);
5114
- defaultPortId = CanvasUtils.getDefaultOutputPortId(node);
5115
-
5116
- } else {
5117
- const portObjs = this.getAllNodeGroupsSelection()
5118
- .selectChildren("." + this.getNodeInputPortClassName())
5119
- .selectChildren(".d3-node-port-input-main");
5120
-
5121
- portId = this.searchForPortNearMouse(
5122
- node, pos, portObjs,
5123
- node.layout.inputPortObject,
5124
- 0);
5125
- defaultPortId = CanvasUtils.getDefaultInputPortId(node);
5126
- }
5127
- }
5128
-
5129
- if (!portId) {
5130
- portId = defaultPortId;
5131
- }
5132
- return portId;
5133
- }
5134
-
5135
- // Returns a port ID for the port identified by the position (pos) on the
5136
- // node (node) further specified by the other parameters.
5137
- searchForPortNearMouse(node, pos, portObjs, portObjectType, nodeWidthOffset) {
5138
- let portId = null;
5139
- portObjs
5140
- .each((p, i, portGrps) => {
5141
- const portSel = d3.select(portGrps[i]);
5142
- if (portObjectType === PORT_OBJECT_IMAGE) {
5143
- const xx = node.x_pos + Number(portSel.attr("x"));
5144
- const yy = node.y_pos + Number(portSel.attr("y"));
5145
- const wd = Number(portSel.attr("width"));
5146
- const ht = Number(portSel.attr("height"));
5147
- if (pos.x >= xx &&
5148
- pos.x <= xx + nodeWidthOffset + wd &&
5149
- pos.y >= yy &&
5150
- pos.y <= yy + ht) {
5151
- portId = portGrps[i].getAttribute("data-port-id");
5152
- }
5153
- } else { // Port must be a circle
5154
- const cx = node.x_pos + Number(portSel.attr("cx"));
5155
- const cy = node.y_pos + Number(portSel.attr("cy"));
5156
- if (pos.x >= cx - node.layout.portRadius && // Target port sticks out by its radius so need to allow for it.
5157
- pos.x <= cx + node.layout.portRadius &&
5158
- pos.y >= cy - node.layout.portRadius &&
5159
- pos.y <= cy + node.layout.portRadius) {
5160
- portId = portGrps[i].getAttribute("data-port-id");
5161
- }
5162
- }
5163
- });
5164
-
5165
- return portId;
5166
- }
5167
-
5168
3170
  // Returns a sizing rectangle for nodes and comments. This extends an
5169
3171
  // invisible area out beyond the highlight sizing line to improve usability
5170
3172
  // when sizing.
@@ -5410,7 +3412,7 @@ export default class SVGCanvasRenderer {
5410
3412
  // necessary with binding nodes with mutiple ports.
5411
3413
  let multiplier = 1;
5412
3414
  if (CanvasUtils.isSuperBindingNode(data)) {
5413
- multiplier = 1 / this.zoomTransform.k;
3415
+ multiplier = 1 / this.zoomUtils.getZoomScale();
5414
3416
  }
5415
3417
 
5416
3418
  ports.forEach((p) => {
@@ -5464,7 +3466,7 @@ export default class SVGCanvasRenderer {
5464
3466
  // necessary with binding nodes with mutiple ports.
5465
3467
  let multiplier = 1;
5466
3468
  if (CanvasUtils.isSuperBindingNode(data)) {
5467
- multiplier = 1 / this.zoomTransform.k;
3469
+ multiplier = 1 / this.zoomUtils.getZoomScale();
5468
3470
  }
5469
3471
  ports.forEach((p) => {
5470
3472
  yPosition += (data.layout.portArcRadius * multiplier);
@@ -5623,10 +3625,11 @@ export default class SVGCanvasRenderer {
5623
3625
  ? markdownIt.render(c.content)
5624
3626
  : escapeText(c.content)));
5625
3627
 
5626
- // Add or remove drag behavior as appropriate
3628
+ // Add or remove drag object behavior for the comment groups.
5627
3629
  if (this.config.enableEditingActions) {
3630
+ const handler = this.dragObjectUtils.getDragObjectHandler();
5628
3631
  joinedCommentGrps
5629
- .call(this.dragHandler);
3632
+ .call(handler);
5630
3633
  } else {
5631
3634
  joinedCommentGrps
5632
3635
  .on(".drag", null);
@@ -5637,6 +3640,9 @@ export default class SVGCanvasRenderer {
5637
3640
  attachCommentGroupListeners(commentGrps) {
5638
3641
  commentGrps
5639
3642
  .on("mouseenter", (d3Event, d) => {
3643
+ if (this.isDragging()) {
3644
+ return;
3645
+ }
5640
3646
  this.setCommentStyles(d, "hover", d3.select(d3Event.currentTarget));
5641
3647
  if (this.config.enableEditingActions && d.id !== this.svgCanvasTextArea.getEditingTextId()) {
5642
3648
  this.createCommentPort(d3Event.currentTarget, d);
@@ -5657,7 +3663,7 @@ export default class SVGCanvasRenderer {
5657
3663
  // Use mouse down instead of click because it gets called before drag start.
5658
3664
  .on("mousedown", (d3Event, d) => {
5659
3665
  this.logger.logStartTimer("Comment Group - mouse down");
5660
- d3Event.stopPropagation(); // Stop event going to canvas when enableEditingActions is false
3666
+ d3Event.stopPropagation();
5661
3667
  if (this.svgCanvasTextArea.isEditingText()) {
5662
3668
  this.svgCanvasTextArea.completeEditing();
5663
3669
  }
@@ -5700,9 +3706,7 @@ export default class SVGCanvasRenderer {
5700
3706
  attachCommentSizingListeners(commentGrps) {
5701
3707
  commentGrps
5702
3708
  .on("mousedown", (d3Event, d) => {
5703
- this.commentSizing = true;
5704
- // Note - comment resizing and finalization of size is handled by drag functions.
5705
- this.addTempCursorOverlay(this.commentSizingCursor);
3709
+ this.dragObjectUtils.mouseDownCommentSizingArea();
5706
3710
  })
5707
3711
  // Use mousemove here rather than mouseenter so the cursor will change
5708
3712
  // if the pointer moves from one area of the node outline to another
@@ -5711,17 +3715,10 @@ export default class SVGCanvasRenderer {
5711
3715
  // pointer leaves the temporary overlay (which is removed) and enters
5712
3716
  // the node outline.
5713
3717
  .on("mousemove mouseenter", (d3Event, d) => {
5714
- if (this.config.enableEditingActions && // Only set cursor when we are able to edit comments
5715
- !this.isRegionSelectOrSizingInProgress()) // Don't switch sizing direction if we are already sizing
5716
- {
5717
- let cursorType = "default";
5718
- if (!this.isPointerCloseToBodyEdge(d3Event, d)) {
5719
- this.commentSizingDirection = this.getSizingDirection(d3Event, d, this.canvasLayout.commentCornerResizeArea);
5720
- this.commentSizingCursor = this.getCursorBasedOnDirection(this.commentSizingDirection);
5721
- cursorType = this.commentSizingCursor;
5722
- }
5723
- d3.select(d3Event.currentTarget).style("cursor", cursorType);
5724
- }
3718
+ this.dragObjectUtils.mouseEnterCommentSizingArea(d3Event, d);
3719
+ })
3720
+ .on("mouseleave", (d3Event, d) => {
3721
+ this.dragObjectUtils.mouseLeaveCommentSizingArea(d3Event, d);
5725
3722
  });
5726
3723
  }
5727
3724
 
@@ -5731,26 +3728,19 @@ export default class SVGCanvasRenderer {
5731
3728
  createCommentPort(commentObj, d) {
5732
3729
  const commentGrp = d3.select(commentObj);
5733
3730
 
5734
- commentGrp
3731
+ const commentPort = commentGrp
5735
3732
  .append("circle")
5736
3733
  .attr("cx", (com) => com.width / 2)
5737
3734
  .attr("cy", (com) => com.height + this.canvasLayout.commentHighlightGap)
5738
3735
  .attr("r", this.canvasLayout.commentPortRadius)
5739
- .attr("class", "d3-comment-port-circle")
5740
- .on("mousedown", (d3Event, cd) => {
5741
- CanvasUtils.stopPropagationAndPreventDefault(d3Event); // Stops the node drag behavior when clicking on the handle/circle
5742
- this.drawingNewLinkData = {
5743
- srcObjId: d.id,
5744
- action: COMMENT_LINK,
5745
- startPos: {
5746
- x: d.x_pos - this.canvasLayout.commentHighlightGap,
5747
- y: d.y_pos - this.canvasLayout.commentHighlightGap
5748
- },
5749
- linkArray: []
5750
- };
5751
- this.drawNewLink(d3Event);
5752
- });
3736
+ .attr("class", "d3-comment-port-circle");
5753
3737
 
3738
+ if (this.config.enableEditingActions) {
3739
+ const handler = this.dragNewLinkUtils.getDragNewLinkHandler();
3740
+ commentPort.call(handler);
3741
+ } else {
3742
+ commentPort.on(".drag", null);
3743
+ }
5754
3744
  }
5755
3745
 
5756
3746
  deleteCommentPort(commentObj) {
@@ -5840,7 +3830,7 @@ export default class SVGCanvasRenderer {
5840
3830
  // Returns true if this renderer or any of its ancestors are currently in the
5841
3831
  // process of selecting a region or sizing a node or comment.
5842
3832
  isRegionSelectOrSizingInProgress() {
5843
- if (this.regionSelect || this.nodeSizing || this.commentSizing) {
3833
+ if (this.regionSelect || this.isSizing()) {
5844
3834
  return true;
5845
3835
  }
5846
3836
  if (this.supernodeInfo.renderer) {
@@ -5851,336 +3841,6 @@ export default class SVGCanvasRenderer {
5851
3841
  return false;
5852
3842
  }
5853
3843
 
5854
- // This method allows us to avoid a strange behavior which only appears in the
5855
- // Chrome browser. That is, when the mouse pointer is inside the
5856
- // node/comment selection highlight area but is close to either the
5857
- // right or bottom side of the node/comment body, any mousedown events will go
5858
- // to the body instead of the highlight area. We use this method to detect
5859
- // this situation and use the result to decide whether to display the sizing
5860
- // cursor or not.
5861
- isPointerCloseToBodyEdge(d3Event, d) {
5862
- const pos = this.getTransformedMousePos(d3Event);
5863
- const rightEdge = d.x_pos + d.width;
5864
- const bottomEdge = d.y_pos + d.height;
5865
-
5866
- // Is the pointer within 1 pixel of the right edge of the node or comment
5867
- const rightEdgeState =
5868
- pos.x >= rightEdge && pos.x <= rightEdge + 1 &&
5869
- pos.y >= 0 && pos.y <= bottomEdge;
5870
-
5871
- // Is the pointer within 1 pixel of the bottom edge of the node or comment
5872
- const bottomEdgeState =
5873
- pos.y >= bottomEdge && pos.y <= bottomEdge + 1 &&
5874
- pos.x >= 0 && pos.x <= rightEdge;
5875
-
5876
- return rightEdgeState || bottomEdgeState;
5877
- }
5878
-
5879
- // Returns the comment or supernode sizing direction (i.e. one of n, s, e, w, nw, ne,
5880
- // sw or se) based on the current mouse position and the position and
5881
- // dimensions of the comment or node outline.
5882
- getSizingDirection(d3Event, d, cornerResizeArea) {
5883
- var xPart = "";
5884
- var yPart = "";
5885
-
5886
- const transPos = this.getTransformedMousePos(d3Event);
5887
- if (transPos.x < d.x_pos + cornerResizeArea) {
5888
- xPart = "w";
5889
- } else if (transPos.x > d.x_pos + d.width - cornerResizeArea) {
5890
- xPart = "e";
5891
- }
5892
- if (transPos.y < d.y_pos + cornerResizeArea) {
5893
- yPart = "n";
5894
- } else if (transPos.y > d.y_pos + d.height - cornerResizeArea) {
5895
- yPart = "s";
5896
- }
5897
-
5898
- return yPart + xPart;
5899
- }
5900
-
5901
- // Returns a cursor type based on the currect comment sizing direction.
5902
- // Possible values are: ns-resize, ew-resize, nwse-resize or nesw-resize.
5903
- getCursorBasedOnDirection(direction) {
5904
- var cursorType;
5905
- switch (direction) {
5906
- case "n":
5907
- case "s":
5908
- cursorType = "ns-resize";
5909
- break;
5910
- case "e":
5911
- case "w":
5912
- cursorType = "ew-resize";
5913
- break;
5914
- case "nw":
5915
- case "se":
5916
- cursorType = "nwse-resize";
5917
- break;
5918
- case "ne":
5919
- case "sw":
5920
- cursorType = "nesw-resize";
5921
- break;
5922
- default:
5923
- cursorType = "";
5924
- }
5925
-
5926
- return cursorType;
5927
- }
5928
-
5929
- // Returns the minimum allowed height for the node passed in. For supernodes
5930
- // this means combining the bigger of the space for the inputs and output ports
5931
- // with some space for the top of the display frame and the padding at the
5932
- // bottom of the frame. Then the bigger of that height versus the default
5933
- // supernode minimum height is retunred.
5934
- getMinHeight(node) {
5935
- if (CanvasUtils.isSupernode(node)) {
5936
- const minHt = Math.max(node.inputPortsHeight, node.outputPortsHeight) +
5937
- this.canvasLayout.supernodeTopAreaHeight + this.canvasLayout.supernodeSVGAreaPadding;
5938
- return Math.max(this.canvasLayout.supernodeMinHeight, minHt);
5939
- }
5940
- return node.layout.defaultNodeHeight;
5941
- }
5942
-
5943
- // Returns the minimum allowed width for the node passed in.
5944
- getMinWidth(node) {
5945
- if (CanvasUtils.isSupernode(node)) {
5946
- return this.canvasLayout.supernodeMinWidth;
5947
- }
5948
- return node.layout.defaultNodeWidth;
5949
- }
5950
-
5951
- // Sets the size and position of the node in the canvasInfo.nodes
5952
- // array based on the position of the pointer during the resize action
5953
- // then redraws the nodes and links (the link positions may move based
5954
- // on the node size change).
5955
- resizeNode(d3Event, resizeObj) {
5956
- const oldSupernode = Object.assign({}, resizeObj);
5957
- const minHeight = this.getMinHeight(resizeObj);
5958
- const minWidth = this.getMinWidth(resizeObj);
5959
-
5960
- const delta = this.resizeObject(d3Event, resizeObj,
5961
- this.nodeSizingDirection, minWidth, minHeight);
5962
-
5963
- if (delta && (delta.x_pos !== 0 || delta.y_pos !== 0 || delta.width !== 0 || delta.height !== 0)) {
5964
- if (CanvasUtils.isSupernode(resizeObj) &&
5965
- this.config.enableMoveNodesOnSupernodeResize) {
5966
- const objectsInfo = CanvasUtils.moveSurroundingObjects(
5967
- oldSupernode,
5968
- this.activePipeline.getNodesAndComments(),
5969
- this.nodeSizingDirection,
5970
- resizeObj.width,
5971
- resizeObj.height,
5972
- true // Pass true to indicate that object positions should be updated.
5973
- );
5974
-
5975
- const linksInfo = CanvasUtils.moveSurroundingDetachedLinks(
5976
- oldSupernode,
5977
- this.activePipeline.links,
5978
- this.nodeSizingDirection,
5979
- resizeObj.width,
5980
- resizeObj.height,
5981
- true // Pass true to indicate that link positions should be updated.
5982
- );
5983
-
5984
- // Overwrite the object and link info with any new info.
5985
- this.nodeSizingObjectsInfo = Object.assign(this.nodeSizingObjectsInfo, objectsInfo);
5986
- this.nodeSizingDetLinksInfo = Object.assign(this.nodeSizingDetLinksInfo, linksInfo);
5987
- }
5988
-
5989
- this.logger.logStartTimer("displayObjects");
5990
-
5991
- this.displayMovedComments();
5992
- this.displayMovedNodes();
5993
- this.displaySingleNode(resizeObj);
5994
- this.displayMovedLinks();
5995
- this.displayCanvasAccoutrements();
5996
-
5997
- if (CanvasUtils.isSupernode(resizeObj)) {
5998
- if (this.dispUtils.isDisplayingSubFlow()) {
5999
- this.displayBindingNodesToFitSVG();
6000
- }
6001
- this.superRenderers.forEach((renderer) => renderer.displaySVGToFitSupernode());
6002
- }
6003
- this.logger.logEndTimer("displayObjects");
6004
- }
6005
- }
6006
-
6007
- // Sets the size and position of the comment in the canvasInfo.comments
6008
- // array based on the position of the pointer during the resize action
6009
- // then redraws the comment and links (the link positions may move based
6010
- // on the comment size change).
6011
- resizeComment(d3Event, resizeObj) {
6012
- this.resizeObject(d3Event, resizeObj, this.commentSizingDirection, 20, 20);
6013
- this.displaySingleComment(resizeObj);
6014
- this.displayMovedLinks();
6015
- this.displayCanvasAccoutrements();
6016
- }
6017
-
6018
- // Sets the size and position of the object in the canvasInfo
6019
- // array based on the position of the pointer during the resize action.
6020
- resizeObject(d3Event, canvasObj, direction, minWidth, minHeight) {
6021
- let incrementX = 0;
6022
- let incrementY = 0;
6023
- let incrementWidth = 0;
6024
- let incrementHeight = 0;
6025
-
6026
- if (direction.indexOf("e") > -1) {
6027
- incrementWidth += d3Event.dx;
6028
- }
6029
- if (direction.indexOf("s") > -1) {
6030
- incrementHeight += d3Event.dy;
6031
- }
6032
- if (direction.indexOf("n") > -1) {
6033
- incrementY += d3Event.dy;
6034
- incrementHeight -= d3Event.dy;
6035
- }
6036
- if (direction.indexOf("w") > -1) {
6037
- incrementX += d3Event.dx;
6038
- incrementWidth -= d3Event.dx;
6039
- }
6040
-
6041
- let xPos = 0;
6042
- let yPos = 0;
6043
- let width = 0;
6044
- let height = 0;
6045
-
6046
- if (this.config.enableSnapToGridType === SNAP_TO_GRID_DURING) {
6047
- // Calculate where the object being resized would be and its size given
6048
- // current increments.
6049
- this.notSnappedXPos += incrementX;
6050
- this.notSnappedYPos += incrementY;
6051
- this.notSnappedWidth += incrementWidth;
6052
- this.notSnappedHeight += incrementHeight;
6053
-
6054
- xPos = CanvasUtils.snapToGrid(this.notSnappedXPos, this.canvasLayout.snapToGridXPx);
6055
- yPos = CanvasUtils.snapToGrid(this.notSnappedYPos, this.canvasLayout.snapToGridYPx);
6056
- width = CanvasUtils.snapToGrid(this.notSnappedWidth, this.canvasLayout.snapToGridXPx);
6057
- height = CanvasUtils.snapToGrid(this.notSnappedHeight, this.canvasLayout.snapToGridYPx);
6058
-
6059
- width = Math.max(width, minWidth);
6060
- height = Math.max(height, minHeight);
6061
-
6062
- } else {
6063
- xPos = canvasObj.x_pos + incrementX;
6064
- yPos = canvasObj.y_pos + incrementY;
6065
- width = canvasObj.width + incrementWidth;
6066
- height = canvasObj.height + incrementHeight;
6067
- }
6068
-
6069
- // Don't allow the object area to shrink below the min width and height.
6070
- // For comment sizing, errors may occur especially if the width becomes
6071
- // less that one character's width. For node sizing we want at least some
6072
- // area to display the sub-flow.
6073
- if (width < minWidth || height < minHeight) {
6074
- return null;
6075
- }
6076
-
6077
- const delta = {
6078
- width: width - canvasObj.width,
6079
- height: height - canvasObj.height,
6080
- x_pos: xPos - canvasObj.x_pos,
6081
- y_pos: yPos - canvasObj.y_pos
6082
- };
6083
-
6084
- canvasObj.x_pos = xPos;
6085
- canvasObj.y_pos = yPos;
6086
- canvasObj.width = width;
6087
- canvasObj.height = height;
6088
-
6089
- return delta;
6090
- }
6091
-
6092
- // Finalises the sizing of a node by calling editActionHandler
6093
- // with an editNode action.
6094
- endNodeSizing(node) {
6095
- let resizeObj = node;
6096
- if (this.config.enableSnapToGridType === SNAP_TO_GRID_AFTER) {
6097
- resizeObj = this.snapToGridObject(resizeObj);
6098
- resizeObj = this.restrictNodeSizingToMinimums(resizeObj);
6099
- }
6100
-
6101
- // If the dimensions or position has changed, issue the "resizeObjects" command.
6102
- // Note: x_pos or y_pos might change on resize if the node is sized
6103
- // upwards or to the left.
6104
- if (this.resizeObjInitialInfo.x_pos !== resizeObj.x_pos ||
6105
- this.resizeObjInitialInfo.y_pos !== resizeObj.y_pos ||
6106
- this.resizeObjInitialInfo.width !== resizeObj.width ||
6107
- this.resizeObjInitialInfo.height !== resizeObj.height) {
6108
- // Add the dimensions of the object being resized to the array of object infos.
6109
- this.nodeSizingObjectsInfo[resizeObj.id] = {
6110
- width: resizeObj.width,
6111
- height: resizeObj.height,
6112
- x_pos: resizeObj.x_pos,
6113
- y_pos: resizeObj.y_pos
6114
- };
6115
-
6116
- // If the node has been resized set the resize properties appropriately.
6117
- // We use some padding because sometimes, when a node is sized back to its
6118
- // original dimensions, it isn't retunred to EXACTLY its default width/height.
6119
- if (resizeObj.height > resizeObj.layout.defaultNodeHeight + 2 ||
6120
- resizeObj.width > resizeObj.layout.defaultNodeWidth + 2) {
6121
- this.nodeSizingObjectsInfo[resizeObj.id].isResized = true;
6122
- this.nodeSizingObjectsInfo[resizeObj.id].resizeWidth = resizeObj.width;
6123
- this.nodeSizingObjectsInfo[resizeObj.id].resizeHeight = resizeObj.height;
6124
- }
6125
-
6126
- this.canvasController.editActionHandler({
6127
- editType: "resizeObjects",
6128
- editSource: "canvas",
6129
- objectsInfo: this.nodeSizingObjectsInfo,
6130
- detachedLinksInfo: this.nodeSizingDetLinksInfo,
6131
- pipelineId: this.pipelineId
6132
- });
6133
- }
6134
- this.nodeSizing = false;
6135
- this.nodeSizingObjectsInfo = {};
6136
- this.nodeSizingDetLinksInfo = {};
6137
- }
6138
-
6139
- // Finalises the sizing of a comment by calling editActionHandler
6140
- // with an editComment action.
6141
- endCommentSizing(comment) {
6142
- let resizeObj = comment;
6143
- if (this.config.enableSnapToGridType === SNAP_TO_GRID_AFTER) {
6144
- resizeObj = this.snapToGridObject(resizeObj);
6145
- }
6146
-
6147
- // If the dimensions or position has changed, issue the command.
6148
- // Note: x_pos or y_pos might change on resize if the node is sized
6149
- // upwards or to the left.
6150
- if (this.resizeObjInitialInfo.x_pos !== resizeObj.x_pos ||
6151
- this.resizeObjInitialInfo.y_pos !== resizeObj.y_pos ||
6152
- this.resizeObjInitialInfo.width !== resizeObj.width ||
6153
- this.resizeObjInitialInfo.height !== resizeObj.height) {
6154
- const commentSizingObjectsInfo = [];
6155
- commentSizingObjectsInfo[resizeObj.id] = {
6156
- width: resizeObj.width,
6157
- height: resizeObj.height,
6158
- x_pos: resizeObj.x_pos,
6159
- y_pos: resizeObj.y_pos
6160
- };
6161
-
6162
- const data = {
6163
- editType: "resizeObjects",
6164
- editSource: "canvas",
6165
- objectsInfo: commentSizingObjectsInfo,
6166
- detachedLinksInfo: {}, // Comments cannot have detached links
6167
- pipelineId: this.pipelineId
6168
- };
6169
- this.canvasController.editActionHandler(data);
6170
- }
6171
- this.commentSizing = false;
6172
- }
6173
-
6174
- // Ensure the snap-to-grid does not make the width or height smaller than
6175
- // the minimums allowed.
6176
- restrictNodeSizingToMinimums(resizeObj) {
6177
- const minHeight = this.getMinHeight(resizeObj);
6178
- const minWidth = this.getMinWidth(resizeObj);
6179
- resizeObj.width = Math.max(resizeObj.width, minWidth);
6180
- resizeObj.height = Math.max(resizeObj.height, minHeight);
6181
- return resizeObj;
6182
- }
6183
-
6184
3844
  // Displays all the links on the canvas either by creating new links,
6185
3845
  // updating existing links or removing unwanted links.
6186
3846
  displayLinks() {
@@ -6238,6 +3898,7 @@ export default class SVGCanvasRenderer {
6238
3898
 
6239
3899
  // Creates all newly created links specified in the enter selection.
6240
3900
  createLinks(enter) {
3901
+ this.logger.logStartTimer("createLinks");
6241
3902
  // Add groups for links
6242
3903
  const newLinkGrps = enter.append("g")
6243
3904
  .attr("data-id", (d) => this.getId("link_grp", d.id))
@@ -6281,6 +3942,8 @@ export default class SVGCanvasRenderer {
6281
3942
  });
6282
3943
  }
6283
3944
 
3945
+ this.logger.logEndTimer("createLinks");
3946
+
6284
3947
  return newLinkGrps;
6285
3948
  }
6286
3949
 
@@ -6288,6 +3951,7 @@ export default class SVGCanvasRenderer {
6288
3951
  // selection object. The selection object will contain newly created links
6289
3952
  // as well as existing links.
6290
3953
  updateLinks(joinedLinkGrps, lineArray) {
3954
+ this.logger.logStartTimer("updateLinks");
6291
3955
  // Update link selection area
6292
3956
  joinedLinkGrps
6293
3957
  .selectAll(".d3-link-selection-area")
@@ -6333,9 +3997,11 @@ export default class SVGCanvasRenderer {
6333
3997
  });
6334
3998
  }
6335
3999
 
6336
- if (!this.dragging) {
4000
+ if (!this.isMoving() && !this.isSizing()) {
6337
4001
  this.setDisplayOrder(joinedLinkGrps);
6338
4002
  }
4003
+
4004
+ this.logger.logEndTimer("updateLinks");
6339
4005
  }
6340
4006
 
6341
4007
  attachLinkGroupListeners(linkGrps) {
@@ -6365,6 +4031,10 @@ export default class SVGCanvasRenderer {
6365
4031
  this.openContextMenu(d3Event, "link", d);
6366
4032
  })
6367
4033
  .on("mouseenter", (d3Event, link) => {
4034
+ if (this.isDragging()) {
4035
+ return;
4036
+ }
4037
+
6368
4038
  const targetObj = d3Event.currentTarget;
6369
4039
 
6370
4040
  if (this.config.enableLinkSelection === LINK_SELECTION_HANDLES ||
@@ -6377,8 +4047,7 @@ export default class SVGCanvasRenderer {
6377
4047
  this.addContextToolbar(d3Event, link, "link");
6378
4048
  }
6379
4049
 
6380
- if (this.canOpenTip(TIP_TYPE_LINK) &&
6381
- !this.draggingLinkData) {
4050
+ if (this.canOpenTip(TIP_TYPE_LINK)) {
6382
4051
  this.canvasController.closeTip();
6383
4052
  this.canvasController.openTip({
6384
4053
  id: this.getId("link_tip", link.id),
@@ -6444,7 +4113,8 @@ export default class SVGCanvasRenderer {
6444
4113
 
6445
4114
  // Add or remove drag behavior as appropriate
6446
4115
  if (this.config.enableEditingActions) {
6447
- startHandle.call(this.dragLinkHandler);
4116
+ const handler = this.dragDetLinkUtils.getDragDetachedLinkHandler();
4117
+ startHandle.call(handler);
6448
4118
  } else {
6449
4119
  startHandle.on(".drag", null);
6450
4120
  }
@@ -6473,7 +4143,8 @@ export default class SVGCanvasRenderer {
6473
4143
 
6474
4144
  // Add or remove drag behavior as appropriate
6475
4145
  if (this.config.enableEditingActions) {
6476
- endHandle.call(this.dragLinkHandler);
4146
+ const handler = this.dragDetLinkUtils.getDragDetachedLinkHandler();
4147
+ endHandle.call(handler);
6477
4148
  } else {
6478
4149
  endHandle.on(".drag", null);
6479
4150
  }
@@ -6623,22 +4294,26 @@ export default class SVGCanvasRenderer {
6623
4294
  }
6624
4295
  }
6625
4296
 
4297
+ // Raises the node, specified by the node ID, above other nodes and objects.
4298
+ // Called by external utils.
4299
+ raiseNodeToTopById(nodeId) {
4300
+ this.getNodeGroupSelectionById(nodeId).raise();
4301
+ }
4302
+
6626
4303
  // Raises the node above other nodes and objects (on the mouse entering
6627
4304
  // the node). This is necessary for apps that have ports that protrude from
6628
4305
  // the side of the node and where those nodes may be positioned close to each
6629
4306
  // other so it makes the ports appear on top of any adjacent node. We don't
6630
4307
  // raise the nodes for various conditions:
6631
4308
  // * The enableRaiseNodesToTopOnHover config option is set to false
6632
- // * We are currently drawing a new link
6633
- // * We are dragging some object(s) around
4309
+ // * We are currently dragging to create a new link, or to move objects or detached links
6634
4310
  // * There are one or more selected links
6635
4311
  // * We are editing text
6636
4312
  raiseNodeToTop(nodeGrp) {
6637
4313
  if (this.config.enableRaiseNodesToTopOnHover &&
6638
- this.drawingNewLinkData === null &&
6639
- !this.dragging &&
6640
- this.activePipeline.getSelectedLinksCount() === 0 &&
6641
- !this.isEditingText()) {
4314
+ !this.isDragging() &&
4315
+ this.activePipeline.getSelectedLinksCount() === 0 &&
4316
+ !this.isEditingText()) {
6642
4317
  nodeGrp.raise();
6643
4318
  }
6644
4319
  }
@@ -6667,11 +4342,9 @@ export default class SVGCanvasRenderer {
6667
4342
  return link.decorations && link.decorations.length > 0;
6668
4343
  }
6669
4344
 
6670
- isLinkBeingDragged(link) {
6671
- return this.draggingLinkData && this.draggingLinkData.link.id === link.id;
6672
- }
6673
-
6674
4345
  buildLinksArray() {
4346
+ this.logger.logStartTimer("buildLinksArray");
4347
+
6675
4348
  let linksArray = [];
6676
4349
 
6677
4350
  if (this.canvasLayout.linkType === LINK_TYPE_STRAIGHT) {
@@ -6681,8 +4354,9 @@ export default class SVGCanvasRenderer {
6681
4354
  this.activePipeline.links.forEach((link) => {
6682
4355
  let linkObj = null;
6683
4356
 
6684
- if (((this.config.enableLinkSelection === LINK_SELECTION_HANDLES && this.isLinkBeingDragged(link)) ||
6685
- this.config.enableLinkSelection === LINK_SELECTION_DETACHABLE) &&
4357
+ if (((this.config.enableLinkSelection === LINK_SELECTION_HANDLES &&
4358
+ this.dragDetLinkUtils.isLinkBeingDragged(link)) ||
4359
+ this.config.enableLinkSelection === LINK_SELECTION_DETACHABLE) &&
6686
4360
  (!link.srcObj || !link.trgNode)) {
6687
4361
  linkObj = this.getDetachedLineObj(link);
6688
4362
 
@@ -6703,6 +4377,8 @@ export default class SVGCanvasRenderer {
6703
4377
  // Add connection path info to the links.
6704
4378
  linksArray = this.linkUtils.addConnectionPaths(linksArray);
6705
4379
 
4380
+ this.logger.logEndTimer("buildLinksArray");
4381
+
6706
4382
  return linksArray;
6707
4383
  }
6708
4384
 
@@ -7258,24 +4934,24 @@ export default class SVGCanvasRenderer {
7258
4934
 
7259
4935
  canOpenTip(tipType) {
7260
4936
  return this.canvasController.isTipEnabled(tipType) &&
7261
- !this.regionSelect && !this.dragging &&
7262
- !this.commentSizing && !this.nodeSizing && !this.drawingNewLinkData;
4937
+ !this.regionSelect && !this.isDragging() && !this.isSizing();
7263
4938
  }
7264
4939
 
7265
- // Return the x,y coordinates of the svg group relative to the window's viewport
7266
- // This is used when a new comment is created from the toolbar to make sure the
7267
- // new comment always appears in the view port.
4940
+ // Return the x,y coordinates for the default position of a new comment
4941
+ // created from the toolbar. This make sure the new comment always appears
4942
+ // in the top left corner of the view port.
7268
4943
  getDefaultCommentOffset() {
7269
4944
  let xPos = this.canvasLayout.addCommentOffsetX;
7270
4945
  let yPos = this.canvasLayout.addCommentOffsetY;
4946
+ const z = this.zoomUtils.getZoomTransform();
7271
4947
 
7272
- if (this.zoomTransform) {
7273
- xPos = this.zoomTransform.x / this.zoomTransform.k;
7274
- yPos = this.zoomTransform.y / this.zoomTransform.k;
4948
+ if (z) {
4949
+ const xPanByScale = z.x / z.k;
4950
+ const yPanByScale = z.y / z.k;
7275
4951
 
7276
- // The window's viewport is in the opposite direction of zoomTransform
7277
- xPos = -xPos + this.canvasLayout.addCommentOffsetX;
7278
- yPos = -yPos + this.canvasLayout.addCommentOffsetY;
4952
+ // Offset in the negative direction.
4953
+ xPos = -xPanByScale + this.canvasLayout.addCommentOffsetX;
4954
+ yPos = -yPanByScale + this.canvasLayout.addCommentOffsetY;
7279
4955
  }
7280
4956
 
7281
4957
  if (this.config.enableSnapToGridType === SNAP_TO_GRID_DURING ||
@@ -7288,14 +4964,11 @@ export default class SVGCanvasRenderer {
7288
4964
  // Returns a string that explains which flags are set to true.
7289
4965
  getFlags() {
7290
4966
  let str = "Flags:";
7291
- if (this.dragging) {
4967
+ if (this.isDragging()) {
7292
4968
  str += " dragging = true";
7293
4969
  }
7294
- if (this.nodeSizing) {
7295
- str += " nodeSizing = true";
7296
- }
7297
- if (this.commentSizing) {
7298
- str += " commentSizing = true";
4970
+ if (this.isSizing()) {
4971
+ str += " sizing = true";
7299
4972
  }
7300
4973
  if (this.regionSelect) {
7301
4974
  str += " regionSelect = true";