@5minds/node-red-dashboard-2-processcube-dynamic-form 2.1.0-file-preview-ea90da-mdpynnv5 → 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 +127 -622
|
@@ -1,98 +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
|
-
{{
|
|
81
|
-
field.customForm ? JSON.parse(field.customForm).hint : undefined
|
|
49
|
+
{{ field.customForm ? JSON.parse(field.customForm).hint : undefined
|
|
82
50
|
}}
|
|
83
51
|
</p>
|
|
84
52
|
</div>
|
|
85
|
-
<component
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
(el) => {
|
|
91
|
-
if (index === 0) firstFormFieldRef = el;
|
|
92
|
-
}
|
|
93
|
-
"
|
|
94
|
-
v-model="formData[field.id]"
|
|
95
|
-
/>
|
|
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]" />
|
|
96
58
|
</v-col>
|
|
97
59
|
</v-row>
|
|
98
60
|
</FormKit>
|
|
@@ -101,24 +63,17 @@
|
|
|
101
63
|
<v-row v-if="errorMsg.length > 0" style="padding: 12px">
|
|
102
64
|
<v-alert type="error">Error: {{ errorMsg }}</v-alert>
|
|
103
65
|
</v-row>
|
|
104
|
-
<UIDynamicFormFooterAction
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
:actionCallback="actionFn"
|
|
108
|
-
:formIsFinished="formIsFinished"
|
|
109
|
-
style="padding: 16px; padding-top: 0px"
|
|
110
|
-
/>
|
|
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" />
|
|
111
69
|
</v-row>
|
|
112
70
|
</div>
|
|
113
71
|
</Transition>
|
|
114
72
|
</v-form>
|
|
115
73
|
</p>
|
|
116
74
|
<p v-else>
|
|
117
|
-
<v-alert
|
|
118
|
-
|
|
119
|
-
:text="props.waiting_info"
|
|
120
|
-
:title="props.waiting_title"
|
|
121
|
-
/>
|
|
75
|
+
<v-alert v-if="props.waiting_info.length > 0 || props.waiting_title.length > 0"
|
|
76
|
+
:text="props.waiting_info" :title="props.waiting_title" />
|
|
122
77
|
</p>
|
|
123
78
|
</div>
|
|
124
79
|
<div v-if="!props.actions_inside_card && actions.length > 0 && hasUserTask" style="padding-top: 32px">
|
|
@@ -139,6 +94,8 @@ import UIDynamicFormFooterAction from './FooterActions.vue';
|
|
|
139
94
|
import UIDynamicFormTitleText from './TitleText.vue';
|
|
140
95
|
|
|
141
96
|
function requiredIf({ value }, [targetField, expectedValue], node) {
|
|
97
|
+
console.debug(arguments);
|
|
98
|
+
|
|
142
99
|
const actual = node?.root?.value?.[targetField];
|
|
143
100
|
const isEmpty = value === '' || value === null || value === undefined;
|
|
144
101
|
|
|
@@ -199,6 +156,9 @@ export default {
|
|
|
199
156
|
},
|
|
200
157
|
},
|
|
201
158
|
setup(props) {
|
|
159
|
+
console.info('UIDynamicForm setup with:', props);
|
|
160
|
+
console.debug('Vue function loaded correctly', markRaw);
|
|
161
|
+
|
|
202
162
|
const instance = getCurrentInstance();
|
|
203
163
|
const app = instance.appContext.app;
|
|
204
164
|
|
|
@@ -206,6 +166,7 @@ export default {
|
|
|
206
166
|
theme: 'genesis',
|
|
207
167
|
locales: { de },
|
|
208
168
|
locale: 'de',
|
|
169
|
+
// eslint-disable-next-line object-shorthand
|
|
209
170
|
rules: { requiredIf: requiredIf },
|
|
210
171
|
});
|
|
211
172
|
app.use(plugin, formkitConfig);
|
|
@@ -221,8 +182,6 @@ export default {
|
|
|
221
182
|
msg: null,
|
|
222
183
|
collapsed: false,
|
|
223
184
|
firstFormFieldRef: null,
|
|
224
|
-
intersectionObserver: null,
|
|
225
|
-
visibleFileFields: new Set(),
|
|
226
185
|
};
|
|
227
186
|
},
|
|
228
187
|
computed: {
|
|
@@ -243,7 +202,7 @@ export default {
|
|
|
243
202
|
);
|
|
244
203
|
},
|
|
245
204
|
isConfirmDialog() {
|
|
246
|
-
return this.userTask
|
|
205
|
+
return this.userTask.userTaskConfig.formFields.some((field) => field.type === 'confirm');
|
|
247
206
|
},
|
|
248
207
|
effectiveTitle() {
|
|
249
208
|
if (this.props.title_text_type === 'str') {
|
|
@@ -254,29 +213,6 @@ export default {
|
|
|
254
213
|
return '';
|
|
255
214
|
}
|
|
256
215
|
},
|
|
257
|
-
// Optimized computed property for field components
|
|
258
|
-
fieldComponents() {
|
|
259
|
-
if (!this.userTask?.userTaskConfig?.formFields) {
|
|
260
|
-
return {};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const components = {};
|
|
264
|
-
const aFields = this.userTask.userTaskConfig.formFields;
|
|
265
|
-
|
|
266
|
-
aFields.forEach((field) => {
|
|
267
|
-
components[field.id] = this.createComponent(field);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
return components;
|
|
271
|
-
},
|
|
272
|
-
// Optimized computed property for fields
|
|
273
|
-
computedFields() {
|
|
274
|
-
const aFields = this.userTask?.userTaskConfig?.formFields ?? [];
|
|
275
|
-
return aFields.map((field) => ({
|
|
276
|
-
...field,
|
|
277
|
-
items: mapItems(field.type, field),
|
|
278
|
-
}));
|
|
279
|
-
},
|
|
280
216
|
},
|
|
281
217
|
watch: {
|
|
282
218
|
formData: {
|
|
@@ -330,9 +266,6 @@ export default {
|
|
|
330
266
|
element.classList.add('test');
|
|
331
267
|
});
|
|
332
268
|
|
|
333
|
-
// Initialize Intersection Observer for lazy loading
|
|
334
|
-
this.initLazyLoading();
|
|
335
|
-
|
|
336
269
|
this.$socket.on('widget-load:' + this.id, (msg) => {
|
|
337
270
|
this.init(msg);
|
|
338
271
|
});
|
|
@@ -347,161 +280,86 @@ export default {
|
|
|
347
280
|
/* Make sure, any events you subscribe to on SocketIO are unsubscribed to here */
|
|
348
281
|
this.$socket?.off('widget-load' + this.id);
|
|
349
282
|
this.$socket?.off('msg-input:' + this.id);
|
|
350
|
-
|
|
351
|
-
// Clean up Intersection Observer
|
|
352
|
-
if (this.intersectionObserver) {
|
|
353
|
-
this.intersectionObserver.disconnect();
|
|
354
|
-
}
|
|
355
283
|
},
|
|
356
284
|
methods: {
|
|
357
|
-
// Simplified component getter - now just returns from computed cache
|
|
358
|
-
getFieldComponent(field) {
|
|
359
|
-
return this.fieldComponents[field.id] || this.createComponent(field);
|
|
360
|
-
},
|
|
361
|
-
|
|
362
|
-
// Clear cache when form data changes
|
|
363
|
-
clearComponentCache() {
|
|
364
|
-
// This is now handled by computed properties automatically
|
|
365
|
-
},
|
|
366
|
-
|
|
367
285
|
createComponent(field) {
|
|
286
|
+
console.debug('Creating component for field:', field);
|
|
368
287
|
const customForm = field.customForm ? JSON.parse(field.customForm) : {};
|
|
369
|
-
const
|
|
288
|
+
const hint = customForm.hint;
|
|
289
|
+
const placeholder = customForm.placeholder;
|
|
290
|
+
const validation = customForm.validation;
|
|
370
291
|
const name = field.id;
|
|
292
|
+
const customProperties = customForm.customProperties ?? [];
|
|
371
293
|
const isReadOnly =
|
|
372
294
|
this.props.readonly ||
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
(entry) => ['readOnly', 'readonly'].includes(entry.name) && entry.value === 'true'
|
|
376
|
-
)
|
|
295
|
+
this.formIsFinished ||
|
|
296
|
+
customProperties.find((entry) => ['readOnly', 'readonly'].includes(entry.name) && entry.value === 'true')
|
|
377
297
|
? 'true'
|
|
378
298
|
: undefined;
|
|
379
|
-
|
|
380
|
-
const commonFormKitProps = {
|
|
381
|
-
id: field.id,
|
|
382
|
-
name,
|
|
383
|
-
label: field.label,
|
|
384
|
-
required: field.required,
|
|
385
|
-
value: this.formData[field.id],
|
|
386
|
-
help: hint,
|
|
387
|
-
wrapperClass: '$remove:formkit-wrapper',
|
|
388
|
-
labelClass: 'ui-dynamic-form-input-label',
|
|
389
|
-
inputClass: `input-${this.theme}`,
|
|
390
|
-
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
391
|
-
readonly: isReadOnly,
|
|
392
|
-
validation,
|
|
393
|
-
validationVisibility: 'live',
|
|
394
|
-
};
|
|
395
|
-
|
|
396
299
|
switch (field.type) {
|
|
397
300
|
case 'long':
|
|
398
301
|
return {
|
|
399
302
|
type: 'FormKit',
|
|
400
303
|
props: {
|
|
401
|
-
...commonFormKitProps,
|
|
402
304
|
type: 'number',
|
|
305
|
+
id: field.id,
|
|
306
|
+
name,
|
|
307
|
+
label: field.label,
|
|
308
|
+
required: field.required,
|
|
309
|
+
value: this.formData[field.id],
|
|
403
310
|
number: 'integer',
|
|
404
311
|
min: 0,
|
|
405
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',
|
|
406
320
|
},
|
|
407
321
|
};
|
|
408
322
|
case 'number':
|
|
409
|
-
const step = customForm.step;
|
|
323
|
+
const step = field.customForm ? JSON.parse(field.customForm).step : undefined;
|
|
410
324
|
return {
|
|
411
325
|
type: 'FormKit',
|
|
412
326
|
props: {
|
|
413
|
-
...commonFormKitProps,
|
|
414
327
|
type: 'number',
|
|
328
|
+
id: field.id,
|
|
329
|
+
name,
|
|
330
|
+
label: field.label,
|
|
331
|
+
required: field.required,
|
|
332
|
+
value: this.formData[field.id],
|
|
415
333
|
step,
|
|
416
334
|
number: 'float',
|
|
417
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',
|
|
418
343
|
},
|
|
419
344
|
};
|
|
420
345
|
case 'date':
|
|
421
346
|
return {
|
|
422
347
|
type: 'FormKit',
|
|
423
348
|
props: {
|
|
424
|
-
...commonFormKitProps,
|
|
425
349
|
type: 'date',
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
type: 'FormKit',
|
|
440
|
-
props: {
|
|
441
|
-
...commonFormKitProps,
|
|
442
|
-
type: 'email',
|
|
443
|
-
placeholder,
|
|
444
|
-
},
|
|
445
|
-
};
|
|
446
|
-
case 'password':
|
|
447
|
-
return {
|
|
448
|
-
type: 'FormKit',
|
|
449
|
-
props: {
|
|
450
|
-
...commonFormKitProps,
|
|
451
|
-
type: 'password',
|
|
452
|
-
placeholder,
|
|
453
|
-
},
|
|
454
|
-
};
|
|
455
|
-
case 'tel':
|
|
456
|
-
return {
|
|
457
|
-
type: 'FormKit',
|
|
458
|
-
props: {
|
|
459
|
-
...commonFormKitProps,
|
|
460
|
-
type: 'tel',
|
|
461
|
-
placeholder,
|
|
462
|
-
},
|
|
463
|
-
};
|
|
464
|
-
case 'url':
|
|
465
|
-
return {
|
|
466
|
-
type: 'FormKit',
|
|
467
|
-
props: {
|
|
468
|
-
...commonFormKitProps,
|
|
469
|
-
type: 'url',
|
|
470
|
-
placeholder,
|
|
471
|
-
},
|
|
472
|
-
};
|
|
473
|
-
case 'time':
|
|
474
|
-
return {
|
|
475
|
-
type: 'FormKit',
|
|
476
|
-
props: {
|
|
477
|
-
...commonFormKitProps,
|
|
478
|
-
type: 'time',
|
|
479
|
-
placeholder,
|
|
480
|
-
},
|
|
481
|
-
};
|
|
482
|
-
case 'week':
|
|
483
|
-
return {
|
|
484
|
-
type: 'FormKit',
|
|
485
|
-
props: {
|
|
486
|
-
...commonFormKitProps,
|
|
487
|
-
type: 'week',
|
|
488
|
-
placeholder,
|
|
489
|
-
},
|
|
490
|
-
};
|
|
491
|
-
case 'month':
|
|
492
|
-
return {
|
|
493
|
-
type: 'FormKit',
|
|
494
|
-
props: {
|
|
495
|
-
...commonFormKitProps,
|
|
496
|
-
type: 'month',
|
|
497
|
-
},
|
|
498
|
-
};
|
|
499
|
-
case 'datetime-local':
|
|
500
|
-
return {
|
|
501
|
-
type: 'FormKit',
|
|
502
|
-
props: {
|
|
503
|
-
...commonFormKitProps,
|
|
504
|
-
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',
|
|
505
363
|
},
|
|
506
364
|
};
|
|
507
365
|
case 'enum':
|
|
@@ -522,9 +380,7 @@ export default {
|
|
|
522
380
|
wrapperClass: '$remove:formkit-wrapper',
|
|
523
381
|
labelClass: 'ui-dynamic-form-input-label',
|
|
524
382
|
inputClass: `input-${this.theme}`,
|
|
525
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
526
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
527
|
-
}`,
|
|
383
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
528
384
|
readonly: isReadOnly,
|
|
529
385
|
disabled: isReadOnly,
|
|
530
386
|
validation,
|
|
@@ -550,9 +406,7 @@ export default {
|
|
|
550
406
|
wrapperClass: '$remove:formkit-wrapper',
|
|
551
407
|
labelClass: 'ui-dynamic-form-input-label',
|
|
552
408
|
inputClass: `input-${this.theme}`,
|
|
553
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
554
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
555
|
-
}`,
|
|
409
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
556
410
|
readonly: isReadOnly,
|
|
557
411
|
disabled: isReadOnly,
|
|
558
412
|
validation,
|
|
@@ -574,9 +428,7 @@ export default {
|
|
|
574
428
|
wrapperClass: '$remove:formkit-wrapper',
|
|
575
429
|
labelClass: 'ui-dynamic-form-input-label',
|
|
576
430
|
inputClass: `input-${this.theme}`,
|
|
577
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
578
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
579
|
-
}`,
|
|
431
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
580
432
|
readonly: isReadOnly,
|
|
581
433
|
validation,
|
|
582
434
|
validationVisibility: 'live',
|
|
@@ -600,84 +452,13 @@ export default {
|
|
|
600
452
|
help: hint,
|
|
601
453
|
labelClass: 'ui-dynamic-form-input-label',
|
|
602
454
|
inputClass: `input-${this.theme}`,
|
|
603
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
604
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
605
|
-
}`,
|
|
455
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
606
456
|
readonly: isReadOnly,
|
|
607
457
|
disabled: isReadOnly,
|
|
608
458
|
validation,
|
|
609
459
|
validationVisibility: 'live',
|
|
610
460
|
},
|
|
611
461
|
};
|
|
612
|
-
case 'file-preview':
|
|
613
|
-
// Handle file preview display only (no upload functionality)
|
|
614
|
-
const originalFieldId = field.id.replace('_preview', '');
|
|
615
|
-
if (this.formData && this.formData[originalFieldId] && this.formData[originalFieldId].length != 0) {
|
|
616
|
-
const fileDataArray = Array.isArray(this.formData[originalFieldId])
|
|
617
|
-
? this.formData[originalFieldId]
|
|
618
|
-
: [this.formData[originalFieldId]];
|
|
619
|
-
|
|
620
|
-
// Create unique container ID for this field
|
|
621
|
-
const containerId = `file-preview-${field.id}`;
|
|
622
|
-
|
|
623
|
-
// Check if this field is already visible (for immediate processing)
|
|
624
|
-
if (this.visibleFileFields.has(field.id)) {
|
|
625
|
-
// Return loading state initially
|
|
626
|
-
const loadingContent = `
|
|
627
|
-
<div id="${containerId}" data-lazy-field="${field.id}">
|
|
628
|
-
<label class="ui-dynamic-form-input-label">${field.label} (Vorschau)${
|
|
629
|
-
field.required ? ' *' : ''
|
|
630
|
-
}</label>
|
|
631
|
-
<div style="margin-top: 8px; padding: 20px; text-align: center; color: #666;">
|
|
632
|
-
<div style="font-size: 1.2em; margin-bottom: 8px;">⏳</div>
|
|
633
|
-
<div>Dateien werden geladen...</div>
|
|
634
|
-
</div>
|
|
635
|
-
</div>
|
|
636
|
-
`;
|
|
637
|
-
|
|
638
|
-
// Process files asynchronously
|
|
639
|
-
setTimeout(() => {
|
|
640
|
-
this.processFilePreview(containerId, fileDataArray, field);
|
|
641
|
-
}, 0);
|
|
642
|
-
|
|
643
|
-
return {
|
|
644
|
-
type: 'div',
|
|
645
|
-
props: {
|
|
646
|
-
innerHTML: loadingContent,
|
|
647
|
-
},
|
|
648
|
-
};
|
|
649
|
-
} else {
|
|
650
|
-
// Return lazy loading placeholder
|
|
651
|
-
const lazyContent = `
|
|
652
|
-
<div id="${containerId}" data-lazy-field="${field.id}" class="lazy-file-preview">
|
|
653
|
-
<label class="ui-dynamic-form-input-label">${field.label} (Vorschau)${
|
|
654
|
-
field.required ? ' *' : ''
|
|
655
|
-
}</label>
|
|
656
|
-
<div style="margin-top: 8px; padding: 40px; text-align: center; color: #999; border: 1px dashed #ddd; border-radius: 4px;">
|
|
657
|
-
<div style="font-size: 1.5em; margin-bottom: 12px;">📁</div>
|
|
658
|
-
<div>Dateien werden geladen, wenn sie sichtbar werden...</div>
|
|
659
|
-
<div style="margin-top: 8px; font-size: 0.9em;">${
|
|
660
|
-
fileDataArray.length
|
|
661
|
-
} Datei(en)</div>
|
|
662
|
-
</div>
|
|
663
|
-
</div>
|
|
664
|
-
`;
|
|
665
|
-
|
|
666
|
-
return {
|
|
667
|
-
type: 'div',
|
|
668
|
-
props: {
|
|
669
|
-
innerHTML: lazyContent,
|
|
670
|
-
},
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
// If no files to preview, return empty div
|
|
675
|
-
return {
|
|
676
|
-
type: 'div',
|
|
677
|
-
props: {
|
|
678
|
-
style: 'display: none;',
|
|
679
|
-
},
|
|
680
|
-
};
|
|
681
462
|
case 'file':
|
|
682
463
|
const multiple = field.customForm ? JSON.parse(field.customForm).multiple === 'true' : false;
|
|
683
464
|
return {
|
|
@@ -688,11 +469,13 @@ export default {
|
|
|
688
469
|
name,
|
|
689
470
|
label: field.label,
|
|
690
471
|
required: field.required,
|
|
472
|
+
value: this.formData[field.id],
|
|
691
473
|
help: hint,
|
|
692
474
|
innerClass: 'reset-background',
|
|
693
475
|
wrapperClass: '$remove:formkit-wrapper',
|
|
694
476
|
labelClass: 'ui-dynamic-form-input-label',
|
|
695
477
|
inputClass: `input-${this.theme}`,
|
|
478
|
+
// innerClass: ui-dynamic-form-input-outlines `${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
696
479
|
readonly: isReadOnly,
|
|
697
480
|
disabled: isReadOnly,
|
|
698
481
|
multiple,
|
|
@@ -718,9 +501,7 @@ export default {
|
|
|
718
501
|
fieldsetClass: 'custom-fieldset',
|
|
719
502
|
labelClass: 'ui-dynamic-form-input-label',
|
|
720
503
|
inputClass: `input-${this.theme}`,
|
|
721
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
722
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
723
|
-
}`,
|
|
504
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
724
505
|
readonly: isReadOnly,
|
|
725
506
|
disabled: isReadOnly,
|
|
726
507
|
validation,
|
|
@@ -758,9 +539,7 @@ export default {
|
|
|
758
539
|
wrapperClass: '$remove:formkit-wrapper',
|
|
759
540
|
labelClass: 'ui-dynamic-form-input-label',
|
|
760
541
|
inputClass: `input-${this.theme}`,
|
|
761
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
762
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
763
|
-
}`,
|
|
542
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
764
543
|
readonly: isReadOnly,
|
|
765
544
|
validation,
|
|
766
545
|
validationVisibility: 'live',
|
|
@@ -781,9 +560,7 @@ export default {
|
|
|
781
560
|
wrapperClass: '$remove:formkit-wrapper',
|
|
782
561
|
labelClass: 'ui-dynamic-form-input-label',
|
|
783
562
|
inputClass: `input-${this.theme}`,
|
|
784
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
785
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
786
|
-
}`,
|
|
563
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
787
564
|
readonly: isReadOnly,
|
|
788
565
|
validation,
|
|
789
566
|
validationVisibility: 'live',
|
|
@@ -823,9 +600,7 @@ export default {
|
|
|
823
600
|
wrapperClass: '$remove:formkit-wrapper',
|
|
824
601
|
labelClass: 'ui-dynamic-form-input-label',
|
|
825
602
|
inputClass: `input-${this.theme}`,
|
|
826
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
827
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
828
|
-
}`,
|
|
603
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
829
604
|
readonly: isReadOnly,
|
|
830
605
|
validation,
|
|
831
606
|
validationVisibility: 'live',
|
|
@@ -854,9 +629,7 @@ export default {
|
|
|
854
629
|
wrapperClass: '$remove:formkit-wrapper',
|
|
855
630
|
labelClass: 'ui-dynamic-form-input-label',
|
|
856
631
|
inputClass: `input-${this.theme}`,
|
|
857
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
858
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
859
|
-
}`,
|
|
632
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
860
633
|
readonly: isReadOnly,
|
|
861
634
|
validation,
|
|
862
635
|
validationVisibility: 'live',
|
|
@@ -880,9 +653,7 @@ export default {
|
|
|
880
653
|
fieldsetClass: 'custom-fieldset',
|
|
881
654
|
labelClass: 'ui-dynamic-form-input-label',
|
|
882
655
|
inputClass: `input-${this.theme}`,
|
|
883
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
884
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
885
|
-
}`,
|
|
656
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
886
657
|
readonly: isReadOnly,
|
|
887
658
|
disabled: isReadOnly,
|
|
888
659
|
validation,
|
|
@@ -929,9 +700,7 @@ export default {
|
|
|
929
700
|
wrapperClass: '$remove:formkit-wrapper',
|
|
930
701
|
labelClass: 'ui-dynamic-form-input-label',
|
|
931
702
|
inputClass: `input-${this.theme}`,
|
|
932
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
933
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
934
|
-
}`,
|
|
703
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
935
704
|
readonly: isReadOnly,
|
|
936
705
|
validation,
|
|
937
706
|
validationVisibility: 'live',
|
|
@@ -954,9 +723,7 @@ export default {
|
|
|
954
723
|
wrapperClass: '$remove:formkit-wrapper',
|
|
955
724
|
labelClass: 'ui-dynamic-form-input-label',
|
|
956
725
|
inputClass: `input-${this.theme}`,
|
|
957
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
958
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
959
|
-
}`,
|
|
726
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
960
727
|
readonly: isReadOnly,
|
|
961
728
|
validation,
|
|
962
729
|
validationVisibility: 'live',
|
|
@@ -977,9 +744,7 @@ export default {
|
|
|
977
744
|
wrapperClass: '$remove:formkit-wrapper',
|
|
978
745
|
labelClass: 'ui-dynamic-form-input-label',
|
|
979
746
|
inputClass: `input-${this.theme}`,
|
|
980
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
981
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
982
|
-
}`,
|
|
747
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
983
748
|
readonly: isReadOnly,
|
|
984
749
|
validation,
|
|
985
750
|
validationVisibility: 'live',
|
|
@@ -1000,9 +765,7 @@ export default {
|
|
|
1000
765
|
wrapperClass: '$remove:formkit-wrapper',
|
|
1001
766
|
labelClass: 'ui-dynamic-form-input-label',
|
|
1002
767
|
inputClass: `input-${this.theme}`,
|
|
1003
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
1004
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
1005
|
-
}`,
|
|
768
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
1006
769
|
readonly: isReadOnly,
|
|
1007
770
|
validation,
|
|
1008
771
|
validationVisibility: 'live',
|
|
@@ -1023,9 +786,7 @@ export default {
|
|
|
1023
786
|
wrapperClass: '$remove:formkit-wrapper',
|
|
1024
787
|
labelClass: 'ui-dynamic-form-input-label',
|
|
1025
788
|
inputClass: `input-${this.theme}`,
|
|
1026
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
1027
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
1028
|
-
}`,
|
|
789
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
1029
790
|
readonly: isReadOnly,
|
|
1030
791
|
validation,
|
|
1031
792
|
validationVisibility: 'live',
|
|
@@ -1044,9 +805,7 @@ export default {
|
|
|
1044
805
|
help: hint,
|
|
1045
806
|
labelClass: 'ui-dynamic-form-input-label',
|
|
1046
807
|
inputClass: `input-${this.theme}`,
|
|
1047
|
-
innerClass: `ui-dynamic-form-input-outlines ${
|
|
1048
|
-
this.theme === 'dark' ? '$remove:formkit-inner' : ''
|
|
1049
|
-
}`,
|
|
808
|
+
innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
|
|
1050
809
|
readonly: isReadOnly,
|
|
1051
810
|
validation,
|
|
1052
811
|
validationVisibility: 'live',
|
|
@@ -1054,220 +813,6 @@ export default {
|
|
|
1054
813
|
};
|
|
1055
814
|
}
|
|
1056
815
|
},
|
|
1057
|
-
initLazyLoading() {
|
|
1058
|
-
// Initialize Intersection Observer for lazy loading of file previews
|
|
1059
|
-
if (typeof IntersectionObserver !== 'undefined') {
|
|
1060
|
-
this.intersectionObserver = new IntersectionObserver(
|
|
1061
|
-
(entries) => {
|
|
1062
|
-
entries.forEach((entry) => {
|
|
1063
|
-
if (entry.isIntersecting) {
|
|
1064
|
-
const element = entry.target;
|
|
1065
|
-
const fieldId = element.getAttribute('data-lazy-field');
|
|
1066
|
-
|
|
1067
|
-
if (fieldId && !this.visibleFileFields.has(fieldId)) {
|
|
1068
|
-
this.visibleFileFields.add(fieldId);
|
|
1069
|
-
this.loadFilePreview(fieldId);
|
|
1070
|
-
this.intersectionObserver.unobserve(element);
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
});
|
|
1074
|
-
},
|
|
1075
|
-
{
|
|
1076
|
-
root: null,
|
|
1077
|
-
rootMargin: '50px', // Start loading 50px before element becomes visible
|
|
1078
|
-
threshold: 0.1,
|
|
1079
|
-
}
|
|
1080
|
-
);
|
|
1081
|
-
|
|
1082
|
-
// Observe all lazy file preview elements
|
|
1083
|
-
this.$nextTick(() => {
|
|
1084
|
-
this.observeLazyElements();
|
|
1085
|
-
});
|
|
1086
|
-
} else {
|
|
1087
|
-
// Fallback for browsers without Intersection Observer
|
|
1088
|
-
// Load all file previews immediately
|
|
1089
|
-
this.loadAllFilePreviews();
|
|
1090
|
-
}
|
|
1091
|
-
},
|
|
1092
|
-
observeLazyElements() {
|
|
1093
|
-
const lazyElements = document.querySelectorAll('.lazy-file-preview[data-lazy-field]');
|
|
1094
|
-
lazyElements.forEach((element) => {
|
|
1095
|
-
if (this.intersectionObserver) {
|
|
1096
|
-
this.intersectionObserver.observe(element);
|
|
1097
|
-
}
|
|
1098
|
-
});
|
|
1099
|
-
},
|
|
1100
|
-
loadFilePreview(fieldId) {
|
|
1101
|
-
// Find the field configuration
|
|
1102
|
-
const field = this.userTask?.userTaskConfig?.formFields?.find((f) => f.id === fieldId);
|
|
1103
|
-
if (!field) return;
|
|
1104
|
-
|
|
1105
|
-
const originalFieldId = fieldId.replace('_preview', '');
|
|
1106
|
-
const fileDataArray = this.formData[originalFieldId];
|
|
1107
|
-
|
|
1108
|
-
if (!fileDataArray || fileDataArray.length === 0) return;
|
|
1109
|
-
|
|
1110
|
-
const containerId = `file-preview-${fieldId}`;
|
|
1111
|
-
const container = document.getElementById(containerId);
|
|
1112
|
-
|
|
1113
|
-
if (container) {
|
|
1114
|
-
// Show loading state
|
|
1115
|
-
container.innerHTML = `
|
|
1116
|
-
<label class="ui-dynamic-form-input-label">${field.label} (Vorschau)${
|
|
1117
|
-
field.required ? ' *' : ''
|
|
1118
|
-
}</label>
|
|
1119
|
-
<div style="margin-top: 8px; padding: 20px; text-align: center; color: #666;">
|
|
1120
|
-
<div style="font-size: 1.2em; margin-bottom: 8px;">⏳</div>
|
|
1121
|
-
<div>Dateien werden geladen...</div>
|
|
1122
|
-
</div>
|
|
1123
|
-
`;
|
|
1124
|
-
|
|
1125
|
-
// Process files
|
|
1126
|
-
setTimeout(() => {
|
|
1127
|
-
this.processFilePreview(containerId, fileDataArray, field);
|
|
1128
|
-
}, 0);
|
|
1129
|
-
}
|
|
1130
|
-
},
|
|
1131
|
-
loadAllFilePreviews() {
|
|
1132
|
-
// Fallback method - load all file previews immediately
|
|
1133
|
-
const fileFields =
|
|
1134
|
-
this.userTask?.userTaskConfig?.formFields?.filter((f) => f.type === 'file-preview') || [];
|
|
1135
|
-
fileFields.forEach((field) => {
|
|
1136
|
-
if (!this.visibleFileFields.has(field.id)) {
|
|
1137
|
-
this.visibleFileFields.add(field.id);
|
|
1138
|
-
this.loadFilePreview(field.id);
|
|
1139
|
-
}
|
|
1140
|
-
});
|
|
1141
|
-
},
|
|
1142
|
-
processFilePreview(containerId, fileDataArray, field) {
|
|
1143
|
-
// Process files in chunks to avoid blocking the UI
|
|
1144
|
-
const processInChunks = async () => {
|
|
1145
|
-
const images = [];
|
|
1146
|
-
const otherFiles = [];
|
|
1147
|
-
|
|
1148
|
-
// Process files in batches to avoid UI blocking
|
|
1149
|
-
const batchSize = 3;
|
|
1150
|
-
|
|
1151
|
-
for (let i = 0; i < fileDataArray.length; i += batchSize) {
|
|
1152
|
-
const batch = fileDataArray.slice(i, i + batchSize);
|
|
1153
|
-
|
|
1154
|
-
for (const fileData of batch) {
|
|
1155
|
-
const fileName = fileData.name || '';
|
|
1156
|
-
const isImage = fileName.toLowerCase().match(/\.(png|jpg|jpeg|gif|webp)$/);
|
|
1157
|
-
|
|
1158
|
-
if (isImage && fileData.file && fileData.file.data) {
|
|
1159
|
-
// Convert buffer to base64 data URL for image display
|
|
1160
|
-
const uint8Array = new Uint8Array(fileData.file.data);
|
|
1161
|
-
let binaryString = '';
|
|
1162
|
-
|
|
1163
|
-
// Process in chunks to avoid call stack overflow
|
|
1164
|
-
const chunkSize = 1024;
|
|
1165
|
-
for (let j = 0; j < uint8Array.length; j += chunkSize) {
|
|
1166
|
-
const chunk = uint8Array.slice(j, j + chunkSize);
|
|
1167
|
-
binaryString += String.fromCharCode.apply(null, chunk);
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
const base64String = btoa(binaryString);
|
|
1171
|
-
const mimeType = fileName.toLowerCase().endsWith('.png')
|
|
1172
|
-
? 'image/png'
|
|
1173
|
-
: fileName.toLowerCase().endsWith('.gif')
|
|
1174
|
-
? 'image/gif'
|
|
1175
|
-
: 'image/jpeg';
|
|
1176
|
-
const dataURL = `data:${mimeType};base64,${base64String}`;
|
|
1177
|
-
|
|
1178
|
-
images.push({ fileName, dataURL, fileData });
|
|
1179
|
-
} else {
|
|
1180
|
-
otherFiles.push({ fileName, fileData });
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
// Allow UI to update between batches
|
|
1185
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
// Build the final content
|
|
1189
|
-
let content = `<label class="ui-dynamic-form-input-label">${field.label} (Vorschau)${
|
|
1190
|
-
field.required ? ' *' : ''
|
|
1191
|
-
}</label>`;
|
|
1192
|
-
|
|
1193
|
-
// Display images
|
|
1194
|
-
if (images.length > 0) {
|
|
1195
|
-
content += '<div style="margin-top: 8px;">';
|
|
1196
|
-
content += '<div style="font-weight: bold; margin-bottom: 8px;">Bilder:</div>';
|
|
1197
|
-
images.forEach((img, index) => {
|
|
1198
|
-
const downloadId = `download-img-${field.id}-${index}`;
|
|
1199
|
-
content += `
|
|
1200
|
-
<div style="display: inline-block; margin: 8px; text-align: center; vertical-align: top;">
|
|
1201
|
-
<img src="${img.dataURL}" alt="${img.fileName}"
|
|
1202
|
-
style="max-width: 300px; max-height: 200px; border: 1px solid #ccc; display: block; cursor: pointer;"
|
|
1203
|
-
onclick="document.getElementById('${downloadId}').click();" />
|
|
1204
|
-
<div style="margin-top: 4px; font-size: 0.9em; color: #666; max-width: 300px; word-break: break-word;">
|
|
1205
|
-
${img.fileName}
|
|
1206
|
-
</div>
|
|
1207
|
-
<a id="${downloadId}" href="${img.dataURL}" download="${img.fileName}" style="display: none;"></a>
|
|
1208
|
-
</div>
|
|
1209
|
-
`;
|
|
1210
|
-
});
|
|
1211
|
-
content += '</div>';
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// Display other files as list
|
|
1215
|
-
if (otherFiles.length > 0) {
|
|
1216
|
-
content +=
|
|
1217
|
-
'<div style="margin-top: 12px; padding: 12px; border: 1px solid #ddd; border-radius: 4px; background: #f9f9f9;">';
|
|
1218
|
-
content += '<div style="font-weight: bold; margin-bottom: 8px;">Weitere Dateien:</div>';
|
|
1219
|
-
otherFiles.forEach((file, index) => {
|
|
1220
|
-
const downloadId = `download-file-${field.id}-${index}`;
|
|
1221
|
-
const uint8Array = new Uint8Array(file.fileData.file.data);
|
|
1222
|
-
let binaryString = '';
|
|
1223
|
-
|
|
1224
|
-
// Process in chunks for download
|
|
1225
|
-
const chunkSize = 1024;
|
|
1226
|
-
for (let j = 0; j < uint8Array.length; j += chunkSize) {
|
|
1227
|
-
const chunk = uint8Array.slice(j, j + chunkSize);
|
|
1228
|
-
binaryString += String.fromCharCode.apply(null, chunk);
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
const base64String = btoa(binaryString);
|
|
1232
|
-
const dataURL = `data:application/octet-stream;base64,${base64String}`;
|
|
1233
|
-
|
|
1234
|
-
content += `
|
|
1235
|
-
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px; padding: 4px; border-radius: 3px; cursor: pointer;"
|
|
1236
|
-
onclick="document.getElementById('${downloadId}').click();"
|
|
1237
|
-
onmouseover="this.style.backgroundColor='#e6e6e6';"
|
|
1238
|
-
onmouseout="this.style.backgroundColor='transparent';">
|
|
1239
|
-
<span style="font-size: 1.2em;">📎</span>
|
|
1240
|
-
<span style="flex: 1; word-break: break-word;">${file.fileName}</span>
|
|
1241
|
-
<span style="font-size: 0.8em; color: #007bff;">Download</span>
|
|
1242
|
-
<a id="${downloadId}" href="${dataURL}" download="${file.fileName}" style="display: none;"></a>
|
|
1243
|
-
</div>
|
|
1244
|
-
`;
|
|
1245
|
-
});
|
|
1246
|
-
content += '</div>';
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
// Update the container with the final content
|
|
1250
|
-
const container = document.getElementById(containerId);
|
|
1251
|
-
if (container) {
|
|
1252
|
-
container.innerHTML = content;
|
|
1253
|
-
}
|
|
1254
|
-
};
|
|
1255
|
-
|
|
1256
|
-
processInChunks().catch((error) => {
|
|
1257
|
-
const container = document.getElementById(containerId);
|
|
1258
|
-
if (container) {
|
|
1259
|
-
container.innerHTML = `
|
|
1260
|
-
<label class="ui-dynamic-form-input-label">${field.label} (Vorschau)${
|
|
1261
|
-
field.required ? ' *' : ''
|
|
1262
|
-
}</label>
|
|
1263
|
-
<div style="margin-top: 8px; padding: 20px; text-align: center; color: #d32f2f;">
|
|
1264
|
-
<div style="font-size: 1.2em; margin-bottom: 8px;">⚠️</div>
|
|
1265
|
-
<div>Fehler beim Laden der Dateien</div>
|
|
1266
|
-
</div>
|
|
1267
|
-
`;
|
|
1268
|
-
}
|
|
1269
|
-
});
|
|
1270
|
-
},
|
|
1271
816
|
toggleCollapse() {
|
|
1272
817
|
this.collapsed = !this.collapsed;
|
|
1273
818
|
},
|
|
@@ -1284,7 +829,13 @@ export default {
|
|
|
1284
829
|
return style;
|
|
1285
830
|
},
|
|
1286
831
|
fields() {
|
|
1287
|
-
|
|
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;
|
|
1288
839
|
},
|
|
1289
840
|
/*
|
|
1290
841
|
widget-action just sends a msg to Node-RED, it does not store the msg state server-side
|
|
@@ -1297,8 +848,6 @@ export default {
|
|
|
1297
848
|
},
|
|
1298
849
|
init(msg) {
|
|
1299
850
|
this.msg = msg;
|
|
1300
|
-
this.clearComponentCache();
|
|
1301
|
-
|
|
1302
851
|
if (!msg) {
|
|
1303
852
|
return;
|
|
1304
853
|
}
|
|
@@ -1312,23 +861,18 @@ export default {
|
|
|
1312
861
|
} else {
|
|
1313
862
|
this.userTask = null;
|
|
1314
863
|
this.formData = {};
|
|
1315
|
-
// Reset lazy loading state
|
|
1316
|
-
this.visibleFileFields.clear();
|
|
1317
864
|
return;
|
|
1318
865
|
}
|
|
1319
866
|
|
|
1320
867
|
const formFields = this.userTask.userTaskConfig.formFields;
|
|
1321
868
|
const formFieldIds = formFields.map((ff) => ff.id);
|
|
1322
|
-
const initialValues = this.userTask.startToken
|
|
869
|
+
const initialValues = this.userTask.startToken;
|
|
1323
870
|
const finishedFormData = msg.payload.formData;
|
|
1324
871
|
this.formIsFinished = !!msg.payload.formData;
|
|
1325
872
|
if (this.formIsFinished) {
|
|
1326
873
|
this.collapsed = this.props.collapse_when_finished;
|
|
1327
874
|
}
|
|
1328
875
|
|
|
1329
|
-
// Reset lazy loading state for new task
|
|
1330
|
-
this.visibleFileFields.clear();
|
|
1331
|
-
|
|
1332
876
|
if (formFields) {
|
|
1333
877
|
formFields.forEach((field) => {
|
|
1334
878
|
this.formData[field.id] = field.defaultValue;
|
|
@@ -1353,18 +897,6 @@ export default {
|
|
|
1353
897
|
];
|
|
1354
898
|
}
|
|
1355
899
|
});
|
|
1356
|
-
|
|
1357
|
-
// Check for file fields and duplicate them as file-preview if initial values exist
|
|
1358
|
-
// Insert preview fields directly before their corresponding file fields
|
|
1359
|
-
for (let i = formFields.length - 1; i >= 0; i--) {
|
|
1360
|
-
const field = formFields[i];
|
|
1361
|
-
if (field.type === 'file' && initialValues && initialValues[field.id]) {
|
|
1362
|
-
const previewField = { ...field };
|
|
1363
|
-
previewField.type = 'file-preview';
|
|
1364
|
-
previewField.id = `${field.id}_preview`; // Give it a unique ID
|
|
1365
|
-
this.userTask.userTaskConfig.formFields.splice(i, 0, previewField);
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
900
|
}
|
|
1369
901
|
|
|
1370
902
|
if (initialValues) {
|
|
@@ -1383,11 +915,8 @@ export default {
|
|
|
1383
915
|
});
|
|
1384
916
|
}
|
|
1385
917
|
|
|
1386
|
-
|
|
1387
|
-
this.$nextTick(() => {
|
|
918
|
+
nextTick(() => {
|
|
1388
919
|
this.focusFirstFormField();
|
|
1389
|
-
// Re-observe lazy elements after DOM update
|
|
1390
|
-
this.observeLazyElements();
|
|
1391
920
|
});
|
|
1392
921
|
},
|
|
1393
922
|
actionFn(action) {
|
|
@@ -1438,7 +967,7 @@ export default {
|
|
|
1438
967
|
this.send(
|
|
1439
968
|
msg,
|
|
1440
969
|
this.actions.findIndex((element) => element.label === action.label) +
|
|
1441
|
-
|
|
970
|
+
(this.isConfirmDialog ? this.props.options.length : 0)
|
|
1442
971
|
);
|
|
1443
972
|
// TODO: mm - end
|
|
1444
973
|
} else {
|
|
@@ -1453,6 +982,7 @@ export default {
|
|
|
1453
982
|
const result = func(this.formData, this.userTask, this.msg);
|
|
1454
983
|
return Boolean(result);
|
|
1455
984
|
} catch (err) {
|
|
985
|
+
console.error('Error while evaluating condition: ' + err);
|
|
1456
986
|
return false;
|
|
1457
987
|
}
|
|
1458
988
|
},
|
|
@@ -1473,14 +1003,14 @@ export default {
|
|
|
1473
1003
|
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(this.firstFormFieldRef.$el.tagName)) {
|
|
1474
1004
|
inputElement = this.firstFormFieldRef.$el;
|
|
1475
1005
|
} else {
|
|
1476
|
-
inputElement = this.firstFormFieldRef.$el.querySelector(
|
|
1477
|
-
'input:not([type="hidden"]), textarea, select'
|
|
1478
|
-
);
|
|
1006
|
+
inputElement = this.firstFormFieldRef.$el.querySelector('input:not([type="hidden"]), textarea, select');
|
|
1479
1007
|
}
|
|
1480
1008
|
}
|
|
1481
1009
|
|
|
1482
1010
|
if (inputElement) {
|
|
1483
1011
|
inputElement.focus();
|
|
1012
|
+
} else {
|
|
1013
|
+
console.warn('Could not find a focusable input element for the first form field.');
|
|
1484
1014
|
}
|
|
1485
1015
|
}
|
|
1486
1016
|
},
|
|
@@ -1502,29 +1032,4 @@ function mapItems(type, field) {
|
|
|
1502
1032
|
<style>
|
|
1503
1033
|
/* CSS is auto scoped, but using named classes is still recommended */
|
|
1504
1034
|
@import '../stylesheets/ui-dynamic-form.css';
|
|
1505
|
-
|
|
1506
|
-
/* Lazy loading styles */
|
|
1507
|
-
.lazy-file-preview {
|
|
1508
|
-
transition: opacity 0.3s ease-in-out;
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
.lazy-file-preview .lazy-placeholder {
|
|
1512
|
-
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.1) 50%, transparent 70%);
|
|
1513
|
-
background-size: 200% 200%;
|
|
1514
|
-
animation: shimmer 2s infinite;
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
@keyframes shimmer {
|
|
1518
|
-
0% {
|
|
1519
|
-
background-position: -200% -200%;
|
|
1520
|
-
}
|
|
1521
|
-
100% {
|
|
1522
|
-
background-position: 200% 200%;
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
.lazy-file-preview:hover {
|
|
1527
|
-
transform: translateY(-1px);
|
|
1528
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
1529
|
-
}
|
|
1530
1035
|
</style>
|