@5minds/node-red-dashboard-2-processcube-dynamic-form 2.1.0-file-preview-1bee64-mdpzqcz2 → 2.1.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 +1 -1
- package/resources/ui-dynamic-form.umd.js +33 -82
- package/ui/components/UIDynamicForm.vue +143 -684
|
@@ -1,96 +1,60 @@
|
|
|
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
|
-
:
|
|
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
|
-
/>
|
|
4
|
+
<UIDynamicFormTitleText v-if="props.title_style === 'outside' && hasUserTask" :style="props.title_style"
|
|
5
|
+
:title="effectiveTitle" :customStyles="props.title_custom_text_styling" :titleIcon="props.title_icon"
|
|
6
|
+
:collapsible="props.collapsible || (props.collapse_when_finished && formIsFinished)" :collapsed="collapsed"
|
|
7
|
+
:toggleCollapse="toggleCollapse" />
|
|
14
8
|
<div className="ui-dynamic-form-wrapper">
|
|
15
9
|
<p v-if="hasUserTask" style="margin-bottom: 0px">
|
|
16
10
|
<v-form ref="form" v-model="form" :class="dynamicClass">
|
|
17
|
-
<UIDynamicFormTitleText
|
|
18
|
-
|
|
19
|
-
:style="props.title_style"
|
|
20
|
-
:title="effectiveTitle"
|
|
21
|
-
:customStyles="props.title_custom_text_styling"
|
|
11
|
+
<UIDynamicFormTitleText v-if="props.title_style != 'outside'" :style="props.title_style"
|
|
12
|
+
:title="effectiveTitle" :customStyles="props.title_custom_text_styling"
|
|
22
13
|
:titleIcon="props.title_icon"
|
|
23
14
|
:collapsible="props.collapsible || (props.collapse_when_finished && formIsFinished)"
|
|
24
|
-
:collapsed="collapsed"
|
|
25
|
-
:toggleCollapse="toggleCollapse"
|
|
26
|
-
/>
|
|
15
|
+
:collapsed="collapsed" :toggleCollapse="toggleCollapse" />
|
|
27
16
|
<Transition name="cardCollapse">
|
|
28
17
|
<div v-if="!collapsed">
|
|
29
|
-
<div
|
|
30
|
-
|
|
31
|
-
:style="props.inner_card_styling"
|
|
32
|
-
:data-columns="props.form_columns || 1"
|
|
33
|
-
>
|
|
18
|
+
<div className="ui-dynamic-form-formfield-positioner" :style="props.inner_card_styling"
|
|
19
|
+
:data-columns="props.form_columns || 1">
|
|
34
20
|
<FormKit id="form" type="group">
|
|
35
|
-
<v-row
|
|
36
|
-
v-for="(field, index) in fields()"
|
|
37
|
-
:key="field"
|
|
21
|
+
<v-row v-for="(field, index) in fields()" :key="field"
|
|
38
22
|
:class="field.type === 'header' ? 'ui-dynamic-form-header-row' : ''"
|
|
39
|
-
:style="getRowWidthStyling(field, index)"
|
|
40
|
-
>
|
|
23
|
+
:style="getRowWidthStyling(field, index)">
|
|
41
24
|
<v-col cols="12">
|
|
42
|
-
<component
|
|
43
|
-
|
|
44
|
-
v-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
v-bind="getFieldComponent(field).props"
|
|
58
|
-
:ref="
|
|
59
|
-
(el) => {
|
|
60
|
-
if (index === 0) firstFormFieldRef = el;
|
|
61
|
-
}
|
|
62
|
-
"
|
|
63
|
-
v-model="formData[field.id]"
|
|
64
|
-
>
|
|
65
|
-
{{ getFieldComponent(field).innerText }}
|
|
25
|
+
<component :is="createComponent(field).type"
|
|
26
|
+
v-if="createComponent(field).innerHTML"
|
|
27
|
+
v-bind="createComponent(field).props"
|
|
28
|
+
:class="createComponent(field).class"
|
|
29
|
+
v-html="createComponent(field).innerHTML" :ref="(el) => {
|
|
30
|
+
if (index === 0) firstFormFieldRef = el;
|
|
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 }}
|
|
66
40
|
</component>
|
|
67
|
-
<div v-else-if="
|
|
41
|
+
<div v-else-if="createComponent(field).type == 'v-slider'">
|
|
68
42
|
<p class="formkit-label">{{ field.label }}</p>
|
|
69
|
-
<component
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (index === 0) firstFormFieldRef = el;
|
|
75
|
-
}
|
|
76
|
-
"
|
|
77
|
-
v-model="field.defaultValue"
|
|
78
|
-
/>
|
|
43
|
+
<component :is="createComponent(field).type"
|
|
44
|
+
v-bind="createComponent(field).props" :ref="(el) => {
|
|
45
|
+
if (index === 0) firstFormFieldRef = el;
|
|
46
|
+
}
|
|
47
|
+
" v-model="field.defaultValue" />
|
|
79
48
|
<p class="formkit-help">
|
|
80
|
-
{{
|
|
49
|
+
{{ field.customForm ? JSON.parse(field.customForm).hint : undefined
|
|
50
|
+
}}
|
|
81
51
|
</p>
|
|
82
52
|
</div>
|
|
83
|
-
<component
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
(el) => {
|
|
89
|
-
if (index === 0) firstFormFieldRef = el;
|
|
90
|
-
}
|
|
91
|
-
"
|
|
92
|
-
v-model="formData[field.id]"
|
|
93
|
-
/>
|
|
53
|
+
<component :is="createComponent(field).type" v-else
|
|
54
|
+
v-bind="createComponent(field).props" :ref="(el) => {
|
|
55
|
+
if (index === 0) firstFormFieldRef = el;
|
|
56
|
+
}
|
|
57
|
+
" v-model="formData[field.id]" />
|
|
94
58
|
</v-col>
|
|
95
59
|
</v-row>
|
|
96
60
|
</FormKit>
|
|
@@ -99,24 +63,17 @@
|
|
|
99
63
|
<v-row v-if="errorMsg.length > 0" style="padding: 12px">
|
|
100
64
|
<v-alert type="error">Error: {{ errorMsg }}</v-alert>
|
|
101
65
|
</v-row>
|
|
102
|
-
<UIDynamicFormFooterAction
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
:actionCallback="actionFn"
|
|
106
|
-
:formIsFinished="formIsFinished"
|
|
107
|
-
style="padding: 16px; padding-top: 0px"
|
|
108
|
-
/>
|
|
66
|
+
<UIDynamicFormFooterAction v-if="props.actions_inside_card && actions.length > 0"
|
|
67
|
+
:actions="actions" :actionCallback="actionFn" :formIsFinished="formIsFinished"
|
|
68
|
+
style="padding: 16px; padding-top: 0px" />
|
|
109
69
|
</v-row>
|
|
110
70
|
</div>
|
|
111
71
|
</Transition>
|
|
112
72
|
</v-form>
|
|
113
73
|
</p>
|
|
114
74
|
<p v-else>
|
|
115
|
-
<v-alert
|
|
116
|
-
|
|
117
|
-
:text="props.waiting_info"
|
|
118
|
-
:title="props.waiting_title"
|
|
119
|
-
/>
|
|
75
|
+
<v-alert v-if="props.waiting_info.length > 0 || props.waiting_title.length > 0"
|
|
76
|
+
:text="props.waiting_info" :title="props.waiting_title" />
|
|
120
77
|
</p>
|
|
121
78
|
</div>
|
|
122
79
|
<div v-if="!props.actions_inside_card && actions.length > 0 && hasUserTask" style="padding-top: 32px">
|
|
@@ -137,6 +94,8 @@ import UIDynamicFormFooterAction from './FooterActions.vue';
|
|
|
137
94
|
import UIDynamicFormTitleText from './TitleText.vue';
|
|
138
95
|
|
|
139
96
|
function requiredIf({ value }, [targetField, expectedValue], node) {
|
|
97
|
+
console.debug(arguments);
|
|
98
|
+
|
|
140
99
|
const actual = node?.root?.value?.[targetField];
|
|
141
100
|
const isEmpty = value === '' || value === null || value === undefined;
|
|
142
101
|
|
|
@@ -197,6 +156,9 @@ export default {
|
|
|
197
156
|
},
|
|
198
157
|
},
|
|
199
158
|
setup(props) {
|
|
159
|
+
console.info('UIDynamicForm setup with:', props);
|
|
160
|
+
console.debug('Vue function loaded correctly', markRaw);
|
|
161
|
+
|
|
200
162
|
const instance = getCurrentInstance();
|
|
201
163
|
const app = instance.appContext.app;
|
|
202
164
|
|
|
@@ -204,6 +166,7 @@ export default {
|
|
|
204
166
|
theme: 'genesis',
|
|
205
167
|
locales: { de },
|
|
206
168
|
locale: 'de',
|
|
169
|
+
// eslint-disable-next-line object-shorthand
|
|
207
170
|
rules: { requiredIf: requiredIf },
|
|
208
171
|
});
|
|
209
172
|
app.use(plugin, formkitConfig);
|
|
@@ -219,8 +182,6 @@ export default {
|
|
|
219
182
|
msg: null,
|
|
220
183
|
collapsed: false,
|
|
221
184
|
firstFormFieldRef: null,
|
|
222
|
-
intersectionObserver: null,
|
|
223
|
-
visibleFileFields: new Set(),
|
|
224
185
|
};
|
|
225
186
|
},
|
|
226
187
|
computed: {
|
|
@@ -241,7 +202,7 @@ export default {
|
|
|
241
202
|
);
|
|
242
203
|
},
|
|
243
204
|
isConfirmDialog() {
|
|
244
|
-
return this.userTask
|
|
205
|
+
return this.userTask.userTaskConfig.formFields.some((field) => field.type === 'confirm');
|
|
245
206
|
},
|
|
246
207
|
effectiveTitle() {
|
|
247
208
|
if (this.props.title_text_type === 'str') {
|
|
@@ -252,29 +213,6 @@ export default {
|
|
|
252
213
|
return '';
|
|
253
214
|
}
|
|
254
215
|
},
|
|
255
|
-
// Optimized computed property for field components
|
|
256
|
-
fieldComponents() {
|
|
257
|
-
if (!this.userTask?.userTaskConfig?.formFields) {
|
|
258
|
-
return {};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const components = {};
|
|
262
|
-
const aFields = this.userTask.userTaskConfig.formFields;
|
|
263
|
-
|
|
264
|
-
aFields.forEach((field) => {
|
|
265
|
-
components[field.id] = this.createComponent(field);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
return components;
|
|
269
|
-
},
|
|
270
|
-
// Optimized computed property for fields
|
|
271
|
-
computedFields() {
|
|
272
|
-
const aFields = this.userTask?.userTaskConfig?.formFields ?? [];
|
|
273
|
-
return aFields.map((field) => ({
|
|
274
|
-
...field,
|
|
275
|
-
items: mapItems(field.type, field),
|
|
276
|
-
}));
|
|
277
|
-
},
|
|
278
216
|
},
|
|
279
217
|
watch: {
|
|
280
218
|
formData: {
|
|
@@ -328,9 +266,6 @@ export default {
|
|
|
328
266
|
element.classList.add('test');
|
|
329
267
|
});
|
|
330
268
|
|
|
331
|
-
// Initialize Intersection Observer for lazy loading
|
|
332
|
-
this.initLazyLoading();
|
|
333
|
-
|
|
334
269
|
this.$socket.on('widget-load:' + this.id, (msg) => {
|
|
335
270
|
this.init(msg);
|
|
336
271
|
});
|
|
@@ -345,192 +280,86 @@ export default {
|
|
|
345
280
|
/* Make sure, any events you subscribe to on SocketIO are unsubscribed to here */
|
|
346
281
|
this.$socket?.off('widget-load' + this.id);
|
|
347
282
|
this.$socket?.off('msg-input:' + this.id);
|
|
348
|
-
|
|
349
|
-
// Clean up Intersection Observer
|
|
350
|
-
if (this.intersectionObserver) {
|
|
351
|
-
this.intersectionObserver.disconnect();
|
|
352
|
-
}
|
|
353
283
|
},
|
|
354
284
|
methods: {
|
|
355
|
-
// Simplified component getter - now just returns from computed cache
|
|
356
|
-
getFieldComponent(field) {
|
|
357
|
-
return this.fieldComponents[field.id] || this.createComponent(field);
|
|
358
|
-
},
|
|
359
|
-
|
|
360
|
-
// Clear cache when form data changes
|
|
361
|
-
clearComponentCache() {
|
|
362
|
-
// This is now handled by computed properties automatically
|
|
363
|
-
},
|
|
364
|
-
|
|
365
|
-
// Safe method to get field hint for template use
|
|
366
|
-
getFieldHint(field) {
|
|
367
|
-
try {
|
|
368
|
-
if (field.customForm) {
|
|
369
|
-
let customForm;
|
|
370
|
-
if (typeof field.customForm === 'string') {
|
|
371
|
-
customForm = JSON.parse(field.customForm);
|
|
372
|
-
} else if (typeof field.customForm === 'object') {
|
|
373
|
-
customForm = field.customForm;
|
|
374
|
-
}
|
|
375
|
-
return customForm?.hint;
|
|
376
|
-
}
|
|
377
|
-
} catch (error) {
|
|
378
|
-
console.warn('Failed to parse customForm hint for field', field.id, error);
|
|
379
|
-
}
|
|
380
|
-
return undefined;
|
|
381
|
-
},
|
|
382
|
-
|
|
383
285
|
createComponent(field) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
customForm = JSON.parse(field.customForm);
|
|
390
|
-
} else if (typeof field.customForm === 'object') {
|
|
391
|
-
customForm = field.customForm;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
} catch (error) {
|
|
395
|
-
console.warn('Failed to parse customForm for field', field.id, error);
|
|
396
|
-
customForm = {};
|
|
397
|
-
}
|
|
398
|
-
const { hint, placeholder, validation, customProperties = [] } = customForm;
|
|
286
|
+
console.debug('Creating component for field:', field);
|
|
287
|
+
const customForm = field.customForm ? JSON.parse(field.customForm) : {};
|
|
288
|
+
const hint = customForm.hint;
|
|
289
|
+
const placeholder = customForm.placeholder;
|
|
290
|
+
const validation = customForm.validation;
|
|
399
291
|
const name = field.id;
|
|
292
|
+
const customProperties = customForm.customProperties ?? [];
|
|
400
293
|
const isReadOnly =
|
|
401
294
|
this.props.readonly ||
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
(entry) => ['readOnly', 'readonly'].includes(entry.name) && entry.value === 'true'
|
|
405
|
-
)
|
|
295
|
+
this.formIsFinished ||
|
|
296
|
+
customProperties.find((entry) => ['readOnly', 'readonly'].includes(entry.name) && entry.value === 'true')
|
|
406
297
|
? 'true'
|
|
407
298
|
: undefined;
|
|
408
|
-
|
|
409
|
-
const commonFormKitProps = {
|
|
410
|
-
id: field.id,
|
|
411
|
-
name,
|
|
412
|
-
label: field.label,
|
|
413
|
-
required: field.required,
|
|
414
|
-
value: this.formData[field.id],
|
|
415
|
-
help: hint,
|
|
416
|
-
wrapperClass: '$remove:formkit-wrapper',
|
|
417
|
-
labelClass: 'ui-dynamic-form-input-label',
|
|
418
|
-
inputClass: `input-${this.theme}`,
|
|
419
|
-
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
420
|
-
readonly: isReadOnly,
|
|
421
|
-
validation,
|
|
422
|
-
validationVisibility: 'live',
|
|
423
|
-
};
|
|
424
|
-
|
|
425
299
|
switch (field.type) {
|
|
426
300
|
case 'long':
|
|
427
301
|
return {
|
|
428
302
|
type: 'FormKit',
|
|
429
303
|
props: {
|
|
430
|
-
...commonFormKitProps,
|
|
431
304
|
type: 'number',
|
|
305
|
+
id: field.id,
|
|
306
|
+
name,
|
|
307
|
+
label: field.label,
|
|
308
|
+
required: field.required,
|
|
309
|
+
value: this.formData[field.id],
|
|
432
310
|
number: 'integer',
|
|
433
311
|
min: 0,
|
|
434
312
|
validation: validation ? `${validation}|number` : 'number',
|
|
313
|
+
help: hint,
|
|
314
|
+
wrapperClass: '$remove:formkit-wrapper',
|
|
315
|
+
labelClass: 'ui-dynamic-form-input-label',
|
|
316
|
+
inputClass: `input-${this.theme}`,
|
|
317
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
318
|
+
readonly: isReadOnly,
|
|
319
|
+
validationVisibility: 'live',
|
|
435
320
|
},
|
|
436
321
|
};
|
|
437
322
|
case 'number':
|
|
438
|
-
const step = customForm.step;
|
|
323
|
+
const step = field.customForm ? JSON.parse(field.customForm).step : undefined;
|
|
439
324
|
return {
|
|
440
325
|
type: 'FormKit',
|
|
441
326
|
props: {
|
|
442
|
-
...commonFormKitProps,
|
|
443
327
|
type: 'number',
|
|
328
|
+
id: field.id,
|
|
329
|
+
name,
|
|
330
|
+
label: field.label,
|
|
331
|
+
required: field.required,
|
|
332
|
+
value: this.formData[field.id],
|
|
444
333
|
step,
|
|
445
334
|
number: 'float',
|
|
446
335
|
validation: validation ? `${validation}|number` : 'number',
|
|
336
|
+
help: hint,
|
|
337
|
+
wrapperClass: '$remove:formkit-wrapper',
|
|
338
|
+
labelClass: 'ui-dynamic-form-input-label',
|
|
339
|
+
inputClass: `input-${this.theme}`,
|
|
340
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
341
|
+
readonly: isReadOnly,
|
|
342
|
+
validationVisibility: 'live',
|
|
447
343
|
},
|
|
448
344
|
};
|
|
449
345
|
case 'date':
|
|
450
346
|
return {
|
|
451
347
|
type: 'FormKit',
|
|
452
348
|
props: {
|
|
453
|
-
...commonFormKitProps,
|
|
454
349
|
type: 'date',
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
type: 'FormKit',
|
|
469
|
-
props: {
|
|
470
|
-
...commonFormKitProps,
|
|
471
|
-
type: 'email',
|
|
472
|
-
placeholder,
|
|
473
|
-
},
|
|
474
|
-
};
|
|
475
|
-
case 'password':
|
|
476
|
-
return {
|
|
477
|
-
type: 'FormKit',
|
|
478
|
-
props: {
|
|
479
|
-
...commonFormKitProps,
|
|
480
|
-
type: 'password',
|
|
481
|
-
placeholder,
|
|
482
|
-
},
|
|
483
|
-
};
|
|
484
|
-
case 'tel':
|
|
485
|
-
return {
|
|
486
|
-
type: 'FormKit',
|
|
487
|
-
props: {
|
|
488
|
-
...commonFormKitProps,
|
|
489
|
-
type: 'tel',
|
|
490
|
-
placeholder,
|
|
491
|
-
},
|
|
492
|
-
};
|
|
493
|
-
case 'url':
|
|
494
|
-
return {
|
|
495
|
-
type: 'FormKit',
|
|
496
|
-
props: {
|
|
497
|
-
...commonFormKitProps,
|
|
498
|
-
type: 'url',
|
|
499
|
-
placeholder,
|
|
500
|
-
},
|
|
501
|
-
};
|
|
502
|
-
case 'time':
|
|
503
|
-
return {
|
|
504
|
-
type: 'FormKit',
|
|
505
|
-
props: {
|
|
506
|
-
...commonFormKitProps,
|
|
507
|
-
type: 'time',
|
|
508
|
-
placeholder,
|
|
509
|
-
},
|
|
510
|
-
};
|
|
511
|
-
case 'week':
|
|
512
|
-
return {
|
|
513
|
-
type: 'FormKit',
|
|
514
|
-
props: {
|
|
515
|
-
...commonFormKitProps,
|
|
516
|
-
type: 'week',
|
|
517
|
-
placeholder,
|
|
518
|
-
},
|
|
519
|
-
};
|
|
520
|
-
case 'month':
|
|
521
|
-
return {
|
|
522
|
-
type: 'FormKit',
|
|
523
|
-
props: {
|
|
524
|
-
...commonFormKitProps,
|
|
525
|
-
type: 'month',
|
|
526
|
-
},
|
|
527
|
-
};
|
|
528
|
-
case 'datetime-local':
|
|
529
|
-
return {
|
|
530
|
-
type: 'FormKit',
|
|
531
|
-
props: {
|
|
532
|
-
...commonFormKitProps,
|
|
533
|
-
type: 'datetime-local',
|
|
350
|
+
id: field.id,
|
|
351
|
+
name,
|
|
352
|
+
label: field.label,
|
|
353
|
+
required: field.required,
|
|
354
|
+
value: this.formData[field.id],
|
|
355
|
+
help: hint,
|
|
356
|
+
wrapperClass: '$remove:formkit-wrapper',
|
|
357
|
+
labelClass: 'ui-dynamic-form-input-label',
|
|
358
|
+
inputClass: `input-${this.theme}`,
|
|
359
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
360
|
+
readonly: isReadOnly,
|
|
361
|
+
validation,
|
|
362
|
+
validationVisibility: 'live',
|
|
534
363
|
},
|
|
535
364
|
};
|
|
536
365
|
case 'enum':
|
|
@@ -551,9 +380,7 @@ export default {
|
|
|
551
380
|
wrapperClass: '$remove:formkit-wrapper',
|
|
552
381
|
labelClass: 'ui-dynamic-form-input-label',
|
|
553
382
|
inputClass: `input-${this.theme}`,
|
|
554
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
555
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
556
|
-
}`,
|
|
383
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
557
384
|
readonly: isReadOnly,
|
|
558
385
|
disabled: isReadOnly,
|
|
559
386
|
validation,
|
|
@@ -561,11 +388,9 @@ export default {
|
|
|
561
388
|
},
|
|
562
389
|
};
|
|
563
390
|
case 'select':
|
|
564
|
-
const selections = customForm.entries
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
})
|
|
568
|
-
: [];
|
|
391
|
+
const selections = JSON.parse(field.customForm).entries.map((obj) => {
|
|
392
|
+
return { value: obj.key, label: obj.value };
|
|
393
|
+
});
|
|
569
394
|
return {
|
|
570
395
|
type: 'FormKit',
|
|
571
396
|
props: {
|
|
@@ -581,9 +406,7 @@ export default {
|
|
|
581
406
|
wrapperClass: '$remove:formkit-wrapper',
|
|
582
407
|
labelClass: 'ui-dynamic-form-input-label',
|
|
583
408
|
inputClass: `input-${this.theme}`,
|
|
584
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
585
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
586
|
-
}`,
|
|
409
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
587
410
|
readonly: isReadOnly,
|
|
588
411
|
disabled: isReadOnly,
|
|
589
412
|
validation,
|
|
@@ -605,9 +428,7 @@ export default {
|
|
|
605
428
|
wrapperClass: '$remove:formkit-wrapper',
|
|
606
429
|
labelClass: 'ui-dynamic-form-input-label',
|
|
607
430
|
inputClass: `input-${this.theme}`,
|
|
608
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
609
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
610
|
-
}`,
|
|
431
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
611
432
|
readonly: isReadOnly,
|
|
612
433
|
validation,
|
|
613
434
|
validationVisibility: 'live',
|
|
@@ -631,86 +452,15 @@ export default {
|
|
|
631
452
|
help: hint,
|
|
632
453
|
labelClass: 'ui-dynamic-form-input-label',
|
|
633
454
|
inputClass: `input-${this.theme}`,
|
|
634
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
635
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
636
|
-
}`,
|
|
455
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
637
456
|
readonly: isReadOnly,
|
|
638
457
|
disabled: isReadOnly,
|
|
639
458
|
validation,
|
|
640
459
|
validationVisibility: 'live',
|
|
641
460
|
},
|
|
642
461
|
};
|
|
643
|
-
case 'file-preview':
|
|
644
|
-
// Handle file preview display only (no upload functionality)
|
|
645
|
-
const originalFieldId = field.id.replace('_preview', '');
|
|
646
|
-
if (this.formData && this.formData[originalFieldId] && this.formData[originalFieldId].length != 0) {
|
|
647
|
-
const fileDataArray = Array.isArray(this.formData[originalFieldId])
|
|
648
|
-
? this.formData[originalFieldId]
|
|
649
|
-
: [this.formData[originalFieldId]];
|
|
650
|
-
|
|
651
|
-
// Create unique container ID for this field
|
|
652
|
-
const containerId = `file-preview-${field.id}`;
|
|
653
|
-
|
|
654
|
-
// Check if this field is already visible (for immediate processing)
|
|
655
|
-
if (this.visibleFileFields.has(field.id)) {
|
|
656
|
-
// Return loading state initially
|
|
657
|
-
const loadingContent = `
|
|
658
|
-
<div id="${containerId}" data-lazy-field="${field.id}">
|
|
659
|
-
<label class="ui-dynamic-form-input-label">${field.label} (Vorschau)${
|
|
660
|
-
field.required ? ' *' : ''
|
|
661
|
-
}</label>
|
|
662
|
-
<div style="margin-top: 8px; padding: 20px; text-align: center; color: #666;">
|
|
663
|
-
<div style="font-size: 1.2em; margin-bottom: 8px;">⏳</div>
|
|
664
|
-
<div>Dateien werden geladen...</div>
|
|
665
|
-
</div>
|
|
666
|
-
</div>
|
|
667
|
-
`;
|
|
668
|
-
|
|
669
|
-
// Process files asynchronously
|
|
670
|
-
setTimeout(() => {
|
|
671
|
-
this.processFilePreview(containerId, fileDataArray, field);
|
|
672
|
-
}, 0);
|
|
673
|
-
|
|
674
|
-
return {
|
|
675
|
-
type: 'div',
|
|
676
|
-
props: {
|
|
677
|
-
innerHTML: loadingContent,
|
|
678
|
-
},
|
|
679
|
-
};
|
|
680
|
-
} else {
|
|
681
|
-
// Return lazy loading placeholder
|
|
682
|
-
const lazyContent = `
|
|
683
|
-
<div id="${containerId}" data-lazy-field="${field.id}" class="lazy-file-preview">
|
|
684
|
-
<label class="ui-dynamic-form-input-label">${field.label} (Vorschau)${
|
|
685
|
-
field.required ? ' *' : ''
|
|
686
|
-
}</label>
|
|
687
|
-
<div style="margin-top: 8px; padding: 40px; text-align: center; color: #999; border: 1px dashed #ddd; border-radius: 4px;">
|
|
688
|
-
<div style="font-size: 1.5em; margin-bottom: 12px;">📁</div>
|
|
689
|
-
<div>Dateien werden geladen, wenn sie sichtbar werden...</div>
|
|
690
|
-
<div style="margin-top: 8px; font-size: 0.9em;">${
|
|
691
|
-
fileDataArray.length
|
|
692
|
-
} Datei(en)</div>
|
|
693
|
-
</div>
|
|
694
|
-
</div>
|
|
695
|
-
`;
|
|
696
|
-
|
|
697
|
-
return {
|
|
698
|
-
type: 'div',
|
|
699
|
-
props: {
|
|
700
|
-
innerHTML: lazyContent,
|
|
701
|
-
},
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
// If no files to preview, return empty div
|
|
706
|
-
return {
|
|
707
|
-
type: 'div',
|
|
708
|
-
props: {
|
|
709
|
-
style: 'display: none;',
|
|
710
|
-
},
|
|
711
|
-
};
|
|
712
462
|
case 'file':
|
|
713
|
-
const multiple = customForm.multiple === 'true';
|
|
463
|
+
const multiple = field.customForm ? JSON.parse(field.customForm).multiple === 'true' : false;
|
|
714
464
|
return {
|
|
715
465
|
type: 'FormKit',
|
|
716
466
|
props: {
|
|
@@ -719,11 +469,13 @@ export default {
|
|
|
719
469
|
name,
|
|
720
470
|
label: field.label,
|
|
721
471
|
required: field.required,
|
|
472
|
+
value: this.formData[field.id],
|
|
722
473
|
help: hint,
|
|
723
474
|
innerClass: 'reset-background',
|
|
724
475
|
wrapperClass: '$remove:formkit-wrapper',
|
|
725
476
|
labelClass: 'ui-dynamic-form-input-label',
|
|
726
477
|
inputClass: `input-${this.theme}`,
|
|
478
|
+
// innerClass: ui-dynamic-form-input-outlines `${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
727
479
|
readonly: isReadOnly,
|
|
728
480
|
disabled: isReadOnly,
|
|
729
481
|
multiple,
|
|
@@ -732,11 +484,9 @@ export default {
|
|
|
732
484
|
},
|
|
733
485
|
};
|
|
734
486
|
case 'checkbox':
|
|
735
|
-
const options = customForm.entries
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
})
|
|
739
|
-
: [];
|
|
487
|
+
const options = JSON.parse(field.customForm).entries.map((obj) => {
|
|
488
|
+
return { value: obj.key, label: obj.value };
|
|
489
|
+
});
|
|
740
490
|
return {
|
|
741
491
|
type: 'FormKit',
|
|
742
492
|
props: {
|
|
@@ -751,9 +501,7 @@ export default {
|
|
|
751
501
|
fieldsetClass: 'custom-fieldset',
|
|
752
502
|
labelClass: 'ui-dynamic-form-input-label',
|
|
753
503
|
inputClass: `input-${this.theme}`,
|
|
754
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
755
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
756
|
-
}`,
|
|
504
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
757
505
|
readonly: isReadOnly,
|
|
758
506
|
disabled: isReadOnly,
|
|
759
507
|
validation,
|
|
@@ -791,9 +539,7 @@ export default {
|
|
|
791
539
|
wrapperClass: '$remove:formkit-wrapper',
|
|
792
540
|
labelClass: 'ui-dynamic-form-input-label',
|
|
793
541
|
inputClass: `input-${this.theme}`,
|
|
794
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
795
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
796
|
-
}`,
|
|
542
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
797
543
|
readonly: isReadOnly,
|
|
798
544
|
validation,
|
|
799
545
|
validationVisibility: 'live',
|
|
@@ -814,9 +560,7 @@ export default {
|
|
|
814
560
|
wrapperClass: '$remove:formkit-wrapper',
|
|
815
561
|
labelClass: 'ui-dynamic-form-input-label',
|
|
816
562
|
inputClass: `input-${this.theme}`,
|
|
817
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
818
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
819
|
-
}`,
|
|
563
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
820
564
|
readonly: isReadOnly,
|
|
821
565
|
validation,
|
|
822
566
|
validationVisibility: 'live',
|
|
@@ -824,10 +568,10 @@ export default {
|
|
|
824
568
|
};
|
|
825
569
|
case 'header':
|
|
826
570
|
let typeToUse = 'h1';
|
|
827
|
-
if (customForm.style === 'heading_2') {
|
|
571
|
+
if (field.customForm && JSON.parse(field.customForm).style === 'heading_2') {
|
|
828
572
|
typeToUse = 'h2';
|
|
829
573
|
}
|
|
830
|
-
if (customForm.style === 'heading_3') {
|
|
574
|
+
if (field.customForm && JSON.parse(field.customForm).style === 'heading_3') {
|
|
831
575
|
typeToUse = 'h3';
|
|
832
576
|
}
|
|
833
577
|
return {
|
|
@@ -856,9 +600,7 @@ export default {
|
|
|
856
600
|
wrapperClass: '$remove:formkit-wrapper',
|
|
857
601
|
labelClass: 'ui-dynamic-form-input-label',
|
|
858
602
|
inputClass: `input-${this.theme}`,
|
|
859
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
860
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
861
|
-
}`,
|
|
603
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
862
604
|
readonly: isReadOnly,
|
|
863
605
|
validation,
|
|
864
606
|
validationVisibility: 'live',
|
|
@@ -887,20 +629,16 @@ export default {
|
|
|
887
629
|
wrapperClass: '$remove:formkit-wrapper',
|
|
888
630
|
labelClass: 'ui-dynamic-form-input-label',
|
|
889
631
|
inputClass: `input-${this.theme}`,
|
|
890
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
891
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
892
|
-
}`,
|
|
632
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
893
633
|
readonly: isReadOnly,
|
|
894
634
|
validation,
|
|
895
635
|
validationVisibility: 'live',
|
|
896
636
|
},
|
|
897
637
|
};
|
|
898
638
|
case 'radio':
|
|
899
|
-
const radioOptions = customForm.entries
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
})
|
|
903
|
-
: [];
|
|
639
|
+
const radioOptions = JSON.parse(field.customForm).entries.map((obj) => {
|
|
640
|
+
return { value: obj.key, label: obj.value };
|
|
641
|
+
});
|
|
904
642
|
return {
|
|
905
643
|
type: 'FormKit',
|
|
906
644
|
props: {
|
|
@@ -915,9 +653,7 @@ export default {
|
|
|
915
653
|
fieldsetClass: 'custom-fieldset',
|
|
916
654
|
labelClass: 'ui-dynamic-form-input-label',
|
|
917
655
|
inputClass: `input-${this.theme}`,
|
|
918
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
919
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
920
|
-
}`,
|
|
656
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
921
657
|
readonly: isReadOnly,
|
|
922
658
|
disabled: isReadOnly,
|
|
923
659
|
validation,
|
|
@@ -925,6 +661,7 @@ export default {
|
|
|
925
661
|
},
|
|
926
662
|
};
|
|
927
663
|
case 'range':
|
|
664
|
+
const customForm = JSON.parse(field.customForm);
|
|
928
665
|
return {
|
|
929
666
|
type: 'v-slider',
|
|
930
667
|
props: {
|
|
@@ -963,16 +700,14 @@ export default {
|
|
|
963
700
|
wrapperClass: '$remove:formkit-wrapper',
|
|
964
701
|
labelClass: 'ui-dynamic-form-input-label',
|
|
965
702
|
inputClass: `input-${this.theme}`,
|
|
966
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
967
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
968
|
-
}`,
|
|
703
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
969
704
|
readonly: isReadOnly,
|
|
970
705
|
validation,
|
|
971
706
|
validationVisibility: 'live',
|
|
972
707
|
},
|
|
973
708
|
};
|
|
974
709
|
case 'textarea':
|
|
975
|
-
const rows = customForm.rows;
|
|
710
|
+
const rows = field.customForm ? JSON.parse(field.customForm).rows : undefined;
|
|
976
711
|
return {
|
|
977
712
|
type: 'FormKit',
|
|
978
713
|
props: {
|
|
@@ -988,9 +723,7 @@ export default {
|
|
|
988
723
|
wrapperClass: '$remove:formkit-wrapper',
|
|
989
724
|
labelClass: 'ui-dynamic-form-input-label',
|
|
990
725
|
inputClass: `input-${this.theme}`,
|
|
991
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
992
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
993
|
-
}`,
|
|
726
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
994
727
|
readonly: isReadOnly,
|
|
995
728
|
validation,
|
|
996
729
|
validationVisibility: 'live',
|
|
@@ -1011,9 +744,7 @@ export default {
|
|
|
1011
744
|
wrapperClass: '$remove:formkit-wrapper',
|
|
1012
745
|
labelClass: 'ui-dynamic-form-input-label',
|
|
1013
746
|
inputClass: `input-${this.theme}`,
|
|
1014
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
1015
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
1016
|
-
}`,
|
|
747
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
1017
748
|
readonly: isReadOnly,
|
|
1018
749
|
validation,
|
|
1019
750
|
validationVisibility: 'live',
|
|
@@ -1034,9 +765,7 @@ export default {
|
|
|
1034
765
|
wrapperClass: '$remove:formkit-wrapper',
|
|
1035
766
|
labelClass: 'ui-dynamic-form-input-label',
|
|
1036
767
|
inputClass: `input-${this.theme}`,
|
|
1037
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
1038
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
1039
|
-
}`,
|
|
768
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
1040
769
|
readonly: isReadOnly,
|
|
1041
770
|
validation,
|
|
1042
771
|
validationVisibility: 'live',
|
|
@@ -1057,9 +786,7 @@ export default {
|
|
|
1057
786
|
wrapperClass: '$remove:formkit-wrapper',
|
|
1058
787
|
labelClass: 'ui-dynamic-form-input-label',
|
|
1059
788
|
inputClass: `input-${this.theme}`,
|
|
1060
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
1061
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
1062
|
-
}`,
|
|
789
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
1063
790
|
readonly: isReadOnly,
|
|
1064
791
|
validation,
|
|
1065
792
|
validationVisibility: 'live',
|
|
@@ -1078,9 +805,7 @@ export default {
|
|
|
1078
805
|
help: hint,
|
|
1079
806
|
labelClass: 'ui-dynamic-form-input-label',
|
|
1080
807
|
inputClass: `input-${this.theme}`,
|
|
1081
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
1082
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
1083
|
-
}`,
|
|
808
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
1084
809
|
readonly: isReadOnly,
|
|
1085
810
|
validation,
|
|
1086
811
|
validationVisibility: 'live',
|
|
@@ -1088,220 +813,6 @@ export default {
|
|
|
1088
813
|
};
|
|
1089
814
|
}
|
|
1090
815
|
},
|
|
1091
|
-
initLazyLoading() {
|
|
1092
|
-
// Initialize Intersection Observer for lazy loading of file previews
|
|
1093
|
-
if (typeof IntersectionObserver !== 'undefined') {
|
|
1094
|
-
this.intersectionObserver = new IntersectionObserver(
|
|
1095
|
-
(entries) => {
|
|
1096
|
-
entries.forEach((entry) => {
|
|
1097
|
-
if (entry.isIntersecting) {
|
|
1098
|
-
const element = entry.target;
|
|
1099
|
-
const fieldId = element.getAttribute('data-lazy-field');
|
|
1100
|
-
|
|
1101
|
-
if (fieldId && !this.visibleFileFields.has(fieldId)) {
|
|
1102
|
-
this.visibleFileFields.add(fieldId);
|
|
1103
|
-
this.loadFilePreview(fieldId);
|
|
1104
|
-
this.intersectionObserver.unobserve(element);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
});
|
|
1108
|
-
},
|
|
1109
|
-
{
|
|
1110
|
-
root: null,
|
|
1111
|
-
rootMargin: '50px', // Start loading 50px before element becomes visible
|
|
1112
|
-
threshold: 0.1,
|
|
1113
|
-
}
|
|
1114
|
-
);
|
|
1115
|
-
|
|
1116
|
-
// Observe all lazy file preview elements
|
|
1117
|
-
this.$nextTick(() => {
|
|
1118
|
-
this.observeLazyElements();
|
|
1119
|
-
});
|
|
1120
|
-
} else {
|
|
1121
|
-
// Fallback for browsers without Intersection Observer
|
|
1122
|
-
// Load all file previews immediately
|
|
1123
|
-
this.loadAllFilePreviews();
|
|
1124
|
-
}
|
|
1125
|
-
},
|
|
1126
|
-
observeLazyElements() {
|
|
1127
|
-
const lazyElements = document.querySelectorAll('.lazy-file-preview[data-lazy-field]');
|
|
1128
|
-
lazyElements.forEach((element) => {
|
|
1129
|
-
if (this.intersectionObserver) {
|
|
1130
|
-
this.intersectionObserver.observe(element);
|
|
1131
|
-
}
|
|
1132
|
-
});
|
|
1133
|
-
},
|
|
1134
|
-
loadFilePreview(fieldId) {
|
|
1135
|
-
// Find the field configuration
|
|
1136
|
-
const field = this.userTask?.userTaskConfig?.formFields?.find((f) => f.id === fieldId);
|
|
1137
|
-
if (!field) return;
|
|
1138
|
-
|
|
1139
|
-
const originalFieldId = fieldId.replace('_preview', '');
|
|
1140
|
-
const fileDataArray = this.formData[originalFieldId];
|
|
1141
|
-
|
|
1142
|
-
if (!fileDataArray || fileDataArray.length === 0) return;
|
|
1143
|
-
|
|
1144
|
-
const containerId = `file-preview-${fieldId}`;
|
|
1145
|
-
const container = document.getElementById(containerId);
|
|
1146
|
-
|
|
1147
|
-
if (container) {
|
|
1148
|
-
// Show loading state
|
|
1149
|
-
container.innerHTML = `
|
|
1150
|
-
<label class="ui-dynamic-form-input-label">${field.label} (Vorschau)${
|
|
1151
|
-
field.required ? ' *' : ''
|
|
1152
|
-
}</label>
|
|
1153
|
-
<div style="margin-top: 8px; padding: 20px; text-align: center; color: #666;">
|
|
1154
|
-
<div style="font-size: 1.2em; margin-bottom: 8px;">⏳</div>
|
|
1155
|
-
<div>Dateien werden geladen...</div>
|
|
1156
|
-
</div>
|
|
1157
|
-
`;
|
|
1158
|
-
|
|
1159
|
-
// Process files
|
|
1160
|
-
setTimeout(() => {
|
|
1161
|
-
this.processFilePreview(containerId, fileDataArray, field);
|
|
1162
|
-
}, 0);
|
|
1163
|
-
}
|
|
1164
|
-
},
|
|
1165
|
-
loadAllFilePreviews() {
|
|
1166
|
-
// Fallback method - load all file previews immediately
|
|
1167
|
-
const fileFields =
|
|
1168
|
-
this.userTask?.userTaskConfig?.formFields?.filter((f) => f.type === 'file-preview') || [];
|
|
1169
|
-
fileFields.forEach((field) => {
|
|
1170
|
-
if (!this.visibleFileFields.has(field.id)) {
|
|
1171
|
-
this.visibleFileFields.add(field.id);
|
|
1172
|
-
this.loadFilePreview(field.id);
|
|
1173
|
-
}
|
|
1174
|
-
});
|
|
1175
|
-
},
|
|
1176
|
-
processFilePreview(containerId, fileDataArray, field) {
|
|
1177
|
-
// Process files in chunks to avoid blocking the UI
|
|
1178
|
-
const processInChunks = async () => {
|
|
1179
|
-
const images = [];
|
|
1180
|
-
const otherFiles = [];
|
|
1181
|
-
|
|
1182
|
-
// Process files in batches to avoid UI blocking
|
|
1183
|
-
const batchSize = 3;
|
|
1184
|
-
|
|
1185
|
-
for (let i = 0; i < fileDataArray.length; i += batchSize) {
|
|
1186
|
-
const batch = fileDataArray.slice(i, i + batchSize);
|
|
1187
|
-
|
|
1188
|
-
for (const fileData of batch) {
|
|
1189
|
-
const fileName = fileData.name || '';
|
|
1190
|
-
const isImage = fileName.toLowerCase().match(/\.(png|jpg|jpeg|gif|webp)$/);
|
|
1191
|
-
|
|
1192
|
-
if (isImage && fileData.file && fileData.file.data) {
|
|
1193
|
-
// Convert buffer to base64 data URL for image display
|
|
1194
|
-
const uint8Array = new Uint8Array(fileData.file.data);
|
|
1195
|
-
let binaryString = '';
|
|
1196
|
-
|
|
1197
|
-
// Process in chunks to avoid call stack overflow
|
|
1198
|
-
const chunkSize = 1024;
|
|
1199
|
-
for (let j = 0; j < uint8Array.length; j += chunkSize) {
|
|
1200
|
-
const chunk = uint8Array.slice(j, j + chunkSize);
|
|
1201
|
-
binaryString += String.fromCharCode.apply(null, chunk);
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
const base64String = btoa(binaryString);
|
|
1205
|
-
const mimeType = fileName.toLowerCase().endsWith('.png')
|
|
1206
|
-
? 'image/png'
|
|
1207
|
-
: fileName.toLowerCase().endsWith('.gif')
|
|
1208
|
-
? 'image/gif'
|
|
1209
|
-
: 'image/jpeg';
|
|
1210
|
-
const dataURL = `data:${mimeType};base64,${base64String}`;
|
|
1211
|
-
|
|
1212
|
-
images.push({ fileName, dataURL, fileData });
|
|
1213
|
-
} else {
|
|
1214
|
-
otherFiles.push({ fileName, fileData });
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
// Allow UI to update between batches
|
|
1219
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// Build the final content
|
|
1223
|
-
let content = `<label class="ui-dynamic-form-input-label">${field.label} (Vorschau)${
|
|
1224
|
-
field.required ? ' *' : ''
|
|
1225
|
-
}</label>`;
|
|
1226
|
-
|
|
1227
|
-
// Display images
|
|
1228
|
-
if (images.length > 0) {
|
|
1229
|
-
content += '<div style="margin-top: 8px;">';
|
|
1230
|
-
content += '<div style="font-weight: bold; margin-bottom: 8px;">Bilder:</div>';
|
|
1231
|
-
images.forEach((img, index) => {
|
|
1232
|
-
const downloadId = `download-img-${field.id}-${index}`;
|
|
1233
|
-
content += `
|
|
1234
|
-
<div style="display: inline-block; margin: 8px; text-align: center; vertical-align: top;">
|
|
1235
|
-
<img src="${img.dataURL}" alt="${img.fileName}"
|
|
1236
|
-
style="max-width: 300px; max-height: 200px; border: 1px solid #ccc; display: block; cursor: pointer;"
|
|
1237
|
-
onclick="document.getElementById('${downloadId}').click();" />
|
|
1238
|
-
<div style="margin-top: 4px; font-size: 0.9em; color: #666; max-width: 300px; word-break: break-word;">
|
|
1239
|
-
${img.fileName}
|
|
1240
|
-
</div>
|
|
1241
|
-
<a id="${downloadId}" href="${img.dataURL}" download="${img.fileName}" style="display: none;"></a>
|
|
1242
|
-
</div>
|
|
1243
|
-
`;
|
|
1244
|
-
});
|
|
1245
|
-
content += '</div>';
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
// Display other files as list
|
|
1249
|
-
if (otherFiles.length > 0) {
|
|
1250
|
-
content +=
|
|
1251
|
-
'<div style="margin-top: 12px; padding: 12px; border: 1px solid #ddd; border-radius: 4px; background: #f9f9f9;">';
|
|
1252
|
-
content += '<div style="font-weight: bold; margin-bottom: 8px;">Weitere Dateien:</div>';
|
|
1253
|
-
otherFiles.forEach((file, index) => {
|
|
1254
|
-
const downloadId = `download-file-${field.id}-${index}`;
|
|
1255
|
-
const uint8Array = new Uint8Array(file.fileData.file.data);
|
|
1256
|
-
let binaryString = '';
|
|
1257
|
-
|
|
1258
|
-
// Process in chunks for download
|
|
1259
|
-
const chunkSize = 1024;
|
|
1260
|
-
for (let j = 0; j < uint8Array.length; j += chunkSize) {
|
|
1261
|
-
const chunk = uint8Array.slice(j, j + chunkSize);
|
|
1262
|
-
binaryString += String.fromCharCode.apply(null, chunk);
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
const base64String = btoa(binaryString);
|
|
1266
|
-
const dataURL = `data:application/octet-stream;base64,${base64String}`;
|
|
1267
|
-
|
|
1268
|
-
content += `
|
|
1269
|
-
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px; padding: 4px; border-radius: 3px; cursor: pointer;"
|
|
1270
|
-
onclick="document.getElementById('${downloadId}').click();"
|
|
1271
|
-
onmouseover="this.style.backgroundColor='#e6e6e6';"
|
|
1272
|
-
onmouseout="this.style.backgroundColor='transparent';">
|
|
1273
|
-
<span style="font-size: 1.2em;">📎</span>
|
|
1274
|
-
<span style="flex: 1; word-break: break-word;">${file.fileName}</span>
|
|
1275
|
-
<span style="font-size: 0.8em; color: #007bff;">Download</span>
|
|
1276
|
-
<a id="${downloadId}" href="${dataURL}" download="${file.fileName}" style="display: none;"></a>
|
|
1277
|
-
</div>
|
|
1278
|
-
`;
|
|
1279
|
-
});
|
|
1280
|
-
content += '</div>';
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
// Update the container with the final content
|
|
1284
|
-
const container = document.getElementById(containerId);
|
|
1285
|
-
if (container) {
|
|
1286
|
-
container.innerHTML = content;
|
|
1287
|
-
}
|
|
1288
|
-
};
|
|
1289
|
-
|
|
1290
|
-
processInChunks().catch((error) => {
|
|
1291
|
-
const container = document.getElementById(containerId);
|
|
1292
|
-
if (container) {
|
|
1293
|
-
container.innerHTML = `
|
|
1294
|
-
<label class="ui-dynamic-form-input-label">${field.label} (Vorschau)${
|
|
1295
|
-
field.required ? ' *' : ''
|
|
1296
|
-
}</label>
|
|
1297
|
-
<div style="margin-top: 8px; padding: 20px; text-align: center; color: #d32f2f;">
|
|
1298
|
-
<div style="font-size: 1.2em; margin-bottom: 8px;">⚠️</div>
|
|
1299
|
-
<div>Fehler beim Laden der Dateien</div>
|
|
1300
|
-
</div>
|
|
1301
|
-
`;
|
|
1302
|
-
}
|
|
1303
|
-
});
|
|
1304
|
-
},
|
|
1305
816
|
toggleCollapse() {
|
|
1306
817
|
this.collapsed = !this.collapsed;
|
|
1307
818
|
},
|
|
@@ -1318,7 +829,13 @@ export default {
|
|
|
1318
829
|
return style;
|
|
1319
830
|
},
|
|
1320
831
|
fields() {
|
|
1321
|
-
|
|
832
|
+
const aFields = this.userTask.userTaskConfig?.formFields ?? [];
|
|
833
|
+
const fieldMap = aFields.map((field) => ({
|
|
834
|
+
...field,
|
|
835
|
+
items: mapItems(field.type, field),
|
|
836
|
+
}));
|
|
837
|
+
|
|
838
|
+
return fieldMap;
|
|
1322
839
|
},
|
|
1323
840
|
/*
|
|
1324
841
|
widget-action just sends a msg to Node-RED, it does not store the msg state server-side
|
|
@@ -1331,8 +848,6 @@ export default {
|
|
|
1331
848
|
},
|
|
1332
849
|
init(msg) {
|
|
1333
850
|
this.msg = msg;
|
|
1334
|
-
this.clearComponentCache();
|
|
1335
|
-
|
|
1336
851
|
if (!msg) {
|
|
1337
852
|
return;
|
|
1338
853
|
}
|
|
@@ -1346,8 +861,6 @@ export default {
|
|
|
1346
861
|
} else {
|
|
1347
862
|
this.userTask = null;
|
|
1348
863
|
this.formData = {};
|
|
1349
|
-
// Reset lazy loading state
|
|
1350
|
-
this.visibleFileFields.clear();
|
|
1351
864
|
return;
|
|
1352
865
|
}
|
|
1353
866
|
|
|
@@ -1360,27 +873,12 @@ export default {
|
|
|
1360
873
|
this.collapsed = this.props.collapse_when_finished;
|
|
1361
874
|
}
|
|
1362
875
|
|
|
1363
|
-
// Reset lazy loading state for new task
|
|
1364
|
-
this.visibleFileFields.clear();
|
|
1365
|
-
|
|
1366
876
|
if (formFields) {
|
|
1367
877
|
formFields.forEach((field) => {
|
|
1368
878
|
this.formData[field.id] = field.defaultValue;
|
|
1369
879
|
|
|
1370
880
|
if (field.type === 'confirm') {
|
|
1371
|
-
|
|
1372
|
-
try {
|
|
1373
|
-
if (field.customForm) {
|
|
1374
|
-
if (typeof field.customForm === 'string') {
|
|
1375
|
-
customForm = JSON.parse(field.customForm);
|
|
1376
|
-
} else if (typeof field.customForm === 'object') {
|
|
1377
|
-
customForm = field.customForm;
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
} catch (error) {
|
|
1381
|
-
console.warn('Failed to parse customForm for confirm field', field.id, error);
|
|
1382
|
-
customForm = {};
|
|
1383
|
-
}
|
|
881
|
+
const customForm = field.customForm ? JSON.parse(field.customForm) : {};
|
|
1384
882
|
const confirmText = customForm.confirmButtonText ?? 'Confirm';
|
|
1385
883
|
const declineText = customForm.declineButtonText ?? 'Decline';
|
|
1386
884
|
this.actions = [
|
|
@@ -1399,18 +897,6 @@ export default {
|
|
|
1399
897
|
];
|
|
1400
898
|
}
|
|
1401
899
|
});
|
|
1402
|
-
|
|
1403
|
-
// Check for file fields and duplicate them as file-preview if initial values exist
|
|
1404
|
-
// Insert preview fields directly before their corresponding file fields
|
|
1405
|
-
for (let i = formFields.length - 1; i >= 0; i--) {
|
|
1406
|
-
const field = formFields[i];
|
|
1407
|
-
if (field.type === 'file' && initialValues && initialValues[field.id]) {
|
|
1408
|
-
const previewField = { ...field };
|
|
1409
|
-
previewField.type = 'file-preview';
|
|
1410
|
-
previewField.id = `${field.id}_preview`; // Give it a unique ID
|
|
1411
|
-
this.userTask.userTaskConfig.formFields.splice(i, 0, previewField);
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
900
|
}
|
|
1415
901
|
|
|
1416
902
|
if (initialValues) {
|
|
@@ -1429,11 +915,8 @@ export default {
|
|
|
1429
915
|
});
|
|
1430
916
|
}
|
|
1431
917
|
|
|
1432
|
-
|
|
1433
|
-
this.$nextTick(() => {
|
|
918
|
+
nextTick(() => {
|
|
1434
919
|
this.focusFirstFormField();
|
|
1435
|
-
// Re-observe lazy elements after DOM update
|
|
1436
|
-
this.observeLazyElements();
|
|
1437
920
|
});
|
|
1438
921
|
},
|
|
1439
922
|
actionFn(action) {
|
|
@@ -1484,7 +967,7 @@ export default {
|
|
|
1484
967
|
this.send(
|
|
1485
968
|
msg,
|
|
1486
969
|
this.actions.findIndex((element) => element.label === action.label) +
|
|
1487
|
-
|
|
970
|
+
(this.isConfirmDialog ? this.props.options.length : 0)
|
|
1488
971
|
);
|
|
1489
972
|
// TODO: mm - end
|
|
1490
973
|
} else {
|
|
@@ -1499,6 +982,7 @@ export default {
|
|
|
1499
982
|
const result = func(this.formData, this.userTask, this.msg);
|
|
1500
983
|
return Boolean(result);
|
|
1501
984
|
} catch (err) {
|
|
985
|
+
console.error('Error while evaluating condition: ' + err);
|
|
1502
986
|
return false;
|
|
1503
987
|
}
|
|
1504
988
|
},
|
|
@@ -1519,14 +1003,14 @@ export default {
|
|
|
1519
1003
|
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(this.firstFormFieldRef.$el.tagName)) {
|
|
1520
1004
|
inputElement = this.firstFormFieldRef.$el;
|
|
1521
1005
|
} else {
|
|
1522
|
-
inputElement = this.firstFormFieldRef.$el.querySelector(
|
|
1523
|
-
'input:not([type="hidden"]), textarea, select'
|
|
1524
|
-
);
|
|
1006
|
+
inputElement = this.firstFormFieldRef.$el.querySelector('input:not([type="hidden"]), textarea, select');
|
|
1525
1007
|
}
|
|
1526
1008
|
}
|
|
1527
1009
|
|
|
1528
1010
|
if (inputElement) {
|
|
1529
1011
|
inputElement.focus();
|
|
1012
|
+
} else {
|
|
1013
|
+
console.warn('Could not find a focusable input element for the first form field.');
|
|
1530
1014
|
}
|
|
1531
1015
|
}
|
|
1532
1016
|
},
|
|
@@ -1548,29 +1032,4 @@ function mapItems(type, field) {
|
|
|
1548
1032
|
<style>
|
|
1549
1033
|
/* CSS is auto scoped, but using named classes is still recommended */
|
|
1550
1034
|
@import '../stylesheets/ui-dynamic-form.css';
|
|
1551
|
-
|
|
1552
|
-
/* Lazy loading styles */
|
|
1553
|
-
.lazy-file-preview {
|
|
1554
|
-
transition: opacity 0.3s ease-in-out;
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
.lazy-file-preview .lazy-placeholder {
|
|
1558
|
-
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.1) 50%, transparent 70%);
|
|
1559
|
-
background-size: 200% 200%;
|
|
1560
|
-
animation: shimmer 2s infinite;
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
@keyframes shimmer {
|
|
1564
|
-
0% {
|
|
1565
|
-
background-position: -200% -200%;
|
|
1566
|
-
}
|
|
1567
|
-
100% {
|
|
1568
|
-
background-position: 200% 200%;
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
.lazy-file-preview:hover {
|
|
1573
|
-
transform: translateY(-1px);
|
|
1574
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
1575
|
-
}
|
|
1576
1035
|
</style>
|