@processmaker/modeler 1.26.0 → 1.27.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.26.0",
3
+ "version": "1.27.0",
4
4
  "scripts": {
5
5
  "serve": "vue-cli-service serve",
6
6
  "open-cypress": "TZ=UTC cypress open",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path fill="#fff" d="M280 64h40c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128C0 92.7 28.7 64 64 64h40 9.6C121 27.5 153.3 0 192 0s71 27.5 78.4 64H280zM64 112c-8.8 0-16 7.2-16 16V448c0 8.8 7.2 16 16 16H320c8.8 0 16-7.2 16-16V128c0-8.8-7.2-16-16-16H304v24c0 13.3-10.7 24-24 24H192 104c-13.3 0-24-10.7-24-24V112H64zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg>
@@ -13,12 +13,12 @@
13
13
  aria-hidden="true"
14
14
  >
15
15
  </crown-button>
16
-
16
+
17
17
  </template>
18
18
 
19
19
  <script>
20
20
  import CrownButton from '@/components/crown/crownButtons/crownButton';
21
- import copyIcon from '@/assets/copy-regular.svg';
21
+ import copyIcon from '@/assets/clipboard.svg';
22
22
  import validCopyElements from '@/components/crown/crownButtons/validCopyElements';
23
23
 
24
24
  export default {
@@ -33,7 +33,7 @@ export default {
33
33
  },
34
34
  methods: {
35
35
  copyElement() {
36
- this.$emit('copy-element', this.node, ++this.copyCount);
36
+ this.$emit('copy-element');
37
37
  },
38
38
  },
39
39
  };
@@ -0,0 +1,40 @@
1
+ <template>
2
+ <crown-button
3
+ v-if="node.isBpmnType(...validCopyElements)"
4
+ :title="$t('Duplicate Element')"
5
+ v-b-tooltip.hover.viewport.d50="{ customClass: 'no-pointer-events' }"
6
+ aria-label="Duplicate Element"
7
+ data-test="duplicate-button"
8
+ role="menuitem"
9
+ @click="duplicateElement"
10
+ >
11
+ <img
12
+ :src="duplicateIcon"
13
+ aria-hidden="true"
14
+ >
15
+ </crown-button>
16
+
17
+ </template>
18
+
19
+ <script>
20
+ import CrownButton from '@/components/crown/crownButtons/crownButton';
21
+ import duplicateIcon from '@/assets/copy-regular.svg';
22
+ import validCopyElements from '@/components/crown/crownButtons/validCopyElements';
23
+
24
+ export default {
25
+ components: { CrownButton },
26
+ props: ['node'],
27
+ data() {
28
+ return {
29
+ copyCount: 0,
30
+ duplicateIcon,
31
+ validCopyElements,
32
+ };
33
+ },
34
+ methods: {
35
+ duplicateElement() {
36
+ this.$emit('duplicate-element', this.node, ++this.copyCount);
37
+ },
38
+ },
39
+ };
40
+ </script>
@@ -50,6 +50,11 @@
50
50
  v-on="$listeners"
51
51
  />
52
52
 
53
+ <duplicate-button
54
+ :node="node"
55
+ v-on="$listeners"
56
+ />
57
+
53
58
  <delete-button
54
59
  :graph="graph"
55
60
  :shape="shape"
@@ -79,6 +84,7 @@ import GenericFlowButton from '@/components/crown/crownButtons/genericFlowButton
79
84
  import AssociationFlowButton from '@/components/crown/crownButtons/associationFlowButton';
80
85
  import DataAssociationFlowButton from '@/components/crown/crownButtons/dataAssociationFlowButton';
81
86
  import CopyButton from '@/components/crown/crownButtons/copyButton.vue';
87
+ import DuplicateButton from '@/components/crown/crownButtons/duplicateButton.vue';
82
88
  import CrownDropdowns from '@/components/crown/crownButtons/crownDropdowns';
83
89
  import DefaultFlow from '@/components/crown/crownButtons/defaultFlowButton.vue';
84
90
  import poolLaneCrownConfig from '@/mixins/poolLaneCrownConfig';
@@ -95,6 +101,7 @@ export default {
95
101
  GenericFlowButton,
96
102
  AssociationFlowButton,
97
103
  CopyButton,
104
+ DuplicateButton,
98
105
  DefaultFlow,
99
106
  DataAssociationFlowButton,
100
107
  },
@@ -42,11 +42,18 @@ export default {
42
42
  nodeToReplace: null,
43
43
  buttons: [
44
44
  {
45
- label: 'Copy Element',
46
- icon: 'copy',
45
+ label: 'Copy Seletion',
46
+ icon: 'clipboard',
47
47
  testId: 'copy-button',
48
48
  role: 'menuitem',
49
- action: this.copyElement,
49
+ action: this.copySelection,
50
+ },
51
+ {
52
+ label: 'Duplicate Selection',
53
+ icon: 'copy',
54
+ testId: 'duplicate-button',
55
+ role: 'menuitem',
56
+ action: this.duplicateSelection,
50
57
  },
51
58
  {
52
59
  label: 'Delete Element',
@@ -70,9 +77,11 @@ export default {
70
77
  highlightedShapes: () => store.getters.highlightedShapes,
71
78
  },
72
79
  methods: {
73
- copyElement() {
74
- // @todo: Implement copyElement
75
- this.$emit('copy-nodes');
80
+ copySelection() {
81
+ this.$emit('copy-selection');
82
+ },
83
+ duplicateSelection() {
84
+ this.$emit('duplicate-selection');
76
85
  },
77
86
  deleteElement() {
78
87
  this.$emit('remove-nodes');
@@ -0,0 +1,23 @@
1
+ export default {
2
+ methods: {
3
+ copyPasteHandler(event, options) {
4
+ const isCopy = event.key === 'c';
5
+ const isPaste = event.key === 'v';
6
+
7
+ if (isCopy && options.mod) {
8
+ this.copy(event);
9
+ }
10
+ if (isPaste && options.mod) {
11
+ this.paste(event);
12
+ }
13
+ },
14
+ copy(event) {
15
+ event.preventDefault();
16
+ window.ProcessMaker.$modeler.copyElement();
17
+ },
18
+ paste(event) {
19
+ event.preventDefault();
20
+ window.ProcessMaker.$modeler.pasteElements();
21
+ },
22
+ },
23
+ };
@@ -1,9 +1,10 @@
1
1
  import ZoomInOut from './zoomInOut';
2
+ import CopyPaste from './copyPaste.js';
2
3
  import store from '@/store';
3
4
  import moveShapeByKeypress from './moveWithArrowKeys';
4
5
 
5
6
  export default {
6
- mixins: [ZoomInOut],
7
+ mixins: [ZoomInOut, CopyPaste],
7
8
  mounted() {
8
9
  document.addEventListener('keydown', this.keydownListener);
9
10
  document.addEventListener('keyup', this.keyupListener);
@@ -12,6 +13,7 @@ export default {
12
13
  handleHotkeys(event, options) {
13
14
  // Pass event to all handlers
14
15
  this.zoomInOutHandler(event, options);
16
+ this.copyPasteHandler(event, options);
15
17
  },
16
18
  keyupListener(event) {
17
19
  if (event.code === 'Space') {
@@ -2,6 +2,7 @@
2
2
  <transition name="inspector">
3
3
  <b-col
4
4
  v-show="!compressed"
5
+ id="inspector"
5
6
  class="pl-0 h-100 overflow-hidden inspector-column"
6
7
  :class="[{ 'ignore-pointer': canvasDragPosition, 'inspector-column-compressed' : compressed }]"
7
8
  data-test="inspector-column"
@@ -103,6 +103,10 @@
103
103
  @replace-node="replaceNode"
104
104
  @replace-generic-flow="replaceGenericFlow"
105
105
  @copy-element="copyElement"
106
+ @copy-selection="copyElement"
107
+ @paste-element="pasteElements"
108
+ @duplicate-element="duplicateElement"
109
+ @duplicate-selection="duplicateSelection"
106
110
  @default-flow="toggleDefaultFlow"
107
111
  @shape-resize="shapeResize"
108
112
  />
@@ -112,6 +116,7 @@
112
116
  :graph="graph"
113
117
  :paperManager="paperManager"
114
118
  :useModelGeometry="false"
119
+ @duplicate-selection="duplicateSelection"
115
120
  @remove-nodes="removeNodes"
116
121
  :processNode="processNode"
117
122
  @save-state="pushToUndoStack"
@@ -188,6 +193,7 @@ export default {
188
193
  mixins: [hotkeys],
189
194
  data() {
190
195
  return {
196
+ internalClipboard: [],
191
197
  tooltipTarget: null,
192
198
 
193
199
  /* Custom parsers for handling certain bpmn node types */
@@ -285,6 +291,7 @@ export default {
285
291
  currentXML() {
286
292
  return undoRedoStore.getters.currentState;
287
293
  },
294
+ copiedElements: () => store.getters.copiedElements,
288
295
  /* connectors expect a highlightedNode property */
289
296
  highlightedNode: () => store.getters.highlightedNodes[0],
290
297
  highlightedNodes: () => store.getters.highlightedNodes,
@@ -308,13 +315,112 @@ export default {
308
315
  }
309
316
  source.set('default', flow);
310
317
  },
311
- copyElement(node, copyCount) {
318
+ duplicateElement(node, copyCount) {
312
319
  const clonedNode = node.clone(this.nodeRegistry, this.moddle, this.$t);
313
320
  const yOffset = (node.diagram.bounds.height + 30) * copyCount;
314
321
 
315
322
  clonedNode.diagram.bounds.y += yOffset;
316
323
  this.addNode(clonedNode);
317
324
  },
325
+ copyElement() {
326
+ // Checking if User selected a single flow and tries to copy it, to deny it.
327
+ const flows = [
328
+ sequenceFlowId,
329
+ dataOutputAssociationFlowId,
330
+ dataInputAssociationFlowId,
331
+ genericFlowId,
332
+ ];
333
+ if (this.highlightedNodes.length === 1 && flows.includes(this.highlightedNodes[0].type)) return;
334
+ store.commit('setCopiedElements', this.cloneSelection());
335
+ this.$bvToast.toast(this.$t('Object(s) have been copied'), { noCloseButton:true, variant: 'success', solid: true, toaster: 'b-toaster-top-center' });
336
+ },
337
+ async pasteElements() {
338
+ if (this.copiedElements) {
339
+ await this.addClonedNodes(this.copiedElements);
340
+ this.$refs.selector.selectElements(this.findViewElementsFromNodes(this.copiedElements));
341
+ store.commit('setCopiedElements', this.cloneSelection());
342
+ }
343
+ },
344
+ cloneSelection() {
345
+ let clonedNodes = [], clonedFlows = [], originalFlows = [];
346
+ const nodes = this.highlightedNodes;
347
+ const selector = this.$refs.selector.$el;
348
+ const { height: sheight } = selector.getBoundingClientRect();
349
+ if (typeof selector.getBoundingClientRect === 'function') {
350
+ // get selector height
351
+ nodes.forEach(node => {
352
+ // Add flows described in the definitions property
353
+ if (node.definition.incoming || node.definition.outgoing) {
354
+ // Since both incoming and outgoing reference the same flow, any of them is copied
355
+ let flowsToCopy = [...(node.definition.incoming || node.definition.outgoing)];
356
+ // Check if flow is already in array before pushing
357
+ flowsToCopy.forEach(flow => {
358
+ if (!originalFlows.some(el => el.id === flow.id)) {
359
+ originalFlows.push(flow);
360
+ }
361
+ });
362
+ }
363
+
364
+ // Check node type to clone
365
+ if ([
366
+ sequenceFlowId,
367
+ laneId,
368
+ associationId,
369
+ messageFlowId,
370
+ dataOutputAssociationFlowId,
371
+ dataInputAssociationFlowId,
372
+ genericFlowId,
373
+ ].includes(node.type)) {
374
+ // Add offset for all waypoints on cloned flow
375
+ const clonedFlow = node.cloneFlow(this.nodeRegistry, this.moddle, this.$t);
376
+ clonedFlow.setIds(this.nodeIdGenerator);
377
+ clonedFlows.push(clonedFlow);
378
+ clonedNodes.push(clonedFlow);
379
+ } else {
380
+ // Clone node and calculate offset
381
+ const clonedNode = node.clone(this.nodeRegistry, this.moddle, this.$t);
382
+ const yOffset = sheight;
383
+ clonedNode.diagram.bounds.y += yOffset;
384
+ // Set cloned node id
385
+ clonedNode.setIds(this.nodeIdGenerator);
386
+ clonedNodes.push(clonedNode);
387
+ }
388
+ });
389
+ }
390
+ // Connect flows
391
+ clonedFlows.forEach(flow => {
392
+ // Look up the original flow
393
+ const flowClonedFrom = { definition: originalFlows.find(el => el.id === flow.definition.cloneOf) };
394
+ // Get the id's of the sourceRef and targetRef of original flow
395
+ const src = flowClonedFrom.definition.sourceRef;
396
+ const target = flowClonedFrom.definition.targetRef;
397
+ const srcClone = clonedNodes.find(node => node.definition.cloneOf === src.id);
398
+ const targetClone = clonedNodes.find(node => node.definition.cloneOf === target.id);
399
+ // Reference the elements to the flow that connects them
400
+ flow.definition.sourceRef = srcClone.definition;
401
+ flow.definition.targetRef = targetClone.definition;
402
+ // Reference the flow to the elements that are connected by it
403
+ srcClone.definition.outgoing ? srcClone.definition.outgoing.push(flow.definition) : srcClone.definition.outgoing = [flow.definition];
404
+ targetClone.definition.incoming ? targetClone.definition.incoming.push(flow.definition) : targetClone.definition.incoming = [flow.definition];
405
+ // Translate flow waypoints to where they should be
406
+ flow.diagram.waypoint.forEach(point => {
407
+ point.y += sheight;
408
+ });
409
+ });
410
+ return clonedNodes;
411
+ },
412
+ async duplicateSelection() {
413
+ const clonedNodes = this.cloneSelection();
414
+ await this.addClonedNodes(clonedNodes);
415
+ this.$refs.selector.selectElements(this.findViewElementsFromNodes(clonedNodes));
416
+ },
417
+ findViewElementsFromNodes(nodes) {
418
+ return nodes.map(node => {
419
+ const component = this.$refs.nodeComponent.find(cmp => cmp.node === node);
420
+ const shape = component.shape;
421
+ return this.paper.findViewByModel(shape);
422
+ });
423
+ },
318
424
  async close() {
319
425
  this.$emit('close');
320
426
  },
@@ -851,6 +957,22 @@ export default {
851
957
  });
852
958
  });
853
959
  },
960
+ async addClonedNodes(nodes) {
961
+ nodes.forEach(node => {
962
+ if (!node.pool) {
963
+ node.pool = this.poolTarget;
964
+ }
965
+
966
+ const targetProcess = node.getTargetProcess(this.processes, this.processNode);
967
+ addNodeToProcess(node, targetProcess);
968
+
969
+ this.planeElements.push(node.diagram);
970
+ store.commit('addNode', node);
971
+ this.poolTarget = null;
972
+ });
973
+
974
+ await this.pushToUndoStack();
975
+ },
854
976
  async removeNode(node, { removeRelationships = true } = {}) {
855
977
  if (removeRelationships) {
856
978
  removeNodeFlows(node, this);
@@ -1153,7 +1275,13 @@ export default {
1153
1275
  }, this);
1154
1276
 
1155
1277
  this.$el.addEventListener('mousemove', event => {
1278
+ const { clientX, clientY } = event;
1156
1279
  this.pointerMoveHandler(event);
1280
+ store.commit('setClientMousePosition', { clientX, clientY });
1281
+ });
1282
+
1283
+ this.$el.addEventListener('mouseleave', () => {
1284
+ store.commit('clientLeftPaper');
1157
1285
  });
1158
1286
 
1159
1287
  this.paperManager.addEventHandler('cell:pointerclick', (cellView, evt, x, y) => {
@@ -29,6 +29,13 @@ import { id as poolId } from '@/components/nodes/pool/config';
29
29
  import { id as laneId } from '@/components/nodes/poolLane/config';
30
30
  import { id as genericFlowId } from '@/components/nodes/genericFlow/config';
31
31
  import { labelWidth, poolPadding } from '../nodes/pool/poolSizes';
32
+ const boundaryElements = [
33
+ 'processmaker-modeler-boundary-timer-event',
34
+ 'processmaker-modeler-boundary-error-event',
35
+ 'processmaker-modeler-boundary-signal-event',
36
+ 'processmaker-modeler-boundary-conditional-event',
37
+ 'processmaker-modeler-boundary-message-event',
38
+ ];
32
39
  export default {
33
40
  name: 'Selection',
34
41
  components: {
@@ -85,6 +92,17 @@ export default {
85
92
  },
86
93
  },
87
94
  methods: {
95
+ async selectElements(elements) {
96
+ await this.$nextTick();
97
+ this.clearSelection();
98
+ this.selected = elements;
99
+ this.showLasso = true;
100
+ this.isSelected = true;
101
+ this.isSelecting = true;
102
+ this.start = null;
103
+ await this.$nextTick();
104
+ this.updateSelectionBox();
105
+ },
88
106
  /**
89
107
  * Select an element dinamically.
90
108
  * Shift key will manage the condition to push to selection
@@ -321,19 +339,36 @@ export default {
321
339
  * Filter the selected elements
322
340
  */
323
341
  filterSelected() {
324
- // remove from selection the selected child nodes in the pool
342
+ // Get the selected pools IDs
325
343
  const selectedPoolsIds = this.selected
326
344
  .filter(shape => shape.model.component)
327
345
  .filter(shape => shape.model.component.node.type === 'processmaker-modeler-pool')
328
346
  .map(shape => shape.model.component.node.id);
347
+ // remove from selection the selected children that belongs to a selected pool
329
348
  this.selected = this.selected.filter(shape => {
330
349
  if (shape.model.component && shape.model.component.node.pool) {
331
350
  return shape.model.component.node.pool && !selectedPoolsIds.includes(shape.model.component.node.pool.component.node.id);
332
351
  }
333
352
  return true;
334
- }).filter(shape => {
335
- return !(shape.model.getParentCell() && shape.model.getParentCell().get('parent'));
336
353
  });
354
+ // A boundary event could only be selected alone
355
+ const firstSelectedBoundary = this.selected.find(shape => {
356
+ return shape.model.component &&
357
+ boundaryElements.includes(shape.model.component.node.type);
358
+ });
359
+ const firstSelectedElement = this.selected[0];
360
+ if (firstSelectedBoundary) {
361
+ this.selected = this.selected.filter(shape => {
362
+ if (firstSelectedElement === firstSelectedBoundary) {
363
+ // boundary event selected alone
364
+ return shape.model.component &&
365
+ shape === firstSelectedBoundary;
366
+ }
367
+ // do not allow to select a boundary event with another element
368
+ return shape.model.component &&
369
+ !boundaryElements.includes(shape.model.component.node.type);
370
+ });
371
+ }
337
372
  },
338
373
  /**
339
374
  * Pan paper handler
@@ -377,7 +412,7 @@ export default {
377
412
  return shapes && selected.length === shapes.length;
378
413
  },
379
414
  /**
380
- * Start the drag procedure for the selext box
415
+ * Start the drag procedure for the select box
381
416
  * @param {Object} event
382
417
  */
383
418
  startDrag(event) {
@@ -11,6 +11,7 @@ import cloneDeep from 'lodash/cloneDeep';
11
11
  export default class Node {
12
12
  static diagramPropertiesToCopy = ['x', 'y', 'width', 'height'];
13
13
  static definitionPropertiesToNotCopy = ['$type', 'id'];
14
+ static flowDefinitionPropertiesToNotCopy = ['$type', 'id', 'sourceRef', 'targetRef'];
14
15
  static eventDefinitionPropertiesToNotCopy = ['errorRef', 'messageRef'];
15
16
 
16
17
  type;
@@ -89,6 +90,8 @@ export default class Node {
89
90
 
90
91
  clonedNode.id = null;
91
92
  clonedNode.pool = this.pool;
93
+ clonedNode.definition.cloneOf = this.id;
94
+
92
95
  Node.diagramPropertiesToCopy.forEach(prop => clonedNode.diagram.bounds[prop] = this.diagram.bounds[prop]);
93
96
  Object.keys(this.definition).filter(key => !Node.definitionPropertiesToNotCopy.includes(key)).forEach(key => {
94
97
  const definition = this.definition.get(key);
@@ -112,6 +115,42 @@ export default class Node {
112
115
  return clonedNode;
113
116
  }
114
117
 
118
+ cloneFlow(nodeRegistry, moddle, $t) {
119
+ const definition = nodeRegistry[this.type].definition(moddle, $t);
120
+ const diagram = nodeRegistry[this.type].diagram(moddle);
121
+ const clonedFlow = new this.constructor(this.type, definition, diagram);
122
+
123
+ clonedFlow.id = null;
124
+ clonedFlow.pool = this.pool;
125
+ clonedFlow.definition.cloneOf = this.id;
126
+ clonedFlow.diagram.waypoint = [];
127
+
128
+ this.diagram.waypoint.forEach(point => clonedFlow.diagram.waypoint.push(point));
129
+
130
+ Object.keys(this.definition).filter(key => !Node.flowDefinitionPropertiesToNotCopy.includes(key)).forEach(key => {
131
+ const definition = this.definition.get(key);
132
+ const clonedDefinition = typeof definition === 'object' ? cloneDeep(definition) : definition;
133
+ if (key === 'eventDefinitions') {
134
+ for (var i in clonedDefinition) {
135
+ if (definition[i].signalRef && !clonedDefinition[i].signalRef) {
136
+ clonedDefinition[i].signalRef = { ...definition[i].signalRef };
137
+ }
138
+ }
139
+ }
140
+ clonedFlow.definition.set(key, clonedDefinition);
141
+ clonedFlow.definition.sourceRef = clonedFlow.definition.targetRef = null;
142
+ });
143
+
144
+ Node.eventDefinitionPropertiesToNotCopy.forEach(
145
+ prop => clonedFlow.definition.eventDefinitions &&
146
+ clonedFlow.definition.eventDefinitions[0] &&
147
+ clonedFlow.definition.eventDefinitions[0].hasOwnProperty(prop) &&
148
+ clonedFlow.definition.eventDefinitions[0].set(prop, null)
149
+ );
150
+
151
+ return clonedFlow;
152
+ }
153
+
115
154
  getTargetProcess(processes, processNode) {
116
155
  return this.pool
117
156
  ? processes.find(({ id }) => id === this.pool.component.node.definition.get('processRef').id)
@@ -101,7 +101,7 @@
101
101
  </span>
102
102
  </div>
103
103
  <a
104
- class="btn btn-sm btn-primary mini-map-btn text-uppercase mx-2"
104
+ class="btn btn-sm btn-primary autosave-btn text-uppercase mx-2"
105
105
  data-test="publish-btn"
106
106
  :title="$t('Publish')"
107
107
  @click="$emit('saveBpmn')"
@@ -109,7 +109,7 @@
109
109
  {{ $t('Publish') }}
110
110
  </a>
111
111
  <a
112
- class="btn btn-sm btn-link toolbar-item mini-map-btn text-black text-uppercase"
112
+ class="btn btn-sm btn-link toolbar-item autosave-btn text-black text-uppercase"
113
113
  data-test="close-btn"
114
114
  :title="$t('Close')"
115
115
  @click="$emit('close')"
@@ -117,9 +117,11 @@
117
117
  {{ $t('Close') }}
118
118
  </a>
119
119
  <EllipsisMenu
120
- @navigate="onNavigate"
121
120
  :actions="ellipsisMenuActions"
122
121
  :divider="false"
122
+ @navigate="onNavigate"
123
+ @show="onShow"
124
+ @hide="onHide"
123
125
  />
124
126
  </template>
125
127
  <b-button
@@ -258,6 +260,18 @@ export default {
258
260
  break;
259
261
  }
260
262
  },
263
+ onShow() {
264
+ const inspectorDiv = document.getElementById('inspector');
265
+ if (inspectorDiv) {
266
+ inspectorDiv.style.zIndex = '1';
267
+ }
268
+ },
269
+ onHide() {
270
+ const inspectorDiv = document.getElementById('inspector');
271
+ if (inspectorDiv) {
272
+ inspectorDiv.style.zIndex = '2';
273
+ }
274
+ },
261
275
  },
262
276
  };
263
277
  </script>
@@ -19,3 +19,10 @@ export default {
19
19
  },
20
20
  };
21
21
  </script>
22
+ <style lang="css" scoped>
23
+ @media screen and (max-width: 1365px) {
24
+ #breadcrumbs {
25
+ width: 290px;
26
+ }
27
+ }
28
+ </style>
@@ -27,3 +27,14 @@ $toolbar-background-color: #fff;
27
27
  .cursor-default {
28
28
  cursor: default !important;
29
29
  }
30
+
31
+ .autosave-btn {
32
+ display: flex;
33
+ justify-content: center;
34
+ align-items: center;
35
+ }
36
+
37
+ .btn-ellipsis {
38
+ border-top-left-radius: 4px !important;
39
+ border-bottom-left-radius: 4px !important;
40
+ }
package/src/store.js CHANGED
@@ -35,6 +35,9 @@ export default new Vuex.Store({
35
35
  autoValidate: false,
36
36
  globalProcesses: [],
37
37
  allowSavingElementPosition: true,
38
+ copiedElements: [],
39
+ clientX: null,
40
+ clientY: null,
38
41
  },
39
42
  getters: {
40
43
  nodes: state => state.nodes,
@@ -52,6 +55,9 @@ export default new Vuex.Store({
52
55
  globalProcesses: state => state.globalProcesses,
53
56
  globalProcessEvents: (state, getters) => flatten(getters.globalProcesses.map(process => process.events)),
54
57
  allowSavingElementPosition: state => state.allowSavingElementPosition,
58
+ copiedElements: state => state.copiedElements,
59
+ clientX: state => state.clientX,
60
+ clientY: state => state.clientY,
55
61
  },
56
62
  mutations: {
57
63
  preventSavingElementPosition(state) {
@@ -138,6 +144,18 @@ export default new Vuex.Store({
138
144
  setGlobalProcesses(state, globalProcesses) {
139
145
  state.globalProcesses = globalProcesses;
140
146
  },
147
+ // Copy Nodes to the clipboard or in this case, to the state
148
+ setCopiedElements(state, elements) {
149
+ state.copiedElements = elements;
150
+ },
151
+ setClientMousePosition(state, position) {
152
+ const { clientX, clientY } = position;
153
+ state = { clientX, clientY };
154
+ },
155
+ clientLeftPaper(state) {
156
+ state.clientX = null;
157
+ state.clientY = null;
158
+ },
141
159
  },
142
160
  actions: {
143
161
  async fetchGlobalProcesses({ commit }) {