@5minds/node-red-dashboard-2-processcube-dynamic-form 2.0.8-feature-01220f-mdiuh7i9 → 2.0.8-file-preview-4a4fff-mdijdu1k

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