@processmaker/screen-builder 2.99.3 → 3.0.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.
Files changed (48) hide show
  1. package/dist/vue-form-builder.css +1 -1
  2. package/dist/vue-form-builder.es.js +9091 -7132
  3. package/dist/vue-form-builder.es.js.map +1 -1
  4. package/dist/vue-form-builder.umd.js +53 -53
  5. package/dist/vue-form-builder.umd.js.map +1 -1
  6. package/package.json +2 -1
  7. package/src/App.vue +14 -2
  8. package/src/DataProvider.js +42 -1
  9. package/src/VariableDataTypeProperties.js +1 -1
  10. package/src/components/ClipboardButton.vue +77 -0
  11. package/src/components/CssIcon.vue +21 -0
  12. package/src/components/ScreenTemplateCard.vue +257 -0
  13. package/src/components/ScreenTemplates.vue +216 -0
  14. package/src/components/ScreenToolbar.vue +24 -2
  15. package/src/components/SelectUserGroup.vue +274 -0
  16. package/src/components/TabsBar.vue +47 -1
  17. package/src/components/accordions.js +7 -1
  18. package/src/components/editor/loop.vue +22 -1
  19. package/src/components/editor/multi-column.vue +22 -2
  20. package/src/components/editor/pagesDropdown.vue +20 -2
  21. package/src/components/index.js +7 -1
  22. package/src/components/inspector/collection-data-source.vue +200 -0
  23. package/src/components/inspector/collection-display-mode.vue +87 -0
  24. package/src/components/inspector/collection-records-list.vue +156 -0
  25. package/src/components/inspector/column-setup.vue +123 -7
  26. package/src/components/inspector/encrypted-config.vue +78 -0
  27. package/src/components/inspector/index.js +4 -0
  28. package/src/components/inspector/page-select.vue +1 -0
  29. package/src/components/renderer/file-upload.vue +136 -3
  30. package/src/components/renderer/form-collection-record-control.vue +248 -0
  31. package/src/components/renderer/form-collection-view-control.vue +236 -0
  32. package/src/components/renderer/form-masked-input.vue +194 -9
  33. package/src/components/renderer/form-record-list.vue +271 -69
  34. package/src/components/renderer/index.js +2 -0
  35. package/src/components/screen-renderer.vue +2 -0
  36. package/src/components/task.vue +2 -1
  37. package/src/components/vue-form-builder.vue +156 -22
  38. package/src/components/vue-form-renderer.vue +10 -2
  39. package/src/form-builder-controls.js +168 -21
  40. package/src/global-properties.js +8 -0
  41. package/src/main.js +60 -1
  42. package/src/mixins/Clipboard.js +153 -0
  43. package/src/mixins/ScreenBase.js +7 -1
  44. package/src/mixins/index.js +1 -0
  45. package/src/store/modules/ClipboardManager.js +79 -0
  46. package/src/store/modules/clipboardModule.js +210 -0
  47. package/src/stories/ClipboardButton.stories.js +66 -0
  48. package/src/stories/PagesDropdown.stories.js +11 -8
@@ -0,0 +1,236 @@
1
+ <template>
2
+ <vue-form-renderer
3
+ ref="collectionViewControl"
4
+ class="form-collection-record-control"
5
+ :placeholder="placeholder"
6
+ v-model="data"
7
+ mode="preview"
8
+ :config="validatedConfig"
9
+ :computed="computed"
10
+ :custom-css="customCss"
11
+ :watchers="watchers"
12
+ :_parent="_parent"
13
+ />
14
+ </template>
15
+
16
+ <script>
17
+ import VueFormRenderer from "../vue-form-renderer.vue";
18
+
19
+ const globalObject = typeof window === "undefined" ? global : window;
20
+
21
+ const defaultConfig = [
22
+ {
23
+ name: "empty",
24
+ items: []
25
+ }
26
+ ];
27
+
28
+ export default {
29
+ components: {
30
+ VueFormRenderer
31
+ },
32
+ props: {
33
+ name: String,
34
+ validationData: null,
35
+ _parent: null,
36
+ record: null,
37
+ collection: {
38
+ type: Object
39
+ },
40
+ taskdraft: Object,
41
+ },
42
+ data() {
43
+ return {
44
+ localData: {},
45
+ config: defaultConfig,
46
+ computed: [],
47
+ customCSS: null,
48
+ watchers: [],
49
+ screenTitle: null,
50
+ selCollectionId: Number,
51
+ selRecordId: Number,
52
+ selDisplayMode: String,
53
+ screenCollectionId: null,
54
+ placeholder: "Select a collection",
55
+ screenType: "",
56
+ hasMustache: false,
57
+ flagDraft: {},
58
+ taskDraft: {},
59
+ collectionmode: "View"
60
+ };
61
+ },
62
+ computed: {
63
+ validatedConfig() {
64
+ return this.config && this.config[0] ? this.config : defaultConfig;
65
+ },
66
+ data: {
67
+ get() {
68
+ if(this.hasMustache) {
69
+ this.clearDataObject();
70
+ }
71
+ return this.localData;
72
+ },
73
+ set(data) {
74
+ Object.keys(data).forEach((variable) => {
75
+ this.validationData && this.$set(this.validationData, variable, data[variable]);
76
+ });
77
+
78
+ if (this.collection) {
79
+ this.$set(this.collection, 'data', Array.isArray(data) ? data : [data]);
80
+ this.$set(this.collection, 'screen', this.screenCollectionId);
81
+ }
82
+ },
83
+ },
84
+ },
85
+ methods: {
86
+ isSubmitButton(item) {
87
+ return (
88
+ item.config &&
89
+ item.component === "FormButton" &&
90
+ item.config.event === "submit"
91
+ );
92
+ },
93
+ hideSubmitButtons(config) {
94
+ config.forEach((item) => {
95
+ //If the element has containers
96
+ if (Array.isArray(item)) {
97
+ this.hideSubmitButtons(item);
98
+ }
99
+
100
+ //If the element has items
101
+ if (item.items) {
102
+ this.hideSubmitButtons(item.items);
103
+ }
104
+
105
+ //hidden buttons
106
+ if (this.isSubmitButton(item)) {
107
+ item.config.hidden = true;
108
+ }
109
+ });
110
+ },
111
+ disableForm(config) {
112
+ config.forEach((item) => {
113
+ //If the element has containers
114
+ if (Array.isArray(item)) {
115
+ this.disableForm(item);
116
+ }
117
+
118
+ //If the element has items
119
+ if (item.items) {
120
+ this.disableForm(item.items);
121
+ }
122
+
123
+ //Disable element
124
+ if (item && item.config) {
125
+ item.config.disabled = true;
126
+ item.config.readonly = true;
127
+ item.config.editable = false;
128
+ }
129
+ });
130
+ },
131
+ loadScreen(id) {
132
+ this.config = defaultConfig;
133
+ this.computed = [];
134
+ this.customCSS = null;
135
+ this.watchers = [];
136
+ this.screenTitle = null;
137
+
138
+ if (id) {
139
+ this.$dataProvider.getScreen(id).then((response) => {
140
+ this.config = response.data.config;
141
+ this.hideSubmitButtons(this.config);
142
+ this.computed = response.data.computed;
143
+ this.customCSS = response.data.custom_css;
144
+ this.watchers = response.data.watchers;
145
+ this.screenTitle = response.data.title;
146
+
147
+ if (this.$attrs["disabled"]) {
148
+ this.disableForm(this.config);
149
+ }
150
+ });
151
+ }
152
+ },
153
+ callbackRecord() {
154
+ this.hasMustache = true;
155
+ this.loadRecordCollection(this.selCollectionId, 1, this.selDisplayMode);
156
+ },
157
+ errors() {
158
+ this.$refs.nestedScreen.isValid();
159
+ return this.$refs.nestedScreen.errors;
160
+ },
161
+ loadRecordCollection(collectionId, recordId, modeId) {
162
+ this.selCollectionId = collectionId;
163
+ this.selRecordId = recordId;
164
+ this.selDisplayMode = modeId;
165
+
166
+ this.$dataProvider
167
+ .getCollectionRecordsView(collectionId, recordId)
168
+ .then((response) => {
169
+ this.placeholder = "";
170
+ const respData = response.data;
171
+ const viewScreen = response.collection.read_screen_id;
172
+ //Choose screen id regarding of the display Mode
173
+ this.screenCollectionId = viewScreen;
174
+ this.loadScreen(this.screenCollectionId);
175
+
176
+ //This section validates if Collection has draft data
177
+ if(this.taskDraft?.draft?.data == null || this.taskDraft.draft.data === '') {
178
+ this.localData = respData;
179
+ }else{
180
+ this.localData = this.taskDraft.draft.data;
181
+ }
182
+
183
+ })
184
+ .catch(() => {
185
+ this.localData = {};
186
+ globalObject.ProcessMaker.alert(this.$t('This content does not exist. We could not locate indicated data'), "danger");
187
+ });;
188
+ },
189
+ isMustache(record) {
190
+ return /\{\{.*\}\}/.test(record);
191
+ },
192
+ clearDataObject() {
193
+ Object.keys(this.localData).forEach(key => {
194
+ if (key !== "id") {
195
+ this.localData[key] = "";
196
+ }
197
+ });
198
+ },
199
+ },
200
+ watch: {
201
+ collection(collection) {
202
+ if(collection) {
203
+ this.selCollectionId = collection.collectionId;
204
+ this.$root.$emit("collection-screen-mode", "display");
205
+ }
206
+ },
207
+ record(record) {
208
+ this.hasMustache = false;
209
+ if (record && !isNaN(record) && record > 0 && this.collection) {
210
+ this.selRecordId = record;
211
+ this.loadRecordCollection(this.selCollectionId, record, this.collectionmode);
212
+ } else {
213
+ if (this.isMustache(record)) {
214
+ this.callbackRecord();
215
+ }
216
+ this.localData = {};
217
+ }
218
+ }
219
+ },
220
+ mounted() {
221
+ this.$root.$on("taskdraft-input", (val)=>{
222
+ this.taskDraft = val;
223
+ });
224
+
225
+ if (this.collection && this.record) {
226
+ this.loadRecordCollection(this.collection.collectionId, this.record, this.collectionmode);
227
+ }
228
+ },
229
+ };
230
+ </script>
231
+
232
+ <style lang="scss">
233
+ .prevent-interaction.form-collection-view-control::after {
234
+ content: attr(placeholder);
235
+ }
236
+ </style>
@@ -3,7 +3,7 @@
3
3
  <required-asterisk /><label v-uni-for="name">{{ label }}</label>
4
4
  <component
5
5
  :is="componentType"
6
- v-if="componentType !== 'input'"
6
+ v-if="componentType !== 'input' && !encryptedConfig?.encrypted"
7
7
  v-model="localValue"
8
8
  v-bind="componentConfigComputed"
9
9
  v-uni-id="name"
@@ -14,7 +14,7 @@
14
14
  @change="onChange"
15
15
  />
16
16
  <input
17
- v-else
17
+ v-else-if="componentType === 'input' && !encryptedConfig?.encrypted"
18
18
  v-model="localValue"
19
19
  v-bind="componentConfig"
20
20
  v-uni-id="name"
@@ -25,6 +25,37 @@
25
25
  :maxlength="maxlength"
26
26
  @change="onChange"
27
27
  />
28
+ <div class="div-data-container" v-else-if="encryptedConfig?.encrypted">
29
+ <component
30
+ :is="componentType"
31
+ v-if="componentType !== 'input'"
32
+ v-model="localValue"
33
+ v-bind="componentConfigComputed"
34
+ v-uni-id="name"
35
+ :name="name"
36
+ class="form-control"
37
+ :class="classList"
38
+ :type="inputType"
39
+ @change="onChange"
40
+ />
41
+ <input
42
+ v-else
43
+ v-model="localValue"
44
+ v-bind="componentConfig"
45
+ v-uni-id="name"
46
+ :name="name"
47
+ class="form-control"
48
+ :class="classList"
49
+ :type="inputType"
50
+ :maxlength="maxlength"
51
+ @change="onChange"
52
+ />
53
+ <b-button variant="outline-secondary conceal-reveal-btn" @click="concealOrReveal">
54
+ <i :class="iconBtn"></i>
55
+ <span> {{ labelBtn }} </span>
56
+ </b-button>
57
+ </div>
58
+
28
59
  <template v-if="validator && validator.errorCount">
29
60
  <div
30
61
  v-for="(errors, index) in validator.errors.all()"
@@ -37,6 +68,7 @@
37
68
  </div>
38
69
  </template>
39
70
  <div v-if="error" class="invalid-feedback">{{ error }}</div>
71
+ <div v-if="encryptedConfig?.encrypted && errorEncryptedField" class="invalid-feedback d-block">{{ errorEncryptedField }}</div>
40
72
  <small v-if="helper" class="form-text text-muted">{{ helper }}</small>
41
73
  </div>
42
74
  </template>
@@ -52,6 +84,7 @@ import {
52
84
  ValidationMixin
53
85
  } from "@processmaker/vue-form-elements";
54
86
  import Inputmasked from "./form-input-masked.vue";
87
+ import { validate as uuidValidate } from 'uuid';
55
88
 
56
89
  const uniqIdsMixin = createUniqIdsMixin();
57
90
  const componentTypes = {
@@ -86,7 +119,8 @@ export default {
86
119
  // these should not be passed by $attrs
87
120
  "transientData",
88
121
  "formConfig",
89
- "form-watchers"
122
+ "form-watchers",
123
+ "encryptedConfig",
90
124
  ],
91
125
  data() {
92
126
  const { dataFormat, customFormatter } = this.config;
@@ -190,20 +224,35 @@ export default {
190
224
  localValue: null,
191
225
  validationRules: {
192
226
  percentage: "regex:/^[+-]?\\d+(\\.\\d+)?$/"
193
- }
227
+ },
228
+ // For encrypted field
229
+ inputType: '',
230
+ iconBtn: '',
231
+ labelBtn: '',
232
+ errorEncryptedField: '',
233
+ concealExecuted: false,
194
234
  };
195
235
  },
196
236
  computed: {
197
237
  classList() {
198
238
  return {
199
239
  "is-invalid":
200
- (this.validator && this.validator.errorCount) || this.error,
240
+ (this.validator && this.validator.errorCount) || this.error || this.errorEncryptedField,
201
241
  [this.controlClass]: !!this.controlClass
202
242
  };
203
243
  },
204
244
  componentConfigComputed() {
205
245
  return JSON.parse(JSON.stringify(this.componentConfig));
206
- }
246
+ },
247
+ mode() {
248
+ return this.$root.$children[0].mode;
249
+ },
250
+ inStandAloneMode() {
251
+ return this.mode === 'preview' && window.exampleScreens !== undefined;
252
+ },
253
+ inPreviewMode() {
254
+ return this.mode === 'preview';
255
+ },
207
256
  },
208
257
  watch: {
209
258
  value(value) {
@@ -212,8 +261,22 @@ export default {
212
261
  }
213
262
  },
214
263
  localValue(value) {
215
- if (value !== this.value) {
216
- this.$emit("input", this.convertToData(value));
264
+ if (!this.encryptedConfig?.encrypted) {
265
+ // Normal behaviour
266
+ if (value !== this.value) {
267
+ this.$emit("input", this.convertToData(value));
268
+ }
269
+ } else {
270
+ // Encrypted field behaviour
271
+ if (!this.concealExecuted) {
272
+ if (value !== this.value) {
273
+ this.$emit("input", this.convertToData(value));
274
+ }
275
+ } else {
276
+ if (uuidValidate(value)) {
277
+ this.$emit("input", value);
278
+ }
279
+ }
217
280
  }
218
281
  }
219
282
  },
@@ -221,6 +284,26 @@ export default {
221
284
  if (this.value !== undefined) {
222
285
  this.localValue = this.value;
223
286
  }
287
+ /*
288
+ * Set initial atttributes for Conceal/Reveal button only if
289
+ * "encrypted" attribute is enabled
290
+ */
291
+ if (this.encryptedConfig?.encrypted) {
292
+ if (uuidValidate(this.localValue)) {
293
+ this.inputType = "password";
294
+ this.iconBtn = "fas fa-eye";
295
+ this.labelBtn = this.$t("Reveal");
296
+ this.concealExecuted = true;
297
+ this.componentConfig.readonly = true;
298
+ } else {
299
+ this.inputType = "text";
300
+ this.iconBtn = "fas fa-eye-slash";
301
+ this.labelBtn = this.$t("Conceal");
302
+ this.componentConfig.readonly = false;
303
+ }
304
+ } else {
305
+ this.inputType = this.dataType;
306
+ }
224
307
  },
225
308
  methods: {
226
309
  onChange() {
@@ -291,7 +374,109 @@ export default {
291
374
  date: ["####-##-##"],
292
375
  dateTime: ["####-##-## ##:##"]
293
376
  };
294
- }
377
+ },
378
+ afterEncrypt(encryptedValue) {
379
+ // Change controls appearance
380
+ this.inputType = "password";
381
+ this.iconBtn = "fas fa-eye";
382
+ this.labelBtn = this.$t("Reveal");
383
+ this.concealExecuted = true;
384
+ this.componentConfig.readonly = true;
385
+ this.errorEncryptedField = "";
386
+
387
+ // Assign uuid from encrypted data
388
+ this.localValue = encryptedValue;
389
+ },
390
+ afterDecrypt(decryptedValue) {
391
+ // Change controls appearance
392
+ this.inputType = this.dataType;
393
+ this.iconBtn = "fas fa-eye-slash";
394
+ this.labelBtn = this.$t("Conceal");
395
+ this.componentConfig.readonly = false;
396
+
397
+ // Assign value decrypted
398
+ this.localValue = decryptedValue;
399
+ },
400
+ concealOrReveal() {
401
+ // Execute only if "encrypted" attribute is enabled
402
+ if (this.encryptedConfig?.encrypted) {
403
+ if (this.inputType === "text") {
404
+ if (this.localValue !== "") {
405
+ if (this.inStandAloneMode || !this.inPreviewMode) {
406
+ // Build data to send
407
+ const dataToEncrypt = {
408
+ uuid: uuidValidate(this.value) ? this.value : null,
409
+ field_name: this.name,
410
+ plain_text: this.localValue,
411
+ screen_id: this.$root.task?.screen?.screen_id || window?.PM4ConfigOverrides?.authParams?.screen_id
412
+ };
413
+
414
+ // Call endpoint to encrypt data
415
+ window.ProcessMaker.apiClient
416
+ .post("/api/1.0/encrypted_data/encryptText", dataToEncrypt)
417
+ .then((response) => {
418
+ const v = response?.data !== undefined ? response?.data : response;
419
+ this.afterEncrypt(v);
420
+ })
421
+ .catch((err) => {
422
+ const { data } = err.response;
423
+ if (data.message) {
424
+ this.errorEncryptedField = data.message;
425
+ }
426
+ });
427
+ } else {
428
+ const response = this.localValue;
429
+ this.afterEncrypt(response);
430
+ }
431
+ } else {
432
+ this.errorEncryptedField = this.$t(
433
+ "The current value is empty but a value is required. Please provide a valid value."
434
+ );
435
+ }
436
+ } else {
437
+ if (this.inStandAloneMode || !this.inPreviewMode) {
438
+ // Build data to send
439
+ const dataToDecrypt = {
440
+ uuid: this.value,
441
+ field_name: this.name,
442
+ screen_id: this.$root.task?.screen?.screen_id || window?.PM4ConfigOverrides?.authParams?.screen_id
443
+ };
444
+
445
+ // Call endpoint to decrypt data
446
+ window.ProcessMaker.apiClient
447
+ .post("/api/1.0/encrypted_data/decryptText", dataToDecrypt)
448
+ .then((response) => {
449
+ const v = response?.data !== undefined ? response?.data : response;
450
+ this.afterDecrypt(v);
451
+ })
452
+ .catch((err) => {
453
+ const { data } = err.response;
454
+ if (data.message) {
455
+ this.errorEncryptedField = data.message;
456
+ }
457
+ });
458
+ } else {
459
+ const response = this.localValue;
460
+ this.afterDecrypt(response);
461
+ }
462
+ }
463
+ }
464
+ },
295
465
  }
296
466
  };
297
467
  </script>
468
+ <style scoped>
469
+ .div-data-container {
470
+ display: inline-flex;
471
+ width: 100%;
472
+ }
473
+ .conceal-reveal-btn {
474
+ width: 25%;
475
+ margin-left: 5px;
476
+ }
477
+ @media screen and (max-width: 1000px) {
478
+ .conceal-reveal-btn span {
479
+ display: none;
480
+ }
481
+ }
482
+ </style>