@5minds/node-red-dashboard-2-processcube-dynamic-form 2.0.8-feature-01220f-mdiuh7i9 → 2.0.8-file-preview-4a4fff-mdijdu1k
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.
|
@@ -1,60 +1,86 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div className="ui-dynamic-form-external-sizing-wrapper" :style="props.card_size_styling">
|
|
3
3
|
<!-- Component must be wrapped in a block so props such as className and style can be passed in from parent -->
|
|
4
|
-
<UIDynamicFormTitleText
|
|
5
|
-
|
|
6
|
-
:
|
|
7
|
-
:
|
|
4
|
+
<UIDynamicFormTitleText
|
|
5
|
+
v-if="props.title_style === 'outside' && hasUserTask"
|
|
6
|
+
:style="props.title_style"
|
|
7
|
+
:title="effectiveTitle"
|
|
8
|
+
:customStyles="props.title_custom_text_styling"
|
|
9
|
+
:titleIcon="props.title_icon"
|
|
10
|
+
:collapsible="props.collapsible || (props.collapse_when_finished && formIsFinished)"
|
|
11
|
+
:collapsed="collapsed"
|
|
12
|
+
:toggleCollapse="toggleCollapse"
|
|
13
|
+
/>
|
|
8
14
|
<div className="ui-dynamic-form-wrapper">
|
|
9
15
|
<p v-if="hasUserTask" style="margin-bottom: 0px">
|
|
10
16
|
<v-form ref="form" v-model="form" :class="dynamicClass">
|
|
11
|
-
<UIDynamicFormTitleText
|
|
12
|
-
|
|
17
|
+
<UIDynamicFormTitleText
|
|
18
|
+
v-if="props.title_style != 'outside'"
|
|
19
|
+
:style="props.title_style"
|
|
20
|
+
:title="effectiveTitle"
|
|
21
|
+
:customStyles="props.title_custom_text_styling"
|
|
13
22
|
:titleIcon="props.title_icon"
|
|
14
23
|
:collapsible="props.collapsible || (props.collapse_when_finished && formIsFinished)"
|
|
15
|
-
:collapsed="collapsed"
|
|
24
|
+
:collapsed="collapsed"
|
|
25
|
+
:toggleCollapse="toggleCollapse"
|
|
26
|
+
/>
|
|
16
27
|
<Transition name="cardCollapse">
|
|
17
28
|
<div v-if="!collapsed">
|
|
18
|
-
<div
|
|
19
|
-
|
|
29
|
+
<div
|
|
30
|
+
className="ui-dynamic-form-formfield-positioner"
|
|
31
|
+
:style="props.inner_card_styling"
|
|
32
|
+
:data-columns="props.form_columns || 1"
|
|
33
|
+
>
|
|
20
34
|
<FormKit id="form" type="group">
|
|
21
|
-
<v-row
|
|
35
|
+
<v-row
|
|
36
|
+
v-for="(field, index) in fields()"
|
|
37
|
+
:key="field"
|
|
22
38
|
:class="field.type === 'header' ? 'ui-dynamic-form-header-row' : ''"
|
|
23
|
-
:style="getRowWidthStyling(field, index)"
|
|
39
|
+
:style="getRowWidthStyling(field, index)"
|
|
40
|
+
>
|
|
24
41
|
<v-col cols="12">
|
|
25
|
-
<component
|
|
26
|
-
|
|
27
|
-
v-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
" />
|
|
33
|
-
<component :is="createComponent(field).type"
|
|
34
|
-
v-else-if="createComponent(field).innerText"
|
|
35
|
-
v-bind="createComponent(field).props" :ref="(el) => {
|
|
36
|
-
if (index === 0) firstFormFieldRef = el;
|
|
37
|
-
}
|
|
38
|
-
" v-model="formData[field.id]">
|
|
39
|
-
{{ createComponent(field).innerText }}
|
|
40
|
-
</component>
|
|
41
|
-
<div v-else-if="createComponent(field).type == 'v-slider'">
|
|
42
|
-
<p class="formkit-label">{{ field.label }}</p>
|
|
43
|
-
<component :is="createComponent(field).type"
|
|
44
|
-
v-bind="createComponent(field).props" :ref="(el) => {
|
|
42
|
+
<component
|
|
43
|
+
:is="getFieldComponent(field).type"
|
|
44
|
+
v-if="getFieldComponent(field).innerText"
|
|
45
|
+
v-bind="getFieldComponent(field).props"
|
|
46
|
+
:ref="
|
|
47
|
+
(el) => {
|
|
45
48
|
if (index === 0) firstFormFieldRef = el;
|
|
46
49
|
}
|
|
47
|
-
|
|
50
|
+
"
|
|
51
|
+
v-model="formData[field.id]"
|
|
52
|
+
>
|
|
53
|
+
{{ getFieldComponent(field).innerText }}
|
|
54
|
+
</component>
|
|
55
|
+
<div v-else-if="getFieldComponent(field).type == 'v-slider'">
|
|
56
|
+
<p class="formkit-label">{{ field.label }}</p>
|
|
57
|
+
<component
|
|
58
|
+
:is="getFieldComponent(field).type"
|
|
59
|
+
v-bind="getFieldComponent(field).props"
|
|
60
|
+
:ref="
|
|
61
|
+
(el) => {
|
|
62
|
+
if (index === 0) firstFormFieldRef = el;
|
|
63
|
+
}
|
|
64
|
+
"
|
|
65
|
+
v-model="field.defaultValue"
|
|
66
|
+
/>
|
|
48
67
|
<p class="formkit-help">
|
|
49
|
-
{{
|
|
68
|
+
{{
|
|
69
|
+
field.customForm ? JSON.parse(field.customForm).hint : undefined
|
|
50
70
|
}}
|
|
51
71
|
</p>
|
|
52
72
|
</div>
|
|
53
|
-
<component
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
73
|
+
<component
|
|
74
|
+
:is="getFieldComponent(field).type"
|
|
75
|
+
v-else
|
|
76
|
+
v-bind="getFieldComponent(field).props"
|
|
77
|
+
:ref="
|
|
78
|
+
(el) => {
|
|
79
|
+
if (index === 0) firstFormFieldRef = el;
|
|
80
|
+
}
|
|
81
|
+
"
|
|
82
|
+
v-model="formData[field.id]"
|
|
83
|
+
/>
|
|
58
84
|
</v-col>
|
|
59
85
|
</v-row>
|
|
60
86
|
</FormKit>
|
|
@@ -63,17 +89,24 @@
|
|
|
63
89
|
<v-row v-if="errorMsg.length > 0" style="padding: 12px">
|
|
64
90
|
<v-alert type="error">Error: {{ errorMsg }}</v-alert>
|
|
65
91
|
</v-row>
|
|
66
|
-
<UIDynamicFormFooterAction
|
|
67
|
-
|
|
68
|
-
|
|
92
|
+
<UIDynamicFormFooterAction
|
|
93
|
+
v-if="props.actions_inside_card && actions.length > 0"
|
|
94
|
+
:actions="actions"
|
|
95
|
+
:actionCallback="actionFn"
|
|
96
|
+
:formIsFinished="formIsFinished"
|
|
97
|
+
style="padding: 16px; padding-top: 0px"
|
|
98
|
+
/>
|
|
69
99
|
</v-row>
|
|
70
100
|
</div>
|
|
71
101
|
</Transition>
|
|
72
102
|
</v-form>
|
|
73
103
|
</p>
|
|
74
104
|
<p v-else>
|
|
75
|
-
<v-alert
|
|
76
|
-
|
|
105
|
+
<v-alert
|
|
106
|
+
v-if="props.waiting_info.length > 0 || props.waiting_title.length > 0"
|
|
107
|
+
:text="props.waiting_info"
|
|
108
|
+
:title="props.waiting_title"
|
|
109
|
+
/>
|
|
77
110
|
</p>
|
|
78
111
|
</div>
|
|
79
112
|
<div v-if="!props.actions_inside_card && actions.length > 0 && hasUserTask" style="padding-top: 32px">
|
|
@@ -82,17 +115,18 @@
|
|
|
82
115
|
</div>
|
|
83
116
|
</template>
|
|
84
117
|
|
|
118
|
+
<!-- eslint-disable no-case-declarations -->
|
|
85
119
|
<script>
|
|
86
120
|
import { de } from '@formkit/i18n';
|
|
87
121
|
import { FormKit, defaultConfig, plugin } from '@formkit/vue';
|
|
88
122
|
import { getCurrentInstance, markRaw, nextTick } from 'vue';
|
|
89
|
-
import { marked } from 'marked';
|
|
90
|
-
import DOMPurify from 'dompurify';
|
|
91
123
|
|
|
124
|
+
// eslint-disable-next-line import/no-unresolved
|
|
92
125
|
import '@formkit/themes/genesis';
|
|
93
126
|
import UIDynamicFormFooterAction from './FooterActions.vue';
|
|
94
127
|
import UIDynamicFormTitleText from './TitleText.vue';
|
|
95
128
|
|
|
129
|
+
// eslint-disable-next-line no-unused-vars
|
|
96
130
|
function requiredIf({ value }, [targetField, expectedValue], node) {
|
|
97
131
|
console.debug(arguments);
|
|
98
132
|
|
|
@@ -100,44 +134,12 @@ function requiredIf({ value }, [targetField, expectedValue], node) {
|
|
|
100
134
|
const isEmpty = value === '' || value === null || value === undefined;
|
|
101
135
|
|
|
102
136
|
if (actual === expectedValue && isEmpty) {
|
|
103
|
-
return false;
|
|
137
|
+
return false; // oder: `return "Dieses Feld ist erforderlich."`
|
|
104
138
|
}
|
|
105
139
|
|
|
106
140
|
return true;
|
|
107
141
|
}
|
|
108
142
|
|
|
109
|
-
class MarkdownRenderer extends marked.Renderer {
|
|
110
|
-
link(params) {
|
|
111
|
-
const link = super.link(params);
|
|
112
|
-
return link.replace('<a', "<a target='_blank'");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
html(params) {
|
|
116
|
-
const result = super.html(params);
|
|
117
|
-
if (result.startsWith('<a ') && !result.includes('target=')) {
|
|
118
|
-
return result.replace('<a ', `<a target="_blank" `);
|
|
119
|
-
}
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
class MarkedHooks extends marked.Hooks {
|
|
125
|
-
postprocess(html) {
|
|
126
|
-
return DOMPurify.sanitize(html, { ADD_ATTR: ['target'] });
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function processMarkdown(content) {
|
|
131
|
-
if (!content) return '';
|
|
132
|
-
|
|
133
|
-
const html = marked.parse(content.toString(), {
|
|
134
|
-
renderer: new MarkdownRenderer(),
|
|
135
|
-
hooks: new MarkedHooks(),
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
return html;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
143
|
export default {
|
|
142
144
|
name: 'UIDynamicForm',
|
|
143
145
|
components: {
|
|
@@ -182,6 +184,7 @@ export default {
|
|
|
182
184
|
msg: null,
|
|
183
185
|
collapsed: false,
|
|
184
186
|
firstFormFieldRef: null,
|
|
187
|
+
componentCache: new Map(), // Add caching for components
|
|
185
188
|
};
|
|
186
189
|
},
|
|
187
190
|
computed: {
|
|
@@ -282,8 +285,27 @@ export default {
|
|
|
282
285
|
this.$socket?.off('msg-input:' + this.id);
|
|
283
286
|
},
|
|
284
287
|
methods: {
|
|
288
|
+
// Performance optimized component caching
|
|
289
|
+
getFieldComponent(field) {
|
|
290
|
+
const cacheKey = `${field.id}_${JSON.stringify(this.formData[field.id])}_${this.formIsFinished}_${
|
|
291
|
+
this.theme
|
|
292
|
+
}`;
|
|
293
|
+
|
|
294
|
+
if (this.componentCache.has(cacheKey)) {
|
|
295
|
+
return this.componentCache.get(cacheKey);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const component = this.createComponent(field);
|
|
299
|
+
this.componentCache.set(cacheKey, component);
|
|
300
|
+
return component;
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
// Clear cache when form data changes
|
|
304
|
+
clearComponentCache() {
|
|
305
|
+
this.componentCache.clear();
|
|
306
|
+
},
|
|
307
|
+
|
|
285
308
|
createComponent(field) {
|
|
286
|
-
console.debug('Creating component for field:', field);
|
|
287
309
|
const customForm = field.customForm ? JSON.parse(field.customForm) : {};
|
|
288
310
|
const hint = customForm.hint;
|
|
289
311
|
const placeholder = customForm.placeholder;
|
|
@@ -292,8 +314,10 @@ export default {
|
|
|
292
314
|
const customProperties = customForm.customProperties ?? [];
|
|
293
315
|
const isReadOnly =
|
|
294
316
|
this.props.readonly ||
|
|
295
|
-
|
|
296
|
-
|
|
317
|
+
this.formIsFinished ||
|
|
318
|
+
customProperties.find(
|
|
319
|
+
(entry) => ['readOnly', 'readonly'].includes(entry.name) && entry.value === 'true'
|
|
320
|
+
)
|
|
297
321
|
? 'true'
|
|
298
322
|
: undefined;
|
|
299
323
|
switch (field.type) {
|
|
@@ -314,7 +338,9 @@ export default {
|
|
|
314
338
|
wrapperClass: '$remove:formkit-wrapper',
|
|
315
339
|
labelClass: 'ui-dynamic-form-input-label',
|
|
316
340
|
inputClass: `input-${this.theme}`,
|
|
317
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
341
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
342
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
343
|
+
}`,
|
|
318
344
|
readonly: isReadOnly,
|
|
319
345
|
validationVisibility: 'live',
|
|
320
346
|
},
|
|
@@ -337,7 +363,9 @@ export default {
|
|
|
337
363
|
wrapperClass: '$remove:formkit-wrapper',
|
|
338
364
|
labelClass: 'ui-dynamic-form-input-label',
|
|
339
365
|
inputClass: `input-${this.theme}`,
|
|
340
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
366
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
367
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
368
|
+
}`,
|
|
341
369
|
readonly: isReadOnly,
|
|
342
370
|
validationVisibility: 'live',
|
|
343
371
|
},
|
|
@@ -356,7 +384,9 @@ export default {
|
|
|
356
384
|
wrapperClass: '$remove:formkit-wrapper',
|
|
357
385
|
labelClass: 'ui-dynamic-form-input-label',
|
|
358
386
|
inputClass: `input-${this.theme}`,
|
|
359
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
387
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
388
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
389
|
+
}`,
|
|
360
390
|
readonly: isReadOnly,
|
|
361
391
|
validation,
|
|
362
392
|
validationVisibility: 'live',
|
|
@@ -380,7 +410,9 @@ export default {
|
|
|
380
410
|
wrapperClass: '$remove:formkit-wrapper',
|
|
381
411
|
labelClass: 'ui-dynamic-form-input-label',
|
|
382
412
|
inputClass: `input-${this.theme}`,
|
|
383
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
413
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
414
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
415
|
+
}`,
|
|
384
416
|
readonly: isReadOnly,
|
|
385
417
|
disabled: isReadOnly,
|
|
386
418
|
validation,
|
|
@@ -406,7 +438,9 @@ export default {
|
|
|
406
438
|
wrapperClass: '$remove:formkit-wrapper',
|
|
407
439
|
labelClass: 'ui-dynamic-form-input-label',
|
|
408
440
|
inputClass: `input-${this.theme}`,
|
|
409
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
441
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
442
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
443
|
+
}`,
|
|
410
444
|
readonly: isReadOnly,
|
|
411
445
|
disabled: isReadOnly,
|
|
412
446
|
validation,
|
|
@@ -428,7 +462,9 @@ export default {
|
|
|
428
462
|
wrapperClass: '$remove:formkit-wrapper',
|
|
429
463
|
labelClass: 'ui-dynamic-form-input-label',
|
|
430
464
|
inputClass: `input-${this.theme}`,
|
|
431
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
465
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
466
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
467
|
+
}`,
|
|
432
468
|
readonly: isReadOnly,
|
|
433
469
|
validation,
|
|
434
470
|
validationVisibility: 'live',
|
|
@@ -452,13 +488,131 @@ export default {
|
|
|
452
488
|
help: hint,
|
|
453
489
|
labelClass: 'ui-dynamic-form-input-label',
|
|
454
490
|
inputClass: `input-${this.theme}`,
|
|
455
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
491
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
492
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
493
|
+
}`,
|
|
456
494
|
readonly: isReadOnly,
|
|
457
495
|
disabled: isReadOnly,
|
|
458
496
|
validation,
|
|
459
497
|
validationVisibility: 'live',
|
|
460
498
|
},
|
|
461
499
|
};
|
|
500
|
+
case 'file-preview':
|
|
501
|
+
// Handle file preview display only (no upload functionality)
|
|
502
|
+
const originalFieldId = field.id.replace('_preview', '');
|
|
503
|
+
if (this.formData && this.formData[originalFieldId] && this.formData[originalFieldId].length != 0) {
|
|
504
|
+
const fileDataArray = Array.isArray(this.formData[originalFieldId])
|
|
505
|
+
? this.formData[originalFieldId]
|
|
506
|
+
: [this.formData[originalFieldId]];
|
|
507
|
+
|
|
508
|
+
// Separate images from other files
|
|
509
|
+
const images = [];
|
|
510
|
+
const otherFiles = [];
|
|
511
|
+
|
|
512
|
+
fileDataArray.forEach((fileData) => {
|
|
513
|
+
const fileName = fileData.name || '';
|
|
514
|
+
const isImage = fileName.toLowerCase().match(/\.(png|jpg|jpeg|gif|webp)$/);
|
|
515
|
+
|
|
516
|
+
if (isImage && fileData.file && fileData.file.data) {
|
|
517
|
+
// Convert buffer to base64 data URL for image display - safe for large files
|
|
518
|
+
const uint8Array = new Uint8Array(fileData.file.data);
|
|
519
|
+
let binaryString = '';
|
|
520
|
+
|
|
521
|
+
// Process in chunks to avoid call stack overflow
|
|
522
|
+
const chunkSize = 1024;
|
|
523
|
+
for (let i = 0; i < uint8Array.length; i += chunkSize) {
|
|
524
|
+
const chunk = uint8Array.slice(i, i + chunkSize);
|
|
525
|
+
binaryString += String.fromCharCode.apply(null, chunk);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const base64String = btoa(binaryString);
|
|
529
|
+
const mimeType = fileName.toLowerCase().endsWith('.png')
|
|
530
|
+
? 'image/png'
|
|
531
|
+
: fileName.toLowerCase().endsWith('.gif')
|
|
532
|
+
? 'image/gif'
|
|
533
|
+
: 'image/jpeg';
|
|
534
|
+
const dataURL = `data:${mimeType};base64,${base64String}`;
|
|
535
|
+
|
|
536
|
+
images.push({ fileName, dataURL, fileData });
|
|
537
|
+
} else {
|
|
538
|
+
otherFiles.push({ fileName, fileData });
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
let content = `<label class="ui-dynamic-form-input-label">${field.label} (Vorschau)${
|
|
543
|
+
field.required ? ' *' : ''
|
|
544
|
+
}</label>`;
|
|
545
|
+
|
|
546
|
+
// Display images
|
|
547
|
+
if (images.length > 0) {
|
|
548
|
+
content += '<div style="margin-top: 8px;">';
|
|
549
|
+
content += '<div style="font-weight: bold; margin-bottom: 8px;">Bilder:</div>';
|
|
550
|
+
images.forEach((img, index) => {
|
|
551
|
+
const downloadId = `download-img-${field.id}-${index}`;
|
|
552
|
+
content += `
|
|
553
|
+
<div style="display: inline-block; margin: 8px; text-align: center; vertical-align: top;">
|
|
554
|
+
<img src="${img.dataURL}" alt="${img.fileName}"
|
|
555
|
+
style="max-width: 300px; max-height: 200px; border: 1px solid #ccc; display: block; cursor: pointer;"
|
|
556
|
+
onclick="document.getElementById('${downloadId}').click();" />
|
|
557
|
+
<div style="margin-top: 4px; font-size: 0.9em; color: #666; max-width: 300px; word-break: break-word;">
|
|
558
|
+
${img.fileName}
|
|
559
|
+
</div>
|
|
560
|
+
<a id="${downloadId}" href="${img.dataURL}" download="${img.fileName}" style="display: none;"></a>
|
|
561
|
+
</div>
|
|
562
|
+
`;
|
|
563
|
+
});
|
|
564
|
+
content += '</div>';
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Display other files as list
|
|
568
|
+
if (otherFiles.length > 0) {
|
|
569
|
+
content +=
|
|
570
|
+
'<div style="margin-top: 12px; padding: 12px; border: 1px solid #ddd; border-radius: 4px; background: #f9f9f9;">';
|
|
571
|
+
content += '<div style="font-weight: bold; margin-bottom: 8px;">Weitere Dateien:</div>';
|
|
572
|
+
otherFiles.forEach((file, index) => {
|
|
573
|
+
const downloadId = `download-file-${field.id}-${index}`;
|
|
574
|
+
const uint8Array = new Uint8Array(file.fileData.file.data);
|
|
575
|
+
let binaryString = '';
|
|
576
|
+
|
|
577
|
+
// Process in chunks for download
|
|
578
|
+
const chunkSize = 1024;
|
|
579
|
+
for (let i = 0; i < uint8Array.length; i += chunkSize) {
|
|
580
|
+
const chunk = uint8Array.slice(i, i + chunkSize);
|
|
581
|
+
binaryString += String.fromCharCode.apply(null, chunk);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const base64String = btoa(binaryString);
|
|
585
|
+
const dataURL = `data:application/octet-stream;base64,${base64String}`;
|
|
586
|
+
|
|
587
|
+
content += `
|
|
588
|
+
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px; padding: 4px; border-radius: 3px; cursor: pointer;"
|
|
589
|
+
onclick="document.getElementById('${downloadId}').click();"
|
|
590
|
+
onmouseover="this.style.backgroundColor='#e6e6e6';"
|
|
591
|
+
onmouseout="this.style.backgroundColor='transparent';">
|
|
592
|
+
<span style="font-size: 1.2em;">📎</span>
|
|
593
|
+
<span style="flex: 1; word-break: break-word;">${file.fileName}</span>
|
|
594
|
+
<span style="font-size: 0.8em; color: #007bff;">Download</span>
|
|
595
|
+
<a id="${downloadId}" href="${dataURL}" download="${file.fileName}" style="display: none;"></a>
|
|
596
|
+
</div>
|
|
597
|
+
`;
|
|
598
|
+
});
|
|
599
|
+
content += '</div>';
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return {
|
|
603
|
+
type: 'div',
|
|
604
|
+
props: {
|
|
605
|
+
innerHTML: content,
|
|
606
|
+
},
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
// If no files to preview, return empty div
|
|
610
|
+
return {
|
|
611
|
+
type: 'div',
|
|
612
|
+
props: {
|
|
613
|
+
style: 'display: none;',
|
|
614
|
+
},
|
|
615
|
+
};
|
|
462
616
|
case 'file':
|
|
463
617
|
const multiple = field.customForm ? JSON.parse(field.customForm).multiple === 'true' : false;
|
|
464
618
|
return {
|
|
@@ -469,13 +623,11 @@ export default {
|
|
|
469
623
|
name,
|
|
470
624
|
label: field.label,
|
|
471
625
|
required: field.required,
|
|
472
|
-
value: this.formData[field.id],
|
|
473
626
|
help: hint,
|
|
474
627
|
innerClass: 'reset-background',
|
|
475
628
|
wrapperClass: '$remove:formkit-wrapper',
|
|
476
629
|
labelClass: 'ui-dynamic-form-input-label',
|
|
477
630
|
inputClass: `input-${this.theme}`,
|
|
478
|
-
// innerClass: ui-dynamic-form-input-outlines `${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
479
631
|
readonly: isReadOnly,
|
|
480
632
|
disabled: isReadOnly,
|
|
481
633
|
multiple,
|
|
@@ -501,7 +653,9 @@ export default {
|
|
|
501
653
|
fieldsetClass: 'custom-fieldset',
|
|
502
654
|
labelClass: 'ui-dynamic-form-input-label',
|
|
503
655
|
inputClass: `input-${this.theme}`,
|
|
504
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
656
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
657
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
658
|
+
}`,
|
|
505
659
|
readonly: isReadOnly,
|
|
506
660
|
disabled: isReadOnly,
|
|
507
661
|
validation,
|
|
@@ -539,7 +693,9 @@ export default {
|
|
|
539
693
|
wrapperClass: '$remove:formkit-wrapper',
|
|
540
694
|
labelClass: 'ui-dynamic-form-input-label',
|
|
541
695
|
inputClass: `input-${this.theme}`,
|
|
542
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
696
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
697
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
698
|
+
}`,
|
|
543
699
|
readonly: isReadOnly,
|
|
544
700
|
validation,
|
|
545
701
|
validationVisibility: 'live',
|
|
@@ -560,7 +716,9 @@ export default {
|
|
|
560
716
|
wrapperClass: '$remove:formkit-wrapper',
|
|
561
717
|
labelClass: 'ui-dynamic-form-input-label',
|
|
562
718
|
inputClass: `input-${this.theme}`,
|
|
563
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
719
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
720
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
721
|
+
}`,
|
|
564
722
|
readonly: isReadOnly,
|
|
565
723
|
validation,
|
|
566
724
|
validationVisibility: 'live',
|
|
@@ -600,19 +758,18 @@ export default {
|
|
|
600
758
|
wrapperClass: '$remove:formkit-wrapper',
|
|
601
759
|
labelClass: 'ui-dynamic-form-input-label',
|
|
602
760
|
inputClass: `input-${this.theme}`,
|
|
603
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
761
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
762
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
763
|
+
}`,
|
|
604
764
|
readonly: isReadOnly,
|
|
605
765
|
validation,
|
|
606
766
|
validationVisibility: 'live',
|
|
607
767
|
},
|
|
608
768
|
};
|
|
609
769
|
case 'paragraph':
|
|
610
|
-
const paragraphContent = this.formData[field.id] || field.defaultValue || field.label || '';
|
|
611
|
-
const processedHtml = processMarkdown(paragraphContent);
|
|
612
770
|
return {
|
|
613
|
-
type: '
|
|
614
|
-
|
|
615
|
-
class: 'ui-dynamic-form-paragraph',
|
|
771
|
+
type: 'p',
|
|
772
|
+
innerText: this.formData[field.id],
|
|
616
773
|
};
|
|
617
774
|
case 'password':
|
|
618
775
|
return {
|
|
@@ -629,7 +786,9 @@ export default {
|
|
|
629
786
|
wrapperClass: '$remove:formkit-wrapper',
|
|
630
787
|
labelClass: 'ui-dynamic-form-input-label',
|
|
631
788
|
inputClass: `input-${this.theme}`,
|
|
632
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
789
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
790
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
791
|
+
}`,
|
|
633
792
|
readonly: isReadOnly,
|
|
634
793
|
validation,
|
|
635
794
|
validationVisibility: 'live',
|
|
@@ -653,7 +812,9 @@ export default {
|
|
|
653
812
|
fieldsetClass: 'custom-fieldset',
|
|
654
813
|
labelClass: 'ui-dynamic-form-input-label',
|
|
655
814
|
inputClass: `input-${this.theme}`,
|
|
656
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
815
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
816
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
817
|
+
}`,
|
|
657
818
|
readonly: isReadOnly,
|
|
658
819
|
disabled: isReadOnly,
|
|
659
820
|
validation,
|
|
@@ -700,7 +861,9 @@ export default {
|
|
|
700
861
|
wrapperClass: '$remove:formkit-wrapper',
|
|
701
862
|
labelClass: 'ui-dynamic-form-input-label',
|
|
702
863
|
inputClass: `input-${this.theme}`,
|
|
703
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
864
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
865
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
866
|
+
}`,
|
|
704
867
|
readonly: isReadOnly,
|
|
705
868
|
validation,
|
|
706
869
|
validationVisibility: 'live',
|
|
@@ -723,7 +886,9 @@ export default {
|
|
|
723
886
|
wrapperClass: '$remove:formkit-wrapper',
|
|
724
887
|
labelClass: 'ui-dynamic-form-input-label',
|
|
725
888
|
inputClass: `input-${this.theme}`,
|
|
726
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
889
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
890
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
891
|
+
}`,
|
|
727
892
|
readonly: isReadOnly,
|
|
728
893
|
validation,
|
|
729
894
|
validationVisibility: 'live',
|
|
@@ -744,7 +909,9 @@ export default {
|
|
|
744
909
|
wrapperClass: '$remove:formkit-wrapper',
|
|
745
910
|
labelClass: 'ui-dynamic-form-input-label',
|
|
746
911
|
inputClass: `input-${this.theme}`,
|
|
747
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
912
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
913
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
914
|
+
}`,
|
|
748
915
|
readonly: isReadOnly,
|
|
749
916
|
validation,
|
|
750
917
|
validationVisibility: 'live',
|
|
@@ -765,7 +932,9 @@ export default {
|
|
|
765
932
|
wrapperClass: '$remove:formkit-wrapper',
|
|
766
933
|
labelClass: 'ui-dynamic-form-input-label',
|
|
767
934
|
inputClass: `input-${this.theme}`,
|
|
768
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
935
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
936
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
937
|
+
}`,
|
|
769
938
|
readonly: isReadOnly,
|
|
770
939
|
validation,
|
|
771
940
|
validationVisibility: 'live',
|
|
@@ -786,7 +955,9 @@ export default {
|
|
|
786
955
|
wrapperClass: '$remove:formkit-wrapper',
|
|
787
956
|
labelClass: 'ui-dynamic-form-input-label',
|
|
788
957
|
inputClass: `input-${this.theme}`,
|
|
789
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
958
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
959
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
960
|
+
}`,
|
|
790
961
|
readonly: isReadOnly,
|
|
791
962
|
validation,
|
|
792
963
|
validationVisibility: 'live',
|
|
@@ -805,7 +976,9 @@ export default {
|
|
|
805
976
|
help: hint,
|
|
806
977
|
labelClass: 'ui-dynamic-form-input-label',
|
|
807
978
|
inputClass: `input-${this.theme}`,
|
|
808
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
979
|
+
innerClass: `ui-dynamic-form-input-outlines ${
|
|
980
|
+
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
981
|
+
}`,
|
|
809
982
|
readonly: isReadOnly,
|
|
810
983
|
validation,
|
|
811
984
|
validationVisibility: 'live',
|
|
@@ -838,9 +1011,9 @@ export default {
|
|
|
838
1011
|
return fieldMap;
|
|
839
1012
|
},
|
|
840
1013
|
/*
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1014
|
+
widget-action just sends a msg to Node-RED, it does not store the msg state server-side
|
|
1015
|
+
alternatively, you can use widget-change, which will also store the msg in the Node's datastore
|
|
1016
|
+
*/
|
|
844
1017
|
send(msg, index) {
|
|
845
1018
|
const msgArr = [];
|
|
846
1019
|
msgArr[index] = msg;
|
|
@@ -848,6 +1021,9 @@ export default {
|
|
|
848
1021
|
},
|
|
849
1022
|
init(msg) {
|
|
850
1023
|
this.msg = msg;
|
|
1024
|
+
// Clear component cache when form data changes for performance
|
|
1025
|
+
this.clearComponentCache();
|
|
1026
|
+
|
|
851
1027
|
if (!msg) {
|
|
852
1028
|
return;
|
|
853
1029
|
}
|
|
@@ -866,7 +1042,7 @@ export default {
|
|
|
866
1042
|
|
|
867
1043
|
const formFields = this.userTask.userTaskConfig.formFields;
|
|
868
1044
|
const formFieldIds = formFields.map((ff) => ff.id);
|
|
869
|
-
const initialValues = this.userTask.startToken;
|
|
1045
|
+
const initialValues = this.userTask.startToken.formData; //luis777
|
|
870
1046
|
const finishedFormData = msg.payload.formData;
|
|
871
1047
|
this.formIsFinished = !!msg.payload.formData;
|
|
872
1048
|
if (this.formIsFinished) {
|
|
@@ -897,14 +1073,29 @@ export default {
|
|
|
897
1073
|
];
|
|
898
1074
|
}
|
|
899
1075
|
});
|
|
1076
|
+
|
|
1077
|
+
// Check for file fields and duplicate them as file-preview if initial values exist
|
|
1078
|
+
// Insert preview fields directly before their corresponding file fields
|
|
1079
|
+
for (let i = formFields.length - 1; i >= 0; i--) {
|
|
1080
|
+
const field = formFields[i];
|
|
1081
|
+
if (field.type === 'file' && initialValues && initialValues[field.id]) {
|
|
1082
|
+
const previewField = { ...field };
|
|
1083
|
+
previewField.type = 'file-preview';
|
|
1084
|
+
previewField.id = `${field.id}_preview`; // Give it a unique ID
|
|
1085
|
+
this.userTask.userTaskConfig.formFields.splice(i, 0, previewField);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
900
1088
|
}
|
|
901
1089
|
|
|
902
1090
|
if (initialValues) {
|
|
903
|
-
|
|
904
|
-
.
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1091
|
+
if (initialValues) {
|
|
1092
|
+
Object.keys(initialValues)
|
|
1093
|
+
.filter((key) => formFieldIds.includes(key))
|
|
1094
|
+
.forEach((key) => {
|
|
1095
|
+
console.log('luis888.2', key, initialValues[key]);
|
|
1096
|
+
this.formData[key] = initialValues[key];
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
908
1099
|
}
|
|
909
1100
|
|
|
910
1101
|
if (this.formIsFinished) {
|
|
@@ -967,7 +1158,7 @@ export default {
|
|
|
967
1158
|
this.send(
|
|
968
1159
|
msg,
|
|
969
1160
|
this.actions.findIndex((element) => element.label === action.label) +
|
|
970
|
-
|
|
1161
|
+
(this.isConfirmDialog ? this.props.options.length : 0)
|
|
971
1162
|
);
|
|
972
1163
|
// TODO: mm - end
|
|
973
1164
|
} else {
|
|
@@ -1003,7 +1194,9 @@ export default {
|
|
|
1003
1194
|
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(this.firstFormFieldRef.$el.tagName)) {
|
|
1004
1195
|
inputElement = this.firstFormFieldRef.$el;
|
|
1005
1196
|
} else {
|
|
1006
|
-
inputElement = this.firstFormFieldRef.$el.querySelector(
|
|
1197
|
+
inputElement = this.firstFormFieldRef.$el.querySelector(
|
|
1198
|
+
'input:not([type="hidden"]), textarea, select'
|
|
1199
|
+
);
|
|
1007
1200
|
}
|
|
1008
1201
|
}
|
|
1009
1202
|
|