@processmaker/modeler 1.39.31 → 1.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@processmaker/modeler",
3
- "version": "1.39.31",
3
+ "version": "1.40.0",
4
4
  "scripts": {
5
5
  "serve": "vue-cli-service serve --mode development",
6
6
  "test:unit": "vue-cli-service test:unit",
@@ -1,65 +1,71 @@
1
1
  <template>
2
- <b-col
3
- v-show="visible"
4
- id="preview_panel"
5
- class="pl-0 h-100 overflow-hidden preview-column"
6
- :style="{ maxWidth: width + 'px' }"
7
- @mousedown="onMouseDown"
2
+ <b-row ref="resizableDiv"
8
3
  @mouseup="onMouseUp"
9
4
  @mousemove="onMouseMove"
10
- data-test="preview-panel"
5
+ v-show="visible"
11
6
  >
12
- <b-row class="control-bar">
13
- <b-col cols="9">
14
- <div>
15
- <i v-show = "selectedPreview == 1" class="fas fa-file-alt"/>
16
- <b v-show = "selectedPreview == 2"> {} </b>
17
- <b-dropdown
18
- variant="ellipsis"
19
- no-caret
20
- no-flip
21
- lazy
22
- class="dropdown-right"
23
- style="margin-top:-10px"
24
- v-model="selectedPreview"
25
- >
26
- <template #button-content>
27
- <i class="fas fa-sort-down" />
28
- </template>
29
-
30
- <b-dropdown-item key="1" class="ellipsis-dropdown-item mx-auto" @click="onSelectedPreview(1)">
31
- <div class="ellipsis-dropdown-content">
32
- <b class="pr-1 fa-fw fas fa-file-alt" />
33
- <span>{{ $t('Document') }}</span>
34
- </div>
35
- </b-dropdown-item>
36
-
37
- </b-dropdown>
38
- <span>{{ $t('Preview') }} - {{ taskTitle }}</span>
39
- </div>
40
- </b-col>
41
- <b-col class="actions">
42
- <div>
7
+ <b-col class="col-auto p-0 resizer-column" @mousedown="onMouseDown" />
8
+ <b-col
9
+ class="pl-0 h-100 overflow-hidden preview-column"
10
+ :style="{ width: panelWidth + 'px' }"
11
+ data-test="preview-panel"
12
+ >
13
+ <b-row class="control-bar">
14
+ <b-col cols="9">
15
+ <div>
16
+ <i v-show = "selectedPreview === 1" class="fas fa-file-alt"/>
17
+ <b v-show = "selectedPreview === 2"> {} </b>
18
+ <b-dropdown
19
+ variant="ellipsis"
20
+ no-caret
21
+ no-flip
22
+ lazy
23
+ class="preview-type-dropdown"
24
+ v-model="selectedPreview"
25
+ >
26
+ <template #button-content>
27
+ <i class="fas fa-sort-down" />
28
+ </template>
29
+
30
+ <b-dropdown-item key="1" class="ellipsis-dropdown-item mx-auto" @click="onSelectedPreview(1)">
31
+ <div class="ellipsis-dropdown-content">
32
+ <b class="pr-1 fa-fw fas fa-file-alt" />
33
+ <span>{{ $t('Document') }}</span>
34
+ </div>
35
+ </b-dropdown-item>
36
+
37
+ </b-dropdown>
38
+ <span>{{ $t('Preview') }} - {{ taskTitle }}</span>
39
+ </div>
40
+ </b-col>
41
+ <b-col class="actions">
43
42
  <i class="fas fa-external-link-alt" v-show="previewUrl" @click="openAsset()"/>
44
43
  <i class="fas fa-times" @click="onClose()" />
45
- </div>
46
- </b-col>
47
- </b-row>
44
+ </b-col>
45
+ </b-row>
46
+
47
+ <b-row class="divider"/>
48
48
 
49
- <b-row>
50
- <div style="background-color: #0074D9; height: 20px; width: 100%">&nbsp;</div>
51
- </b-row>
49
+ <b-row>
50
+ <div class="task-title"> {{ taskTitle }} </div>
51
+ </b-row>
52
52
 
53
- <b-row>
54
- <div class="task-title"> {{ taskTitle }} </div>
55
- </b-row>
53
+ <loading-preview v-show="showSpinner"/>
56
54
 
57
- <loading-preview v-if="showSpinner"/>
55
+ <no-preview-available v-show="!previewUrl"/>
58
56
 
59
- <no-preview-available v-show="!previewUrl"/>
60
- <iframe title="Preview" v-show="!!previewUrl && !showSpinner" :src="previewUrl" style="width:100%; height:100%;border: none;" @load="loading"/>
61
- </b-col>
57
+ <iframe
58
+ title="Preview"
59
+ v-show="!!previewUrl && !showSpinner"
60
+ :src="previewUrl"
61
+ class="preview-iframe"
62
+ @load="loading"
63
+ @mousemove="onMouseMove"
64
+ @mouseup="onMouseUp"
65
+ />
62
66
 
67
+ </b-col>
68
+ </b-row>
63
69
  </template>
64
70
 
65
71
  <script>
@@ -68,7 +74,7 @@ import NoPreviewAvailable from '@/components/inspectors/NoPreviewAvailable';
68
74
  import LoadingPreview from '@/components/inspectors/LoadingPreview.vue';
69
75
 
70
76
  export default {
71
- props: ['nodeRegistry', 'visible', 'previewConfigs'],
77
+ props: ['nodeRegistry', 'visible', 'previewConfigs', 'panelWidth'],
72
78
  components: { NoPreviewAvailable, LoadingPreview },
73
79
  data() {
74
80
  return {
@@ -79,7 +85,6 @@ export default {
79
85
  taskTitle: '',
80
86
  itemTitle: '',
81
87
  width: 600,
82
- isDragging: false,
83
88
  currentPos: 600,
84
89
  };
85
90
  },
@@ -148,7 +153,6 @@ export default {
148
153
  this.$emit('togglePreview', false);
149
154
  }
150
155
  },
151
-
152
156
  onSelectedPreview(item) {
153
157
  this.selectedPreview = item;
154
158
  },
@@ -174,26 +178,26 @@ export default {
174
178
  const nodeHasConfigParams = Object.keys(clone).length > 0;
175
179
  this.previewUrl = previewConfig && nodeHasConfigParams ? `${previewConfig.url}?node=${nodeData}` : null;
176
180
  this.taskTitle = this.highlightedNode?.definition?.name;
177
- this.showPanel = true;
178
181
  },
179
182
  onClose() {
180
183
  this.$emit('togglePreview', false);
181
184
  },
182
185
  onMouseDown(event) {
183
- this.isDragging = true;
184
- this.currentPos = event.x;
186
+ this.$emit('startResize', event);
185
187
  },
186
188
  onMouseUp() {
187
- this.isDragging = false;
189
+ this.$emit('stopResize');
188
190
  },
189
191
  onMouseMove(event) {
190
- if (this.isDragging) {
191
- const dx = this.currentPos - event.x;
192
- this.currentPos = event.x;
193
- this.width = parseInt(this.width) + dx;
194
- this.$emit('previewResize', this.width);
192
+ if (window.ProcessMaker.$modeler.isResizingPreview) {
193
+ this.$emit('previewResize', event);
195
194
  }
196
195
  },
196
+ setWidth(positionX) {
197
+ const dx = this.currentPos - positionX;
198
+ this.currentPos = positionX;
199
+ this.width = parseInt(this.width) + dx;
200
+ },
197
201
  },
198
202
  };
199
203
  </script>
@@ -1,28 +1,18 @@
1
- $inspector-column-max-width: 400px;
2
1
  $inspector-column-min-width: 200px;
3
2
 
4
- #preview_panel::after {
5
- display: flex;
6
- content: '';
7
- left: 0;
8
- width: 5px;
9
- height: 100%;
10
- cursor: ew-resize;
11
- }
12
-
13
3
  .preview-column {
14
- max-width: $inspector-column-max-width;
15
4
  min-width: $inspector-column-min-width;
16
5
  resize: both;
17
6
  overflow:auto;
18
7
  background-color: #fff;
19
- border-left: 8px solid #EBEEF2;
20
8
  z-index: 2;
21
9
  }
22
10
 
23
- .paneiframe {
24
- display:block;
25
- width:100%;
11
+ .preview-iframe {
12
+ width:90%;
13
+ height:100%;
14
+ padding: 0 30px 0 50px;
15
+ border: none;
26
16
  }
27
17
 
28
18
  .preview-column .control-bar {
@@ -31,12 +21,29 @@ $inspector-column-min-width: 200px;
31
21
  color: #7f7f7f;
32
22
  }
33
23
 
24
+ .preview-type-dropdown {
25
+ margin-top:-10px;
26
+ }
27
+
28
+ .resizer-column {
29
+ cursor:ew-resize;
30
+ width:5px;
31
+ background-color:#EBEEF2;
32
+ }
33
+
34
34
  .preview-column .control-bar .actions {
35
35
  text-align: right;
36
36
  }
37
37
 
38
+ .preview-column .divider {
39
+ background-color: #0074D9;
40
+ height: 20px;
41
+ width: 100%;
42
+ }
43
+
38
44
  .preview-column .control-bar .actions div {
39
45
  width: 100%;
46
+ padding-right: 15px;
40
47
  text-align: right;
41
48
  }
42
49
 
@@ -45,16 +52,6 @@ $inspector-column-min-width: 200px;
45
52
  cursor: pointer;
46
53
  }
47
54
 
48
- .item-title {
49
- margin-top: 15px;
50
- width: 100%;
51
- height: 30px;
52
- font-size: larger;
53
- text-align: center;
54
- align-items: center;
55
- color: #7f7f7f;
56
- }
57
-
58
55
  .task-title {
59
56
  width: 100%;
60
57
  height: 60px;
@@ -45,6 +45,8 @@
45
45
  ref="paper-container"
46
46
  :class="[cursor, { 'grabbing-cursor' : isGrabbing }]"
47
47
  :style="{ width: parentWidth, height: parentHeight }"
48
+ @mouseup="onMouseUp"
49
+ @mousemove="[onMouseMove($event), setInspectorButtonPosition($event)]"
48
50
  >
49
51
 
50
52
  <div ref="paper" data-test="paper" class="main-paper" />
@@ -64,10 +66,13 @@
64
66
 
65
67
  <PreviewPanel ref="preview-panel"
66
68
  @togglePreview="[handleTogglePreview($event), setInspectorButtonPosition($event)]"
67
- @previewResize="setInspectorButtonPosition"
69
+ @previewResize="[onMouseMove($event), setInspectorButtonPosition($event)]"
70
+ @startResize="onStartPreviewResize"
71
+ @stopResize="onMouseUp"
68
72
  :visible="isOpenPreview"
69
73
  :nodeRegistry="nodeRegistry"
70
74
  :previewConfigs="previewConfigs"
75
+ :panelWidth="previewPanelWidth"
71
76
  />
72
77
 
73
78
  <InspectorPanel
@@ -164,6 +169,7 @@
164
169
  </template>
165
170
 
166
171
  <script>
172
+
167
173
  import Vue from 'vue';
168
174
  import _ from 'lodash';
169
175
  import { dia } from 'jointjs';
@@ -212,7 +218,14 @@ import hotkeys from '@/components/hotkeys/main';
212
218
  import TimerEventNode from '@/components/nodes/timerEventNode';
213
219
  import focusNameInputAndHighlightLabel from '@/components/modeler/focusNameInputAndHighlightLabel';
214
220
  import XMLManager from '@/components/modeler/XMLManager';
215
- import { removeNodeFlows, removeNodeMessageFlows, removeNodeAssociations, removeOutgoingAndIncomingRefsToFlow, removeBoundaryEvents, removeSourceDefault } from '@/components/crown/utils';
221
+ import {
222
+ removeBoundaryEvents,
223
+ removeNodeAssociations,
224
+ removeNodeFlows,
225
+ removeNodeMessageFlows,
226
+ removeOutgoingAndIncomingRefsToFlow,
227
+ removeSourceDefault,
228
+ } from '@/components/crown/utils';
216
229
  import { getInvalidNodes } from '@/components/modeler/modelerUtils';
217
230
  import { NodeMigrator } from '@/components/modeler/NodeMigrator';
218
231
  import addLoopCharacteristics from '@/setup/addLoopCharacteristics';
@@ -330,6 +343,9 @@ export default {
330
343
  showInspectorButton: true,
331
344
  inspectorButtonRight: 65,
332
345
  previewConfigs: [],
346
+ isResizingPreview: false,
347
+ currentCursorPosition: 0,
348
+ previewPanelWidth: 600,
333
349
  flowTypes: [
334
350
  'processmaker-modeler-sequence-flow',
335
351
  'processmaker-modeler-message-flow',
@@ -404,6 +420,20 @@ export default {
404
420
  isMultiplayer: () => store.getters.isMultiplayer,
405
421
  },
406
422
  methods: {
423
+ onStartPreviewResize(event) {
424
+ this.isResizingPreview = true;
425
+ this.currentCursorPosition = event.x;
426
+ },
427
+ onMouseUp() {
428
+ this.isResizingPreview = false;
429
+ },
430
+ onMouseMove(event) {
431
+ if (this.isResizingPreview) {
432
+ const dx = this.currentCursorPosition - event.x;
433
+ this.previewPanelWidth = parseInt(this.previewPanelWidth) + dx;
434
+ this.currentCursorPosition = event.x;
435
+ }
436
+ },
407
437
  registerPreview(config) {
408
438
  this.previewConfigs.push(config);
409
439
  },
@@ -427,13 +457,12 @@ export default {
427
457
  this.isOpenPreview = value;
428
458
  },
429
459
  setInspectorButtonPosition() {
430
- const previewWidth = this.$refs['preview-panel'].width;
431
460
  if (this.isOpenInspector) {
432
461
  return;
433
462
  }
434
463
 
435
464
  if (this.isOpenPreview && !this.isOpenInspector) {
436
- this.inspectorButtonRight = 65 + previewWidth;
465
+ this.inspectorButtonRight = 65 + this.previewPanelWidth;
437
466
  }
438
467
 
439
468
  if (!this.isOpenPreview && !this.isOpenInspector) {
@@ -1193,14 +1222,20 @@ export default {
1193
1222
  sourceRefId = Array.isArray(node.definition.sourceRef) && node.definition.sourceRef[0]?.id;
1194
1223
  targetRefId = node.definition.targetRef?.$parent?.$parent?.get('id');
1195
1224
  }
1196
-
1225
+ const waypoint = [];
1226
+ node.diagram.waypoint.forEach(point => {
1227
+ waypoint.push({
1228
+ x: point.x,
1229
+ y: point.y,
1230
+ });
1231
+ });
1197
1232
  if (sourceRefId && targetRefId) {
1198
1233
  const flowData = {
1199
1234
  id: node.definition.id,
1200
1235
  type: node.type,
1201
1236
  sourceRefId,
1202
1237
  targetRefId,
1203
- waypoint: node.diagram.waypoint,
1238
+ waypoint,
1204
1239
  name: node.definition.name,
1205
1240
  conditionExpression: null,
1206
1241
  };
@@ -14,17 +14,23 @@ export class NodeMigrator {
14
14
  if (keepOriginalName(this._nodeThatWillBeReplaced)) {
15
15
  this._definition.name = this._nodeThatWillBeReplaced.definition.name;
16
16
  }
17
-
17
+ const flowNodes = [];
18
18
  const forceNodeToRemount = definition => {
19
19
  const shape = this._graph.getLinks().find(element => {
20
20
  return element.component && element.component.node.definition === definition;
21
21
  });
22
22
  shape.component.node._modelerId += '_replaced';
23
+ flowNodes.push({
24
+ id: shape.component.node.definition.id,
25
+ type: shape.component.node.type,
26
+ name: shape.component.node.definition.name,
27
+ sourceRefId: shape.component.node.definition.sourceRef.id,
28
+ targetRefId: shape.component.node.definition.targetRef.id,
29
+ });
23
30
  };
24
31
 
25
32
  const incoming = this._nodeThatWillBeReplaced.definition.get('incoming');
26
33
  const outgoing = this._nodeThatWillBeReplaced.definition.get('outgoing');
27
-
28
34
  this._definition.get('incoming').push(...incoming);
29
35
  this._definition.get('outgoing').push(...outgoing);
30
36
 
@@ -62,6 +68,8 @@ export class NodeMigrator {
62
68
  forceNodeToRemount(flow);
63
69
  });
64
70
  }
71
+ // multiplayer hook to update the flows
72
+ window.ProcessMaker.EventBus.$emit('multiplayer-updateFlows', flowNodes);
65
73
  }
66
74
 
67
75
  _handleSequenceFlowForGateway(ref) {
@@ -32,6 +32,14 @@ export default class Multiplayer {
32
32
  // Connect to websocket server
33
33
  this.clientIO = io(window.ProcessMaker.multiplayer.host, { transports: ['websocket', 'polling']});
34
34
 
35
+ if (window.ProcessMaker.multiplayer.enabled) {
36
+ this.webSocketEvents();
37
+ this.multiplayerEvents();
38
+ } else {
39
+ this.clientIO.disconnect();
40
+ }
41
+ }
42
+ webSocketEvents() {
35
43
  this.clientIO.on('connect', () => {
36
44
  // Join the room
37
45
  this.clientIO.emit('joinRoom', {
@@ -134,10 +142,22 @@ export default class Multiplayer {
134
142
  Y.applyUpdate(this.yDoc, new Uint8Array(updateDoc));
135
143
  });
136
144
 
145
+ this.clientIO.on('updateFlows', (payload) => {
146
+ const { updateDoc, updatedNodes } = payload;
147
+ // Update the elements in the process
148
+ updatedNodes.forEach((data) => {
149
+ this.updateFlowClient(data);
150
+ });
151
+ // Update the element in the shared array
152
+ Y.applyUpdate(this.yDoc, new Uint8Array(updateDoc));
153
+ });
154
+ }
137
155
 
156
+ multiplayerEvents() {
138
157
  window.ProcessMaker.EventBus.$on('multiplayer-addNode', ( data ) => {
139
158
  this.addNode(data);
140
159
  });
160
+
141
161
  window.ProcessMaker.EventBus.$on('multiplayer-removeNode', ( data ) => {
142
162
  this.removeNode(data);
143
163
  });
@@ -161,11 +181,17 @@ export default class Multiplayer {
161
181
  window.ProcessMaker.EventBus.$on('multiplayer-addLanes', ( lanes ) => {
162
182
  this.addLaneNodes(lanes);
163
183
  });
184
+
164
185
  window.ProcessMaker.EventBus.$on('multiplayer-updateInspectorProperty', ( data ) => {
165
186
  if (this.modeler.isMultiplayer) {
166
187
  this.updateInspectorProperty(data);
167
188
  }
168
189
  });
190
+ window.ProcessMaker.EventBus.$on('multiplayer-updateFlows', ( data ) => {
191
+ if (this.modeler.isMultiplayer) {
192
+ this.updateFlows(data);
193
+ }
194
+ });
169
195
  }
170
196
  /**
171
197
  * Sync the modeler nodes with the microservice
@@ -245,7 +271,7 @@ export default class Multiplayer {
245
271
  return node;
246
272
  }
247
273
  removeShape(node) {
248
- this.modeler.removeNodeProcedure(node, true);
274
+ this.modeler.removeNodeProcedure(node, { removeRelationships: false });
249
275
  }
250
276
  getRemovedNodes(array1, array2) {
251
277
  return array1.filter(object1 => {
@@ -565,4 +591,56 @@ export default class Multiplayer {
565
591
  }
566
592
  }
567
593
  }
594
+ /**
595
+ * Update the shared document and emit socket sign to update the flows
596
+ * @param {Object} data
597
+ */
598
+ updateFlows(data){
599
+ data.forEach((value) => {
600
+ const index = this.getIndex(value.id);
601
+ const nodeToUpdate = this.yArray.get(index);
602
+ this.yDoc.transact(() => {
603
+ for (const key in value) {
604
+ if (Object.hasOwn(value, key)) {
605
+ nodeToUpdate.set(key, value[key]);
606
+ }
607
+ }
608
+ });
609
+ });
610
+ // Encode the state as an update and send it to the server
611
+ const stateUpdate = Y.encodeStateAsUpdate(this.yDoc);
612
+ this.clientIO.emit('updateFlows', { updateDoc: stateUpdate, isReplaced: false });
613
+ }
614
+ /**
615
+ * Update the flow client, All node refs will be updated and forced to remount
616
+ * @param {Object} data
617
+ */
618
+ updateFlowClient(data) {
619
+ let remount = false;
620
+ const flow = this.getNodeById(data.id);
621
+ if (flow && data.sourceRefId) {
622
+ const sourceRef = this.getNodeById(data.sourceRefId);
623
+ flow.definition.set('sourceRef', sourceRef.definition);
624
+ const outgoing = sourceRef.definition.get('outgoing')
625
+ .find((element) => element.id === flow.definition.id);
626
+ if (!outgoing) {
627
+ sourceRef.definition.get('outgoing').push(...[flow.definition]);
628
+ }
629
+ remount = true;
630
+ }
631
+ if (flow && data.targetRefId) {
632
+ const targetRef = this.getNodeById(data.targetRefId);
633
+ flow.definition.set('targetRef', targetRef.definition);
634
+ const incoming = targetRef.definition.get('incoming')
635
+ .find((element) => element.id === flow.definition.id);
636
+ if (!incoming) {
637
+ targetRef.definition.get('incoming').push(...[flow.definition]);
638
+ }
639
+ remount = true;
640
+ }
641
+ if (remount) {
642
+ // Force Remount Flow
643
+ flow._modelerId += '_replaced';
644
+ }
645
+ }
568
646
  }
@@ -36,6 +36,7 @@ window.ProcessMaker = {
36
36
  },
37
37
  multiplayer:{
38
38
  host: process.env.VUE_APP_WEBSOCKET_PROVIDER,
39
+ enabled: /^true$/i.test(process.env.VUE_APP_COLLABORATIVE_ENABLED),
39
40
  },
40
41
  EventBus: new Vue(),
41
42
  apiClient: axios,
@@ -63,5 +64,4 @@ window.ProcessMaker = {
63
64
  fullName: faker.person.fullName(),
64
65
  avatar: null,
65
66
  },
66
-
67
67
  };