@processmaker/modeler 1.39.18 → 1.39.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@processmaker/modeler",
3
- "version": "1.39.18",
3
+ "version": "1.39.20",
4
4
  "scripts": {
5
5
  "serve": "vue-cli-service serve --mode development",
6
6
  "test:unit": "vue-cli-service test:unit",
@@ -137,6 +137,20 @@ export default {
137
137
  store.commit('updateNodeProp', { node: this.node, key: 'color', value: color });
138
138
  Vue.set(this.node.definition, 'color', color);
139
139
  this.$emit('save-state');
140
+
141
+ const properties = {
142
+ id: this.node.definition.id,
143
+ properties: {
144
+ color: this.node.definition.color,
145
+ x: this.node.diagram.bounds.x,
146
+ y: this.node.diagram.bounds.y,
147
+ height: this.node.diagram.bounds.height,
148
+ width: this.node.diagram.bounds.width,
149
+ type: this.node.type,
150
+ id: this.node.definition.id,
151
+ },
152
+ };
153
+ window.ProcessMaker.EventBus.$emit('multiplayer-updateNodes', [properties]);
140
154
  },
141
155
  resetNodeColor() {
142
156
  store.commit('updateNodeProp', { node: this.node, key: 'color', value: undefined });
@@ -189,7 +189,7 @@ import getValidationProperties from '@/targetValidationUtils';
189
189
  import { id as laneId } from '@/components/nodes/poolLane/config';
190
190
  import { id as processId } from '@/components/inspectors/process';
191
191
  import { id as sequenceFlowId } from '../nodes/sequenceFlow';
192
- import { id as associationId } from '../nodes/association';
192
+ import { id as associationId } from '../nodes/association/associationConfig';
193
193
  import { id as messageFlowId } from '../nodes/messageFlow/config';
194
194
  import { id as dataOutputAssociationFlowId } from '../nodes/dataOutputAssociation/config';
195
195
  import { id as dataInputAssociationFlowId } from '../nodes/dataInputAssociation/config';
@@ -332,6 +332,13 @@ export default {
332
332
  previewConfigs: [],
333
333
  multiplayer: null,
334
334
  isMultiplayer: false,
335
+ flowTypes: [
336
+ 'processmaker-modeler-sequence-flow',
337
+ 'processmaker-modeler-message-flow',
338
+ 'processmaker-modeler-data-input-association',
339
+ 'processmaker-modeler-data-output-association',
340
+ 'processmaker-modeler-association',
341
+ ],
335
342
  };
336
343
  },
337
344
  watch: {
@@ -1052,7 +1059,7 @@ export default {
1052
1059
  this.validateBpmnDiagram();
1053
1060
  }
1054
1061
  },
1055
-
1062
+
1056
1063
  async handleDrop(data) {
1057
1064
  const { clientX, clientY, control, nodeThatWillBeReplaced } = data;
1058
1065
  this.validateDropTarget({ clientX, clientY, control });
@@ -1112,17 +1119,15 @@ export default {
1112
1119
  'processmaker-modeler-sequence-flow',
1113
1120
  'processmaker-modeler-association',
1114
1121
  'processmaker-modeler-data-input-association',
1115
- 'processmaker-modeler-data-input-association',
1116
- ];
1117
- const flowTypes = [
1118
- 'processmaker-modeler-sequence-flow',
1119
- 'processmaker-modeler-message-flow',
1122
+ 'processmaker-modeler-data-output-association',
1120
1123
  ];
1124
+
1121
1125
  if (!this.isMultiplayer) {
1122
1126
  return;
1123
1127
  }
1128
+
1124
1129
  if (!fromClient) {
1125
- if (!blackList.includes(node.type) && !flowTypes.includes(node.type)) {
1130
+ if (!blackList.includes(node.type) && !this.flowTypes.includes(node.type)) {
1126
1131
  const defaultData = {
1127
1132
  x: node.diagram.bounds.x,
1128
1133
  y: node.diagram.bounds.y,
@@ -1131,20 +1136,31 @@ export default {
1131
1136
  type: node.type,
1132
1137
  id: node.definition.id,
1133
1138
  isAddingLaneAbove: true,
1139
+ color: node.color,
1134
1140
  };
1135
1141
  if (node?.pool?.component) {
1136
1142
  defaultData['poolId'] = node.pool.component.id;
1137
1143
  }
1138
1144
  window.ProcessMaker.EventBus.$emit('multiplayer-addNode', defaultData);
1139
1145
  }
1140
- if (flowTypes.includes(node.type)) {
1141
- window.ProcessMaker.EventBus.$emit('multiplayer-addFlow', {
1142
- id: node.definition.id,
1143
- type: node.type,
1144
- sourceRefId: node.definition.sourceRef.id,
1145
- targetRefId: node.definition.targetRef.id,
1146
- waypoint: node.diagram.waypoint,
1147
- });
1146
+ if (this.flowTypes.includes(node.type)) {
1147
+ let sourceRefId = node.definition.sourceRef?.id;
1148
+ let targetRefId = node.definition.targetRef?.id;
1149
+
1150
+ if (node.type === 'processmaker-modeler-data-input-association') {
1151
+ sourceRefId = Array.isArray(node.definition.sourceRef) && node.definition.sourceRef[0]?.id;
1152
+ targetRefId = node.definition.targetRef?.$parent?.$parent.get('id');
1153
+ }
1154
+
1155
+ if (sourceRefId && targetRefId) {
1156
+ window.ProcessMaker.EventBus.$emit('multiplayer-addFlow', {
1157
+ id: node.definition.id,
1158
+ type: node.type,
1159
+ sourceRefId,
1160
+ targetRefId,
1161
+ waypoint: node.diagram.waypoint,
1162
+ });
1163
+ }
1148
1164
  }
1149
1165
  }
1150
1166
  },
@@ -1338,9 +1354,10 @@ export default {
1338
1354
  await this.paperManager.performAtomicAction(async() => {
1339
1355
  await this.highlightNode(null);
1340
1356
  await this.$nextTick();
1341
- await this.addNode(actualFlow);
1342
1357
  await store.commit('removeNode', genericFlow);
1343
1358
  await this.$nextTick();
1359
+ await this.addNode(actualFlow, genericFlow.definition.id);
1360
+ await this.$nextTick();
1344
1361
  await this.highlightNode(targetNode);
1345
1362
  });
1346
1363
  });
@@ -31,7 +31,7 @@ import { id as poolId } from '@/components/nodes/pool/config';
31
31
  import { id as laneId } from '@/components/nodes/poolLane/config';
32
32
  import { id as genericFlowId } from '@/components/nodes/genericFlow/config';
33
33
  import { id as sequenceFlowId } from '@/components/nodes/sequenceFlow';
34
- import { id as associationId } from '@/components/nodes/association';
34
+ import { id as associationId } from '@/components/nodes/association/associationConfig';
35
35
  import { id as messageFlowId } from '@/components/nodes/messageFlow/config';
36
36
  import { id as dataOutputAssociationFlowId } from '@/components/nodes/dataOutputAssociation/config';
37
37
  import { id as dataInputAssociationFlowId } from '@/components/nodes/dataInputAssociation/config';
@@ -577,7 +577,7 @@ export default {
577
577
  await this.paperManager.awaitScheduledUpdates();
578
578
  this.overPoolStopDrag();
579
579
  this.updateSelectionBox();
580
- if (this.isMultiplayer) {
580
+ if (this.isMultiplayer) {
581
581
  window.ProcessMaker.EventBus.$emit('multiplayer-updateNodes', this.getProperties(this.selected));
582
582
  }
583
583
  },
@@ -604,6 +604,7 @@ export default {
604
604
  y: shape.model.get('position').y,
605
605
  height: shape.model.get('size').height,
606
606
  width: shape.model.get('size').width,
607
+ color: shape.model.get('color'),
607
608
  },
608
609
  };
609
610
  if (node?.pool?.component) {
@@ -624,6 +625,7 @@ export default {
624
625
  y: model.get('position').y,
625
626
  height: model.get('size').height,
626
627
  width: model.get('size').width,
628
+ color: model.get('color'),
627
629
  },
628
630
  });
629
631
  });
@@ -0,0 +1,27 @@
1
+ import Node from '@/components/nodes/node';
2
+ import * as associationConfig from './associationConfig';
3
+
4
+ export class AssociationFlow {
5
+ constructor(nodeRegistry, moddle, paper) {
6
+ this.nodeRegistry = nodeRegistry;
7
+ this.moddle = moddle;
8
+ this.paper = paper;
9
+ }
10
+
11
+ makeFlowNode(sourceShape, targetShape, waypoint) {
12
+ const diagram = associationConfig.diagram(this.moddle);
13
+ const associationFlow = associationConfig.definition(this.moddle);
14
+
15
+ associationFlow.set('sourceRef', sourceShape.component.node.definition);
16
+ associationFlow.set('targetRef', targetShape.component.node.definition);
17
+
18
+ diagram.waypoint = waypoint.map(point => this.moddle.create('dc:Point', point));
19
+
20
+ const node = new Node(
21
+ associationConfig.id,
22
+ associationFlow,
23
+ diagram,
24
+ );
25
+ return node;
26
+ }
27
+ }
@@ -1 +1,16 @@
1
+ export const id = 'processmaker-modeler-association';
2
+
3
+ export const bpmnType = 'bpmn:Association';
4
+
1
5
  export const direction = { none: 'None', one: 'One', both: 'Both' };
6
+
7
+ export function definition(moddle) {
8
+ return moddle.create(bpmnType, {
9
+ associationDirection: direction.none,
10
+ targetRef: { x: undefined, y: undefined },
11
+ });
12
+ }
13
+
14
+ export function diagram(moddle) {
15
+ return moddle.create('bpmndi:BPMNEdge');
16
+ }
@@ -1,22 +1,16 @@
1
+ import { getNodeIdGenerator } from '@/NodeIdGenerator';
1
2
  import component from './association.vue';
2
- import { direction } from './associationConfig';
3
+ import { id, bpmnType, direction, definition, diagram } from './associationConfig';
3
4
  import idConfigSettings from '@/components/inspectors/idConfigSettings';
4
-
5
- export const id = 'processmaker-modeler-association';
5
+ import { AssociationFlow } from './AssociationFlow';
6
6
 
7
7
  export default {
8
8
  id,
9
9
  component,
10
- bpmnType: 'bpmn:Association',
10
+ bpmnType,
11
11
  control: false,
12
- definition(moddle) {
13
- return moddle.create('bpmn:Association', {
14
- associationDirection: `${direction.none}`,
15
- });
16
- },
17
- diagram(moddle) {
18
- return moddle.create('bpmndi:BPMNEdge');
19
- },
12
+ definition,
13
+ diagram,
20
14
  inspectorConfig: [
21
15
  {
22
16
  name: 'Data Association',
@@ -53,4 +47,17 @@ export default {
53
47
  ],
54
48
  },
55
49
  ],
50
+ async multiplayerClient(modeler, data) {
51
+ const { paper } = modeler;
52
+ const sourceElem = modeler.getElementByNodeId(data.sourceRefId);
53
+ const targetElem = modeler.getElementByNodeId(data.targetRefId);
54
+ if (sourceElem && targetElem) {
55
+ const flow = new AssociationFlow(modeler.nodeRegistry, modeler.moddle, paper);
56
+ const actualFlow = flow.makeFlowNode(sourceElem, targetElem, data.waypoint);
57
+ // add Nodes
58
+ modeler.addNode(actualFlow, data.id, true);
59
+ const nodeIdIterator = getNodeIdGenerator(modeler.definitions);
60
+ nodeIdIterator.updateCounters();
61
+ }
62
+ },
56
63
  };
@@ -0,0 +1,30 @@
1
+ import Node from '@/components/nodes/node';
2
+ import * as associationConfig from './config';
3
+ import { getOrFindDataInput } from '@/components/crown/utils';
4
+
5
+ export class DataInputAssociation {
6
+ constructor(nodeRegistry, moddle, paper) {
7
+ this.nodeRegistry = nodeRegistry;
8
+ this.moddle = moddle;
9
+ this.paper = paper;
10
+ }
11
+
12
+ makeFlowNode(sourceShape, targetShape, waypoint) {
13
+ const diagram = associationConfig.diagram(this.moddle);
14
+ const associationFlow = associationConfig.definition(this.moddle);
15
+
16
+ // When saving the BPMN, if this is not an array the sourceRef is not stored
17
+ const dataInput = getOrFindDataInput(this.moddle, targetShape.component.node, sourceShape.component.node.definition);
18
+ associationFlow.set('targetRef', dataInput);
19
+ associationFlow.set('sourceRef', [sourceShape.component.node.definition]);
20
+
21
+ diagram.waypoint = waypoint.map(point => this.moddle.create('dc:Point', point));
22
+
23
+ const node = new Node(
24
+ associationConfig.id,
25
+ associationFlow,
26
+ diagram,
27
+ );
28
+ return node;
29
+ }
30
+ }
@@ -1,6 +1,8 @@
1
1
  import component from './dataInputAssociation.vue';
2
2
  import idConfigSettings from '@/components/inspectors/idConfigSettings';
3
3
  import * as config from './config';
4
+ import { DataInputAssociation } from './DataInputAssociation';
5
+ import { getNodeIdGenerator } from '@/NodeIdGenerator';
4
6
 
5
7
  export default {
6
8
  ...config,
@@ -29,4 +31,19 @@ export default {
29
31
  ],
30
32
  },
31
33
  ],
34
+ async multiplayerClient(modeler, data) {
35
+ const { paper } = modeler;
36
+ const sourceElem = modeler.getElementByNodeId(data.sourceRefId);
37
+ const targetElem = modeler.getElementByNodeId(data.targetRefId);
38
+ if (sourceElem && targetElem) {
39
+ const flow = new DataInputAssociation(modeler.nodeRegistry, modeler.moddle, paper);
40
+ const actualFlow = flow.makeFlowNode(sourceElem, targetElem, data.waypoint);
41
+
42
+ targetElem.component.node.definition.set('dataInputAssociations', [actualFlow.definition]);
43
+ // add Nodes
44
+ modeler.addNode(actualFlow, data.id, true);
45
+ const nodeIdIterator = getNodeIdGenerator(modeler.definitions);
46
+ nodeIdIterator.updateCounters();
47
+ }
48
+ },
32
49
  };
@@ -1,6 +1,8 @@
1
1
  import component from './dataOutputAssociation.vue';
2
2
  import idConfigSettings from '@/components/inspectors/idConfigSettings';
3
3
  import * as config from './config';
4
+ import { getNodeIdGenerator } from '@/NodeIdGenerator';
5
+ import DataOutputAssociation from '../genericFlow/DataOutputAssociation';
4
6
 
5
7
  export default {
6
8
  ...config,
@@ -29,4 +31,17 @@ export default {
29
31
  ],
30
32
  },
31
33
  ],
34
+ async multiplayerClient(modeler, data) {
35
+ const { paper } = modeler;
36
+ const sourceElem = modeler.getElementByNodeId(data.sourceRefId);
37
+ const targetElem = modeler.getElementByNodeId(data.targetRefId);
38
+ if (sourceElem && targetElem) {
39
+ const flow = new DataOutputAssociation(modeler.nodeRegistry, modeler.moddle, paper);
40
+ const actualFlow = flow.makeFlowNode(sourceElem, targetElem, data.waypoint);
41
+ // add Nodes
42
+ modeler.addNode(actualFlow, data.id, true);
43
+ const nodeIdIterator = getNodeIdGenerator(modeler.definitions);
44
+ nodeIdIterator.updateCounters();
45
+ }
46
+ },
32
47
  };
@@ -55,7 +55,7 @@ export default class DataOutputAssociation extends DataAssociation {
55
55
  const sourceIsDataObject = sourceNode.definition.$type === 'bpmn:DataObjectReference';
56
56
  const targetIsDataStore = targetNode.definition.$type === 'bpmn:DataStoreReference';
57
57
  const targetIsDataObject = targetNode.definition.$type === 'bpmn:DataObjectReference';
58
-
58
+
59
59
  if (sourceIsDataStore && dataStoreValidTargets.includes(targetType)) {
60
60
  return true;
61
61
  }
@@ -109,6 +109,7 @@ export default {
109
109
  const flow = new bpmnFlow.factory(this.nodeRegistry, this.moddle, this.paper);
110
110
  const genericLink = this.shape.findView(this.paper);
111
111
  const waypoint = [genericLink.sourceAnchor.toJSON(), genericLink.targetAnchor.toJSON()];
112
+
112
113
  this.$emit('replace-generic-flow', {
113
114
  actualFlow: flow.makeFlowNode(this.sourceShape, this.target, waypoint),
114
115
  genericFlow: this.node,
@@ -1,6 +1,6 @@
1
1
  import { id as laneId } from '../components/nodes/poolLane/config';
2
2
  import { id as sequenceFlowId } from '../components/nodes/sequenceFlow';
3
- import { id as associationId } from '../components/nodes/association';
3
+ import { id as associationId } from '../components/nodes/association/associationConfig';
4
4
  import { id as messageFlowId } from '../components/nodes/messageFlow/config';
5
5
  import { id as dataOutputAssociationFlowId } from '../components/nodes/dataOutputAssociation/config';
6
6
  import { id as dataInputAssociationFlowId } from '../components/nodes/dataInputAssociation/config';
@@ -23,6 +23,7 @@ export default {
23
23
  props: ['highlighted', 'paper', 'paperManager', 'isCompleted', 'isIdle'],
24
24
  data() {
25
25
  return {
26
+ linkView: null,
26
27
  sourceShape: null,
27
28
  target: null,
28
29
  listeningToMouseup: false,
@@ -163,6 +164,31 @@ export default {
163
164
  this.updateWaypoints();
164
165
  await this.$nextTick();
165
166
 
167
+ if (this.$parent.isMultiplayer && this.linkView) {
168
+ // update waypoints in multiplayer mode
169
+ const nodeType = this.linkView.model.component.node.type;
170
+ const sourceRefId = this.linkView.sourceView.model.component.node.definition.id;
171
+ const targetRefId = this.linkView.targetView.model.component.node.definition.id;
172
+
173
+ const changes = [
174
+ {
175
+ id: this.linkView.model.component.node.definition.id,
176
+ properties: {
177
+ type: nodeType,
178
+ waypoint: [
179
+ this.linkView.sourceAnchor.toJSON(),
180
+ ...this.shape.vertices(),
181
+ this.linkView.targetAnchor.toJSON(),
182
+ ],
183
+ sourceRefId,
184
+ targetRefId,
185
+ },
186
+ },
187
+ ];
188
+
189
+ window.ProcessMaker.EventBus.$emit('multiplayer-updateNodes', changes);
190
+ }
191
+
166
192
  this.listeningToMouseleave = true;
167
193
  this.$emit('save-state');
168
194
  }
@@ -174,14 +200,15 @@ export default {
174
200
  * @param {Object} options
175
201
  */
176
202
  async onChangeTargets(link, vertices, options){
177
- if (options && options.ui) {
203
+ if (options?.ui) {
178
204
  await this.$nextTick();
179
205
  await this.waitForUpdateWaypoints();
206
+ this.listeningToMouseleave = false;
180
207
  await this.storeWaypoints();
181
208
  }
182
209
  },
183
210
  async onChangeVertices(link, vertices, options){
184
- if (options && options.ui) {
211
+ if (options?.ui) {
185
212
  this.updateWaypoints();
186
213
  await this.$nextTick();
187
214
  this.listeningToMouseleave = false;
@@ -189,9 +216,9 @@ export default {
189
216
  }
190
217
  },
191
218
  updateWaypoints() {
192
- const linkView = this.shape.findView(this.paper);
193
- const start = linkView.sourceAnchor;
194
- const end = linkView.targetAnchor;
219
+ this.linkView = this.shape.findView(this.paper);
220
+ const start = this.linkView.sourceAnchor;
221
+ const end = this.linkView.targetAnchor;
195
222
 
196
223
  this.node.diagram.waypoint = [start,
197
224
  ...this.shape.vertices(),
@@ -239,6 +266,11 @@ export default {
239
266
  if (this.updateDefinitionLinks) {
240
267
  this.updateDefinitionLinks();
241
268
  }
269
+
270
+ if (this.linkView && ['processmaker-modeler-association', 'processmaker-modeler-data-input-association'].includes(this.shape.component.node.type)) {
271
+ this.$parent.multiplayerHook(this.shape.component.node, false);
272
+ }
273
+
242
274
  this.$emit('save-state');
243
275
  });
244
276
 
@@ -1,7 +1,9 @@
1
1
  import { io } from 'socket.io-client';
2
2
  import * as Y from 'yjs';
3
3
  import { getNodeIdGenerator } from '../NodeIdGenerator';
4
+ import { getDefaultAnchorPoint } from '@/portsUtils';
4
5
  import Room from './room';
6
+ import store from '@/store';
5
7
  export default class Multiplayer {
6
8
  clientIO = null;
7
9
  yDoc = null;
@@ -142,12 +144,12 @@ export default class Multiplayer {
142
144
  this.modeler.addRemoteNode(value);
143
145
  }
144
146
  this.#nodeIdGenerator.updateCounters();
145
-
147
+
146
148
  }
147
149
  createRemoteShape(changes) {
148
150
  return new Promise(resolve => {
149
151
  changes.map((data) => {
150
- this.createShape(data);
152
+ this.createShape(data);
151
153
  });
152
154
  resolve();
153
155
  });
@@ -227,6 +229,7 @@ export default class Multiplayer {
227
229
  clientY,
228
230
  control: { type: updatedNode.type },
229
231
  nodeThatWillBeReplaced: node,
232
+ color: node.color,
230
233
  id: updatedNode.id,
231
234
  };
232
235
 
@@ -252,23 +255,59 @@ export default class Multiplayer {
252
255
  const { paper } = this.modeler;
253
256
  const element = this.modeler.getElementByNodeId(data.id);
254
257
  const newPool = this.modeler.getElementByNodeId(data.poolId);
255
- // Update the element's position attribute
256
- element.resize(
257
- /* Add labelWidth to ensure elements don't overlap with the pool label */
258
- data.width,
259
- data.height,
260
- );
261
- element.set('position', { x: data.x, y: data.y });
262
- // Trigger a rendering of the element on the paper
263
- await paper.findViewByModel(element).update();
264
- // validate if the parent pool was updated
265
- await element.component.$nextTick();
266
- await this.modeler.paperManager.awaitScheduledUpdates();
267
- if (newPool && element.component.node.pool && element.component.node.pool.component.id !== data.poolId) {
268
- element.component.node.pool.component.moveElementRemote(element, newPool);
258
+
259
+ if (this.modeler.flowTypes.includes(data.type)) {
260
+ // Update the element's waypoints
261
+ // Get the source and target elements
262
+ const sourceElem = this.modeler.getElementByNodeId(data.sourceRefId);
263
+ const targetElem = this.modeler.getElementByNodeId(data.targetRefId);
264
+
265
+ const { waypoint } = data;
266
+ const startWaypoint = waypoint.shift();
267
+ const endWaypoint = waypoint.pop();
268
+
269
+ // Update the element's waypoints
270
+ const newWaypoint = waypoint.map(point => this.modeler.moddle.create('dc:Point', point));
271
+ element.set('vertices', newWaypoint);
272
+
273
+ // Update the element's source anchor
274
+ element.source(sourceElem, {
275
+ anchor: () => {
276
+ return getDefaultAnchorPoint(this.getConnectionPoint(sourceElem, startWaypoint), sourceElem.findView(paper));
277
+ },
278
+ connectionPoint: { name: 'boundary' },
279
+ });
280
+
281
+ // Update the element's target anchor
282
+ element.target(targetElem, {
283
+ anchor: () => {
284
+ return getDefaultAnchorPoint(this.getConnectionPoint(targetElem, endWaypoint), targetElem.findView(paper));
285
+ },
286
+ connectionPoint: { name: 'boundary' },
287
+ });
288
+ } else {
289
+ // Update the element's position attribute
290
+ element.resize(
291
+ /* Add labelWidth to ensure elements don't overlap with the pool label */
292
+ data.width,
293
+ data.height,
294
+ );
295
+ element.set('position', { x: data.x, y: data.y });
296
+
297
+ const node = this.getNodeById(data.id);
298
+ store.commit('updateNodeProp', { node, key: 'color', value: data.color });
299
+
300
+ // Trigger a rendering of the element on the paper
301
+ await paper.findViewByModel(element).update();
302
+ // validate if the parent pool was updated
303
+ await element.component.$nextTick();
304
+ await this.modeler.paperManager.awaitScheduledUpdates();
305
+ if (newPool && element.component.node.pool && element.component.node.pool.component.id !== data.poolId) {
306
+ element.component.node.pool.component.moveElementRemote(element, newPool);
307
+ }
269
308
  }
270
309
  }
271
-
310
+
272
311
  addFlow(data) {
273
312
  const yMapNested = new Y.Map();
274
313
  this.doTransact(yMapNested, data);
@@ -320,8 +359,21 @@ export default class Multiplayer {
320
359
  getPool(lanes) {
321
360
  if (lanes && lanes.length > 0) {
322
361
  return lanes[0].pool;
323
- }
362
+ }
324
363
  return false;
325
364
  }
365
+ getConnectionPoint(element, newPosition) {
366
+ const { x: elemX, y: elemY } = element.position();
367
+ const connectionOffset = {
368
+ x: newPosition.x - elemX,
369
+ y: newPosition.y - elemY,
370
+ };
326
371
 
372
+ const { x, y } = element.position();
373
+ const { width, height } = element.size();
374
+
375
+ return connectionOffset
376
+ ? { x: x + connectionOffset.x, y: y + connectionOffset.y }
377
+ : { x: x + (width / 2), y: y + (height / 2) };
378
+ }
327
379
  }