@5minds/node-red-dashboard-2-processcube-dynamic-form 2.2.1-file-preview-2-247fbf-mfdx0ry5 → 2.2.1-file-preview-2-892712-mffeuvkr
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.
|
@@ -79,6 +79,53 @@
|
|
|
79
79
|
{{ field.customForm ? field.customForm.hint : undefined }}
|
|
80
80
|
</p>
|
|
81
81
|
</div>
|
|
82
|
+
<div v-else-if="createComponent(field).isFileField" class="ui-dynamic-form-file-wrapper">
|
|
83
|
+
<div v-if="getFilePreviewsForField(field.id).length > 0" class="ui-dynamic-form-image-previews">
|
|
84
|
+
<h4>Current files:</h4>
|
|
85
|
+
<div class="ui-dynamic-form-preview-grid">
|
|
86
|
+
<div
|
|
87
|
+
v-for="preview in getFilePreviewsForField(field.id)"
|
|
88
|
+
:key="preview.name"
|
|
89
|
+
class="ui-dynamic-form-preview-item"
|
|
90
|
+
>
|
|
91
|
+
<img
|
|
92
|
+
:src="preview.url"
|
|
93
|
+
:alt="preview.name"
|
|
94
|
+
class="ui-dynamic-form-preview-image"
|
|
95
|
+
@click="openImageModal(preview)"
|
|
96
|
+
/>
|
|
97
|
+
<div class="ui-dynamic-form-preview-info">
|
|
98
|
+
<span class="ui-dynamic-form-preview-name">{{ preview.name }}</span>
|
|
99
|
+
<span class="ui-dynamic-form-preview-size">{{ formatFileSize(preview.size) }}</span>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<FormKit
|
|
106
|
+
type="file"
|
|
107
|
+
:id="field.id"
|
|
108
|
+
:name="createComponent(field).name"
|
|
109
|
+
:label="field.label"
|
|
110
|
+
:required="field.required"
|
|
111
|
+
v-model="formData[field.id]"
|
|
112
|
+
:help="createComponent(field).hint"
|
|
113
|
+
innerClass="reset-background"
|
|
114
|
+
wrapperClass="$remove:formkit-wrapper"
|
|
115
|
+
labelClass="ui-dynamic-form-input-label"
|
|
116
|
+
:inputClass="`input-${theme}`"
|
|
117
|
+
:readonly="createComponent(field).isReadOnly"
|
|
118
|
+
:disabled="createComponent(field).isReadOnly"
|
|
119
|
+
:multiple="createComponent(field).multiple"
|
|
120
|
+
:validation="createComponent(field).validation"
|
|
121
|
+
validationVisibility="live"
|
|
122
|
+
:ref="
|
|
123
|
+
(el) => {
|
|
124
|
+
if (index === 0) firstFormFieldRef = el;
|
|
125
|
+
}
|
|
126
|
+
"
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
82
129
|
<component
|
|
83
130
|
:is="createComponent(field).type"
|
|
84
131
|
v-else
|
|
@@ -121,6 +168,27 @@
|
|
|
121
168
|
<div v-if="!props.actions_inside_card && hasUserTask && actions.length > 0" style="padding-top: 32px">
|
|
122
169
|
<UIDynamicFormFooterAction :actions="actions" :actionCallback="actionFn" />
|
|
123
170
|
</div>
|
|
171
|
+
|
|
172
|
+
<v-dialog v-model="imageModalOpen" max-width="800px">
|
|
173
|
+
<v-card v-if="modalImage">
|
|
174
|
+
<v-card-title class="headline">{{ modalImage.name }}</v-card-title>
|
|
175
|
+
<v-card-text>
|
|
176
|
+
<img
|
|
177
|
+
:src="modalImage.url"
|
|
178
|
+
:alt="modalImage.name"
|
|
179
|
+
style="width: 100%; max-height: 70vh; object-fit: contain"
|
|
180
|
+
/>
|
|
181
|
+
<div style="margin-top: 16px">
|
|
182
|
+
<strong>Size:</strong> {{ formatFileSize(modalImage.size) }}<br />
|
|
183
|
+
<strong>Type:</strong> {{ modalImage.type }}
|
|
184
|
+
</div>
|
|
185
|
+
</v-card-text>
|
|
186
|
+
<v-card-actions>
|
|
187
|
+
<v-spacer></v-spacer>
|
|
188
|
+
<v-btn color="primary" text @click="closeImageModal">Close</v-btn>
|
|
189
|
+
</v-card-actions>
|
|
190
|
+
</v-card>
|
|
191
|
+
</v-dialog>
|
|
124
192
|
</div>
|
|
125
193
|
</template>
|
|
126
194
|
|
|
@@ -215,6 +283,7 @@ export default {
|
|
|
215
283
|
return {
|
|
216
284
|
actions: [],
|
|
217
285
|
formData: {},
|
|
286
|
+
originalFileData: {},
|
|
218
287
|
userTask: null,
|
|
219
288
|
theme: '',
|
|
220
289
|
errorMsg: '',
|
|
@@ -222,6 +291,10 @@ export default {
|
|
|
222
291
|
msg: null,
|
|
223
292
|
collapsed: false,
|
|
224
293
|
firstFormFieldRef: null,
|
|
294
|
+
imageModalOpen: false,
|
|
295
|
+
modalImage: null,
|
|
296
|
+
objectUrlsByField: {},
|
|
297
|
+
previewCache: {},
|
|
225
298
|
};
|
|
226
299
|
},
|
|
227
300
|
computed: {
|
|
@@ -258,27 +331,55 @@ export default {
|
|
|
258
331
|
watch: {
|
|
259
332
|
formData: {
|
|
260
333
|
handler(newData, oldData) {
|
|
334
|
+
if (!oldData) return;
|
|
335
|
+
|
|
336
|
+
if (this.userTask && this.userTask.userTaskConfig && this.userTask.userTaskConfig.formFields) {
|
|
337
|
+
const fileFields = this.userTask.userTaskConfig.formFields.filter((field) => field.type === 'file');
|
|
338
|
+
|
|
339
|
+
fileFields.forEach((field) => {
|
|
340
|
+
const fieldId = field.id;
|
|
341
|
+
const newValue = newData[fieldId];
|
|
342
|
+
const oldValue = oldData[fieldId];
|
|
343
|
+
|
|
344
|
+
if (newValue !== oldValue && this.originalFileData[fieldId]) {
|
|
345
|
+
const hasNewFiles = this.hasActualFileObjects(newValue);
|
|
346
|
+
|
|
347
|
+
if (hasNewFiles) {
|
|
348
|
+
delete this.originalFileData[fieldId];
|
|
349
|
+
|
|
350
|
+
Object.keys(this.previewCache).forEach((key) => {
|
|
351
|
+
if (key.startsWith(fieldId + '_')) {
|
|
352
|
+
delete this.previewCache[key];
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
this.cleanupObjectUrls(fieldId);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
261
362
|
if (this.props.trigger_on_change) {
|
|
262
363
|
const res = { payload: { formData: newData, userTask: this.userTask } };
|
|
263
364
|
this.send(res, this.totalOutputs - 1);
|
|
264
365
|
}
|
|
265
366
|
},
|
|
266
|
-
collapsed(newVal) {
|
|
267
|
-
if (!newVal && this.hasUserTask) {
|
|
268
|
-
nextTick(() => {
|
|
269
|
-
this.focusFirstFormField();
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
userTask(newVal) {
|
|
274
|
-
if (newVal && !this.collapsed) {
|
|
275
|
-
nextTick(() => {
|
|
276
|
-
this.focusFirstFormField();
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
367
|
deep: true,
|
|
281
368
|
},
|
|
369
|
+
collapsed(newVal) {
|
|
370
|
+
if (!newVal && this.hasUserTask) {
|
|
371
|
+
nextTick(() => {
|
|
372
|
+
this.focusFirstFormField();
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
userTask(newVal) {
|
|
377
|
+
if (newVal && !this.collapsed) {
|
|
378
|
+
nextTick(() => {
|
|
379
|
+
this.focusFirstFormField();
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
},
|
|
282
383
|
},
|
|
283
384
|
created() {
|
|
284
385
|
const currentPath = window.location.pathname;
|
|
@@ -314,6 +415,7 @@ export default {
|
|
|
314
415
|
},
|
|
315
416
|
unmounted() {
|
|
316
417
|
this.$socket?.off('msg-input:' + this.id);
|
|
418
|
+
this.cleanupObjectUrls();
|
|
317
419
|
},
|
|
318
420
|
methods: {
|
|
319
421
|
createComponent(field) {
|
|
@@ -495,25 +597,17 @@ export default {
|
|
|
495
597
|
case 'file':
|
|
496
598
|
const multiple = field.customForm ? JSON.parse(JSON.stringify(field.customForm)).multiple === 'true' : false;
|
|
497
599
|
return {
|
|
498
|
-
type: '
|
|
600
|
+
type: 'div',
|
|
499
601
|
props: {
|
|
500
|
-
|
|
501
|
-
id: field.id,
|
|
502
|
-
name,
|
|
503
|
-
label: field.label,
|
|
504
|
-
required: field.required,
|
|
505
|
-
value: this.formData[field.id],
|
|
506
|
-
help: hint,
|
|
507
|
-
innerClass: 'reset-background',
|
|
508
|
-
wrapperClass: '$remove:formkit-wrapper',
|
|
509
|
-
labelClass: 'ui-dynamic-form-input-label',
|
|
510
|
-
inputClass: `input-${this.theme}`,
|
|
511
|
-
readonly: isReadOnly,
|
|
512
|
-
disabled: isReadOnly,
|
|
513
|
-
multiple,
|
|
514
|
-
validation,
|
|
515
|
-
validationVisibility: 'live',
|
|
602
|
+
class: 'ui-dynamic-form-file-wrapper',
|
|
516
603
|
},
|
|
604
|
+
isFileField: true,
|
|
605
|
+
field: field,
|
|
606
|
+
multiple: multiple,
|
|
607
|
+
isReadOnly: isReadOnly,
|
|
608
|
+
hint: hint,
|
|
609
|
+
validation: validation,
|
|
610
|
+
name: name,
|
|
517
611
|
};
|
|
518
612
|
case 'checkbox':
|
|
519
613
|
const options = JSON.parse(JSON.stringify(field.customForm)).entries.map((obj) => {
|
|
@@ -874,6 +968,9 @@ export default {
|
|
|
874
968
|
return;
|
|
875
969
|
}
|
|
876
970
|
|
|
971
|
+
this.originalFileData = {};
|
|
972
|
+
this.previewCache = {};
|
|
973
|
+
this.cleanupObjectUrls();
|
|
877
974
|
this.actions = this.props.options;
|
|
878
975
|
|
|
879
976
|
const hasTask = msg.payload && msg.payload.userTask;
|
|
@@ -883,14 +980,14 @@ export default {
|
|
|
883
980
|
} else {
|
|
884
981
|
this.userTask = null;
|
|
885
982
|
this.formData = {};
|
|
983
|
+
this.originalFileData = {};
|
|
984
|
+
this.cleanupObjectUrls();
|
|
886
985
|
return;
|
|
887
986
|
}
|
|
888
987
|
|
|
889
988
|
const formFields = this.userTask.userTaskConfig.formFields;
|
|
890
989
|
const formFieldIds = formFields.map((ff) => ff.id);
|
|
891
990
|
const initialValues = this.userTask.startToken;
|
|
892
|
-
console.log('Initial Values:', initialValues);
|
|
893
|
-
console.log('Form Fields:', formFields);
|
|
894
991
|
const finishedFormData = msg.payload.formData;
|
|
895
992
|
this.formIsFinished = !!msg.payload.formData;
|
|
896
993
|
if (this.formIsFinished) {
|
|
@@ -972,7 +1069,12 @@ export default {
|
|
|
972
1069
|
Object.keys(initialValues)
|
|
973
1070
|
.filter((key) => formFieldIds.includes(key))
|
|
974
1071
|
.forEach((key) => {
|
|
975
|
-
|
|
1072
|
+
const field = formFields.find((f) => f.id === key);
|
|
1073
|
+
if (field && field.type === 'file') {
|
|
1074
|
+
this.formData[key] = this.transformBase64ToFormKitFormat(initialValues[key], field);
|
|
1075
|
+
} else {
|
|
1076
|
+
this.formData[key] = initialValues[key];
|
|
1077
|
+
}
|
|
976
1078
|
});
|
|
977
1079
|
}
|
|
978
1080
|
|
|
@@ -980,7 +1082,12 @@ export default {
|
|
|
980
1082
|
Object.keys(finishedFormData)
|
|
981
1083
|
.filter((key) => formFieldIds.includes(key))
|
|
982
1084
|
.forEach((key) => {
|
|
983
|
-
|
|
1085
|
+
const field = formFields.find((f) => f.id === key);
|
|
1086
|
+
if (field && field.type === 'file') {
|
|
1087
|
+
this.formData[key] = this.transformBase64ToFormKitFormat(finishedFormData[key], field);
|
|
1088
|
+
} else {
|
|
1089
|
+
this.formData[key] = finishedFormData[key];
|
|
1090
|
+
}
|
|
984
1091
|
});
|
|
985
1092
|
}
|
|
986
1093
|
|
|
@@ -1000,14 +1107,8 @@ export default {
|
|
|
1000
1107
|
const formFields = this.userTask.userTaskConfig.formFields;
|
|
1001
1108
|
|
|
1002
1109
|
try {
|
|
1003
|
-
console.log(
|
|
1004
|
-
'Processing file fields:',
|
|
1005
|
-
formFields.filter((f) => f.type === 'file')
|
|
1006
|
-
);
|
|
1007
1110
|
processedFormData = await this.processFileFields(processedFormData, formFields);
|
|
1008
|
-
console.log('File processing completed successfully');
|
|
1009
1111
|
} catch (error) {
|
|
1010
|
-
console.error('Error processing file fields:', error);
|
|
1011
1112
|
this.showError('Fehler beim Verarbeiten der Dateien');
|
|
1012
1113
|
return;
|
|
1013
1114
|
}
|
|
@@ -1091,6 +1192,88 @@ export default {
|
|
|
1091
1192
|
}
|
|
1092
1193
|
}
|
|
1093
1194
|
},
|
|
1195
|
+
/**
|
|
1196
|
+
* Transforms base64 file data to FormKit-compatible format for display.
|
|
1197
|
+
*
|
|
1198
|
+
* FormKit file inputs expect an array of objects with 'name' property for display.
|
|
1199
|
+
* This function converts the base64 format (with 'data' property) to the display format,
|
|
1200
|
+
* while preserving the original data in originalFileData for form submission.
|
|
1201
|
+
*
|
|
1202
|
+
* Expected input format from initialValues:
|
|
1203
|
+
* {
|
|
1204
|
+
* name: "filename.ext",
|
|
1205
|
+
* size: 12345,
|
|
1206
|
+
* type: "image/png",
|
|
1207
|
+
* data: "base64string..."
|
|
1208
|
+
* }
|
|
1209
|
+
*
|
|
1210
|
+
* Output format for FormKit:
|
|
1211
|
+
* [
|
|
1212
|
+
* { name: "filename.ext" }
|
|
1213
|
+
* ]
|
|
1214
|
+
*
|
|
1215
|
+
* @param {Object|Array} fileData - The file data from initial values
|
|
1216
|
+
* @param {Object} field - The form field configuration
|
|
1217
|
+
* @returns {Array} FormKit-compatible file array
|
|
1218
|
+
*/
|
|
1219
|
+
transformBase64ToFormKitFormat(fileData, field) {
|
|
1220
|
+
if (
|
|
1221
|
+
Array.isArray(fileData) &&
|
|
1222
|
+
fileData.length > 0 &&
|
|
1223
|
+
typeof fileData[0] === 'object' &&
|
|
1224
|
+
fileData[0].name &&
|
|
1225
|
+
!fileData[0].data
|
|
1226
|
+
) {
|
|
1227
|
+
return fileData;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
const fieldId = field.id;
|
|
1231
|
+
const multiple = field.customForm ? JSON.parse(JSON.stringify(field.customForm)).multiple === 'true' : false;
|
|
1232
|
+
|
|
1233
|
+
if (fileData && typeof fileData === 'object' && fileData.name && fileData.data) {
|
|
1234
|
+
this.originalFileData[fieldId] = fileData;
|
|
1235
|
+
|
|
1236
|
+
const formKitFile = {
|
|
1237
|
+
name: fileData.name,
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
return multiple ? [formKitFile] : [formKitFile];
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (Array.isArray(fileData)) {
|
|
1244
|
+
this.originalFileData[fieldId] = fileData;
|
|
1245
|
+
|
|
1246
|
+
const formKitFiles = fileData
|
|
1247
|
+
.filter((file) => file && typeof file === 'object' && file.name)
|
|
1248
|
+
.map((file) => ({
|
|
1249
|
+
name: file.name,
|
|
1250
|
+
}));
|
|
1251
|
+
|
|
1252
|
+
return formKitFiles;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
return [];
|
|
1256
|
+
},
|
|
1257
|
+
hasActualFileObjects(value) {
|
|
1258
|
+
if (!value) return false;
|
|
1259
|
+
|
|
1260
|
+
if (Array.isArray(value)) {
|
|
1261
|
+
return value.some((item) => {
|
|
1262
|
+
return (
|
|
1263
|
+
item instanceof File ||
|
|
1264
|
+
(item && item.file instanceof File) ||
|
|
1265
|
+
(item && item.file instanceof FileList && item.file.length > 0)
|
|
1266
|
+
);
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
return (
|
|
1271
|
+
value instanceof File ||
|
|
1272
|
+
(value instanceof FileList && value.length > 0) ||
|
|
1273
|
+
(value && value.file instanceof File) ||
|
|
1274
|
+
(value && value.file instanceof FileList && value.file.length > 0)
|
|
1275
|
+
);
|
|
1276
|
+
},
|
|
1094
1277
|
async convertFileToBase64(file) {
|
|
1095
1278
|
return new Promise((resolve, reject) => {
|
|
1096
1279
|
const reader = new FileReader();
|
|
@@ -1114,6 +1297,12 @@ export default {
|
|
|
1114
1297
|
for (const field of formFields) {
|
|
1115
1298
|
if (field.type === 'file') {
|
|
1116
1299
|
const fieldValue = processedData[field.id];
|
|
1300
|
+
const fieldId = field.id;
|
|
1301
|
+
|
|
1302
|
+
if (this.originalFileData[fieldId]) {
|
|
1303
|
+
processedData[fieldId] = this.originalFileData[fieldId];
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1117
1306
|
|
|
1118
1307
|
if (fieldValue) {
|
|
1119
1308
|
const multiple = field.customForm
|
|
@@ -1121,7 +1310,6 @@ export default {
|
|
|
1121
1310
|
: false;
|
|
1122
1311
|
|
|
1123
1312
|
if (multiple && Array.isArray(fieldValue)) {
|
|
1124
|
-
// Handle multiple files
|
|
1125
1313
|
const base64Files = [];
|
|
1126
1314
|
for (const file of fieldValue) {
|
|
1127
1315
|
const processedFile = await this.processIndividualFile(file);
|
|
@@ -1184,6 +1372,158 @@ export default {
|
|
|
1184
1372
|
|
|
1185
1373
|
return fileData;
|
|
1186
1374
|
},
|
|
1375
|
+
isImageFile(fileData) {
|
|
1376
|
+
if (!fileData || typeof fileData !== 'object') return false;
|
|
1377
|
+
|
|
1378
|
+
const mimeType = fileData.type;
|
|
1379
|
+
if (!mimeType) return false;
|
|
1380
|
+
|
|
1381
|
+
return mimeType.startsWith('image/');
|
|
1382
|
+
},
|
|
1383
|
+
generateImagePreviewUrl(fileData) {
|
|
1384
|
+
if (!fileData || !fileData.data || !fileData.type) return null;
|
|
1385
|
+
|
|
1386
|
+
return `data:${fileData.type};base64,${fileData.data}`;
|
|
1387
|
+
},
|
|
1388
|
+
getFilePreviewsForField(fieldId) {
|
|
1389
|
+
const currentData = this.formData[fieldId] || null;
|
|
1390
|
+
const cachedOriginalData = this.originalFileData[fieldId] || null;
|
|
1391
|
+
const cacheKey = `${fieldId}_${JSON.stringify(currentData)}_${JSON.stringify(cachedOriginalData)}`;
|
|
1392
|
+
|
|
1393
|
+
if (this.previewCache[cacheKey]) {
|
|
1394
|
+
return this.previewCache[cacheKey];
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
const currentFormData = this.formData[fieldId];
|
|
1398
|
+
const originalFileData = this.originalFileData[fieldId];
|
|
1399
|
+
|
|
1400
|
+
if (!currentFormData && !originalFileData) {
|
|
1401
|
+
this.previewCache[cacheKey] = [];
|
|
1402
|
+
return [];
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
let previews = [];
|
|
1406
|
+
|
|
1407
|
+
const hasNewFileObjects =
|
|
1408
|
+
currentFormData &&
|
|
1409
|
+
Array.isArray(currentFormData) &&
|
|
1410
|
+
currentFormData.length > 0 &&
|
|
1411
|
+
this.hasActualFileObjects(currentFormData);
|
|
1412
|
+
|
|
1413
|
+
if (hasNewFileObjects) {
|
|
1414
|
+
previews = this.generatePreviewsFromCurrentFiles(currentFormData, fieldId);
|
|
1415
|
+
} else if (originalFileData) {
|
|
1416
|
+
const fileArray = Array.isArray(originalFileData) ? originalFileData : [originalFileData];
|
|
1417
|
+
previews = fileArray
|
|
1418
|
+
.filter((file) => this.isImageFile(file))
|
|
1419
|
+
.map((file) => ({
|
|
1420
|
+
name: file.name,
|
|
1421
|
+
url: this.generateImagePreviewUrl(file),
|
|
1422
|
+
size: file.size,
|
|
1423
|
+
type: file.type,
|
|
1424
|
+
}));
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
this.previewCache[cacheKey] = previews;
|
|
1428
|
+
|
|
1429
|
+
return previews;
|
|
1430
|
+
},
|
|
1431
|
+
generatePreviewsFromCurrentFiles(fileArray, fieldId = null) {
|
|
1432
|
+
if (fieldId && this.objectUrlsByField[fieldId]) {
|
|
1433
|
+
this.objectUrlsByField[fieldId].forEach((url) => URL.revokeObjectURL(url));
|
|
1434
|
+
this.objectUrlsByField[fieldId] = [];
|
|
1435
|
+
} else if (fieldId) {
|
|
1436
|
+
this.objectUrlsByField[fieldId] = [];
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
const previews = fileArray
|
|
1440
|
+
.filter((fileItem) => {
|
|
1441
|
+
let actualFile = null;
|
|
1442
|
+
|
|
1443
|
+
if (fileItem instanceof File) {
|
|
1444
|
+
actualFile = fileItem;
|
|
1445
|
+
} else if (fileItem && fileItem.file instanceof File) {
|
|
1446
|
+
actualFile = fileItem.file;
|
|
1447
|
+
} else if (Array.isArray(fileItem) && fileItem[0] instanceof File) {
|
|
1448
|
+
actualFile = fileItem[0];
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
return actualFile && actualFile.type && actualFile.type.startsWith('image/');
|
|
1452
|
+
})
|
|
1453
|
+
.map((fileItem) => {
|
|
1454
|
+
let actualFile = null;
|
|
1455
|
+
|
|
1456
|
+
if (fileItem instanceof File) {
|
|
1457
|
+
actualFile = fileItem;
|
|
1458
|
+
} else if (fileItem && fileItem.file instanceof File) {
|
|
1459
|
+
actualFile = fileItem.file;
|
|
1460
|
+
} else if (Array.isArray(fileItem) && fileItem[0] instanceof File) {
|
|
1461
|
+
actualFile = fileItem[0];
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const url = actualFile ? URL.createObjectURL(actualFile) : null;
|
|
1465
|
+
|
|
1466
|
+
if (url && fieldId) {
|
|
1467
|
+
if (!this.objectUrlsByField[fieldId]) {
|
|
1468
|
+
this.objectUrlsByField[fieldId] = [];
|
|
1469
|
+
}
|
|
1470
|
+
this.objectUrlsByField[fieldId].push(url);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
return {
|
|
1474
|
+
name: actualFile.name,
|
|
1475
|
+
url: url,
|
|
1476
|
+
size: actualFile.size,
|
|
1477
|
+
type: actualFile.type,
|
|
1478
|
+
isObjectUrl: true,
|
|
1479
|
+
};
|
|
1480
|
+
})
|
|
1481
|
+
.filter((preview) => preview.url !== null);
|
|
1482
|
+
|
|
1483
|
+
return previews;
|
|
1484
|
+
},
|
|
1485
|
+
formatFileSize(bytes) {
|
|
1486
|
+
if (!bytes) return '0 B';
|
|
1487
|
+
|
|
1488
|
+
const k = 1024;
|
|
1489
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
1490
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1491
|
+
|
|
1492
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
1493
|
+
},
|
|
1494
|
+
openImageModal(preview) {
|
|
1495
|
+
this.modalImage = preview;
|
|
1496
|
+
this.imageModalOpen = true;
|
|
1497
|
+
},
|
|
1498
|
+
closeImageModal() {
|
|
1499
|
+
this.imageModalOpen = false;
|
|
1500
|
+
this.modalImage = null;
|
|
1501
|
+
},
|
|
1502
|
+
cleanupObjectUrls(fieldId = null) {
|
|
1503
|
+
if (fieldId) {
|
|
1504
|
+
if (this.objectUrlsByField[fieldId]) {
|
|
1505
|
+
this.objectUrlsByField[fieldId].forEach((url) => {
|
|
1506
|
+
URL.revokeObjectURL(url);
|
|
1507
|
+
});
|
|
1508
|
+
this.objectUrlsByField[fieldId] = [];
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
Object.keys(this.previewCache).forEach((key) => {
|
|
1512
|
+
if (key.startsWith(fieldId + '_')) {
|
|
1513
|
+
delete this.previewCache[key];
|
|
1514
|
+
}
|
|
1515
|
+
});
|
|
1516
|
+
} else {
|
|
1517
|
+
Object.keys(this.objectUrlsByField).forEach((fieldId) => {
|
|
1518
|
+
this.objectUrlsByField[fieldId].forEach((url) => {
|
|
1519
|
+
URL.revokeObjectURL(url);
|
|
1520
|
+
});
|
|
1521
|
+
});
|
|
1522
|
+
this.objectUrlsByField = {};
|
|
1523
|
+
|
|
1524
|
+
this.previewCache = {};
|
|
1525
|
+
}
|
|
1526
|
+
},
|
|
1187
1527
|
},
|
|
1188
1528
|
};
|
|
1189
1529
|
|
|
@@ -68,7 +68,7 @@ code {
|
|
|
68
68
|
.ui-dynamic-form-title-default {
|
|
69
69
|
background: linear-gradient(131deg, #18181a 26.76%, #242326 100.16%);
|
|
70
70
|
padding: 32px;
|
|
71
|
-
color: #
|
|
71
|
+
color: #fff;
|
|
72
72
|
font-size: 36px;
|
|
73
73
|
font-weight: 700;
|
|
74
74
|
border-radius: 8px 8px 0px 0px;
|
|
@@ -593,3 +593,111 @@ code {
|
|
|
593
593
|
margin: revert;
|
|
594
594
|
padding: revert;
|
|
595
595
|
}
|
|
596
|
+
|
|
597
|
+
.ui-dynamic-form-image-previews {
|
|
598
|
+
margin-bottom: 16px;
|
|
599
|
+
padding: 16px;
|
|
600
|
+
border: 1px solid #e0e0e0;
|
|
601
|
+
border-radius: 8px;
|
|
602
|
+
background-color: #f9f9f9;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.ui-dynamic-form-image-previews h4 {
|
|
606
|
+
margin: 0 0 12px 0;
|
|
607
|
+
color: #333;
|
|
608
|
+
font-size: 14px;
|
|
609
|
+
font-weight: 600;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.ui-dynamic-form-preview-grid {
|
|
613
|
+
display: flex;
|
|
614
|
+
flex-wrap: wrap;
|
|
615
|
+
gap: 12px;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.ui-dynamic-form-preview-item {
|
|
619
|
+
display: flex;
|
|
620
|
+
flex-direction: column;
|
|
621
|
+
align-items: center;
|
|
622
|
+
padding: 8px;
|
|
623
|
+
border: 1px solid #ddd;
|
|
624
|
+
border-radius: 6px;
|
|
625
|
+
background-color: white;
|
|
626
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
627
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
628
|
+
max-width: 150px;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.ui-dynamic-form-preview-item:hover {
|
|
632
|
+
transform: translateY(-2px);
|
|
633
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.ui-dynamic-form-preview-image {
|
|
637
|
+
width: 120px;
|
|
638
|
+
height: 80px;
|
|
639
|
+
object-fit: cover;
|
|
640
|
+
border-radius: 4px;
|
|
641
|
+
cursor: pointer;
|
|
642
|
+
border: 2px solid transparent;
|
|
643
|
+
transition: border-color 0.2s ease;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.ui-dynamic-form-preview-image:hover {
|
|
647
|
+
border-color: #1976d2;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.ui-dynamic-form-preview-info {
|
|
651
|
+
display: flex;
|
|
652
|
+
flex-direction: column;
|
|
653
|
+
align-items: center;
|
|
654
|
+
margin-top: 8px;
|
|
655
|
+
text-align: center;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.ui-dynamic-form-preview-name {
|
|
659
|
+
font-size: 12px;
|
|
660
|
+
font-weight: 500;
|
|
661
|
+
color: #333;
|
|
662
|
+
word-break: break-word;
|
|
663
|
+
max-width: 120px;
|
|
664
|
+
overflow: hidden;
|
|
665
|
+
text-overflow: ellipsis;
|
|
666
|
+
display: -webkit-box;
|
|
667
|
+
-webkit-line-clamp: 2;
|
|
668
|
+
line-clamp: 2;
|
|
669
|
+
-webkit-box-orient: vertical;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.ui-dynamic-form-preview-size {
|
|
673
|
+
font-size: 10px;
|
|
674
|
+
color: #666;
|
|
675
|
+
margin-top: 4px;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.ui-dynamic-form-file-wrapper {
|
|
679
|
+
width: 100%;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/* Dark theme adjustments */
|
|
683
|
+
.ui-dynamic-form-dark .ui-dynamic-form-image-previews {
|
|
684
|
+
background-color: #2d2d2d;
|
|
685
|
+
border-color: #555;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.ui-dynamic-form-dark .ui-dynamic-form-image-previews h4 {
|
|
689
|
+
color: #fff;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.ui-dynamic-form-dark .ui-dynamic-form-preview-item {
|
|
693
|
+
background-color: #3d3d3d;
|
|
694
|
+
border-color: #555;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.ui-dynamic-form-dark .ui-dynamic-form-preview-name {
|
|
698
|
+
color: #fff;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.ui-dynamic-form-dark .ui-dynamic-form-preview-size {
|
|
702
|
+
color: #ccc;
|
|
703
|
+
}
|