@processmaker/screen-builder 2.18.1 → 2.21.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/screen-builder",
3
- "version": "2.18.1",
3
+ "version": "2.21.0",
4
4
  "scripts": {
5
5
  "serve": "vue-cli-service serve",
6
6
  "build": "vue-cli-service build",
@@ -44,7 +44,7 @@
44
44
  "@cypress/code-coverage": "^3.8.1",
45
45
  "@fortawesome/fontawesome-free": "^5.6.1",
46
46
  "@panter/vue-i18next": "^0.15.2",
47
- "@processmaker/vue-form-elements": "0.26.2",
47
+ "@processmaker/vue-form-elements": "0.28.2",
48
48
  "@processmaker/vue-multiselect": "^2.2.0",
49
49
  "@vue/cli-plugin-babel": "^3.6.0",
50
50
  "@vue/cli-plugin-e2e-cypress": "^4.0.3",
@@ -85,7 +85,7 @@
85
85
  },
86
86
  "peerDependencies": {
87
87
  "@panter/vue-i18next": "^0.15.0",
88
- "@processmaker/vue-form-elements": "0.26.1",
88
+ "@processmaker/vue-form-elements": "0.28.1",
89
89
  "i18next": "^15.0.8",
90
90
  "vue": "^2.6.12",
91
91
  "vuex": "^3.1.1"
@@ -4,6 +4,7 @@ import _ from 'lodash';
4
4
 
5
5
  export default {
6
6
  screensCache: [],
7
+ cachedScreenPromises: [],
7
8
 
8
9
  install(Vue) {
9
10
  Vue.prototype.$dataProvider = this;
@@ -45,6 +46,9 @@ export default {
45
46
  post(...args) {
46
47
  return this.apiInstance().post(...args);
47
48
  },
49
+ delete(...args) {
50
+ return this.apiInstance().delete(...args);
51
+ },
48
52
  token() {
49
53
  return localStorage.getItem('token');
50
54
  },
@@ -77,23 +81,32 @@ export default {
77
81
  }
78
82
  });
79
83
  },
80
- getScreen(id, query = '') {
81
- const endpoint = _.get(window, 'PM4ConfigOverrides.getScreenEndpoint', '/screens');
82
- return new Promise((resolve, reject) => {
83
- const cache = this.screensCache.find(screen => screen.id == id);
84
- if (cache) {
85
- resolve({data: cache});
86
- }
87
- if (!cache && id != undefined) {
88
- const request = this.get(endpoint + `/${id}${query}`);
89
- request.then(response => {
90
- if (response.data.nested) {
91
- this.addNestedScreenCache(response.data.nested);
92
- }
93
- resolve(response);
94
- }).catch(response => reject(response));
84
+ getScreen(id, query='') {
85
+ let cachedPromise = this.cachedScreenPromises.find(item => item.id === id && item.query === query);
86
+ if (cachedPromise) {
87
+ return cachedPromise.screenPromise;
88
+ }
89
+ else {
90
+ const endpoint = _.get(window, 'PM4ConfigOverrides.getScreenEndpoint', '/screens');
91
+
92
+ const screensCacheHit = this.screensCache.find(screen => screen.id == id);
93
+ if (screensCacheHit) {
94
+ return Promise.resolve({data: screensCacheHit});
95
95
  }
96
- });
96
+
97
+ let screenPromise = new Promise((resolve, reject) => {
98
+ this.get(endpoint + `/${id}${query}`)
99
+ .then(response => {
100
+ if (response.data.nested) {
101
+ this.addNestedScreenCache(response.data.nested);
102
+ }
103
+ resolve(response);
104
+ })
105
+ .catch(response => reject(response));
106
+ });
107
+ this.cachedScreenPromises.push({id, query, screenPromise});
108
+ return screenPromise;
109
+ }
97
110
  },
98
111
 
99
112
  postScript(id, params, options = {}) {
@@ -136,4 +149,16 @@ export default {
136
149
 
137
150
  return query;
138
151
  },
152
+
153
+ deleteFile(id, token = null) {
154
+ let url = `files/${id}`;
155
+ if (token) {
156
+ url += `?token=${token}`;
157
+ }
158
+ return this.delete(url);
159
+ },
160
+
161
+ download(url) {
162
+ return this.apiInstance().get(url, {responseType: 'blob'});
163
+ },
139
164
  };
@@ -186,7 +186,7 @@
186
186
  <small class="form-text text-muted mb-3">{{ $t("Select 'Single Value' to use parts of the selected object. Select 'Object' to use the entire selected value.") }}</small>
187
187
 
188
188
  <div v-if="dataSource === dataSourceValues.dataConnector">
189
- <div>
189
+ <div v-if="valueTypeReturned === 'single'">
190
190
  <label for="key">{{ $t('Value') }}</label>
191
191
  <mustache-helper/>
192
192
  <b-form-input id="key" v-model="key" @change="keyChanged" data-cy="inspector-datasource-value"/>
@@ -1,14 +1,22 @@
1
1
  <template>
2
- <b-row class="justify-content-md-center" v-if="value && config.settings.add">
3
- <b-col md="auto">
4
- <b-button size="sm" variant="secondary" class="ml-1 mr-1" @click="add" :title="$t('Add Item')" :data-cy="`loop-${config.name}-add`">
5
- <i class="fas fa-plus"/>
6
- </b-button>
7
- <b-button v-if="value.length > 0" size="sm" variant="outline-danger" class="ml-1 mr-1" @click="removeConfirm" :title="$t('Delete Item')" :data-cy="`loop-${config.name}-remove`">
8
- <i class="fas fa-minus"/>
9
- </b-button>
10
- </b-col>
11
- </b-row>
2
+ <div>
3
+ <b-row class="justify-content-start" v-if="error">
4
+ <b-col md="auto">
5
+ <div class="invalid-feedback d-block">{{ error }}</div>
6
+ </b-col>
7
+ </b-row>
8
+
9
+ <b-row class="justify-content-md-center" v-if="value && config.settings.add">
10
+ <b-col md="auto">
11
+ <b-button size="sm" variant="secondary" class="ml-1 mr-1" @click="add" :title="$t('Add Item')" :data-cy="`loop-${config.name}-add`">
12
+ <i class="fas fa-plus"/>
13
+ </b-button>
14
+ <b-button v-if="value.length > 0" size="sm" variant="outline-danger" class="ml-1 mr-1" @click="removeConfirm" :title="$t('Delete Item')" :data-cy="`loop-${config.name}-remove`">
15
+ <i class="fas fa-minus"/>
16
+ </b-button>
17
+ </b-col>
18
+ </b-row>
19
+ </div>
12
20
  </template>
13
21
 
14
22
  <script>
@@ -16,6 +24,7 @@ export default {
16
24
  props: {
17
25
  value: Array,
18
26
  config: Object,
27
+ error: String,
19
28
  },
20
29
  methods: {
21
30
  add() {
@@ -4,26 +4,23 @@
4
4
  <b-card v-if="inPreviewMode" class="mb-2">
5
5
  {{ messageForPreview }}
6
6
  </b-card>
7
+ <b-card v-else-if="donwloadingNotAvailable" class="mb-2">
8
+ {{ messageForNotAvailable }}
9
+ </b-card>
7
10
  <div v-else>
8
- <div v-if="loading">
9
- <i class="fas fa-cog fa-spin text-muted"/>
10
- {{ $t('Loading...') }}
11
- </div>
12
- <div v-else>
13
- <template v-if="!loading && filesInfo">
14
- <div v-for="file in asArray(filesInfo)" :key="file.id" :nada="JSON.stringify(file)" :data-cy="file.id + '-' + (file.name ? file.name : file.file_name).replace(/[^0-9a-zA-Z\-]/g, '-')">
15
- <b-btn v-show="!isReadOnly"
16
- class="mb-2 d-print-none" variant="primary" :aria-label="$attrs['aria-label']"
17
- @click="downloadFile(file)"
18
- >
19
- <i class="fas fa-file-download"/> {{ $t('Download') }}
20
- </b-btn>
21
- {{ file.name ? file.name : file.file_name }}
22
- </div>
23
- </template>
24
- <div v-else>
25
- {{ $t('No files available for download') }}
11
+ <template v-if="filesInfo.length > 0">
12
+ <div v-for="(file, idx) in filesInfo" :key="idx" :data-cy="file.id + '-' + file.name.replace(/[^0-9a-zA-Z\-]/g, '-')">
13
+ <b-btn v-show="!isReadOnly"
14
+ class="mb-2 d-print-none" variant="primary" :aria-label="$attrs['aria-label']"
15
+ @click="downloadFile(file)"
16
+ >
17
+ <i class="fas fa-file-download"/> {{ $t('Download') }}
18
+ </b-btn>
19
+ {{ file.name }}
26
20
  </div>
21
+ </template>
22
+ <div v-else>
23
+ {{ $t('No files available for download') }}
27
24
  </div>
28
25
  </div>
29
26
  </div>
@@ -37,62 +34,40 @@ export default {
37
34
  inheritAttrs: false,
38
35
  data() {
39
36
  return {
40
- fileType: null,
41
- loading: true,
42
- filesInfo: null,
43
- fileName: null,
44
- requestId: null,
45
- collectionId: null,
46
- recordId: null,
37
+ filesInfo: [],
47
38
  prefix: '',
48
- recordListVarName: null,
39
+ rowId: null,
49
40
  };
50
41
  },
51
42
  props: ['name', 'value', 'endpoint', 'requestFiles', 'label'],
52
- beforeMount() {
53
- this.fileType = this.getFileType();
54
-
55
- if (this.fileType == 'request') {
56
- this.requestId = this.getRequestId();
57
- }
58
-
59
- if (this.fileType == 'collection') {
60
- // Fill this.recordId and collectionId
61
- this.getCollectionInfo();
62
- }
63
- },
64
43
  mounted() {
65
44
  this.$root.$on('set-upload-data-name',
66
45
  (recordList, index, id) => this.listenRecordList(recordList, index, id));
67
46
 
68
- if (!this.fileType) {
47
+ if (this.donwloadingNotAvailable) {
69
48
  // Not somewhere we can download anything (like web entry start event)
70
- this.loading = false;
71
49
  return;
72
50
  }
73
51
 
52
+ this.checkIfInRecordList();
74
53
  this.setPrefix();
75
-
76
- if (this.fileType == 'request') {
77
- this.getFilesInfo();
78
- }
79
-
80
- if (this.fileType == 'collection') {
81
- this.getCollectionFiles();
82
- }
54
+ this.setFilesInfo();
83
55
  },
84
56
  watch: {
85
- value(value) {
86
- this.fileName = value;
87
- if (this.parentRecordList((this)) === null) {
88
- this.getFilesInfo();
89
- }
90
- else {
91
- this.getFilesInfo(this.recordListVarName);
92
- }
57
+ value: {
58
+ handler() {
59
+ this.setFilesInfo();
60
+ },
61
+ deep: true,
62
+ },
63
+ fileDataName() {
64
+ this.setFilesInfo();
93
65
  },
94
66
  },
95
67
  computed: {
68
+ donwloadingNotAvailable() {
69
+ return !this.collection && !this.requestId;
70
+ },
96
71
  inPreviewMode() {
97
72
  return this.mode === 'preview' && !window.exampleScreens;
98
73
  },
@@ -102,12 +77,32 @@ export default {
102
77
  { fileName: this.name }
103
78
  );
104
79
  },
80
+ messageForNotAvailable() {
81
+ return this.$t('Downloading files is not available.');
82
+ },
105
83
  mode() {
106
84
  return this.$root.$children[0].mode;
107
85
  },
108
86
  isReadOnly() {
109
87
  return this.$attrs.readonly ? this.$attrs.readonly : false;
110
88
  },
89
+ fileDataName() {
90
+ return this.prefix + this.name + (this.rowId ? '.' + this.rowId : '');
91
+ },
92
+ requestId() {
93
+ let node = document.head.querySelector('meta[name="request-id"]');
94
+ if (node === null) {
95
+ return null;
96
+ }
97
+ return node.content;
98
+ },
99
+ collection() {
100
+ const collectionIdNode = document.head.querySelector('meta[name="collection-id"]');
101
+ if (collectionIdNode) {
102
+ return collectionIdNode.content;
103
+ }
104
+ return false;
105
+ },
111
106
  },
112
107
  methods: {
113
108
  parentRecordList(node) {
@@ -121,19 +116,16 @@ export default {
121
116
  },
122
117
  listenRecordList(recordList, index, id) {
123
118
  const parent = this.parentRecordList(this);
124
- if (parent === recordList) {
125
- const prefix = (this.parentRecordList(this) === null) ? '' : recordList.name + '.';
126
- this.recordListVarName = prefix + this.name + (id ? '.' + id : '');
127
- this.getFilesInfo(this.recordListVarName);
119
+ if (parent !== recordList) {
120
+ return;
128
121
  }
122
+ this.rowId = (parent !== null) ? id : null;
129
123
  },
130
124
  downloadFile(file) {
131
- if (this.fileType == 'request') {
132
- this.downloadRequestFile(file);
133
- }
134
-
135
- if (this.fileType == 'collection') {
125
+ if (this.collection) {
136
126
  this.downloadCollectionFile(file);
127
+ } else {
128
+ this.downloadRequestFile(file);
137
129
  }
138
130
  },
139
131
  requestEndpoint(file) {
@@ -143,12 +135,11 @@ export default {
143
135
  endpoint = window.PM4ConfigOverrides.getFileEndpoint;
144
136
  }
145
137
 
146
- if (endpoint && this.filesInfo) {
147
- const query = '?name=' + encodeURIComponent(this.prefix + this.name) + '&token=' + this.filesInfo.token;
148
- return endpoint + query;
138
+ if (endpoint && file.token) {
139
+ return `${endpoint}/${file.id}?&token=${file.token}`;
149
140
  }
150
141
 
151
- return '/request/' + this.requestId + '/files/' + file.id;
142
+ return `/files/${file.id}/contents`;
152
143
  },
153
144
  setPrefix() {
154
145
  let parent = this.$parent;
@@ -172,188 +163,107 @@ export default {
172
163
  }
173
164
  },
174
165
  downloadRequestFile(file) {
175
- window.ProcessMaker.apiClient({
176
- baseURL: '/',
177
- url: this.requestEndpoint(file),
178
- method: 'GET',
179
- responseType: 'blob', // important
180
- }).then(response => {
181
- //axios needs to be told to open the file
182
- const url = window.URL.createObjectURL(new Blob([response.data]));
183
- const link = document.createElement('a');
184
- link.href = url;
185
- link.setAttribute('download', (file.name ? file.name : file.file_name));
186
- document.body.appendChild(link);
187
- link.click();
166
+ this.$dataProvider.download(this.requestEndpoint(file)).then(response => {
167
+ this.sendToBrowser(response, file);
188
168
  });
189
169
  },
190
170
  downloadCollectionFile(file) {
191
- window.ProcessMaker.apiClient({
192
- url: '/files/' + file.id + '/contents',
193
- method: 'GET',
194
- responseType: 'blob', // important
195
- }).then(response => {
196
- //axios needs to be told to open the file
197
- const url = window.URL.createObjectURL(new Blob([response.data]));
198
- const link = document.createElement('a');
199
- link.href = url;
200
- link.setAttribute('download', file.name);
201
- document.body.appendChild(link);
202
- link.click();
171
+ this.$dataProvider.download('/files/' + file.id + '/contents').then(response => {
172
+ this.sendToBrowser(response, file);
203
173
  });
204
174
  },
205
- getFileType() {
206
- let result = null;
207
- const requestIdNode = document.head.querySelector('meta[name="request-id"]');
208
- if (requestIdNode && requestIdNode.content) {
209
- result = 'request';
210
- }
211
-
212
- if (document.head.querySelector('meta[name="collection-id"]')) {
213
- result = 'collection';
214
- }
215
- return result;
216
- },
217
- getRequestId() {
218
- let node = document.head.querySelector('meta[name="request-id"]');
219
- if (node === null) {
220
- this.loading = false;
221
- return;
222
- }
223
- return node.content;
175
+ sendToBrowser(response, file) {
176
+ //axios needs to be told to open the file
177
+ const url = window.URL.createObjectURL(new Blob([response.data]));
178
+ const link = document.createElement('a');
179
+ link.href = url;
180
+ link.setAttribute('download', file.name);
181
+ document.body.appendChild(link);
182
+ link.click();
224
183
  },
225
- getCollectionInfo() {
226
- let collectionId, recordId = null;
227
- let collectionNode = document.head.querySelector('meta[name="collection-id"]');
228
- if (collectionNode === null) {
229
- this.loading = false;
230
- return;
184
+ setFilesInfo() {
185
+ if (this.collection) {
186
+ this.setFilesInfoFromCollectionValue();
187
+ } else {
188
+ this.setFilesInfoFromRequest();
231
189
  }
232
- this.collectionId = collectionNode.content;
233
-
234
- let recordNode = document.head.querySelector('meta[name="record-id"]');
235
- if (recordNode === null) {
236
- this.loading = false;
237
- return;
238
- }
239
- this.recordId = recordNode.content;
240
- return {collectionId, recordId};
241
190
  },
242
- getFilesInfo(variableName = null) {
243
- if (this.getFileType() === 'collection') {
244
- this.filesInfo = this.value;
191
+ setFilesInfoFromRequest() {
192
+ if (!this.value) {
245
193
  return;
246
194
  }
247
195
 
248
- if (this.value === null) {
249
- this.loading=false;
250
- return;
251
- }
252
- const name = variableName ? variableName : this.prefix + this.name;
253
- const fileIds = this.asArray(this.value);
254
- let requestFilesForVarExist = _.has(window, ['PM4ConfigOverrides', 'requestFiles', name]);
196
+ let requestFiles = _.get(
197
+ window,
198
+ `PM4ConfigOverrides.requestFiles["${this.fileDataName}"]`,
199
+ []
200
+ );
255
201
 
256
- const allFilesInValueHaveData =
257
- requestFilesForVarExist &&
258
- fileIds.every(fileId => {
259
- let fieldIsFoundinRequestData = false;
260
- for (const variable in window.PM4ConfigOverrides.requestFiles) {
261
- const varData = this.asArray(window.PM4ConfigOverrides.requestFiles[variable]);
262
- fieldIsFoundinRequestData = fieldIsFoundinRequestData || varData.some(requestFile => requestFile.id === fileId);
263
- }
264
- return fieldIsFoundinRequestData;
265
- });
202
+ requestFiles = requestFiles.filter(file => {
203
+ // Filter any requestFiles that don't exist in this component's value. This can happen if
204
+ // a file is uploaded but the task is not saved.
205
+ if (Array.isArray(this.value)) {
206
+ return this.value.some(valueFile => valueFile.file === file.id);
207
+ } else {
208
+ return file.id === this.value;
209
+ }
210
+ });
266
211
 
267
- if (allFilesInValueHaveData) {
268
- this.setFileInfo(name);
269
- }
270
- else {
271
- window.ProcessMaker.apiClient
272
- .get(`requests/${this.requestId}/files`)
273
- .then(response => {
274
- const data = response.data.data;
275
- let filesData = {};
276
- data.forEach(fileData => {
277
- const varName = _.get(fileData, 'custom_properties.data_name', null);
278
- if (varName) {
279
- const item = {
280
- id: fileData.id,
281
- file_name: fileData.file_name,
282
- };
283
- if (filesData[varName]) {
284
- if (!Array.isArray(filesData[varName])) {
285
- filesData[varName] = [filesData[varName]];
286
- }
287
- filesData[varName].push(item);
288
- }
289
- else {
290
- filesData[varName] = item;
291
- }
292
- }
293
- _.set(window, 'PM4ConfigOverrides.requestFiles', filesData);
294
- this.setFileInfo(name);
295
- });
296
- this.loading = false;
297
- });
298
- }
299
- },
300
- setFileInfo(name) {
301
- let requestFiles = this.requestFiles;
302
- if (_.has(window, 'PM4ConfigOverrides.requestFiles')) {
303
- requestFiles = window.PM4ConfigOverrides.requestFiles;
212
+ // Might be accessing individual files from inside a loop
213
+ if (requestFiles.length === 0 && this.fileDataName.endsWith('.file')) {
214
+ requestFiles = this.requestFileInsideALoop();
304
215
  }
305
216
 
306
- if (requestFiles[name]) {
307
- this.loading = false;
308
- //return always an array
309
- const filesData = Array.isArray(requestFiles[name]) ? requestFiles[name] : [requestFiles[name]];
310
- const valueIds = Array.isArray(this.value) ? this.value : [this.value];
311
- let result = [];
312
- valueIds.forEach(valueId => {
313
- const fileData = filesData.find(item => item.id === valueId);
314
- if (fileData) {
315
- result.push(fileData);
316
- }
317
- });
318
- this.filesInfo = result;
319
- }
320
- },
321
- asArray(value) {
322
- if (value === null || value === undefined) {
323
- return null;
324
- }
325
- return Array.isArray(value) ? value : [value];
217
+ this.filesInfo = requestFiles.map(file => {
218
+ const info = { id: file.id, name: file.file_name };
219
+ if (file.token) {
220
+ // web entry
221
+ info.token = file.token;
222
+ }
223
+ return info;
224
+ });
326
225
  },
327
- setFileInfoFromCache() {
328
- const info = this.asArray(_.get(window.ProcessMaker.CollectionData, this.prefix + this.name, null));
329
- if (info) {
330
- this.filesInfo = info.map(item => {
331
- return {...item, file_name: item.name};
332
- });
226
+ requestFileInsideALoop() {
227
+ const path = this.fileDataName.slice(0, -5);
228
+ const loopFile = _.get(
229
+ window,
230
+ `PM4ConfigOverrides.requestFiles.${path}`,
231
+ null
232
+ );
233
+ if (loopFile) {
234
+ return [loopFile]; // Treat as single file download
333
235
  }
236
+ return [];
334
237
  },
335
- getCollectionFiles() {
336
- if (this.collectionId === null || this.recordId === null) {
337
- this.loading = false;
238
+ setFilesInfoFromCollectionValue() {
239
+ if (!this.value) {
240
+ this.filesInfo = [];
338
241
  return;
339
242
  }
243
+ if (Array.isArray(this.value)) {
244
+ // multi file upload
245
+ this.filesInfo = this.value.map(value => value.file);
246
+ } else {
247
+ this.filesInfo = [this.value];
248
+ }
340
249
 
341
- window.ProcessMaker.EventBus.$on('got-collection-data', () => {
342
- this.setFileInfoFromCache();
343
- this.loading = false;
344
- });
345
-
346
- if (!window.ProcessMaker.CollectionData) {
347
- window.ProcessMaker.CollectionData = {};
348
- window.ProcessMaker.apiClient
349
- .get('collections/' + this.collectionId + '/records/' + this.recordId)
350
- .then(response => {
351
- window.ProcessMaker.CollectionData = response.data.data;
352
- this.filesInfo = this.value;
353
- window.ProcessMaker.EventBus.$emit('got-collection-data');
354
- });
250
+ },
251
+ checkIfInRecordList() {
252
+ const parent = this.parentRecordList(this);
253
+ if (parent !== null) {
254
+ const recordList = parent;
255
+ const prefix = recordList.name + '.';
256
+ this.setFileUploadNameForChildren(recordList.$children, prefix);
355
257
  }
356
- this.filesInfo = this.value;
258
+ },
259
+ setFileUploadNameForChildren(children, prefix) {
260
+ children.forEach(child => {
261
+ if (_.get(child, '$options.name') === 'FileDownload') {
262
+ child.prefix = prefix;
263
+ } else if (_.get(child, '$children', []).length > 0) {
264
+ this.setFileUploadNameForChildren(child.$children, prefix);
265
+ }
266
+ });
357
267
  },
358
268
  },
359
269
  };