@epistola.app/valtimo-plugin 0.0.1

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.
Files changed (54) hide show
  1. package/dist/fesm2022/epistola.app-valtimo-plugin.mjs +1393 -0
  2. package/dist/fesm2022/epistola.app-valtimo-plugin.mjs.map +1 -0
  3. package/dist/index.d.ts +5 -0
  4. package/dist/lib/assets/epistola-logo.d.ts +1 -0
  5. package/dist/lib/assets/index.d.ts +1 -0
  6. package/dist/lib/components/check-job-status-configuration/check-job-status-configuration.component.d.ts +25 -0
  7. package/dist/lib/components/data-mapping-tree/data-mapping-tree.component.d.ts +33 -0
  8. package/dist/lib/components/download-document-configuration/download-document-configuration.component.d.ts +25 -0
  9. package/dist/lib/components/epistola-configuration/epistola-configuration.component.d.ts +29 -0
  10. package/dist/lib/components/epistola-download/epistola-download.component.d.ts +24 -0
  11. package/dist/lib/components/epistola-download/epistola-download.formio.d.ts +4 -0
  12. package/dist/lib/components/field-tree/field-tree.component.d.ts +75 -0
  13. package/dist/lib/components/generate-document-configuration/generate-document-configuration.component.d.ts +78 -0
  14. package/dist/lib/epistola.module.d.ts +17 -0
  15. package/dist/lib/epistola.specification.d.ts +3 -0
  16. package/dist/lib/models/config.d.ts +49 -0
  17. package/dist/lib/models/index.d.ts +2 -0
  18. package/dist/lib/models/template.d.ts +63 -0
  19. package/dist/lib/services/epistola-plugin.service.d.ts +42 -0
  20. package/dist/lib/services/index.d.ts +1 -0
  21. package/dist/public_api.d.ts +12 -0
  22. package/ng-package.json +17 -0
  23. package/package.json +38 -0
  24. package/src/lib/assets/epistola-logo.ts +4 -0
  25. package/src/lib/assets/index.ts +1 -0
  26. package/src/lib/components/check-job-status-configuration/check-job-status-configuration.component.html +51 -0
  27. package/src/lib/components/check-job-status-configuration/check-job-status-configuration.component.scss +1 -0
  28. package/src/lib/components/check-job-status-configuration/check-job-status-configuration.component.ts +71 -0
  29. package/src/lib/components/data-mapping-tree/data-mapping-tree.component.html +23 -0
  30. package/src/lib/components/data-mapping-tree/data-mapping-tree.component.scss +38 -0
  31. package/src/lib/components/data-mapping-tree/data-mapping-tree.component.ts +124 -0
  32. package/src/lib/components/download-document-configuration/download-document-configuration.component.html +29 -0
  33. package/src/lib/components/download-document-configuration/download-document-configuration.component.scss +1 -0
  34. package/src/lib/components/download-document-configuration/download-document-configuration.component.ts +71 -0
  35. package/src/lib/components/epistola-configuration/epistola-configuration.component.html +74 -0
  36. package/src/lib/components/epistola-configuration/epistola-configuration.component.scss +1 -0
  37. package/src/lib/components/epistola-configuration/epistola-configuration.component.ts +96 -0
  38. package/src/lib/components/epistola-download/epistola-download.component.ts +79 -0
  39. package/src/lib/components/epistola-download/epistola-download.formio.ts +19 -0
  40. package/src/lib/components/field-tree/field-tree.component.html +192 -0
  41. package/src/lib/components/field-tree/field-tree.component.scss +255 -0
  42. package/src/lib/components/field-tree/field-tree.component.ts +321 -0
  43. package/src/lib/components/generate-document-configuration/generate-document-configuration.component.html +182 -0
  44. package/src/lib/components/generate-document-configuration/generate-document-configuration.component.scss +150 -0
  45. package/src/lib/components/generate-document-configuration/generate-document-configuration.component.ts +422 -0
  46. package/src/lib/epistola.module.ts +50 -0
  47. package/src/lib/epistola.specification.ts +208 -0
  48. package/src/lib/models/config.ts +53 -0
  49. package/src/lib/models/index.ts +2 -0
  50. package/src/lib/models/template.ts +70 -0
  51. package/src/lib/services/epistola-plugin.service.ts +82 -0
  52. package/src/lib/services/index.ts +1 -0
  53. package/src/public_api.ts +16 -0
  54. package/tsconfig.lib.json +21 -0
@@ -0,0 +1,1393 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, EventEmitter, Output, Input, Component, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/common/http';
4
+ import { HttpClientModule } from '@angular/common/http';
5
+ import * as i2 from '@valtimo/shared';
6
+ import * as i1$1 from '@angular/common';
7
+ import { CommonModule } from '@angular/common';
8
+ import * as i2$1 from '@valtimo/plugin';
9
+ import { PluginTranslatePipeModule } from '@valtimo/plugin';
10
+ import * as i3 from '@valtimo/components';
11
+ import { FormModule, InputModule, ValuePathSelectorPrefix, ValuePathSelectorComponent, SelectModule, registerCustomFormioComponent } from '@valtimo/components';
12
+ import { BehaviorSubject, combineLatest, take, Subject, merge, of } from 'rxjs';
13
+ import { startWith, delay, takeUntil, filter, map, catchError, take as take$1 } from 'rxjs/operators';
14
+ import * as i4 from '@angular/forms';
15
+ import { FormsModule } from '@angular/forms';
16
+ import * as i2$2 from '@valtimo/process-link';
17
+
18
+ /**
19
+ * Service for interacting with Epistola plugin API endpoints.
20
+ * Provides methods to fetch templates, environments, variants,
21
+ * process variables, and validate mappings.
22
+ */
23
+ class EpistolaPluginService {
24
+ http;
25
+ configService;
26
+ apiEndpoint;
27
+ constructor(http, configService) {
28
+ this.http = http;
29
+ this.configService = configService;
30
+ this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
31
+ }
32
+ /**
33
+ * Get all available templates for a plugin configuration.
34
+ */
35
+ getTemplates(pluginConfigurationId) {
36
+ return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates`);
37
+ }
38
+ /**
39
+ * Get template details including its fields.
40
+ */
41
+ getTemplateDetails(pluginConfigurationId, templateId) {
42
+ return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates/${templateId}`);
43
+ }
44
+ /**
45
+ * Get all available environments for a plugin configuration.
46
+ */
47
+ getEnvironments(pluginConfigurationId) {
48
+ return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/environments`);
49
+ }
50
+ /**
51
+ * Get all variants for a specific template.
52
+ */
53
+ getVariants(pluginConfigurationId, templateId) {
54
+ return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates/${templateId}/variants`);
55
+ }
56
+ /**
57
+ * Discover process variable names for a given process definition.
58
+ */
59
+ getProcessVariables(processDefinitionKey) {
60
+ return this.http.get(`${this.apiEndpoint}/process-variables`, { params: { processDefinitionKey } });
61
+ }
62
+ /**
63
+ * Validate that a data mapping covers all required template fields.
64
+ */
65
+ validateMapping(pluginConfigurationId, templateId, dataMapping) {
66
+ return this.http.post(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates/${templateId}/validate-mapping`, { dataMapping });
67
+ }
68
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginService, deps: [{ token: i1.HttpClient }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Injectable });
69
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginService });
70
+ }
71
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginService, decorators: [{
72
+ type: Injectable
73
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.ConfigService }] });
74
+
75
+ class EpistolaConfigurationComponent {
76
+ save$;
77
+ disabled$;
78
+ pluginId;
79
+ prefillConfiguration$;
80
+ valid = new EventEmitter();
81
+ configuration = new EventEmitter();
82
+ /** Epistola slug pattern: lowercase alphanumeric with hyphens, no leading/trailing hyphens. */
83
+ static SLUG_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
84
+ saveSubscription;
85
+ formValue$ = new BehaviorSubject(null);
86
+ valid$ = new BehaviorSubject(false);
87
+ safeDisabled$;
88
+ ngOnInit() {
89
+ // Wrap disabled$ with startWith and delay to prevent NG0100 ExpressionChangedAfterItHasBeenCheckedError
90
+ // The disabled$ observable from Valtimo's plugin framework can emit value changes after Angular's
91
+ // change detection cycle completes, causing the error.
92
+ this.safeDisabled$ = this.disabled$.pipe(startWith(true), delay(0));
93
+ this.openSaveSubscription();
94
+ }
95
+ ngOnDestroy() {
96
+ this.saveSubscription?.unsubscribe();
97
+ }
98
+ formValueChange(formOutput) {
99
+ const formValue = formOutput;
100
+ this.formValue$.next(formValue);
101
+ this.handleValid(formValue);
102
+ }
103
+ handleValid(formValue) {
104
+ const valid = !!(formValue?.configurationTitle &&
105
+ formValue?.baseUrl &&
106
+ formValue?.apiKey &&
107
+ formValue?.tenantId &&
108
+ this.isValidSlug(formValue.tenantId, 3, 63) &&
109
+ this.isValidOptionalSlug(formValue.defaultEnvironmentId, 3, 30));
110
+ this.valid$.next(valid);
111
+ this.valid.emit(valid);
112
+ }
113
+ isValidSlug(value, minLength, maxLength) {
114
+ return (value.length >= minLength &&
115
+ value.length <= maxLength &&
116
+ EpistolaConfigurationComponent.SLUG_PATTERN.test(value));
117
+ }
118
+ isValidOptionalSlug(value, minLength, maxLength) {
119
+ if (!value) {
120
+ return true;
121
+ }
122
+ return this.isValidSlug(value, minLength, maxLength);
123
+ }
124
+ openSaveSubscription() {
125
+ this.saveSubscription = this.save$?.subscribe(() => {
126
+ combineLatest([this.formValue$, this.valid$])
127
+ .pipe(take(1))
128
+ .subscribe(([formValue, valid]) => {
129
+ if (valid) {
130
+ this.configuration.emit(formValue);
131
+ }
132
+ });
133
+ });
134
+ }
135
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
136
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: EpistolaConfigurationComponent, isStandalone: true, selector: "epistola-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"configurationTitle\"\n [title]=\"'configurationTitle' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.configurationTitle\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"baseUrl\"\n [title]=\"'baseUrl' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'baseUrlTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.baseUrl\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"apiKey\"\n [title]=\"'apiKey' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'apiKeyTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.apiKey\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n type=\"password\"\n >\n </v-input>\n\n <v-input\n name=\"tenantId\"\n [title]=\"'tenantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'tenantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.tenantId\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"defaultEnvironmentId\"\n [title]=\"'defaultEnvironmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'defaultEnvironmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.defaultEnvironmentId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"templateSyncEnabled\"\n type=\"checkbox\"\n [title]=\"'templateSyncEnabled' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateSyncEnabledTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.templateSyncEnabled ? 'true' : ''\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
137
+ }
138
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaConfigurationComponent, decorators: [{
139
+ type: Component,
140
+ args: [{ selector: 'epistola-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"configurationTitle\"\n [title]=\"'configurationTitle' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.configurationTitle\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"baseUrl\"\n [title]=\"'baseUrl' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'baseUrlTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.baseUrl\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"apiKey\"\n [title]=\"'apiKey' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'apiKeyTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.apiKey\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n type=\"password\"\n >\n </v-input>\n\n <v-input\n name=\"tenantId\"\n [title]=\"'tenantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'tenantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.tenantId\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"defaultEnvironmentId\"\n [title]=\"'defaultEnvironmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'defaultEnvironmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.defaultEnvironmentId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"templateSyncEnabled\"\n type=\"checkbox\"\n [title]=\"'templateSyncEnabled' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateSyncEnabledTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.templateSyncEnabled ? 'true' : ''\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n" }]
141
+ }], propDecorators: { save$: [{
142
+ type: Input
143
+ }], disabled$: [{
144
+ type: Input
145
+ }], pluginId: [{
146
+ type: Input
147
+ }], prefillConfiguration$: [{
148
+ type: Input
149
+ }], valid: [{
150
+ type: Output
151
+ }], configuration: [{
152
+ type: Output
153
+ }] } });
154
+
155
+ /**
156
+ * Recursive component that renders a single TemplateField node as part of a tree form.
157
+ *
158
+ * - SCALAR fields render as a label + input row with a 3-mode selector (browse / pv / expression)
159
+ * - OBJECT fields render as a collapsible section with children indented inside
160
+ * - ARRAY fields render as a collapsible section with source collection input and optional per-item field mappings
161
+ *
162
+ * Input modes:
163
+ * - Browse (⊞): ValuePathSelector for doc:/case: paths
164
+ * - PV (pv): Dropdown of discovered process variables (text fallback when none found)
165
+ * - Expression (fx): Free-text input for manual expressions
166
+ */
167
+ class FieldTreeComponent {
168
+ field;
169
+ value = undefined;
170
+ pluginId;
171
+ caseDefinitionKey = null;
172
+ processVariables = [];
173
+ disabled = false;
174
+ valueChange = new EventEmitter();
175
+ ValuePathSelectorPrefix = ValuePathSelectorPrefix;
176
+ inputMode = 'browse';
177
+ expanded = false;
178
+ /** For ARRAY fields: whether to show per-item field mapping */
179
+ arrayPerFieldMode = false;
180
+ /** Completeness badge for collapsed object/array sections */
181
+ mappedCount = 0;
182
+ totalRequired = 0;
183
+ ngOnChanges(changes) {
184
+ if (changes['value'] || changes['field']) {
185
+ this.updateCompleteness();
186
+ // Auto-expand if there are unmapped required children
187
+ if (!this.expanded && this.totalRequired > 0 && this.mappedCount < this.totalRequired) {
188
+ this.expanded = true;
189
+ }
190
+ }
191
+ // Detect input mode from prefill value
192
+ if (changes['value'] && this.value != null) {
193
+ if (this.field?.fieldType === 'SCALAR') {
194
+ this.inputMode = this.detectInputMode(this.value);
195
+ }
196
+ else if (this.field?.fieldType === 'ARRAY') {
197
+ const sourceValue = this.getSourceValue();
198
+ if (sourceValue) {
199
+ this.inputMode = this.detectInputMode(sourceValue);
200
+ }
201
+ // Detect per-field mode from value shape
202
+ if (typeof this.value === 'object' && this.value !== null && '_source' in this.value) {
203
+ this.arrayPerFieldMode = true;
204
+ }
205
+ }
206
+ }
207
+ }
208
+ toggleExpanded() {
209
+ this.expanded = !this.expanded;
210
+ }
211
+ setInputMode(mode) {
212
+ this.inputMode = mode;
213
+ }
214
+ /** Handle value change from ValuePathSelector (browse mode) */
215
+ onBrowseValueChange(newValue) {
216
+ this.emitScalarValue(newValue);
217
+ }
218
+ /** Handle value change from PV dropdown */
219
+ onPvChange(newValue) {
220
+ if (newValue) {
221
+ this.emitScalarValue('pv:' + newValue);
222
+ }
223
+ else {
224
+ this.emitScalarValue('');
225
+ }
226
+ }
227
+ /** Handle value change from text input (expression mode) */
228
+ onExpressionValueChange(newValue) {
229
+ this.emitScalarValue(newValue);
230
+ }
231
+ /** Handle child value change for OBJECT fields */
232
+ onChildChange(childName, childValue) {
233
+ const current = (typeof this.value === 'object' && this.value !== null) ? { ...this.value } : {};
234
+ if (childValue === undefined || childValue === null || childValue === '') {
235
+ delete current[childName];
236
+ }
237
+ else {
238
+ current[childName] = childValue;
239
+ }
240
+ this.valueChange.emit(Object.keys(current).length > 0 ? current : undefined);
241
+ }
242
+ /** Get the current value for a child field within an OBJECT */
243
+ getChildValue(childName) {
244
+ if (typeof this.value === 'object' && this.value !== null) {
245
+ return this.value[childName];
246
+ }
247
+ return undefined;
248
+ }
249
+ /** Get the string value for display (for SCALAR leaf inputs and direct array mode) */
250
+ getStringValue() {
251
+ return typeof this.value === 'string' ? this.value : '';
252
+ }
253
+ /** Get the PV name from a pv: prefixed value (for PV dropdown default selection) */
254
+ getPvName() {
255
+ const str = this.getStringValue();
256
+ return str.startsWith('pv:') ? str.substring(3) : '';
257
+ }
258
+ // --- ARRAY-specific methods ---
259
+ /** Get the source collection value (works for both direct string and _source format) */
260
+ getSourceValue() {
261
+ if (typeof this.value === 'string') {
262
+ return this.value;
263
+ }
264
+ if (typeof this.value === 'object' && this.value !== null && '_source' in this.value) {
265
+ return this.value['_source'] || '';
266
+ }
267
+ return '';
268
+ }
269
+ /** Get the PV name from the source value */
270
+ getSourcePvName() {
271
+ const source = this.getSourceValue();
272
+ return source.startsWith('pv:') ? source.substring(3) : '';
273
+ }
274
+ toggleArrayPerFieldMode() {
275
+ this.arrayPerFieldMode = !this.arrayPerFieldMode;
276
+ if (this.arrayPerFieldMode) {
277
+ // Switch from direct to per-field: convert string value to _source object
278
+ const currentSource = this.getSourceValue();
279
+ const obj = { _source: currentSource };
280
+ this.valueChange.emit(obj);
281
+ }
282
+ else {
283
+ // Switch from per-field to direct: extract _source as string value
284
+ const source = this.getSourceValue();
285
+ this.valueChange.emit(source || undefined);
286
+ }
287
+ }
288
+ /** Handle source collection value change (used in ARRAY mode) */
289
+ onSourceBrowseChange(newValue) {
290
+ this.updateSourceValue(newValue);
291
+ }
292
+ onSourcePvChange(newValue) {
293
+ this.updateSourceValue(newValue ? 'pv:' + newValue : '');
294
+ }
295
+ onSourceExpressionChange(newValue) {
296
+ this.updateSourceValue(newValue);
297
+ }
298
+ /** Handle per-item field mapping change */
299
+ onItemFieldChange(childName, sourceFieldName) {
300
+ const current = (typeof this.value === 'object' && this.value !== null) ? { ...this.value } : { _source: '' };
301
+ if (sourceFieldName && sourceFieldName.trim().length > 0) {
302
+ current[childName] = sourceFieldName;
303
+ }
304
+ else {
305
+ delete current[childName];
306
+ }
307
+ this.valueChange.emit(current);
308
+ }
309
+ /** Get the current source field name mapping for a child */
310
+ getItemFieldValue(childName) {
311
+ if (typeof this.value === 'object' && this.value !== null) {
312
+ return this.value[childName] || '';
313
+ }
314
+ return '';
315
+ }
316
+ /** Check if the array has any children that can be mapped per-item */
317
+ hasArrayChildren() {
318
+ return !!(this.field?.children && this.field.children.length > 0);
319
+ }
320
+ emitScalarValue(newValue) {
321
+ this.valueChange.emit(newValue || undefined);
322
+ }
323
+ updateSourceValue(newValue) {
324
+ if (this.arrayPerFieldMode) {
325
+ const current = (typeof this.value === 'object' && this.value !== null) ? { ...this.value } : {};
326
+ current['_source'] = newValue || '';
327
+ this.valueChange.emit(current);
328
+ }
329
+ else {
330
+ this.valueChange.emit(newValue || undefined);
331
+ }
332
+ }
333
+ updateCompleteness() {
334
+ if (this.field?.fieldType === 'OBJECT' && this.field.children) {
335
+ const stats = this.countRequiredMapped(this.field.children, this.value || {});
336
+ this.mappedCount = stats.mapped;
337
+ this.totalRequired = stats.total;
338
+ }
339
+ else if (this.field?.fieldType === 'ARRAY') {
340
+ this.updateArrayCompleteness();
341
+ }
342
+ }
343
+ updateArrayCompleteness() {
344
+ if (!this.field?.children || this.field.children.length === 0) {
345
+ // No children: just check if source is set
346
+ this.totalRequired = this.field?.required ? 1 : 0;
347
+ this.mappedCount = this.getSourceValue() ? (this.field?.required ? 1 : 0) : 0;
348
+ return;
349
+ }
350
+ if (typeof this.value === 'object' && this.value !== null && '_source' in this.value) {
351
+ // Per-field mode: count source + required children
352
+ let total = 0;
353
+ let mapped = 0;
354
+ // Source counts as 1 required
355
+ if (this.field?.required) {
356
+ total++;
357
+ if (this.value['_source'] && this.value['_source'].trim().length > 0) {
358
+ mapped++;
359
+ }
360
+ }
361
+ // Count required children
362
+ for (const child of this.field.children) {
363
+ if (child.required) {
364
+ total++;
365
+ const val = this.value[child.name];
366
+ if (typeof val === 'string' && val.trim().length > 0) {
367
+ mapped++;
368
+ }
369
+ }
370
+ }
371
+ this.mappedCount = mapped;
372
+ this.totalRequired = total;
373
+ }
374
+ else {
375
+ // Direct mode: just check if source is set
376
+ this.totalRequired = this.field?.required ? 1 : 0;
377
+ this.mappedCount = this.getSourceValue() ? (this.field?.required ? 1 : 0) : 0;
378
+ }
379
+ }
380
+ countRequiredMapped(fields, mapping) {
381
+ let mapped = 0;
382
+ let total = 0;
383
+ for (const field of fields) {
384
+ if (field.fieldType === 'SCALAR' && field.required) {
385
+ total++;
386
+ const val = mapping[field.name];
387
+ if (typeof val === 'string' && val.trim().length > 0) {
388
+ mapped++;
389
+ }
390
+ }
391
+ else if (field.fieldType === 'ARRAY' && field.required) {
392
+ total++;
393
+ const val = mapping[field.name];
394
+ if (typeof val === 'string' && val.trim().length > 0) {
395
+ mapped++;
396
+ }
397
+ else if (typeof val === 'object' && val !== null && '_source' in val) {
398
+ if (typeof val['_source'] === 'string' && val['_source'].trim().length > 0) {
399
+ mapped++;
400
+ }
401
+ }
402
+ }
403
+ else if (field.fieldType === 'OBJECT' && field.children) {
404
+ const nested = (typeof mapping[field.name] === 'object' && mapping[field.name] !== null)
405
+ ? mapping[field.name]
406
+ : {};
407
+ const childStats = this.countRequiredMapped(field.children, nested);
408
+ mapped += childStats.mapped;
409
+ total += childStats.total;
410
+ }
411
+ }
412
+ return { mapped, total };
413
+ }
414
+ detectInputMode(value) {
415
+ if (typeof value !== 'string')
416
+ return 'browse';
417
+ if (value.startsWith('doc:') || value.startsWith('case:'))
418
+ return 'browse';
419
+ if (value.startsWith('pv:'))
420
+ return 'pv';
421
+ if (value.length > 0)
422
+ return 'expression';
423
+ return 'browse';
424
+ }
425
+ isResolvableValue(value) {
426
+ return value.startsWith('doc:') || value.startsWith('case:') || value.startsWith('pv:') || value.startsWith('template:');
427
+ }
428
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FieldTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
429
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: FieldTreeComponent, isStandalone: true, selector: "epistola-field-tree", inputs: { field: "field", value: "value", pluginId: "pluginId", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables", disabled: "disabled" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<!-- SCALAR field: label row + input with 3-mode selector -->\n<div *ngIf=\"field.fieldType === 'SCALAR'\" class=\"field-row\" [class.field-required-unmapped]=\"field.required && !getStringValue()\">\n <div class=\"field-label\">\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">({{ field.type }}{{ field.required ? ', required' : '' }})</span>\n </div>\n <div class=\"field-input\">\n <div class=\"input-mode-group\">\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'browse'\" [disabled]=\"disabled\" (click)=\"setInputMode('browse')\" [title]=\"'browseMode' | pluginTranslate: pluginId | async\">\u229E</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'pv'\" [disabled]=\"disabled\" (click)=\"setInputMode('pv')\" [title]=\"'pvMode' | pluginTranslate: pluginId | async\">pv</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'expression'\" [disabled]=\"disabled\" (click)=\"setInputMode('expression')\" [title]=\"'expressionMode' | pluginTranslate: pluginId | async\">fx</button>\n </div>\n\n <!-- Browse mode: ValuePathSelector -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"'field_' + field.path\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"getStringValue()\"\n [showCaseDefinitionSelector]=\"!caseDefinitionKey\"\n (valueChangeEvent)=\"onBrowseValueChange($event)\"\n ></valtimo-value-path-selector>\n </div>\n\n <!-- PV mode: dropdown (when available) or text fallback -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n (change)=\"onPvChange($any($event.target).value)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\" [selected]=\"pv === getPvName()\">{{ pv }}</option>\n </select>\n <ng-template #pvFallback>\n <v-input\n [name]=\"'pvfb_' + field.path\"\n [defaultValue]=\"getPvName()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'pvPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onPvChange($event)\"\n ></v-input>\n </ng-template>\n </div>\n\n <!-- Expression mode: text input -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + field.path\"\n [defaultValue]=\"getStringValue()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'e.g. pv:variableName or doc:path.to.field'\"\n (valueChange)=\"onExpressionValueChange($event)\"\n ></v-input>\n </div>\n </div>\n</div>\n\n<!-- OBJECT field: collapsible section with children -->\n<div *ngIf=\"field.fieldType === 'OBJECT'\" class=\"field-object\">\n <div class=\"field-object-header\" (click)=\"toggleExpanded()\" [class.field-required-unmapped]=\"totalRequired > 0 && mappedCount < totalRequired\">\n <span class=\"expand-icon\">{{ expanded ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">(object{{ field.required ? ', required' : '' }})</span>\n <span class=\"completeness-badge\" *ngIf=\"totalRequired > 0 && !expanded\">\n {{ mappedCount }}/{{ totalRequired }}\n </span>\n </div>\n <div class=\"field-object-children\" *ngIf=\"expanded\">\n <epistola-field-tree\n *ngFor=\"let child of field.children\"\n [field]=\"child\"\n [value]=\"getChildValue(child.name)\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onChildChange(child.name, $event)\"\n ></epistola-field-tree>\n </div>\n</div>\n\n<!-- ARRAY field: collapsible section with source collection + optional per-item mapping -->\n<div *ngIf=\"field.fieldType === 'ARRAY'\" class=\"field-array\">\n <div class=\"field-array-header\" (click)=\"toggleExpanded()\" [class.field-required-unmapped]=\"field.required && !getSourceValue()\">\n <span class=\"expand-icon\">{{ expanded ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">(array{{ field.required ? ', required' : '' }})</span>\n <span class=\"completeness-badge\" *ngIf=\"totalRequired > 0 && !expanded\">\n {{ mappedCount }}/{{ totalRequired }}\n </span>\n <span class=\"mapped-indicator\" *ngIf=\"totalRequired === 0 && getSourceValue() && !expanded\">\u2713</span>\n </div>\n <div class=\"field-array-content\" *ngIf=\"expanded\">\n <!-- Source collection input -->\n <div class=\"field-row\">\n <div class=\"field-label\">\n <span class=\"field-name\">{{ 'mapCollectionTo' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div class=\"field-input\">\n <div class=\"input-mode-group\">\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'browse'\" [disabled]=\"disabled\" (click)=\"setInputMode('browse')\" [title]=\"'browseMode' | pluginTranslate: pluginId | async\">\u229E</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'pv'\" [disabled]=\"disabled\" (click)=\"setInputMode('pv')\" [title]=\"'pvMode' | pluginTranslate: pluginId | async\">pv</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'expression'\" [disabled]=\"disabled\" (click)=\"setInputMode('expression')\" [title]=\"'expressionMode' | pluginTranslate: pluginId | async\">fx</button>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"'field_' + field.path\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"getSourceValue()\"\n [showCaseDefinitionSelector]=\"!caseDefinitionKey\"\n (valueChangeEvent)=\"onSourceBrowseChange($event)\"\n ></valtimo-value-path-selector>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvSourceFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n (change)=\"onSourcePvChange($any($event.target).value)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\" [selected]=\"pv === getSourcePvName()\">{{ pv }}</option>\n </select>\n <ng-template #pvSourceFallback>\n <v-input\n [name]=\"'pvfb_' + field.path\"\n [defaultValue]=\"getSourcePvName()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'pvPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onSourcePvChange($event)\"\n ></v-input>\n </ng-template>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + field.path\"\n [defaultValue]=\"getSourceValue()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'e.g. pv:variableName or doc:path.to.field'\"\n (valueChange)=\"onSourceExpressionChange($event)\"\n ></v-input>\n </div>\n </div>\n </div>\n\n <!-- Per-item field mapping toggle (only shown when array has children) -->\n <div class=\"array-per-field-toggle\" *ngIf=\"hasArrayChildren()\">\n <label class=\"toggle-label\">\n <input\n type=\"checkbox\"\n [checked]=\"arrayPerFieldMode\"\n [disabled]=\"disabled\"\n (change)=\"toggleArrayPerFieldMode()\"\n />\n <span>{{ 'itemFieldMapping' | pluginTranslate: pluginId | async }}</span>\n </label>\n </div>\n\n <!-- Per-item field mappings -->\n <div class=\"array-item-fields\" *ngIf=\"arrayPerFieldMode && hasArrayChildren()\">\n <div class=\"item-fields-header\">\n <span class=\"item-fields-title\">{{ 'itemFieldMappingTitle' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div class=\"item-field-row\" *ngFor=\"let child of field.children\">\n <div class=\"item-field-label\">\n <span class=\"field-name\">{{ child.name }}</span>\n <span class=\"field-meta\">({{ child.type }}{{ child.required ? ', required' : '' }})</span>\n </div>\n <div class=\"item-field-input\">\n <v-input\n [name]=\"'itemField_' + child.path\"\n [defaultValue]=\"getItemFieldValue(child.name)\"\n [disabled]=\"disabled\"\n [placeholder]=\"'sourceFieldPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onItemFieldChange(child.name, $event)\"\n ></v-input>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [".field-row{display:flex;align-items:flex-start;gap:.75rem;padding:.5rem 0;border-left:3px solid transparent}.field-row.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5;padding-left:.5rem}.field-label{flex:0 0 200px;min-width:140px;padding-top:.5rem;word-break:break-word}.field-label .field-name{font-weight:500}.field-label .field-meta{font-size:.8125rem;color:#6c757d;margin-left:.25rem}.field-input{flex:1;display:flex;align-items:flex-start;gap:.5rem;min-width:0}.field-input-control{flex:1;min-width:0}.input-mode-group{flex:0 0 auto;display:flex;margin-top:.25rem;border:1px solid #c6c6c6;border-radius:4px;overflow:hidden}.input-mode-group .mode-btn{width:32px;height:32px;padding:0;border:none;border-right:1px solid #c6c6c6;background:#f4f4f4;color:#525252;font-size:.75rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background-color .15s}.input-mode-group .mode-btn:last-child{border-right:none}.input-mode-group .mode-btn:hover:not(:disabled){background:#e0e0e0}.input-mode-group .mode-btn.mode-active{background:#0f62fe;color:#fff}.input-mode-group .mode-btn.mode-active:hover:not(:disabled){background:#0353e9}.input-mode-group .mode-btn:disabled{opacity:.5;cursor:not-allowed}.pv-select{width:100%;height:2.5rem;padding:0 .75rem;border:1px solid #c6c6c6;border-radius:4px;background:#fff;color:#161616;font-size:.875rem;cursor:pointer}.pv-select:focus{outline:2px solid #0f62fe;outline-offset:-2px}.pv-select:disabled{opacity:.5;cursor:not-allowed;background:#f4f4f4}.field-object{margin:.25rem 0}.field-object-header,.field-array-header{display:flex;align-items:center;gap:.5rem;padding:.5rem .25rem;cursor:pointer;-webkit-user-select:none;user-select:none;border-left:3px solid transparent;border-radius:2px}.field-object-header:hover,.field-array-header:hover{background:#f4f4f4}.field-object-header.field-required-unmapped,.field-array-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-object-header .expand-icon,.field-array-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-object-header .field-name,.field-array-header .field-name{font-weight:500}.field-object-header .field-meta,.field-array-header .field-meta{font-size:.8125rem;color:#6c757d}.field-object-header .completeness-badge,.field-array-header .completeness-badge{margin-left:auto;font-size:.75rem;padding:.125rem .5rem;border-radius:10px;background:#e0e0e0;color:#525252;font-weight:500}.field-object-header .mapped-indicator,.field-array-header .mapped-indicator{margin-left:auto;color:#198754;font-weight:600}.field-object-children{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.field-array{margin:.25rem 0}.field-array-content{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.array-per-field-toggle{padding:.5rem 0}.array-per-field-toggle .toggle-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;font-size:.875rem;color:#525252}.array-per-field-toggle .toggle-label input[type=checkbox]{cursor:pointer}.array-item-fields{margin-left:.5rem;border-left:1px dashed #c6c6c6;padding:.25rem 0 .5rem 1rem}.item-fields-header{padding-bottom:.25rem}.item-fields-header .item-fields-title{font-size:.8125rem;font-weight:500;color:#6c757d}.item-field-row{display:flex;align-items:center;gap:.75rem;padding:.25rem 0}.item-field-label{flex:0 0 180px;min-width:120px;word-break:break-word}.item-field-label .field-name{font-weight:500;font-size:.875rem}.item-field-label .field-meta{font-size:.75rem;color:#6c757d;margin-left:.25rem}.item-field-input{flex:1;min-width:0}\n"], dependencies: [{ kind: "component", type: FieldTreeComponent, selector: "epistola-field-tree", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }, { kind: "component", type: ValuePathSelectorComponent, selector: "valtimo-value-path-selector", inputs: ["name", "appendInline", "margin", "marginLg", "marginXl", "disabled", "caseDefinitionKey", "caseDefinitionVersionTag", "prefixes", "label", "tooltip", "required", "showCaseDefinitionSelector", "notation", "defaultValue", "type", "parentItem"], outputs: ["valueChangeEvent", "collectionSelected"] }] });
430
+ }
431
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FieldTreeComponent, decorators: [{
432
+ type: Component,
433
+ args: [{ selector: 'epistola-field-tree', standalone: true, imports: [
434
+ CommonModule,
435
+ FormsModule,
436
+ PluginTranslatePipeModule,
437
+ InputModule,
438
+ ValuePathSelectorComponent
439
+ ], template: "<!-- SCALAR field: label row + input with 3-mode selector -->\n<div *ngIf=\"field.fieldType === 'SCALAR'\" class=\"field-row\" [class.field-required-unmapped]=\"field.required && !getStringValue()\">\n <div class=\"field-label\">\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">({{ field.type }}{{ field.required ? ', required' : '' }})</span>\n </div>\n <div class=\"field-input\">\n <div class=\"input-mode-group\">\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'browse'\" [disabled]=\"disabled\" (click)=\"setInputMode('browse')\" [title]=\"'browseMode' | pluginTranslate: pluginId | async\">\u229E</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'pv'\" [disabled]=\"disabled\" (click)=\"setInputMode('pv')\" [title]=\"'pvMode' | pluginTranslate: pluginId | async\">pv</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'expression'\" [disabled]=\"disabled\" (click)=\"setInputMode('expression')\" [title]=\"'expressionMode' | pluginTranslate: pluginId | async\">fx</button>\n </div>\n\n <!-- Browse mode: ValuePathSelector -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"'field_' + field.path\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"getStringValue()\"\n [showCaseDefinitionSelector]=\"!caseDefinitionKey\"\n (valueChangeEvent)=\"onBrowseValueChange($event)\"\n ></valtimo-value-path-selector>\n </div>\n\n <!-- PV mode: dropdown (when available) or text fallback -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n (change)=\"onPvChange($any($event.target).value)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\" [selected]=\"pv === getPvName()\">{{ pv }}</option>\n </select>\n <ng-template #pvFallback>\n <v-input\n [name]=\"'pvfb_' + field.path\"\n [defaultValue]=\"getPvName()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'pvPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onPvChange($event)\"\n ></v-input>\n </ng-template>\n </div>\n\n <!-- Expression mode: text input -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + field.path\"\n [defaultValue]=\"getStringValue()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'e.g. pv:variableName or doc:path.to.field'\"\n (valueChange)=\"onExpressionValueChange($event)\"\n ></v-input>\n </div>\n </div>\n</div>\n\n<!-- OBJECT field: collapsible section with children -->\n<div *ngIf=\"field.fieldType === 'OBJECT'\" class=\"field-object\">\n <div class=\"field-object-header\" (click)=\"toggleExpanded()\" [class.field-required-unmapped]=\"totalRequired > 0 && mappedCount < totalRequired\">\n <span class=\"expand-icon\">{{ expanded ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">(object{{ field.required ? ', required' : '' }})</span>\n <span class=\"completeness-badge\" *ngIf=\"totalRequired > 0 && !expanded\">\n {{ mappedCount }}/{{ totalRequired }}\n </span>\n </div>\n <div class=\"field-object-children\" *ngIf=\"expanded\">\n <epistola-field-tree\n *ngFor=\"let child of field.children\"\n [field]=\"child\"\n [value]=\"getChildValue(child.name)\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onChildChange(child.name, $event)\"\n ></epistola-field-tree>\n </div>\n</div>\n\n<!-- ARRAY field: collapsible section with source collection + optional per-item mapping -->\n<div *ngIf=\"field.fieldType === 'ARRAY'\" class=\"field-array\">\n <div class=\"field-array-header\" (click)=\"toggleExpanded()\" [class.field-required-unmapped]=\"field.required && !getSourceValue()\">\n <span class=\"expand-icon\">{{ expanded ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">(array{{ field.required ? ', required' : '' }})</span>\n <span class=\"completeness-badge\" *ngIf=\"totalRequired > 0 && !expanded\">\n {{ mappedCount }}/{{ totalRequired }}\n </span>\n <span class=\"mapped-indicator\" *ngIf=\"totalRequired === 0 && getSourceValue() && !expanded\">\u2713</span>\n </div>\n <div class=\"field-array-content\" *ngIf=\"expanded\">\n <!-- Source collection input -->\n <div class=\"field-row\">\n <div class=\"field-label\">\n <span class=\"field-name\">{{ 'mapCollectionTo' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div class=\"field-input\">\n <div class=\"input-mode-group\">\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'browse'\" [disabled]=\"disabled\" (click)=\"setInputMode('browse')\" [title]=\"'browseMode' | pluginTranslate: pluginId | async\">\u229E</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'pv'\" [disabled]=\"disabled\" (click)=\"setInputMode('pv')\" [title]=\"'pvMode' | pluginTranslate: pluginId | async\">pv</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'expression'\" [disabled]=\"disabled\" (click)=\"setInputMode('expression')\" [title]=\"'expressionMode' | pluginTranslate: pluginId | async\">fx</button>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"'field_' + field.path\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"getSourceValue()\"\n [showCaseDefinitionSelector]=\"!caseDefinitionKey\"\n (valueChangeEvent)=\"onSourceBrowseChange($event)\"\n ></valtimo-value-path-selector>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvSourceFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n (change)=\"onSourcePvChange($any($event.target).value)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\" [selected]=\"pv === getSourcePvName()\">{{ pv }}</option>\n </select>\n <ng-template #pvSourceFallback>\n <v-input\n [name]=\"'pvfb_' + field.path\"\n [defaultValue]=\"getSourcePvName()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'pvPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onSourcePvChange($event)\"\n ></v-input>\n </ng-template>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + field.path\"\n [defaultValue]=\"getSourceValue()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'e.g. pv:variableName or doc:path.to.field'\"\n (valueChange)=\"onSourceExpressionChange($event)\"\n ></v-input>\n </div>\n </div>\n </div>\n\n <!-- Per-item field mapping toggle (only shown when array has children) -->\n <div class=\"array-per-field-toggle\" *ngIf=\"hasArrayChildren()\">\n <label class=\"toggle-label\">\n <input\n type=\"checkbox\"\n [checked]=\"arrayPerFieldMode\"\n [disabled]=\"disabled\"\n (change)=\"toggleArrayPerFieldMode()\"\n />\n <span>{{ 'itemFieldMapping' | pluginTranslate: pluginId | async }}</span>\n </label>\n </div>\n\n <!-- Per-item field mappings -->\n <div class=\"array-item-fields\" *ngIf=\"arrayPerFieldMode && hasArrayChildren()\">\n <div class=\"item-fields-header\">\n <span class=\"item-fields-title\">{{ 'itemFieldMappingTitle' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div class=\"item-field-row\" *ngFor=\"let child of field.children\">\n <div class=\"item-field-label\">\n <span class=\"field-name\">{{ child.name }}</span>\n <span class=\"field-meta\">({{ child.type }}{{ child.required ? ', required' : '' }})</span>\n </div>\n <div class=\"item-field-input\">\n <v-input\n [name]=\"'itemField_' + child.path\"\n [defaultValue]=\"getItemFieldValue(child.name)\"\n [disabled]=\"disabled\"\n [placeholder]=\"'sourceFieldPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onItemFieldChange(child.name, $event)\"\n ></v-input>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [".field-row{display:flex;align-items:flex-start;gap:.75rem;padding:.5rem 0;border-left:3px solid transparent}.field-row.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5;padding-left:.5rem}.field-label{flex:0 0 200px;min-width:140px;padding-top:.5rem;word-break:break-word}.field-label .field-name{font-weight:500}.field-label .field-meta{font-size:.8125rem;color:#6c757d;margin-left:.25rem}.field-input{flex:1;display:flex;align-items:flex-start;gap:.5rem;min-width:0}.field-input-control{flex:1;min-width:0}.input-mode-group{flex:0 0 auto;display:flex;margin-top:.25rem;border:1px solid #c6c6c6;border-radius:4px;overflow:hidden}.input-mode-group .mode-btn{width:32px;height:32px;padding:0;border:none;border-right:1px solid #c6c6c6;background:#f4f4f4;color:#525252;font-size:.75rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background-color .15s}.input-mode-group .mode-btn:last-child{border-right:none}.input-mode-group .mode-btn:hover:not(:disabled){background:#e0e0e0}.input-mode-group .mode-btn.mode-active{background:#0f62fe;color:#fff}.input-mode-group .mode-btn.mode-active:hover:not(:disabled){background:#0353e9}.input-mode-group .mode-btn:disabled{opacity:.5;cursor:not-allowed}.pv-select{width:100%;height:2.5rem;padding:0 .75rem;border:1px solid #c6c6c6;border-radius:4px;background:#fff;color:#161616;font-size:.875rem;cursor:pointer}.pv-select:focus{outline:2px solid #0f62fe;outline-offset:-2px}.pv-select:disabled{opacity:.5;cursor:not-allowed;background:#f4f4f4}.field-object{margin:.25rem 0}.field-object-header,.field-array-header{display:flex;align-items:center;gap:.5rem;padding:.5rem .25rem;cursor:pointer;-webkit-user-select:none;user-select:none;border-left:3px solid transparent;border-radius:2px}.field-object-header:hover,.field-array-header:hover{background:#f4f4f4}.field-object-header.field-required-unmapped,.field-array-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-object-header .expand-icon,.field-array-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-object-header .field-name,.field-array-header .field-name{font-weight:500}.field-object-header .field-meta,.field-array-header .field-meta{font-size:.8125rem;color:#6c757d}.field-object-header .completeness-badge,.field-array-header .completeness-badge{margin-left:auto;font-size:.75rem;padding:.125rem .5rem;border-radius:10px;background:#e0e0e0;color:#525252;font-weight:500}.field-object-header .mapped-indicator,.field-array-header .mapped-indicator{margin-left:auto;color:#198754;font-weight:600}.field-object-children{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.field-array{margin:.25rem 0}.field-array-content{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.array-per-field-toggle{padding:.5rem 0}.array-per-field-toggle .toggle-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;font-size:.875rem;color:#525252}.array-per-field-toggle .toggle-label input[type=checkbox]{cursor:pointer}.array-item-fields{margin-left:.5rem;border-left:1px dashed #c6c6c6;padding:.25rem 0 .5rem 1rem}.item-fields-header{padding-bottom:.25rem}.item-fields-header .item-fields-title{font-size:.8125rem;font-weight:500;color:#6c757d}.item-field-row{display:flex;align-items:center;gap:.75rem;padding:.25rem 0}.item-field-label{flex:0 0 180px;min-width:120px;word-break:break-word}.item-field-label .field-name{font-weight:500;font-size:.875rem}.item-field-label .field-meta{font-size:.75rem;color:#6c757d;margin-left:.25rem}.item-field-input{flex:1;min-width:0}\n"] }]
440
+ }], propDecorators: { field: [{
441
+ type: Input
442
+ }], value: [{
443
+ type: Input
444
+ }], pluginId: [{
445
+ type: Input
446
+ }], caseDefinitionKey: [{
447
+ type: Input
448
+ }], processVariables: [{
449
+ type: Input
450
+ }], disabled: [{
451
+ type: Input
452
+ }], valueChange: [{
453
+ type: Output
454
+ }] } });
455
+
456
+ /**
457
+ * Top-level wrapper that hosts FieldTreeComponent instances for each top-level template field.
458
+ * Manages the full nested mapping object, completeness tracking, and emits mapping changes.
459
+ */
460
+ class DataMappingTreeComponent {
461
+ pluginId;
462
+ templateFields$;
463
+ prefillMapping$;
464
+ disabled$;
465
+ caseDefinitionKey = null;
466
+ processVariables = [];
467
+ mappingChange = new EventEmitter();
468
+ requiredFieldsStatus = new EventEmitter();
469
+ templateFields = [];
470
+ mapping = {};
471
+ disabled = false;
472
+ destroy$ = new Subject();
473
+ ngOnInit() {
474
+ this.templateFields$.pipe(takeUntil(this.destroy$)).subscribe(fields => {
475
+ this.templateFields = fields;
476
+ this.emitRequiredFieldsStatus();
477
+ });
478
+ this.prefillMapping$.pipe(takeUntil(this.destroy$)).subscribe(mapping => {
479
+ if (mapping && Object.keys(mapping).length > 0) {
480
+ this.mapping = { ...mapping };
481
+ }
482
+ this.emitRequiredFieldsStatus();
483
+ });
484
+ this.disabled$.pipe(takeUntil(this.destroy$)).subscribe(disabled => {
485
+ this.disabled = disabled;
486
+ });
487
+ }
488
+ ngOnDestroy() {
489
+ this.destroy$.next();
490
+ this.destroy$.complete();
491
+ }
492
+ onFieldValueChange(fieldName, value) {
493
+ if (value === undefined || value === null || value === '') {
494
+ const { [fieldName]: _, ...rest } = this.mapping;
495
+ this.mapping = rest;
496
+ }
497
+ else {
498
+ this.mapping = { ...this.mapping, [fieldName]: value };
499
+ }
500
+ this.mappingChange.emit(this.mapping);
501
+ this.emitRequiredFieldsStatus();
502
+ }
503
+ getFieldValue(fieldName) {
504
+ return this.mapping[fieldName];
505
+ }
506
+ emitRequiredFieldsStatus() {
507
+ const stats = this.countRequiredMapped(this.templateFields, this.mapping);
508
+ this.requiredFieldsStatus.emit(stats);
509
+ }
510
+ countRequiredMapped(fields, mapping) {
511
+ let mapped = 0;
512
+ let total = 0;
513
+ for (const field of fields) {
514
+ if (field.fieldType === 'SCALAR' && field.required) {
515
+ total++;
516
+ const val = mapping[field.name];
517
+ if (typeof val === 'string' && val.trim().length > 0) {
518
+ mapped++;
519
+ }
520
+ }
521
+ else if (field.fieldType === 'ARRAY' && field.required) {
522
+ total++;
523
+ const val = mapping[field.name];
524
+ if (typeof val === 'string' && val.trim().length > 0) {
525
+ mapped++;
526
+ }
527
+ else if (typeof val === 'object' && val !== null && '_source' in val) {
528
+ if (typeof val['_source'] === 'string' && val['_source'].trim().length > 0) {
529
+ mapped++;
530
+ }
531
+ }
532
+ }
533
+ else if (field.fieldType === 'OBJECT' && field.children) {
534
+ const nested = (typeof mapping[field.name] === 'object' && mapping[field.name] !== null)
535
+ ? mapping[field.name]
536
+ : {};
537
+ const childStats = this.countRequiredMapped(field.children, nested);
538
+ mapped += childStats.mapped;
539
+ total += childStats.total;
540
+ }
541
+ }
542
+ return { mapped, total };
543
+ }
544
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DataMappingTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
545
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: DataMappingTreeComponent, isStandalone: true, selector: "epistola-data-mapping-tree", inputs: { pluginId: "pluginId", templateFields$: "templateFields$", prefillMapping$: "prefillMapping$", disabled$: "disabled$", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables" }, outputs: { mappingChange: "mappingChange", requiredFieldsStatus: "requiredFieldsStatus" }, ngImport: i0, template: "<div class=\"data-mapping-tree\">\n <div class=\"mapping-header\">\n <h5>{{ 'dataMappingTitle' | pluginTranslate: pluginId | async }}</h5>\n <p class=\"helper-text\">{{ 'dataMappingDescription' | pluginTranslate: pluginId | async }}</p>\n </div>\n\n <div class=\"field-tree-root\" *ngIf=\"templateFields.length > 0\">\n <epistola-field-tree\n *ngFor=\"let field of templateFields\"\n [field]=\"field\"\n [value]=\"getFieldValue(field.name)\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onFieldValueChange(field.name, $event)\"\n ></epistola-field-tree>\n </div>\n\n <div class=\"no-fields\" *ngIf=\"templateFields.length === 0\">\n <p>{{ 'noTemplateFields' | pluginTranslate: pluginId | async }}</p>\n </div>\n</div>\n", styles: [".data-mapping-tree{margin-top:1rem;margin-bottom:1rem}.mapping-header{margin-bottom:.75rem}.mapping-header h5{margin-bottom:.25rem;font-weight:600}.mapping-header .helper-text{color:#6c757d;font-size:.875rem;margin-bottom:0}.field-tree-root{border:1px solid #e0e0e0;border-radius:4px;padding:.5rem .75rem}.no-fields{padding:1rem;text-align:center;color:#6c757d;background-color:#f8f9fa;border:1px solid #dee2e6;border-radius:4px}.no-fields p{margin-bottom:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "component", type: FieldTreeComponent, selector: "epistola-field-tree", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }] });
546
+ }
547
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DataMappingTreeComponent, decorators: [{
548
+ type: Component,
549
+ args: [{ selector: 'epistola-data-mapping-tree', standalone: true, imports: [
550
+ CommonModule,
551
+ PluginTranslatePipeModule,
552
+ FieldTreeComponent
553
+ ], template: "<div class=\"data-mapping-tree\">\n <div class=\"mapping-header\">\n <h5>{{ 'dataMappingTitle' | pluginTranslate: pluginId | async }}</h5>\n <p class=\"helper-text\">{{ 'dataMappingDescription' | pluginTranslate: pluginId | async }}</p>\n </div>\n\n <div class=\"field-tree-root\" *ngIf=\"templateFields.length > 0\">\n <epistola-field-tree\n *ngFor=\"let field of templateFields\"\n [field]=\"field\"\n [value]=\"getFieldValue(field.name)\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onFieldValueChange(field.name, $event)\"\n ></epistola-field-tree>\n </div>\n\n <div class=\"no-fields\" *ngIf=\"templateFields.length === 0\">\n <p>{{ 'noTemplateFields' | pluginTranslate: pluginId | async }}</p>\n </div>\n</div>\n", styles: [".data-mapping-tree{margin-top:1rem;margin-bottom:1rem}.mapping-header{margin-bottom:.75rem}.mapping-header h5{margin-bottom:.25rem;font-weight:600}.mapping-header .helper-text{color:#6c757d;font-size:.875rem;margin-bottom:0}.field-tree-root{border:1px solid #e0e0e0;border-radius:4px;padding:.5rem .75rem}.no-fields{padding:1rem;text-align:center;color:#6c757d;background-color:#f8f9fa;border:1px solid #dee2e6;border-radius:4px}.no-fields p{margin-bottom:0}\n"] }]
554
+ }], propDecorators: { pluginId: [{
555
+ type: Input
556
+ }], templateFields$: [{
557
+ type: Input
558
+ }], prefillMapping$: [{
559
+ type: Input
560
+ }], disabled$: [{
561
+ type: Input
562
+ }], caseDefinitionKey: [{
563
+ type: Input
564
+ }], processVariables: [{
565
+ type: Input
566
+ }], mappingChange: [{
567
+ type: Output
568
+ }], requiredFieldsStatus: [{
569
+ type: Output
570
+ }] } });
571
+
572
+ class GenerateDocumentConfigurationComponent {
573
+ epistolaPluginService;
574
+ processLinkStateService;
575
+ // Required inputs from FunctionConfigurationComponent
576
+ save$;
577
+ disabled$;
578
+ pluginId;
579
+ prefillConfiguration$;
580
+ // Optional inputs from FunctionConfigurationComponent
581
+ selectedPluginConfigurationData$;
582
+ context$;
583
+ valid = new EventEmitter();
584
+ configuration = new EventEmitter();
585
+ // Template options loaded from API
586
+ templateOptions$ = new BehaviorSubject([]);
587
+ templatesLoading$ = new BehaviorSubject(false);
588
+ // Variant options loaded based on selected template
589
+ variantOptions$ = new BehaviorSubject([]);
590
+ variantsLoading$ = new BehaviorSubject(false);
591
+ // Environment options loaded from API
592
+ environmentOptions$ = new BehaviorSubject([]);
593
+ environmentsLoading$ = new BehaviorSubject(false);
594
+ // Template fields for data mapping
595
+ templateFields$ = new BehaviorSubject([]);
596
+ templateFieldsLoading$ = new BehaviorSubject(false);
597
+ // Current data mapping (nested structure mirroring template schema)
598
+ dataMapping$ = new BehaviorSubject({});
599
+ // Prefill data mapping observable for the tree
600
+ prefillDataMapping$;
601
+ outputFormatOptions = [
602
+ { id: 'PDF', text: 'PDF' },
603
+ { id: 'HTML', text: 'HTML' }
604
+ ];
605
+ // Show data mapping builder only when template is selected
606
+ selectedTemplateId$ = new BehaviorSubject('');
607
+ selectedVariantId$ = new BehaviorSubject('');
608
+ // Variant selection mode: 'explicit' (dropdown) or 'attributes' (key-value pairs)
609
+ variantSelectionMode = 'explicit';
610
+ // Variant attributes for attribute-based selection
611
+ variantAttributeEntries = [];
612
+ // Case definition key from context (for ValuePathSelector)
613
+ caseDefinitionKey = null;
614
+ // Discovered process variables
615
+ processVariables = [];
616
+ // Required fields status
617
+ requiredFieldsStatus = { mapped: 0, total: 0 };
618
+ destroy$ = new Subject();
619
+ saveSubscription;
620
+ formValue$ = new BehaviorSubject(null);
621
+ valid$ = new BehaviorSubject(false);
622
+ pluginConfigurationId$ = new BehaviorSubject('');
623
+ constructor(epistolaPluginService, processLinkStateService) {
624
+ this.epistolaPluginService = epistolaPluginService;
625
+ this.processLinkStateService = processLinkStateService;
626
+ }
627
+ ngOnInit() {
628
+ this.initContext();
629
+ this.initPrefillDataMapping();
630
+ this.initPluginConfiguration();
631
+ this.initTemplatesLoading();
632
+ this.initEnvironmentsLoading();
633
+ this.initVariantsLoading();
634
+ this.initTemplateFieldsLoading();
635
+ this.openSaveSubscription();
636
+ }
637
+ ngOnDestroy() {
638
+ this.destroy$.next();
639
+ this.destroy$.complete();
640
+ this.saveSubscription?.unsubscribe();
641
+ }
642
+ formValueChange(formOutput) {
643
+ const formValue = formOutput;
644
+ this.formValue$.next(formValue);
645
+ // Update selected template if changed (also clears variant selection)
646
+ if (formValue.templateId && formValue.templateId !== this.selectedTemplateId$.getValue()) {
647
+ this.selectedTemplateId$.next(formValue.templateId);
648
+ this.selectedVariantId$.next('');
649
+ }
650
+ // Update selected variant if changed
651
+ if (formValue.variantId && formValue.variantId !== this.selectedVariantId$.getValue()) {
652
+ this.selectedVariantId$.next(formValue.variantId);
653
+ }
654
+ this.handleValid(formValue);
655
+ }
656
+ onDataMappingChange(mapping) {
657
+ this.dataMapping$.next(mapping);
658
+ // Re-validate when data mapping changes
659
+ const currentFormValue = this.formValue$.getValue();
660
+ if (currentFormValue) {
661
+ this.handleValid(currentFormValue);
662
+ }
663
+ }
664
+ onRequiredFieldsStatusChange(status) {
665
+ this.requiredFieldsStatus = status;
666
+ // Re-validate when required fields status changes
667
+ const currentFormValue = this.formValue$.getValue();
668
+ if (currentFormValue) {
669
+ this.handleValid(currentFormValue);
670
+ }
671
+ }
672
+ onVariantSelectionModeChange(mode) {
673
+ this.variantSelectionMode = mode;
674
+ if (mode === 'attributes' && this.variantAttributeEntries.length === 0) {
675
+ this.variantAttributeEntries = [{ key: '', value: '' }];
676
+ }
677
+ // Re-validate
678
+ const currentFormValue = this.formValue$.getValue();
679
+ if (currentFormValue) {
680
+ this.handleValid(currentFormValue);
681
+ }
682
+ }
683
+ addAttributeEntry() {
684
+ this.variantAttributeEntries = [...this.variantAttributeEntries, { key: '', value: '' }];
685
+ this.revalidate();
686
+ }
687
+ removeAttributeEntry(index) {
688
+ this.variantAttributeEntries = this.variantAttributeEntries.filter((_, i) => i !== index);
689
+ this.revalidate();
690
+ }
691
+ onAttributeEntryChange() {
692
+ this.revalidate();
693
+ }
694
+ revalidate() {
695
+ const currentFormValue = this.formValue$.getValue();
696
+ if (currentFormValue) {
697
+ this.handleValid(currentFormValue);
698
+ }
699
+ }
700
+ formatAttributes(attributes) {
701
+ const entries = Object.entries(attributes || {});
702
+ if (entries.length === 0)
703
+ return '';
704
+ return ` (${entries.map(([k, v]) => `${k}=${v}`).join(', ')})`;
705
+ }
706
+ initContext() {
707
+ if (this.context$) {
708
+ this.context$.pipe(takeUntil(this.destroy$), filter(([context]) => context === 'case')).subscribe(([, params]) => {
709
+ this.caseDefinitionKey = params.caseDefinitionKey;
710
+ });
711
+ }
712
+ }
713
+ initPrefillDataMapping() {
714
+ if (this.prefillConfiguration$) {
715
+ this.prefillDataMapping$ = this.prefillConfiguration$.pipe(map(config => config?.dataMapping || {}));
716
+ // Also set initial selected template, variant mode, and variant
717
+ this.prefillConfiguration$.pipe(takeUntil(this.destroy$), filter(config => !!config?.templateId)).subscribe(config => {
718
+ this.selectedTemplateId$.next(config.templateId);
719
+ if (config.variantAttributes && Object.keys(config.variantAttributes).length > 0) {
720
+ this.variantSelectionMode = 'attributes';
721
+ this.variantAttributeEntries = Object.entries(config.variantAttributes)
722
+ .map(([key, value]) => ({ key, value }));
723
+ }
724
+ else if (config.variantId) {
725
+ this.variantSelectionMode = 'explicit';
726
+ this.selectedVariantId$.next(config.variantId);
727
+ }
728
+ if (config.dataMapping) {
729
+ this.dataMapping$.next(config.dataMapping);
730
+ }
731
+ });
732
+ }
733
+ else {
734
+ this.prefillDataMapping$ = new BehaviorSubject({}).asObservable();
735
+ }
736
+ }
737
+ initPluginConfiguration() {
738
+ const sources = [];
739
+ // Create mode: framework emits when user selects a plugin configuration
740
+ if (this.selectedPluginConfigurationData$) {
741
+ sources.push(this.selectedPluginConfigurationData$.pipe(filter(config => !!config?.configurationId), map(config => config.configurationId)));
742
+ }
743
+ // Edit mode: read pluginConfigurationId from the ProcessLink entity
744
+ // (selectedPluginConfigurationData$ does not emit in edit mode)
745
+ sources.push(this.processLinkStateService.selectedProcessLink$.pipe(filter(processLink => !!processLink?.pluginConfigurationId), map(processLink => processLink.pluginConfigurationId)));
746
+ merge(...sources).pipe(takeUntil(this.destroy$)).subscribe(configurationId => {
747
+ this.pluginConfigurationId$.next(configurationId);
748
+ });
749
+ }
750
+ initTemplatesLoading() {
751
+ this.pluginConfigurationId$.pipe(takeUntil(this.destroy$), filter(id => !!id)).subscribe(configurationId => {
752
+ this.templatesLoading$.next(true);
753
+ this.epistolaPluginService.getTemplates(configurationId).pipe(takeUntil(this.destroy$), catchError(() => of([]))).subscribe(templates => {
754
+ const options = templates.map(t => ({
755
+ id: t.id,
756
+ text: t.name
757
+ }));
758
+ this.templateOptions$.next(options);
759
+ this.templatesLoading$.next(false);
760
+ });
761
+ });
762
+ }
763
+ initEnvironmentsLoading() {
764
+ this.pluginConfigurationId$.pipe(takeUntil(this.destroy$), filter(id => !!id)).subscribe(configurationId => {
765
+ this.environmentsLoading$.next(true);
766
+ this.epistolaPluginService.getEnvironments(configurationId).pipe(takeUntil(this.destroy$), catchError(() => of([]))).subscribe(environments => {
767
+ const options = environments.map(e => ({
768
+ id: e.id,
769
+ text: e.name
770
+ }));
771
+ this.environmentOptions$.next(options);
772
+ this.environmentsLoading$.next(false);
773
+ });
774
+ });
775
+ }
776
+ initVariantsLoading() {
777
+ combineLatest([
778
+ this.pluginConfigurationId$,
779
+ this.selectedTemplateId$
780
+ ]).pipe(takeUntil(this.destroy$), filter(([configId, templateId]) => !!configId && !!templateId)).subscribe(([configurationId, templateId]) => {
781
+ this.variantsLoading$.next(true);
782
+ this.epistolaPluginService.getVariants(configurationId, templateId).pipe(takeUntil(this.destroy$), catchError(() => of([]))).subscribe(variants => {
783
+ const options = variants.map(v => ({
784
+ id: v.id,
785
+ text: v.name + this.formatAttributes(v.attributes)
786
+ }));
787
+ this.variantOptions$.next(options);
788
+ this.variantsLoading$.next(false);
789
+ });
790
+ });
791
+ }
792
+ initTemplateFieldsLoading() {
793
+ combineLatest([
794
+ this.pluginConfigurationId$,
795
+ this.selectedTemplateId$
796
+ ]).pipe(takeUntil(this.destroy$), filter(([configId, templateId]) => !!configId && !!templateId)).subscribe(([configurationId, templateId]) => {
797
+ this.templateFieldsLoading$.next(true);
798
+ this.epistolaPluginService.getTemplateDetails(configurationId, templateId).pipe(takeUntil(this.destroy$), catchError(() => of({ fields: [] }))).subscribe(details => {
799
+ this.templateFields$.next(details.fields || []);
800
+ this.templateFieldsLoading$.next(false);
801
+ });
802
+ // Also load process variables if we have a case context
803
+ this.loadProcessVariables();
804
+ });
805
+ }
806
+ loadProcessVariables() {
807
+ // Try to discover process variables (best-effort, may not always have context)
808
+ if (this.caseDefinitionKey) {
809
+ this.epistolaPluginService.getProcessVariables(this.caseDefinitionKey).pipe(takeUntil(this.destroy$), catchError(() => of([]))).subscribe(variables => {
810
+ this.processVariables = variables;
811
+ });
812
+ }
813
+ }
814
+ handleValid(formValue) {
815
+ const baseComplete = !!(formValue?.templateId &&
816
+ formValue?.outputFormat &&
817
+ formValue?.filename &&
818
+ formValue?.resultProcessVariable);
819
+ // Variant selection: if attribute mode is used, all entries must have both key and value filled.
820
+ // Neither variant nor attributes are required — omitting both uses the template's default variant.
821
+ let variantValid = true;
822
+ if (this.variantSelectionMode === 'attributes' && this.variantAttributeEntries.length > 0) {
823
+ variantValid = this.variantAttributeEntries.every(e => !!e.key && !!e.value);
824
+ }
825
+ // Check if all required template fields are mapped
826
+ const requiredFieldsMapped = this.requiredFieldsStatus.total === 0 ||
827
+ this.requiredFieldsStatus.mapped === this.requiredFieldsStatus.total;
828
+ const valid = baseComplete && variantValid && requiredFieldsMapped;
829
+ this.valid$.next(valid);
830
+ this.valid.emit(valid);
831
+ }
832
+ openSaveSubscription() {
833
+ this.saveSubscription = this.save$?.subscribe(() => {
834
+ combineLatest([this.formValue$, this.valid$, this.dataMapping$])
835
+ .pipe(take$1(1))
836
+ .subscribe(([formValue, valid, dataMapping]) => {
837
+ if (valid && formValue) {
838
+ const config = {
839
+ templateId: formValue.templateId,
840
+ environmentId: formValue.environmentId || undefined,
841
+ dataMapping: dataMapping,
842
+ outputFormat: formValue.outputFormat,
843
+ filename: formValue.filename,
844
+ correlationId: formValue.correlationId || undefined,
845
+ resultProcessVariable: formValue.resultProcessVariable
846
+ };
847
+ if (this.variantSelectionMode === 'explicit') {
848
+ config.variantId = formValue.variantId;
849
+ }
850
+ else {
851
+ config.variantAttributes = {};
852
+ for (const entry of this.variantAttributeEntries) {
853
+ if (entry.key && entry.value) {
854
+ config.variantAttributes[entry.key] = entry.value;
855
+ }
856
+ }
857
+ }
858
+ this.configuration.emit(config);
859
+ }
860
+ });
861
+ });
862
+ }
863
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: GenerateDocumentConfigurationComponent, deps: [{ token: EpistolaPluginService }, { token: i2$2.ProcessLinkStateService }], target: i0.ɵɵFactoryTarget.Component });
864
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: GenerateDocumentConfigurationComponent, isStandalone: true, selector: "epistola-generate-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$", selectedPluginConfigurationData$: "selectedPluginConfigurationData$", context$: "context$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n templateOptions: templateOptions$ | async,\n templatesLoading: templatesLoading$ | async,\n variantOptions: variantOptions$ | async,\n variantsLoading: variantsLoading$ | async,\n environmentOptions: environmentOptions$ | async,\n environmentsLoading: environmentsLoading$ | async,\n templateFieldsLoading: templateFieldsLoading$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templateOptions\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templatesLoading\"\n [required]=\"true\"\n [loading]=\"obs.templatesLoading\"\n >\n </v-select>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{ 'variantSelectionMode' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByVariant' | pluginTranslate: pluginId | async }}</button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByAttributes' | pluginTranslate: pluginId | async }}</button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variantOptions\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variantsLoading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variantsLoading\"\n >\n </v-select>\n\n <!-- Attribute-based variant selection -->\n <div *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\" class=\"variant-attributes-section\">\n <label class=\"variant-attributes-label\">{{ 'variantAttributes' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-attributes-list\">\n <div *ngFor=\"let entry of variantAttributeEntries; let i = index\" class=\"variant-attribute-row\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >&times;</button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >+ {{ 'addAttribute' | pluginTranslate: pluginId | async }}</button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environmentOptions\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environmentsLoading\"\n [required]=\"false\"\n [loading]=\"obs.environmentsLoading\"\n >\n </v-select>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields$]=\"templateFields$\"\n [disabled$]=\"disabled$\"\n [prefillMapping$]=\"prefillDataMapping$\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n (mappingChange)=\"onDataMappingChange($event)\"\n (requiredFieldsStatus)=\"onRequiredFieldsStatusChange($event)\"\n></epistola-data-mapping-tree>\n\n<div class=\"validation-summary\" *ngIf=\"(selectedTemplateId$ | async) && requiredFieldsStatus.total > 0\">\n <span *ngIf=\"requiredFieldsStatus.mapped === requiredFieldsStatus.total\" class=\"validation-complete\">\n {{ 'requiredFieldsComplete' | pluginTranslate: pluginId | async }}\n </span>\n <span *ngIf=\"requiredFieldsStatus.mapped < requiredFieldsStatus.total\" class=\"validation-incomplete\">\n {{ requiredFieldsStatus.mapped }} / {{ requiredFieldsStatus.total }}\n {{ 'validationSummary' | pluginTranslate: pluginId | async }}\n </span>\n</div>\n", styles: [".validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.SelectComponent, selector: "v-select", inputs: ["items", "defaultSelection", "defaultSelectionId", "defaultSelectionIds", "disabled", "dropUp", "invalid", "multiple", "margin", "widthInPx", "notFoundText", "clearAllText", "clearText", "clearable", "name", "title", "titleTranslationKey", "clearSelectionSubject$", "tooltip", "required", "loading", "loadingText", "placeholder", "smallMargin", "carbonTheme", "appendInline", "dataTestId"], outputs: ["selectedChange"] }, { kind: "component", type: DataMappingTreeComponent, selector: "epistola-data-mapping-tree", inputs: ["pluginId", "templateFields$", "prefillMapping$", "disabled$", "caseDefinitionKey", "processVariables"], outputs: ["mappingChange", "requiredFieldsStatus"] }] });
865
+ }
866
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: GenerateDocumentConfigurationComponent, decorators: [{
867
+ type: Component,
868
+ args: [{ selector: 'epistola-generate-document-configuration', standalone: true, imports: [
869
+ CommonModule,
870
+ FormsModule,
871
+ PluginTranslatePipeModule,
872
+ FormModule,
873
+ InputModule,
874
+ SelectModule,
875
+ DataMappingTreeComponent
876
+ ], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n templateOptions: templateOptions$ | async,\n templatesLoading: templatesLoading$ | async,\n variantOptions: variantOptions$ | async,\n variantsLoading: variantsLoading$ | async,\n environmentOptions: environmentOptions$ | async,\n environmentsLoading: environmentsLoading$ | async,\n templateFieldsLoading: templateFieldsLoading$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templateOptions\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templatesLoading\"\n [required]=\"true\"\n [loading]=\"obs.templatesLoading\"\n >\n </v-select>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{ 'variantSelectionMode' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByVariant' | pluginTranslate: pluginId | async }}</button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByAttributes' | pluginTranslate: pluginId | async }}</button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variantOptions\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variantsLoading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variantsLoading\"\n >\n </v-select>\n\n <!-- Attribute-based variant selection -->\n <div *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\" class=\"variant-attributes-section\">\n <label class=\"variant-attributes-label\">{{ 'variantAttributes' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-attributes-list\">\n <div *ngFor=\"let entry of variantAttributeEntries; let i = index\" class=\"variant-attribute-row\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >&times;</button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >+ {{ 'addAttribute' | pluginTranslate: pluginId | async }}</button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environmentOptions\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environmentsLoading\"\n [required]=\"false\"\n [loading]=\"obs.environmentsLoading\"\n >\n </v-select>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields$]=\"templateFields$\"\n [disabled$]=\"disabled$\"\n [prefillMapping$]=\"prefillDataMapping$\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n (mappingChange)=\"onDataMappingChange($event)\"\n (requiredFieldsStatus)=\"onRequiredFieldsStatusChange($event)\"\n></epistola-data-mapping-tree>\n\n<div class=\"validation-summary\" *ngIf=\"(selectedTemplateId$ | async) && requiredFieldsStatus.total > 0\">\n <span *ngIf=\"requiredFieldsStatus.mapped === requiredFieldsStatus.total\" class=\"validation-complete\">\n {{ 'requiredFieldsComplete' | pluginTranslate: pluginId | async }}\n </span>\n <span *ngIf=\"requiredFieldsStatus.mapped < requiredFieldsStatus.total\" class=\"validation-incomplete\">\n {{ requiredFieldsStatus.mapped }} / {{ requiredFieldsStatus.total }}\n {{ 'validationSummary' | pluginTranslate: pluginId | async }}\n </span>\n</div>\n", styles: [".validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}\n"] }]
877
+ }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$2.ProcessLinkStateService }], propDecorators: { save$: [{
878
+ type: Input
879
+ }], disabled$: [{
880
+ type: Input
881
+ }], pluginId: [{
882
+ type: Input
883
+ }], prefillConfiguration$: [{
884
+ type: Input
885
+ }], selectedPluginConfigurationData$: [{
886
+ type: Input
887
+ }], context$: [{
888
+ type: Input
889
+ }], valid: [{
890
+ type: Output
891
+ }], configuration: [{
892
+ type: Output
893
+ }] } });
894
+
895
+ class CheckJobStatusConfigurationComponent {
896
+ save$;
897
+ disabled$;
898
+ pluginId;
899
+ prefillConfiguration$;
900
+ valid = new EventEmitter();
901
+ configuration = new EventEmitter();
902
+ saveSubscription;
903
+ formValue$ = new BehaviorSubject(null);
904
+ valid$ = new BehaviorSubject(false);
905
+ safeDisabled$;
906
+ ngOnInit() {
907
+ this.safeDisabled$ = this.disabled$.pipe(startWith(true), delay(0));
908
+ this.openSaveSubscription();
909
+ }
910
+ ngOnDestroy() {
911
+ this.saveSubscription?.unsubscribe();
912
+ }
913
+ formValueChange(formOutput) {
914
+ const formValue = formOutput;
915
+ this.formValue$.next(formValue);
916
+ this.handleValid(formValue);
917
+ }
918
+ handleValid(formValue) {
919
+ const valid = !!(formValue?.requestIdVariable &&
920
+ formValue?.statusVariable);
921
+ this.valid$.next(valid);
922
+ this.valid.emit(valid);
923
+ }
924
+ openSaveSubscription() {
925
+ this.saveSubscription = this.save$?.subscribe(() => {
926
+ combineLatest([this.formValue$, this.valid$])
927
+ .pipe(take(1))
928
+ .subscribe(([formValue, valid]) => {
929
+ if (valid && formValue) {
930
+ this.configuration.emit(formValue);
931
+ }
932
+ });
933
+ });
934
+ }
935
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CheckJobStatusConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
936
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: CheckJobStatusConfigurationComponent, isStandalone: true, selector: "epistola-check-job-status-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"requestIdVariable\"\n [title]=\"'requestIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'requestIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.requestIdVariable || 'epistolaRequestId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"statusVariable\"\n [title]=\"'statusVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'statusVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.statusVariable || 'epistolaStatus'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.documentIdVariable || 'epistolaDocumentId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"errorMessageVariable\"\n [title]=\"'errorMessageVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'errorMessageVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.errorMessageVariable || 'epistolaErrorMessage'\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
937
+ }
938
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CheckJobStatusConfigurationComponent, decorators: [{
939
+ type: Component,
940
+ args: [{ selector: 'epistola-check-job-status-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"requestIdVariable\"\n [title]=\"'requestIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'requestIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.requestIdVariable || 'epistolaRequestId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"statusVariable\"\n [title]=\"'statusVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'statusVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.statusVariable || 'epistolaStatus'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.documentIdVariable || 'epistolaDocumentId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"errorMessageVariable\"\n [title]=\"'errorMessageVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'errorMessageVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.errorMessageVariable || 'epistolaErrorMessage'\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n" }]
941
+ }], propDecorators: { save$: [{
942
+ type: Input
943
+ }], disabled$: [{
944
+ type: Input
945
+ }], pluginId: [{
946
+ type: Input
947
+ }], prefillConfiguration$: [{
948
+ type: Input
949
+ }], valid: [{
950
+ type: Output
951
+ }], configuration: [{
952
+ type: Output
953
+ }] } });
954
+
955
+ class DownloadDocumentConfigurationComponent {
956
+ save$;
957
+ disabled$;
958
+ pluginId;
959
+ prefillConfiguration$;
960
+ valid = new EventEmitter();
961
+ configuration = new EventEmitter();
962
+ saveSubscription;
963
+ formValue$ = new BehaviorSubject(null);
964
+ valid$ = new BehaviorSubject(false);
965
+ safeDisabled$;
966
+ ngOnInit() {
967
+ this.safeDisabled$ = this.disabled$.pipe(startWith(true), delay(0));
968
+ this.openSaveSubscription();
969
+ }
970
+ ngOnDestroy() {
971
+ this.saveSubscription?.unsubscribe();
972
+ }
973
+ formValueChange(formOutput) {
974
+ const formValue = formOutput;
975
+ this.formValue$.next(formValue);
976
+ this.handleValid(formValue);
977
+ }
978
+ handleValid(formValue) {
979
+ const valid = !!(formValue?.documentIdVariable &&
980
+ formValue?.contentVariable);
981
+ this.valid$.next(valid);
982
+ this.valid.emit(valid);
983
+ }
984
+ openSaveSubscription() {
985
+ this.saveSubscription = this.save$?.subscribe(() => {
986
+ combineLatest([this.formValue$, this.valid$])
987
+ .pipe(take(1))
988
+ .subscribe(([formValue, valid]) => {
989
+ if (valid && formValue) {
990
+ this.configuration.emit(formValue);
991
+ }
992
+ });
993
+ });
994
+ }
995
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DownloadDocumentConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
996
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: DownloadDocumentConfigurationComponent, isStandalone: true, selector: "epistola-download-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.documentIdVariable || 'epistolaDocumentId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"contentVariable\"\n [title]=\"'contentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'contentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.contentVariable || 'documentContent'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
997
+ }
998
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DownloadDocumentConfigurationComponent, decorators: [{
999
+ type: Component,
1000
+ args: [{ selector: 'epistola-download-document-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.documentIdVariable || 'epistolaDocumentId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"contentVariable\"\n [title]=\"'contentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'contentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.contentVariable || 'documentContent'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n" }]
1001
+ }], propDecorators: { save$: [{
1002
+ type: Input
1003
+ }], disabled$: [{
1004
+ type: Input
1005
+ }], pluginId: [{
1006
+ type: Input
1007
+ }], prefillConfiguration$: [{
1008
+ type: Input
1009
+ }], valid: [{
1010
+ type: Output
1011
+ }], configuration: [{
1012
+ type: Output
1013
+ }] } });
1014
+
1015
+ class EpistolaDownloadComponent {
1016
+ http;
1017
+ value;
1018
+ valueChange = new EventEmitter();
1019
+ disabled = false;
1020
+ filename = 'document.pdf';
1021
+ label = 'Download PDF';
1022
+ downloading = false;
1023
+ error = null;
1024
+ get buttonLabel() {
1025
+ return this.label || 'Download PDF';
1026
+ }
1027
+ constructor(http) {
1028
+ this.http = http;
1029
+ }
1030
+ hasRequiredData() {
1031
+ return !!(this.value?.documentId && this.value?.tenantId);
1032
+ }
1033
+ download() {
1034
+ if (!this.hasRequiredData() || this.downloading) {
1035
+ return;
1036
+ }
1037
+ this.downloading = true;
1038
+ this.error = null;
1039
+ const { documentId, tenantId } = this.value;
1040
+ const url = `/api/v1/plugin/epistola/documents/${encodeURIComponent(documentId)}/download`
1041
+ + `?tenantId=${encodeURIComponent(tenantId)}`
1042
+ + `&filename=${encodeURIComponent(this.filename)}`;
1043
+ this.http.get(url, { responseType: 'blob' }).subscribe({
1044
+ next: (blob) => {
1045
+ const objectUrl = URL.createObjectURL(blob);
1046
+ const anchor = document.createElement('a');
1047
+ anchor.href = objectUrl;
1048
+ anchor.download = this.filename;
1049
+ anchor.click();
1050
+ URL.revokeObjectURL(objectUrl);
1051
+ this.downloading = false;
1052
+ },
1053
+ error: (err) => {
1054
+ console.error('Download failed', err);
1055
+ this.error = 'Download mislukt. Probeer opnieuw.';
1056
+ this.downloading = false;
1057
+ },
1058
+ });
1059
+ }
1060
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaDownloadComponent, deps: [{ token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
1061
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: EpistolaDownloadComponent, isStandalone: true, selector: "epistola-download-component", inputs: { value: "value", disabled: "disabled", filename: "filename", label: "label" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: `
1062
+ <button
1063
+ type="button"
1064
+ class="btn btn-outline-primary"
1065
+ [disabled]="disabled || downloading || !hasRequiredData()"
1066
+ (click)="download()"
1067
+ >
1068
+ <i class="mdi mdi-download mr-1"></i>
1069
+ {{ downloading ? 'Downloading...' : buttonLabel }}
1070
+ </button>
1071
+ <span *ngIf="error" class="text-danger ml-2">{{ error }}</span>
1072
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1073
+ }
1074
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaDownloadComponent, decorators: [{
1075
+ type: Component,
1076
+ args: [{
1077
+ standalone: true,
1078
+ imports: [CommonModule],
1079
+ selector: 'epistola-download-component',
1080
+ template: `
1081
+ <button
1082
+ type="button"
1083
+ class="btn btn-outline-primary"
1084
+ [disabled]="disabled || downloading || !hasRequiredData()"
1085
+ (click)="download()"
1086
+ >
1087
+ <i class="mdi mdi-download mr-1"></i>
1088
+ {{ downloading ? 'Downloading...' : buttonLabel }}
1089
+ </button>
1090
+ <span *ngIf="error" class="text-danger ml-2">{{ error }}</span>
1091
+ `,
1092
+ }]
1093
+ }], ctorParameters: () => [{ type: i1.HttpClient }], propDecorators: { value: [{
1094
+ type: Input
1095
+ }], valueChange: [{
1096
+ type: Output
1097
+ }], disabled: [{
1098
+ type: Input
1099
+ }], filename: [{
1100
+ type: Input
1101
+ }], label: [{
1102
+ type: Input
1103
+ }] } });
1104
+
1105
+ class EpistolaPluginModule {
1106
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1107
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginModule, imports: [CommonModule,
1108
+ HttpClientModule,
1109
+ PluginTranslatePipeModule,
1110
+ FormModule,
1111
+ InputModule,
1112
+ SelectModule,
1113
+ EpistolaConfigurationComponent,
1114
+ GenerateDocumentConfigurationComponent,
1115
+ CheckJobStatusConfigurationComponent,
1116
+ DownloadDocumentConfigurationComponent,
1117
+ DataMappingTreeComponent,
1118
+ FieldTreeComponent,
1119
+ EpistolaDownloadComponent], exports: [EpistolaConfigurationComponent,
1120
+ GenerateDocumentConfigurationComponent,
1121
+ CheckJobStatusConfigurationComponent,
1122
+ DownloadDocumentConfigurationComponent,
1123
+ DataMappingTreeComponent,
1124
+ FieldTreeComponent,
1125
+ EpistolaDownloadComponent] });
1126
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginModule, providers: [
1127
+ EpistolaPluginService
1128
+ ], imports: [CommonModule,
1129
+ HttpClientModule,
1130
+ PluginTranslatePipeModule,
1131
+ FormModule,
1132
+ InputModule,
1133
+ SelectModule,
1134
+ EpistolaConfigurationComponent,
1135
+ GenerateDocumentConfigurationComponent,
1136
+ CheckJobStatusConfigurationComponent,
1137
+ DownloadDocumentConfigurationComponent,
1138
+ DataMappingTreeComponent,
1139
+ FieldTreeComponent,
1140
+ EpistolaDownloadComponent] });
1141
+ }
1142
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginModule, decorators: [{
1143
+ type: NgModule,
1144
+ args: [{
1145
+ imports: [
1146
+ CommonModule,
1147
+ HttpClientModule,
1148
+ PluginTranslatePipeModule,
1149
+ FormModule,
1150
+ InputModule,
1151
+ SelectModule,
1152
+ EpistolaConfigurationComponent,
1153
+ GenerateDocumentConfigurationComponent,
1154
+ CheckJobStatusConfigurationComponent,
1155
+ DownloadDocumentConfigurationComponent,
1156
+ DataMappingTreeComponent,
1157
+ FieldTreeComponent,
1158
+ EpistolaDownloadComponent
1159
+ ],
1160
+ exports: [
1161
+ EpistolaConfigurationComponent,
1162
+ GenerateDocumentConfigurationComponent,
1163
+ CheckJobStatusConfigurationComponent,
1164
+ DownloadDocumentConfigurationComponent,
1165
+ DataMappingTreeComponent,
1166
+ FieldTreeComponent,
1167
+ EpistolaDownloadComponent
1168
+ ],
1169
+ providers: [
1170
+ EpistolaPluginService
1171
+ ]
1172
+ }]
1173
+ }] });
1174
+
1175
+ // Placeholder logo - a simple document icon in SVG format, base64 encoded
1176
+ // TODO: Replace with actual Epistola logo
1177
+ const EPISTOLA_PLUGIN_LOGO_BASE64 = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzMzNjZjYyI+PHBhdGggZD0iTTE0IDJINmMtMS4xIDAtMiAuOS0yIDJ2MTZjMCAxLjEuOSAyIDIgMmgxMmMxLjEgMCAyLS45IDItMlY4bC02LTZ6bTQgMThINlY0aDd2NWg1djExeiIvPjxwYXRoIGQ9Ik04IDEyaDh2Mkg4em0wIDRoOHYtMkg4em0wLThWNmg0djJ6Ii8+PC9zdmc+';
1178
+
1179
+ const epistolaPluginSpecification = {
1180
+ // Must match backend @Plugin(key = "epistola")
1181
+ pluginId: 'epistola',
1182
+ // Component for plugin-level configuration (tenantId)
1183
+ pluginConfigurationComponent: EpistolaConfigurationComponent,
1184
+ // Plugin logo
1185
+ pluginLogoBase64: EPISTOLA_PLUGIN_LOGO_BASE64,
1186
+ // Map action keys to their configuration components
1187
+ functionConfigurationComponents: {
1188
+ 'generate-document': GenerateDocumentConfigurationComponent,
1189
+ 'check-job-status': CheckJobStatusConfigurationComponent,
1190
+ 'download-document': DownloadDocumentConfigurationComponent,
1191
+ },
1192
+ // Translations
1193
+ pluginTranslations: {
1194
+ nl: {
1195
+ title: 'Epistola Document Suite',
1196
+ description: 'Documentgeneratie met Epistola',
1197
+ configurationTitle: 'Configuratienaam',
1198
+ baseUrl: 'Base URL',
1199
+ baseUrlTooltip: 'De basis URL van de Epistola API (bijv. https://api.epistola.app)',
1200
+ apiKey: 'API Key',
1201
+ apiKeyTooltip: 'De API sleutel voor authenticatie met Epistola',
1202
+ tenantId: 'Tenant ID',
1203
+ tenantIdTooltip: 'De tenant slug in Epistola (3-63 tekens, alleen kleine letters, cijfers en koppeltekens, bijv. "mijn-tenant")',
1204
+ defaultEnvironmentId: 'Standaard Omgeving',
1205
+ defaultEnvironmentIdTooltip: 'De standaard omgeving voor documentgeneratie (3-30 tekens, alleen kleine letters, cijfers en koppeltekens, bijv. "productie")',
1206
+ templateSyncEnabled: 'Template synchronisatie',
1207
+ templateSyncEnabledTooltip: 'Synchroniseer template definities automatisch van het classpath naar Epistola bij het opstarten',
1208
+ 'generate-document': 'Genereer Document',
1209
+ templateId: 'Template',
1210
+ templateIdTooltip: 'Selecteer het template dat gebruikt wordt voor documentgeneratie',
1211
+ variantId: 'Variant',
1212
+ variantIdTooltip: 'Selecteer de template variant',
1213
+ variantSelectionMode: 'Variant selectie',
1214
+ variantSelectionModeTooltip: 'Kies hoe de variant geselecteerd wordt',
1215
+ selectByVariant: 'Selecteer variant',
1216
+ selectByAttributes: 'Selecteer op kenmerken',
1217
+ variantAttributes: 'Variant kenmerken',
1218
+ variantAttributesTooltip: 'Kenmerken voor automatische variant selectie. Waarden kunnen expressies zijn (doc:, pv:, case:).',
1219
+ attributeKey: 'Kenmerk',
1220
+ attributeValue: 'Waarde',
1221
+ addAttribute: 'Kenmerk toevoegen',
1222
+ removeAttribute: 'Kenmerk verwijderen',
1223
+ environmentId: 'Omgeving',
1224
+ environmentIdTooltip: 'Selecteer de doelomgeving (optioneel)',
1225
+ correlationId: 'Correlatie ID',
1226
+ correlationIdTooltip: 'Een optioneel correlatie ID voor het traceren van dit verzoek',
1227
+ dataMapping: 'Data Mapping',
1228
+ dataMappingTooltip: 'Koppeling van template velden naar data bronnen (doc:, pv:, case:)',
1229
+ outputFormat: 'Uitvoerformaat',
1230
+ outputFormatTooltip: 'Het gewenste formaat van het gegenereerde document',
1231
+ filename: 'Bestandsnaam',
1232
+ filenameTooltip: 'De bestandsnaam voor het gegenereerde document',
1233
+ resultProcessVariable: 'Resultaat Procesvariabele',
1234
+ resultProcessVariableTooltip: 'De naam van de procesvariabele waarin het request ID wordt opgeslagen',
1235
+ pdf: 'PDF',
1236
+ html: 'HTML',
1237
+ // Data mapping builder translations
1238
+ dataMappingTitle: 'Data Mapping',
1239
+ dataMappingDescription: 'Koppel template velden aan Valtimo data bronnen',
1240
+ templateField: 'Template veld',
1241
+ dataSource: 'Data bron',
1242
+ addMapping: 'Mapping toevoegen',
1243
+ noMappings: 'Nog geen mappings toegevoegd. Klik op "Mapping toevoegen" om te beginnen.',
1244
+ documentFields: 'Document velden',
1245
+ processVariables: 'Procesvariabelen',
1246
+ caseProperties: 'Zaak eigenschappen',
1247
+ sourceType: 'Brontype',
1248
+ sourceTypeDocument: 'Document veld',
1249
+ sourceTypeProcessVariable: 'Procesvariabele',
1250
+ sourceTypeManual: 'Handmatig',
1251
+ requiredFieldsMissing: 'Niet alle verplichte velden zijn gekoppeld',
1252
+ requiredFieldsComplete: 'Alle verplichte velden zijn gekoppeld',
1253
+ validationSummary: 'verplichte velden gekoppeld',
1254
+ fieldRequired: 'Verplicht',
1255
+ fieldOptional: 'Optioneel',
1256
+ mapCollectionTo: 'Koppel collectie aan',
1257
+ browseMode: 'Bladermodus',
1258
+ pvMode: 'Procesvariabele modus',
1259
+ pvPlaceholder: 'Naam procesvariabele',
1260
+ expressionMode: 'Expressiemodus',
1261
+ itemFieldMapping: 'Veldnamen per item koppelen',
1262
+ itemFieldMappingTitle: 'Veldkoppeling per item:',
1263
+ sourceFieldPlaceholder: 'Bronveldnaam',
1264
+ noTemplateFields: 'Geen template velden beschikbaar',
1265
+ // Check job status action
1266
+ 'check-job-status': 'Controleer Taakstatus',
1267
+ requestIdVariable: 'Request ID Variabele',
1268
+ requestIdVariableTooltip: 'Naam van de procesvariabele met het Epistola request ID',
1269
+ statusVariable: 'Status Variabele',
1270
+ statusVariableTooltip: 'Naam van de procesvariabele waarin de status wordt opgeslagen',
1271
+ documentIdVariable: 'Document ID Variabele',
1272
+ documentIdVariableTooltip: 'Naam van de procesvariabele waarin het document ID wordt opgeslagen (bij voltooiing)',
1273
+ errorMessageVariable: 'Foutmelding Variabele',
1274
+ errorMessageVariableTooltip: 'Naam van de procesvariabele waarin de foutmelding wordt opgeslagen (bij fout)',
1275
+ // Download document action
1276
+ 'download-document': 'Download Document',
1277
+ contentVariable: 'Inhoud Variabele',
1278
+ contentVariableTooltip: 'Naam van de procesvariabele waarin de documentinhoud (Base64) wordt opgeslagen'
1279
+ },
1280
+ en: {
1281
+ title: 'Epistola Document Suite',
1282
+ description: 'Document generation using Epistola',
1283
+ configurationTitle: 'Configuration name',
1284
+ baseUrl: 'Base URL',
1285
+ baseUrlTooltip: 'The base URL of the Epistola API (e.g. https://api.epistola.app)',
1286
+ apiKey: 'API Key',
1287
+ apiKeyTooltip: 'The API key for authentication with Epistola',
1288
+ tenantId: 'Tenant ID',
1289
+ tenantIdTooltip: 'The tenant slug in Epistola (3-63 chars, lowercase letters, digits and hyphens only, e.g. "my-tenant")',
1290
+ defaultEnvironmentId: 'Default Environment',
1291
+ defaultEnvironmentIdTooltip: 'The default environment for document generation (3-30 chars, lowercase letters, digits and hyphens only, e.g. "production")',
1292
+ templateSyncEnabled: 'Template sync',
1293
+ templateSyncEnabledTooltip: 'Automatically synchronize template definitions from classpath to Epistola on startup',
1294
+ 'generate-document': 'Generate Document',
1295
+ templateId: 'Template',
1296
+ templateIdTooltip: 'Select the template to use for document generation',
1297
+ variantId: 'Variant',
1298
+ variantIdTooltip: 'Select the template variant',
1299
+ variantSelectionMode: 'Variant selection',
1300
+ variantSelectionModeTooltip: 'Choose how the variant is selected',
1301
+ selectByVariant: 'Select variant',
1302
+ selectByAttributes: 'Select by attributes',
1303
+ variantAttributes: 'Variant attributes',
1304
+ variantAttributesTooltip: 'Attributes for automatic variant selection. Values can be expressions (doc:, pv:, case:).',
1305
+ attributeKey: 'Attribute',
1306
+ attributeValue: 'Value',
1307
+ addAttribute: 'Add attribute',
1308
+ removeAttribute: 'Remove attribute',
1309
+ environmentId: 'Environment',
1310
+ environmentIdTooltip: 'Select the target environment (optional)',
1311
+ correlationId: 'Correlation ID',
1312
+ correlationIdTooltip: 'An optional correlation ID for tracking this request',
1313
+ dataMapping: 'Data Mapping',
1314
+ dataMappingTooltip: 'Mapping of template fields to data sources (doc:, pv:, case:)',
1315
+ outputFormat: 'Output Format',
1316
+ outputFormatTooltip: 'The desired format of the generated document',
1317
+ filename: 'Filename',
1318
+ filenameTooltip: 'The filename for the generated document',
1319
+ resultProcessVariable: 'Result Process Variable',
1320
+ resultProcessVariableTooltip: 'The name of the process variable to store the request ID in',
1321
+ pdf: 'PDF',
1322
+ html: 'HTML',
1323
+ // Data mapping builder translations
1324
+ dataMappingTitle: 'Data Mapping',
1325
+ dataMappingDescription: 'Map template fields to Valtimo data sources',
1326
+ templateField: 'Template field',
1327
+ dataSource: 'Data source',
1328
+ addMapping: 'Add mapping',
1329
+ noMappings: 'No mappings added yet. Click "Add mapping" to start.',
1330
+ documentFields: 'Document fields',
1331
+ processVariables: 'Process variables',
1332
+ caseProperties: 'Case properties',
1333
+ sourceType: 'Source type',
1334
+ sourceTypeDocument: 'Document field',
1335
+ sourceTypeProcessVariable: 'Process variable',
1336
+ sourceTypeManual: 'Manual value',
1337
+ requiredFieldsMissing: 'Not all required fields are mapped',
1338
+ requiredFieldsComplete: 'All required fields are mapped',
1339
+ validationSummary: 'required fields mapped',
1340
+ fieldRequired: 'Required',
1341
+ fieldOptional: 'Optional',
1342
+ mapCollectionTo: 'Map collection to',
1343
+ browseMode: 'Browse mode',
1344
+ pvMode: 'Process variable mode',
1345
+ pvPlaceholder: 'Process variable name',
1346
+ expressionMode: 'Expression mode',
1347
+ itemFieldMapping: 'Map field names per item',
1348
+ itemFieldMappingTitle: 'Item field mapping:',
1349
+ sourceFieldPlaceholder: 'Source field name',
1350
+ noTemplateFields: 'No template fields available',
1351
+ // Check job status action
1352
+ 'check-job-status': 'Check Job Status',
1353
+ requestIdVariable: 'Request ID Variable',
1354
+ requestIdVariableTooltip: 'Name of the process variable containing the Epistola request ID',
1355
+ statusVariable: 'Status Variable',
1356
+ statusVariableTooltip: 'Name of the process variable to store the status in',
1357
+ documentIdVariable: 'Document ID Variable',
1358
+ documentIdVariableTooltip: 'Name of the process variable to store the document ID in (when completed)',
1359
+ errorMessageVariable: 'Error Message Variable',
1360
+ errorMessageVariableTooltip: 'Name of the process variable to store the error message in (when failed)',
1361
+ // Download document action
1362
+ 'download-document': 'Download Document',
1363
+ contentVariable: 'Content Variable',
1364
+ contentVariableTooltip: 'Name of the process variable to store the document content (Base64) in'
1365
+ }
1366
+ }
1367
+ };
1368
+
1369
+ const EPISTOLA_DOWNLOAD_OPTIONS = {
1370
+ type: 'epistola-download',
1371
+ selector: 'epistola-download-button',
1372
+ title: 'Epistola Download',
1373
+ group: 'basic',
1374
+ icon: 'download',
1375
+ emptyValue: null,
1376
+ fieldOptions: ['filename', 'label'],
1377
+ };
1378
+ function registerEpistolaDownloadComponent(injector) {
1379
+ if (!customElements.get(EPISTOLA_DOWNLOAD_OPTIONS.selector)) {
1380
+ registerCustomFormioComponent(EPISTOLA_DOWNLOAD_OPTIONS, EpistolaDownloadComponent, injector);
1381
+ }
1382
+ }
1383
+
1384
+ /*
1385
+ * Public API Surface of epistola plugin
1386
+ */
1387
+
1388
+ /**
1389
+ * Generated bundle index. Do not edit.
1390
+ */
1391
+
1392
+ export { CheckJobStatusConfigurationComponent, DataMappingTreeComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOWNLOAD_OPTIONS, EpistolaConfigurationComponent, EpistolaDownloadComponent, EpistolaPluginModule, EpistolaPluginService, FieldTreeComponent, GenerateDocumentConfigurationComponent, epistolaPluginSpecification, registerEpistolaDownloadComponent };
1393
+ //# sourceMappingURL=epistola.app-valtimo-plugin.mjs.map