@processmaker/modeler 1.39.8 → 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.
Files changed (32) hide show
  1. package/dist/img/proceC2.b229e6b6.svg +3 -0
  2. package/dist/modeler.common.js +3295 -401
  3. package/dist/modeler.common.js.map +1 -1
  4. package/dist/modeler.umd.js +3273 -379
  5. package/dist/modeler.umd.js.map +1 -1
  6. package/dist/modeler.umd.min.js +4 -4
  7. package/dist/modeler.umd.min.js.map +1 -1
  8. package/package.json +7 -6
  9. package/src/assets/corneaIcon.svg +3 -0
  10. package/src/assets/proceC2.svg +3 -0
  11. package/src/components/crown/crownButtons/crownColorDropdown.vue +5 -1
  12. package/src/components/crown/crownButtons/crownDropdowns.vue +5 -0
  13. package/src/components/crown/crownConfig/crownConfig.vue +5 -0
  14. package/src/components/iconColors.js +50 -9
  15. package/src/components/inspectors/InspectorPanel.vue +13 -2
  16. package/src/components/inspectors/PreviewPanel.vue +1 -1
  17. package/src/components/modeler/Modeler.vue +73 -14
  18. package/src/components/modeler/Selection.vue +19 -0
  19. package/src/components/nodes/node.js +1 -1
  20. package/src/components/nodes/task/shape.js +4 -0
  21. package/src/components/nodes/task/task.vue +2 -0
  22. package/src/components/railBottom/controls/SubmenuPopper/SubmenuPopper.vue +7 -3
  23. package/src/components/rails/explorer-rail/nodeTypesLoop/nodeTypesLoop.vue +14 -4
  24. package/src/components/welcome/WelcomeMessage.vue +82 -0
  25. package/src/mixins/clickAndDrop.js +19 -9
  26. package/src/mixins/customIcon.js +23 -3
  27. package/src/mixins/iconHelper.js +14 -0
  28. package/src/multiplayer/multiplayer.js +135 -22
  29. package/src/multiplayer/room.js +1 -2
  30. package/src/setup/addLoopCharacteristics.js +4 -0
  31. package/src/setup/globals.js +1 -1
  32. package/src/setup/initialLoad.js +3 -0
@@ -3,10 +3,11 @@ import pinIcon from '@/assets/pin-angle.svg';
3
3
  import pinFillIcon from '@/assets/pin-angle-fill.svg';
4
4
  import nodeTypesStore from '@/nodeTypesStore';
5
5
  import clickAndDrop from '@/mixins/clickAndDrop';
6
+ import iconHelper from '@/mixins/iconHelper';
6
7
 
7
8
  export default {
8
9
  name: 'NodeTypesLoop',
9
- mixins: [clickAndDrop],
10
+ mixins: [clickAndDrop, iconHelper],
10
11
  data() {
11
12
  return {
12
13
  pinIcon,
@@ -57,13 +58,15 @@ export default {
57
58
  <template v-for="object in filteredNodes">
58
59
  <div
59
60
  class="node-types__item"
61
+ :class="{'node-types__item__highlight': object.type === 'processmaker-ai-assistant'}"
60
62
  :data-test="object.type"
61
63
  :key="object.id"
62
64
  @mouseover="showPin = true"
63
65
  @mouseleave="showPin = false"
64
66
  @click.self="onClickHandler($event, object)"
65
67
  >
66
- <img class="node-types__item__icon" :src="object.icon" :alt="$t(object.label)">
68
+ <i v-if="!containsSvg(object.icon)" :class="object.icon" class="fa-lg"/>
69
+ <img v-else class="node-types__item__icon" :src="object.icon" :alt="$t(object.label)">
67
70
  <span>{{ $t(object.label) }}</span>
68
71
  <img
69
72
  v-if="nodeTypeAlreadyPinned(object, object.type)"
@@ -88,13 +91,15 @@ export default {
88
91
  <template v-for="pinnedObject in pinnedObjects">
89
92
  <div
90
93
  class="node-types__item"
94
+ :class="{'node-types__item__highlight': pinnedObject.type === 'processmaker-ai-assistant'}"
91
95
  :data-test="pinnedObject.type"
92
96
  :key="pinnedObject.id"
93
97
  @mouseover="showPin = true"
94
98
  @mouseleave="showPin = false"
95
99
  @click.stop="onClickHandler($event, pinnedObject)"
96
100
  >
97
- <img class="node-types__item__icon" :src="pinnedObject.icon" :alt="$t(pinnedObject.label)">
101
+ <i v-if="!containsSvg(pinnedObject.icon)" :class="pinnedObject.icon" class="fa-lg"/>
102
+ <img v-else class="node-types__item__icon" :src="pinnedObject.icon" :alt="$t(pinnedObject.label)">
98
103
  <span>{{ $t(pinnedObject.label) }}</span>
99
104
  <img
100
105
  :src="pinFillIcon"
@@ -110,13 +115,15 @@ export default {
110
115
  <template v-for="nodeType in unpinnedObjects">
111
116
  <div
112
117
  class="node-types__item"
118
+ :class="{'node-types__item__highlight': nodeType.type === 'processmaker-ai-assistant'}"
113
119
  :data-test="nodeType.type"
114
120
  :key="nodeType.id"
115
121
  @mouseover="showPin = true"
116
122
  @mouseleave="showPin = false"
117
123
  @click.stop="onClickHandler($event, nodeType)"
118
124
  >
119
- <img class="node-types__item__icon" :src="nodeType.icon" :alt="$t(nodeType.label)">
125
+ <i v-if="!containsSvg(nodeType.icon)" :class="nodeType.icon" class="fa-lg"/>
126
+ <img v-else class="node-types__item__icon" :src="nodeType.icon" :alt="$t(nodeType.label)">
120
127
  <span>{{ $t(nodeType.label) }}</span>
121
128
  <img
122
129
  :src="pinIcon"
@@ -154,6 +161,9 @@ export default {
154
161
  width: 1.5rem;
155
162
  height: 1.5rem;
156
163
  }
164
+ &__highlight {
165
+ background-color: #FFF4D3;
166
+ }
157
167
  span {
158
168
  margin-left: 0.8rem;
159
169
  font-size: 13px;
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <div class="message d-flex flex-column w-100 align-items-center">
3
+ <div class="d-flex justify-content-center align-items-center flex-column justify-content-center">
4
+ <div class="w-100">
5
+ {{
6
+ $t("To start, click and drop objects to build the process you like.")
7
+ }}
8
+ </div>
9
+ <div class="justify-content-center align-items-center w-100 text-center my-4">
10
+ <div class="text-align-center d-flex text-center justify-content-center align-items-center">
11
+ <div class="hyphen" />
12
+ or
13
+ <div class="hyphen" />
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <b-button class="new-process-btn d-flex flex-row p-2 align-items-center" @click="onClick">
18
+ <inline-svg :src="proceC2Icon" class="mx-2 ai-icon" />
19
+ <div class="mr-3">{{ $t("Kick-start a new process with our generative AI") }}</div>
20
+ <span class="fa fa-chevron-right" />
21
+ </b-button>
22
+ </div>
23
+ </template>
24
+ <script>
25
+ import InlineSvg from 'vue-inline-svg';
26
+
27
+ export default {
28
+ name: 'WelcomeMessage',
29
+ components: {
30
+ InlineSvg,
31
+ },
32
+ data() {
33
+ return {
34
+ proceC2Icon: require('@/assets/proceC2.svg'),
35
+ };
36
+ },
37
+ methods: {},
38
+ };
39
+ </script>
40
+ <style>
41
+ .message {
42
+ color: #5f666d;
43
+ font-style: italic;
44
+ line-height: 27px;
45
+ word-wrap: break-word;
46
+ position: absolute;
47
+ display: flex;
48
+ justify-content: center;
49
+ align-items: center;
50
+ left: 0;
51
+ border: 0 none;
52
+ z-index: 1;
53
+ font-size: 110%;
54
+ font-weight: 100;
55
+ height: calc(100% - 130px);
56
+ }
57
+
58
+ .hyphen {
59
+ transform: rotate(0deg);
60
+ transform-origin: 0 0;
61
+ border: 1px #5f666d solid;
62
+ width: 30px;
63
+ margin: 5px 13px 0px 13px;
64
+ }
65
+ .new-process-btn {
66
+ color: #0872C2;
67
+ border-color: #0872C2;
68
+ background-color: #ffffff;
69
+ box-shadow: 0 3px 9px -3px #c2c2c2;
70
+ }
71
+
72
+ .new-process-btn:hover {
73
+ background-color: #edf7ff;
74
+ color: #0872C2;
75
+ border-color: #0872C2;
76
+ }
77
+
78
+ .ai-icon {
79
+ width: 22px;
80
+ height: 22px;
81
+ }
82
+ </style>
@@ -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,48 +1,161 @@
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;
12
+ deletedItem = null;
13
+
11
14
  constructor(modeler) {
12
15
  // define document
13
- 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
14
20
  this.modeler = modeler;
15
21
  }
16
22
  init() {
23
+ // Get the node id generator
17
24
  this.#nodeIdGenerator = getNodeIdGenerator(this.modeler.definitions);
25
+ // Get the room name from the process id
26
+ this.room = new Room(`room-${window.ProcessMaker.modeler.process.id}`);
18
27
 
19
- this.room = new Room('room-' + window.ProcessMaker.modeler.process.id);
20
- const wsProvider = new WebsocketProvider('ws://localhost:1234', this.room.getRoom(), this.ydoc);
21
- wsProvider.on('status', () => {
22
- // todo status handler
23
- });
24
- // array of numbers which produce a sum
25
- this.yarray = this.ydoc.getArray('modeler');
26
- // observe changes of the diagram
27
- this.yarray.observe(event => {
28
- event.changes.delta.forEach((value) =>{
29
- if (value.insert) {
30
- value.insert.forEach((value) => {
31
- this.createShape(value);
32
- this.#nodeIdGenerator.updateCounters();
33
- });
34
- }
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());
34
+ });
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));
42
+ });
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);
35
51
  });
52
+ // Remove the element from the shared array
53
+ Y.applyUpdate(this.yDoc, new Uint8Array(payload.updateDoc));
36
54
  });
55
+
37
56
  window.ProcessMaker.EventBus.$on('multiplayer-addNode', ( data ) => {
38
57
  this.addNode(data);
39
58
  });
59
+
60
+ window.ProcessMaker.EventBus.$on('multiplayer-removeNode', ( data ) => {
61
+ this.removeNode(data);
62
+ });
63
+ window.ProcessMaker.EventBus.$on('multiplayer-updateNodes', ( data ) => {
64
+ this.updateNodes(data);
65
+ });
40
66
  }
41
67
  addNode(data) {
42
- this.yarray.push([data]);
43
-
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);
44
79
  }
45
80
  createShape(value) {
46
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
+ });
92
+ }
93
+ removeNode(data) {
94
+ const index = this.getIndex(data.definition.id);
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);
102
+ }
103
+ getIndex(id) {
104
+ let index = -1;
105
+ for (const value of this.yArray) {
106
+ index ++;
107
+ if (value.get('id') === id) {
108
+ break ;
109
+ }
110
+ }
111
+ return index;
112
+ }
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);
120
+ }
121
+ getRemovedNodes(array1, array2) {
122
+ return array1.filter(object1 => {
123
+ return !array2.some(object2 => {
124
+ return object1.definition.id === object2.get('id');
125
+ });
126
+ });
127
+ }
128
+ updateNodes(data) {
129
+ data.forEach((value) => {
130
+ const index = this.getIndex(value.id);
131
+ const nodeToUpdate = this.yarray.get(index);
132
+ this.doTransact(nodeToUpdate, value.properties);
133
+ });
134
+ }
135
+ doTransact(yMapNested, data) {
136
+ this.yDoc.transact(() => {
137
+ for (const key in data) {
138
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
139
+ yMapNested.set(key, data[key]);
140
+ }
141
+ }
142
+ });
143
+ }
144
+ updateShapes(data) {
145
+ const { paper } = this.modeler;
146
+ const element = this.getJointElement(paper.model, data.id);
147
+ // Update the element's position attribute
148
+ element.set('position', { x:data.clientX, y:data.clientY });
149
+ // Trigger a rendering of the element on the paper
150
+ paper.findViewByModel(element).update();
151
+ }
152
+ getJointElement(graph, targetValue) {
153
+ const cells = graph.getCells();
154
+ for (const cell of cells) {
155
+ if (cell.component.id === targetValue) {
156
+ return cell;
157
+ }
158
+ }
159
+ return null; // Return null if no matching element is found
47
160
  }
48
161
  }
@@ -1,4 +1,3 @@
1
-
2
1
  export default class Room {
3
2
  #room ='';
4
3
  constructor(name) {
@@ -7,4 +6,4 @@ export default class Room {
7
6
  getRoom() {
8
7
  return this.#room;
9
8
  }
10
- }
9
+ }
@@ -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
 
@@ -51,7 +51,7 @@ window.ProcessMaker = {
51
51
  },
52
52
  modeler: {
53
53
  process: {
54
- id: 3,
54
+ id: 1,
55
55
  },
56
56
  },
57
57
  };
@@ -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();