@processmaker/modeler 1.39.9 → 1.39.10

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.
@@ -1,6 +1,8 @@
1
1
  import nodeTypesStore from '@/nodeTypesStore';
2
+ import iconHelper from '@/mixins/iconHelper';
2
3
 
3
4
  export default {
5
+ mixins: [iconHelper],
4
6
  data() {
5
7
  return {
6
8
  wasClicked: false,
@@ -68,20 +70,28 @@ export default {
68
70
  nodeTypesStore.commit('setGhostNode', tempGhost);
69
71
  },
70
72
  createDraggingHelper(event, control) {
73
+
71
74
  if (this.movedElement) {
72
75
  document.removeEventListener('mousemove', this.setDraggingPosition);
73
76
  document.body.removeChild(this.movedElement);
74
- nodeTypesStore.commit('setGhostNode', null);
75
77
  }
76
- const sourceElement = event.target;
77
- nodeTypesStore.commit('setGhostNode', document.createElement('img'));
78
- let tempGhost = this.movedElement;
79
- Object.keys(this.helperStyles).forEach((property) => {
80
- tempGhost.style[property] = this.helperStyles[property];
81
- });
82
- tempGhost.src = control.icon;
78
+
79
+ const isSvgIcon = this.containsSvg(control.icon);
80
+ const tempGhost = isSvgIcon ? document.createElement('img') : document.createElement('i');
81
+
82
+ Object.assign(tempGhost.style, this.helperStyles);
83
+
84
+ // Set the appropriate attribute or property based on the control icon type
85
+ if (isSvgIcon) {
86
+ tempGhost.src = control.icon;
87
+ } else {
88
+ tempGhost.setAttribute('class', control.icon);
89
+ }
90
+
91
+ document.body.appendChild(tempGhost);
83
92
  nodeTypesStore.commit('setGhostNode', tempGhost);
84
- document.body.appendChild(this.movedElement);
93
+
94
+ const sourceElement = event.target;
85
95
  this.xOffset = event.clientX - sourceElement.getBoundingClientRect().left;
86
96
  this.yOffset = event.clientY - sourceElement.getBoundingClientRect().top;
87
97
  },
@@ -6,6 +6,13 @@ export default {
6
6
  iconName: '',
7
7
  };
8
8
  },
9
+ watch: {
10
+ shape() {
11
+ if (this.node.definition.get('customIcon')) {
12
+ this.setCustomIcon(this.node.definition.get('customIcon'));
13
+ }
14
+ },
15
+ },
9
16
 
10
17
  methods: {
11
18
  setCustomIconName(iconName) {
@@ -14,11 +21,24 @@ export default {
14
21
  resetCustomIconName() {
15
22
  this.setCustomIconName('');
16
23
  },
17
- setCustomIcon(base64Icon) {
18
- if (!this.shape){
24
+ setCustomIcon(icon) {
25
+ if (!this.shape) {
19
26
  return;
20
27
  }
21
- this.shape.attr('image/xlink:href', coloredIcon(atob(base64Icon), this.node));
28
+
29
+ try {
30
+ const decodedIcon = atob(icon);
31
+ this.updateIcon(decodedIcon);
32
+ } catch (error) {
33
+ this.updateIcon(icon);
34
+ }
35
+ },
36
+ updateIcon(icon) {
37
+ const iconURL = this.getIconURL(icon);
38
+ this.shape.attr('image/xlink:href', iconURL);
39
+ },
40
+ getIconURL(icon) {
41
+ return coloredIcon(icon, this.node);
22
42
  },
23
43
  },
24
44
  mounted() {
@@ -0,0 +1,14 @@
1
+ export default {
2
+ methods: {
3
+ containsSvg(icon) {
4
+ // Regular expression to match a URL pattern
5
+ const urlPattern = /^(https?:\/\/)?([\w-]+(\.[\w-]+)+)(\/[\w@?^=%&:/~+#-]*)*$/;
6
+
7
+ // Regular expression to check if the string contains '.svg'
8
+ const svgPattern = /\.svg/;
9
+
10
+ // Check if the variable is a string and either a URL or contains '.svg'
11
+ return typeof icon === 'string' && (urlPattern.test(icon) || svgPattern.test(icon));
12
+ },
13
+ },
14
+ };
@@ -1,66 +1,62 @@
1
+ import { io } from 'socket.io-client';
1
2
  import * as Y from 'yjs';
2
- import { WebsocketProvider } from 'y-websocket';
3
3
  import { getNodeIdGenerator } from '../NodeIdGenerator';
4
4
  import Room from './room';
5
5
  export default class Multiplayer {
6
- ydoc = null;
7
- yarray = null;
6
+ clientIO = null;
7
+ yDoc = null;
8
+ yArray = null;
8
9
  modeler = null;
9
10
  #nodeIdGenerator = null;
10
11
  room = null;
11
12
  deletedItem = null;
13
+
12
14
  constructor(modeler) {
13
15
  // define document
14
- this.ydoc = new Y.Doc();
16
+ this.yDoc = new Y.Doc();
17
+ // Create a shared array
18
+ this.yArray = this.yDoc.getArray('elements');
19
+ // Create a Modeler instance
15
20
  this.modeler = modeler;
16
21
  }
17
22
  init() {
23
+ // Get the node id generator
18
24
  this.#nodeIdGenerator = getNodeIdGenerator(this.modeler.definitions);
19
-
25
+ // Get the room name from the process id
20
26
  this.room = new Room(`room-${window.ProcessMaker.modeler.process.id}`);
21
- const wsProvider = new WebsocketProvider(process.env.VUE_APP_WEBSOCKET_PROVIDER, this.room.getRoom(), this.ydoc);
22
- wsProvider.on('status', () => {
23
- // todo status handler
27
+
28
+ // Connect to websocket server
29
+ this.clientIO = io(process.env.VUE_APP_WEBSOCKET_PROVIDER, { transports: ['websocket', 'polling']});
30
+
31
+ this.clientIO.on('connect', () => {
32
+ // Join the room
33
+ this.clientIO.emit('joinRoom', this.room.getRoom());
24
34
  });
25
- // array of numbers which produce a sum
26
- this.yarray = this.ydoc.getArray('modeler');
27
- // observe changes of the diagram
28
- this.yarray.observe(event => {
29
- event.changes.delta.forEach((value) => {
30
- if (value.insert) {
31
- value.insert.forEach((value) => {
32
- this.createShape(value.toJSON());
33
- this.#nodeIdGenerator.updateCounters();
34
- });
35
- }
36
- });
37
- // remove nodes observer
38
- if (event.changes.deleted && event.changes.deleted.size > 0) {
39
- this.removeShape();
40
- }
35
+
36
+ // Listen for updates when a new element is added
37
+ this.clientIO.on('createElement', async(payload) => {
38
+ // Create the new element in the process
39
+ await this.createRemoteShape(payload.changes);
40
+ // Add the new element to the shared array
41
+ Y.applyUpdate(this.yDoc, new Uint8Array(payload.updateDoc));
41
42
  });
42
- this.yarray.observeDeep(ymapEventArray => {
43
- ymapEventArray.forEach((ymap) => {
44
- const ymapNested = ymap.target ;
45
- const newProperties = {};
46
- ymap.changes.keys.forEach((change, key) => {
47
- if (change.action === 'add') {
48
- // TODO add new properties
49
- } else if (change.action === 'update') {
50
- newProperties[key] = ymapNested.get(key);
51
- } else if (change.action === 'delete') {
52
- // TODO delete propertiees
53
- }
54
- });
55
- if (Object.keys(newProperties).length > 0 ) {
56
- newProperties['id'] = ymapNested.get('id');
57
- this.updateShapes(newProperties);
58
- }
43
+
44
+ // Listen for updates when an element is removed
45
+ this.clientIO.on('removeElement', (payload) => {
46
+ payload.deletedNodes.forEach(nodeId => {
47
+ // Get the node id
48
+ const node = this.getNodeById(nodeId);
49
+ // Remove the element from the process
50
+ this.removeShape(node);
59
51
  });
52
+ // Remove the element from the shared array
53
+ Y.applyUpdate(this.yDoc, new Uint8Array(payload.updateDoc));
60
54
  });
55
+
61
56
  window.ProcessMaker.EventBus.$on('multiplayer-addNode', ( data ) => {
62
57
  this.addNode(data);
63
58
  });
59
+
64
60
  window.ProcessMaker.EventBus.$on('multiplayer-removeNode', ( data ) => {
65
61
  this.removeNode(data);
66
62
  });
@@ -69,20 +65,44 @@ export default class Multiplayer {
69
65
  });
70
66
  }
71
67
  addNode(data) {
72
- const ymapNested = new Y.Map();
73
- this.doTransact(ymapNested, data);
74
- this.yarray.push([ymapNested]);
68
+ // Add the new element to the process
69
+ this.createShape(data);
70
+ // Add the new element to the shared array
71
+ // this.yArray.push([data]);
72
+ const yMapNested = new Y.Map();
73
+ this.doTransact(yMapNested, data);
74
+ this.yArray.push([yMapNested]);
75
+ // Encode the state as an update and send it to the server
76
+ const stateUpdate = Y.encodeStateAsUpdate(this.yDoc);
77
+ // Send the update to the web socket server
78
+ this.clientIO.emit('createElement', stateUpdate);
75
79
  }
76
80
  createShape(value) {
77
81
  this.modeler.handleDropProcedure(value, false);
82
+ this.#nodeIdGenerator.updateCounters();
83
+ }
84
+ createRemoteShape(changes) {
85
+ return new Promise(resolve => {
86
+ changes.map((data) => {
87
+ this.createShape(data);
88
+ });
89
+
90
+ resolve();
91
+ });
78
92
  }
79
93
  removeNode(data) {
80
94
  const index = this.getIndex(data.definition.id);
81
- this.yarray.delete(index, 1); // delete one element
95
+ this.removeShape(data);
96
+ this.yArray.delete(index, 1); // delete one element
97
+
98
+ // Encode the state as an update and send it to the server
99
+ const stateUpdate = Y.encodeStateAsUpdate(this.yDoc);
100
+ // Send the update to the web socket server
101
+ this.clientIO.emit('removeElement', stateUpdate);
82
102
  }
83
103
  getIndex(id) {
84
104
  let index = -1;
85
- for (const value of this.yarray) {
105
+ for (const value of this.yArray) {
86
106
  index ++;
87
107
  if (value.get('id') === id) {
88
108
  break ;
@@ -90,11 +110,13 @@ export default class Multiplayer {
90
110
  }
91
111
  return index;
92
112
  }
93
- removeShape() {
94
- const nodes = this.getRemovedNodes(this.modeler.nodes, this.yarray.toArray());
95
- nodes.forEach((value) => {
96
- this.modeler.removeNodeProcedure(value, true);
97
- });
113
+ getNodeById(nodeId) {
114
+ const node = this.modeler.nodes.find((element) => element.definition && element.definition.id === nodeId);
115
+
116
+ return node;
117
+ }
118
+ removeShape(node) {
119
+ this.modeler.removeNodeProcedure(node, true);
98
120
  }
99
121
  getRemovedNodes(array1, array2) {
100
122
  return array1.filter(object1 => {
@@ -110,11 +132,11 @@ export default class Multiplayer {
110
132
  this.doTransact(nodeToUpdate, value.properties);
111
133
  });
112
134
  }
113
- doTransact(ymapNested, data) {
114
- this.ydoc.transact(() => {
135
+ doTransact(yMapNested, data) {
136
+ this.yDoc.transact(() => {
115
137
  for (const key in data) {
116
138
  if (Object.prototype.hasOwnProperty.call(data, key)) {
117
- ymapNested.set(key, data[key]);
139
+ yMapNested.set(key, data[key]);
118
140
  }
119
141
  }
120
142
  });
@@ -16,6 +16,10 @@ export default (node) => {
16
16
  return;
17
17
  }
18
18
 
19
+ if (node.implementation === 'package-ai/processmaker-ai-assistant') {
20
+ return;
21
+ }
22
+
19
23
  // Insert the loop config inspector at the specified index
20
24
  node.inspectorConfig[0].items.splice(node.loopInspectorIndex || 1, 0, loopCharacteristicsInspector);
21
25
 
@@ -20,6 +20,9 @@ window.ProcessMaker.EventBus.$on('modeler-init', registerNodes);
20
20
  window.ProcessMaker.EventBus.$on('modeler-start', ({ loadXML }) => {
21
21
  loadXML(blank);
22
22
  });
23
+ /**
24
+ * TODO: Remove this block when the collaborative mode will be dynamic
25
+ */
23
26
  window.ProcessMaker.EventBus.$on('multiplayer-start', (params) => {
24
27
  const multiplayer = new Multiplayer(params.modeler);
25
28
  multiplayer.init();