@epistola.app/valtimo-plugin 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/fesm2022/epistola.app-valtimo-plugin.mjs +1622 -508
  2. package/fesm2022/epistola.app-valtimo-plugin.mjs.map +1 -1
  3. package/lib/components/epistola-admin-page/epistola-admin-page.component.d.ts +52 -0
  4. package/lib/components/expected-structure/expected-structure.component.d.ts +11 -0
  5. package/lib/components/generate-document-configuration/generate-document-configuration.component.d.ts +10 -3
  6. package/lib/components/jsonata-editor/jsonata-editor.component.d.ts +29 -0
  7. package/lib/components/mapping-builder/builder-field/builder-field.component.d.ts +21 -0
  8. package/lib/components/mapping-builder/mapping-builder.component.d.ts +37 -0
  9. package/lib/components/mapping-preview/mapping-preview.component.d.ts +26 -0
  10. package/lib/epistola-admin-routing.module.d.ts +7 -0
  11. package/lib/epistola.module.d.ts +11 -14
  12. package/lib/models/admin.d.ts +49 -0
  13. package/lib/models/config.d.ts +10 -1
  14. package/lib/models/expression.d.ts +13 -0
  15. package/lib/models/index.d.ts +2 -0
  16. package/lib/models/template.d.ts +0 -6
  17. package/lib/services/epistola-admin.service.d.ts +37 -0
  18. package/lib/services/epistola-menu.service.d.ts +13 -0
  19. package/lib/services/epistola-plugin.service.d.ts +11 -3
  20. package/lib/services/index.d.ts +2 -0
  21. package/lib/utils/jsonata-converter.d.ts +26 -0
  22. package/lib/utils/jsonata-monaco.d.ts +14 -0
  23. package/package.json +7 -5
  24. package/public_api.d.ts +4 -5
  25. package/lib/components/array-field/array-field.component.d.ts +0 -27
  26. package/lib/components/data-mapping-tree/data-mapping-tree.component.d.ts +0 -27
  27. package/lib/components/field-tree/field-tree.component.d.ts +0 -27
  28. package/lib/components/scalar-field/scalar-field.component.d.ts +0 -16
  29. package/lib/components/value-input/value-input.component.d.ts +0 -34
  30. package/lib/utils/template-field-utils.d.ts +0 -6
@@ -1,22 +1,31 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, EventEmitter, Output, Input, Component, ChangeDetectionStrategy, forwardRef, ENVIRONMENT_INITIALIZER, inject, Injector, NgModule } from '@angular/core';
2
+ import { Injectable, EventEmitter, Output, Input, Component, ChangeDetectionStrategy, NgModule, ENVIRONMENT_INITIALIZER, inject, Injector } from '@angular/core';
3
3
  import * as i1 from '@angular/common/http';
4
4
  import { HttpHeaders, HttpClientModule } from '@angular/common/http';
5
5
  import * as i2 from '@valtimo/shared';
6
+ import { ROLE_ADMIN } from '@valtimo/shared';
7
+ import { of, BehaviorSubject, combineLatest, take, Subject, debounceTime, takeUntil, merge } from 'rxjs';
8
+ import * as i3 from '@valtimo/components';
9
+ import { FormModule, InputModule, EditorModule, SelectModule, registerCustomFormioComponent } from '@valtimo/components';
6
10
  import * as i1$1 from '@angular/common';
7
11
  import { CommonModule } from '@angular/common';
8
12
  import * as i2$1 from '@valtimo/plugin';
9
13
  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, of, merge } from 'rxjs';
13
- import { startWith, delay, shareReplay, take as take$1, takeUntil, filter, map, distinctUntilChanged, tap, switchMap, catchError, debounceTime } from 'rxjs/operators';
14
+ import { startWith, delay, shareReplay, take as take$1, takeUntil as takeUntil$1, filter, map, distinctUntilChanged, tap, switchMap, catchError, debounceTime as debounceTime$1 } from 'rxjs/operators';
14
15
  import * as i4 from '@angular/forms';
15
16
  import { FormsModule } from '@angular/forms';
17
+ import * as _jsonata from 'jsonata';
16
18
  import * as i2$2 from '@valtimo/process-link';
17
19
  import * as i7 from '@formio/angular';
18
20
  import { FormioModule } from '@formio/angular';
19
21
  import * as i4$1 from '@angular/platform-browser';
22
+ import * as i2$3 from '@angular/router';
23
+ import { RouterModule } from '@angular/router';
24
+ import * as i5 from 'carbon-components-angular/tabs';
25
+ import { TabsModule } from 'carbon-components-angular/tabs';
26
+ import * as i6 from 'carbon-components-angular/tag';
27
+ import { TagModule } from 'carbon-components-angular/tag';
28
+ import { AuthGuardService } from '@valtimo/security';
20
29
 
21
30
  function initialResource(empty) {
22
31
  return { data: empty, loading: false, error: null };
@@ -31,6 +40,94 @@ function errorResource(current, error) {
31
40
  return { data: current, loading: false, error };
32
41
  }
33
42
 
43
+ /**
44
+ * Service for Epistola plugin administrative operations.
45
+ * Provides health checks, version info, and usage overview.
46
+ */
47
+ class EpistolaAdminService {
48
+ http;
49
+ configService;
50
+ apiEndpoint;
51
+ constructor(http, configService) {
52
+ this.http = http;
53
+ this.configService = configService;
54
+ this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola/admin`;
55
+ }
56
+ /**
57
+ * Check connectivity to Epistola for all plugin configurations.
58
+ */
59
+ getConnectionStatus() {
60
+ return this.http.get(`${this.apiEndpoint}/health`);
61
+ }
62
+ /**
63
+ * Get version information for the plugin and connected Epistola server.
64
+ */
65
+ getVersions() {
66
+ return this.http.get(`${this.apiEndpoint}/versions`);
67
+ }
68
+ /**
69
+ * Get an overview of all Epistola plugin usages across process definitions.
70
+ */
71
+ getPluginUsage() {
72
+ return this.http.get(`${this.apiEndpoint}/usage`);
73
+ }
74
+ /**
75
+ * Get all process instances currently waiting for an Epistola document generation result.
76
+ */
77
+ getPendingJobs() {
78
+ return this.http.get(`${this.apiEndpoint}/pending`);
79
+ }
80
+ /**
81
+ * Export a single process link as a .process-link.json file.
82
+ */
83
+ exportProcessLink(processLinkId) {
84
+ return this.http.get(`${this.apiEndpoint}/export/${encodeURIComponent(processLinkId)}`, {
85
+ responseType: 'blob',
86
+ });
87
+ }
88
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminService, deps: [{ token: i1.HttpClient }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Injectable });
89
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminService });
90
+ }
91
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminService, decorators: [{
92
+ type: Injectable
93
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.ConfigService }] });
94
+
95
+ /**
96
+ * Registers the Epistola admin page menu item under the Admin > Other section.
97
+ * Instantiated eagerly via ENVIRONMENT_INITIALIZER so the menu item
98
+ * appears without any manual configuration in the host application.
99
+ */
100
+ class EpistolaMenuService {
101
+ menuService;
102
+ constructor(menuService) {
103
+ this.menuService = menuService;
104
+ this.menuService.registerAppendMenuItemsFunction((items) => {
105
+ return of(items.map((item) => {
106
+ const isAdminMenu = item.roles?.includes(ROLE_ADMIN) && item.children;
107
+ if (!isAdminMenu) {
108
+ return item;
109
+ }
110
+ return {
111
+ ...item,
112
+ children: [
113
+ ...item.children,
114
+ {
115
+ link: ['/epistola'],
116
+ title: 'Epistola',
117
+ sequence: 18,
118
+ },
119
+ ],
120
+ };
121
+ }));
122
+ });
123
+ }
124
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaMenuService, deps: [{ token: i3.MenuService }], target: i0.ɵɵFactoryTarget.Injectable });
125
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaMenuService });
126
+ }
127
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaMenuService, decorators: [{
128
+ type: Injectable
129
+ }], ctorParameters: () => [{ type: i3.MenuService }] });
130
+
34
131
  /**
35
132
  * Service for interacting with Epistola plugin API endpoints.
36
133
  * Provides methods to fetch templates, environments, variants,
@@ -94,13 +191,32 @@ class EpistolaPluginService {
94
191
  * Discover process variable names for a given process definition.
95
192
  */
96
193
  getProcessVariables(processDefinitionKey) {
97
- return this.http.get(`${this.apiEndpoint}/process-variables`, { params: { processDefinitionKey } });
194
+ return this.http.get(`${this.apiEndpoint}/process-variables`, {
195
+ params: { processDefinitionKey },
196
+ });
98
197
  }
99
198
  /**
100
- * Validate that a data mapping covers all required template fields.
199
+ * Get variable suggestions for JSONata autocompletion.
101
200
  */
102
- validateMapping(pluginConfigurationId, templateId, dataMapping) {
103
- return this.http.post(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates/${templateId}/validate-mapping`, { dataMapping });
201
+ getVariableSuggestions(caseDefinitionKey, processDefinitionKey) {
202
+ const params = {};
203
+ if (caseDefinitionKey)
204
+ params['caseDefinitionKey'] = caseDefinitionKey;
205
+ if (processDefinitionKey)
206
+ params['processDefinitionKey'] = processDefinitionKey;
207
+ return this.http.get(`${this.apiEndpoint}/variable-suggestions`, {
208
+ params,
209
+ });
210
+ }
211
+ /**
212
+ * Evaluate a JSONata expression against a real document.
213
+ */
214
+ evaluateMapping(expression, documentId, processInstanceId) {
215
+ return this.http.post(`${this.apiEndpoint}/evaluate-mapping`, {
216
+ expression,
217
+ documentId,
218
+ processInstanceId: processInstanceId ?? null,
219
+ });
104
220
  }
105
221
  /**
106
222
  * Get a dynamically generated Formio form for retrying a failed document generation.
@@ -115,11 +231,19 @@ class EpistolaPluginService {
115
231
  }
116
232
  return this.http.get(`${this.apiEndpoint}/retry-form`, { params });
117
233
  }
234
+ /**
235
+ * List all available expression functions for expr: data mapping values.
236
+ */
237
+ getExpressionFunctions() {
238
+ return this.http.get(`${this.apiEndpoint}/expression-functions`);
239
+ }
118
240
  /**
119
241
  * Discover all previewable document sources for a given Valtimo document.
120
242
  */
121
243
  getPreviewSources(documentId) {
122
- return this.http.get(`${this.apiEndpoint}/preview-sources`, { params: { documentId } });
244
+ return this.http.get(`${this.apiEndpoint}/preview-sources`, {
245
+ params: { documentId },
246
+ });
123
247
  }
124
248
  /**
125
249
  * Preview a document by dry-running the generate-document process link.
@@ -131,7 +255,7 @@ class EpistolaPluginService {
131
255
  processDefinitionKey,
132
256
  sourceActivityId,
133
257
  processInstanceId: processInstanceId || null,
134
- overrides: overrides || null
258
+ overrides: overrides || null,
135
259
  });
136
260
  }
137
261
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginService, deps: [{ token: i1.HttpClient }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Injectable });
@@ -202,11 +326,11 @@ class EpistolaConfigurationComponent {
202
326
  });
203
327
  }
204
328
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
205
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", 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"] }] });
329
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", 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"] }] });
206
330
  }
207
331
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaConfigurationComponent, decorators: [{
208
332
  type: Component,
209
- 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" }]
333
+ 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" }]
210
334
  }], propDecorators: { save$: [{
211
335
  type: Input
212
336
  }], disabled$: [{
@@ -221,452 +345,1109 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
221
345
  type: Output
222
346
  }] } });
223
347
 
224
- function countRequiredMapped(fields, mapping) {
225
- let mapped = 0;
226
- let total = 0;
227
- for (const field of fields) {
228
- if (field.fieldType === 'SCALAR' && field.required) {
229
- total++;
230
- const val = mapping[field.name];
231
- if (typeof val === 'string' && val.trim().length > 0) {
232
- mapped++;
348
+ /**
349
+ * Shared state for the JSONata completion provider.
350
+ * Updated by the editor component when suggestions/functions change.
351
+ */
352
+ const jsonataCompletionData = {
353
+ suggestions: null,
354
+ functions: [],
355
+ };
356
+ /**
357
+ * Register the JSONata language in Monaco editor.
358
+ * Call this once when Monaco is available (e.g., in editor component OnInit).
359
+ */
360
+ function registerJsonataLanguage(monaco) {
361
+ // Only register once
362
+ if (monaco.languages.getLanguages().some((lang) => lang.id === 'jsonata')) {
363
+ return;
364
+ }
365
+ monaco.languages.register({ id: 'jsonata' });
366
+ // Syntax highlighting via Monarch tokenizer
367
+ monaco.languages.setMonarchTokensProvider('jsonata', {
368
+ defaultToken: '',
369
+ tokenPostfix: '.jsonata',
370
+ keywords: ['true', 'false', 'null', 'in', 'and', 'or', 'not'],
371
+ operators: ['&', '?', ':', '=', '!=', '>', '<', '>=', '<=', '+', '-', '*', '/', '%', '~>'],
372
+ symbols: /[=><!~?:&|+\-*/^%]+/,
373
+ tokenizer: {
374
+ root: [
375
+ // Variables: $identifier
376
+ [/\$[a-zA-Z_]\w*/, 'variable'],
377
+ // Identifiers and keywords
378
+ [
379
+ /[a-zA-Z_]\w*/,
380
+ {
381
+ cases: {
382
+ '@keywords': 'keyword',
383
+ '@default': 'identifier',
384
+ },
385
+ },
386
+ ],
387
+ // Whitespace
388
+ { include: '@whitespace' },
389
+ // Strings
390
+ [/"([^"\\]|\\.)*$/, 'string.invalid'],
391
+ [/"/, 'string', '@string_double'],
392
+ [/'([^'\\]|\\.)*$/, 'string.invalid'],
393
+ [/'/, 'string', '@string_single'],
394
+ // Numbers
395
+ [/\d+(\.\d+)?([eE][-+]?\d+)?/, 'number'],
396
+ // Delimiters and operators
397
+ [/[{}()\[\]]/, '@brackets'],
398
+ [/[,;.]/, 'delimiter'],
399
+ [
400
+ /@symbols/,
401
+ {
402
+ cases: {
403
+ '@operators': 'operator',
404
+ '@default': '',
405
+ },
406
+ },
407
+ ],
408
+ ],
409
+ string_double: [
410
+ [/[^\\"]+/, 'string'],
411
+ [/\\./, 'string.escape'],
412
+ [/"/, 'string', '@pop'],
413
+ ],
414
+ string_single: [
415
+ [/[^\\']+/, 'string'],
416
+ [/\\./, 'string.escape'],
417
+ [/'/, 'string', '@pop'],
418
+ ],
419
+ whitespace: [[/[ \t\r\n]+/, 'white']],
420
+ },
421
+ });
422
+ // Autocomplete provider
423
+ monaco.languages.registerCompletionItemProvider('jsonata', {
424
+ triggerCharacters: ['$', '.'],
425
+ provideCompletionItems: (model, position) => {
426
+ const textUntilPosition = model.getValueInRange({
427
+ startLineNumber: position.lineNumber,
428
+ startColumn: 1,
429
+ endLineNumber: position.lineNumber,
430
+ endColumn: position.column,
431
+ });
432
+ const suggestions = [];
433
+ const CompletionItemKind = monaco.languages.CompletionItemKind;
434
+ // After "$" — suggest variables and functions
435
+ if (textUntilPosition.endsWith('$')) {
436
+ suggestions.push(...['doc', 'pv', 'case'].map((v) => ({
437
+ label: `$${v}`,
438
+ kind: CompletionItemKind.Variable,
439
+ insertText: v,
440
+ detail: `Context variable`,
441
+ })));
442
+ // Custom functions
443
+ for (const func of jsonataCompletionData.functions) {
444
+ suggestions.push({
445
+ label: `$${func.name}`,
446
+ kind: CompletionItemKind.Function,
447
+ insertText: `${func.name}($0)`,
448
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
449
+ detail: func.description || 'Custom function',
450
+ });
451
+ }
452
+ // Built-in JSONata functions
453
+ const builtins = [
454
+ 'string',
455
+ 'number',
456
+ 'boolean',
457
+ 'length',
458
+ 'substring',
459
+ 'uppercase',
460
+ 'lowercase',
461
+ 'trim',
462
+ 'contains',
463
+ 'split',
464
+ 'join',
465
+ 'sum',
466
+ 'count',
467
+ 'max',
468
+ 'min',
469
+ 'average',
470
+ 'append',
471
+ 'sort',
472
+ 'reverse',
473
+ 'keys',
474
+ 'values',
475
+ 'lookup',
476
+ 'now',
477
+ 'exists',
478
+ 'type',
479
+ 'not',
480
+ ];
481
+ for (const fn of builtins) {
482
+ suggestions.push({
483
+ label: `$${fn}`,
484
+ kind: CompletionItemKind.Function,
485
+ insertText: `${fn}($0)`,
486
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
487
+ detail: 'Built-in JSONata function',
488
+ });
489
+ }
233
490
  }
234
- }
235
- else if (field.fieldType === 'ARRAY' && field.required) {
236
- total++;
237
- const val = mapping[field.name];
238
- if (typeof val === 'string' && val.trim().length > 0) {
239
- mapped++;
491
+ // After "$doc." — suggest document paths
492
+ if (/\$doc\.\s*$/.test(textUntilPosition) || /\$doc\.[a-zA-Z_]*$/.test(textUntilPosition)) {
493
+ const docPaths = jsonataCompletionData.suggestions?.doc || [];
494
+ for (const path of docPaths) {
495
+ suggestions.push({
496
+ label: path,
497
+ kind: CompletionItemKind.Field,
498
+ insertText: path,
499
+ detail: 'Document field',
500
+ });
501
+ }
240
502
  }
241
- else if (typeof val === 'object' && val !== null && '_source' in val) {
242
- if (typeof val['_source'] === 'string' && val['_source'].trim().length > 0) {
243
- mapped++;
503
+ // After "$pv." suggest process variables
504
+ if (/\$pv\.\s*$/.test(textUntilPosition) || /\$pv\.[a-zA-Z_]*$/.test(textUntilPosition)) {
505
+ const pvNames = jsonataCompletionData.suggestions?.pv || [];
506
+ for (const name of pvNames) {
507
+ suggestions.push({
508
+ label: name,
509
+ kind: CompletionItemKind.Variable,
510
+ insertText: name,
511
+ detail: 'Process variable',
512
+ });
244
513
  }
245
514
  }
246
- }
247
- else if (field.fieldType === 'OBJECT' && field.children) {
248
- const nested = (typeof mapping[field.name] === 'object' && mapping[field.name] !== null)
249
- ? mapping[field.name]
250
- : {};
251
- const childStats = countRequiredMapped(field.children, nested);
252
- mapped += childStats.mapped;
253
- total += childStats.total;
254
- }
255
- }
256
- return { mapped, total };
515
+ return { suggestions };
516
+ },
517
+ });
257
518
  }
258
519
 
259
- /**
260
- * Reusable 3-mode input (browse / pv / expression) for value resolver expressions.
261
- * Used by both ScalarFieldComponent and ArrayFieldComponent for source mapping.
262
- */
263
- class ValueInputComponent {
264
- cdr;
265
- name = '';
266
- value = '';
267
- pluginId = '';
268
- caseDefinitionKey = null;
269
- processVariables = [];
520
+ const jsonata$1 = _jsonata.default || _jsonata;
521
+ class JsonataEditorComponent {
522
+ expression = '';
270
523
  disabled = false;
271
- placeholder = 'e.g. pv:variableName or doc:path.to.field';
272
- valueChange = new EventEmitter();
273
- ValuePathSelectorPrefix = ValuePathSelectorPrefix;
274
- inputMode = 'browse';
275
- selectedPv = '';
276
- browseDefault = '';
277
- constructor(cdr) {
278
- this.cdr = cdr;
524
+ suggestions = null;
525
+ functions = [];
526
+ expressionChange = new EventEmitter();
527
+ validChange = new EventEmitter();
528
+ editorModel = { value: '', language: 'jsonata' };
529
+ editorOptions = {
530
+ minimap: { enabled: false },
531
+ lineNumbers: 'on',
532
+ scrollBeyondLastLine: false,
533
+ fontSize: 13,
534
+ tabSize: 2,
535
+ wordWrap: 'on',
536
+ renderWhitespace: 'none',
537
+ };
538
+ error = null;
539
+ destroy$ = new Subject();
540
+ validate$ = new Subject();
541
+ suppressChange = false;
542
+ languageRegistered = false;
543
+ constructor() {
544
+ this.validate$.pipe(debounceTime(300), takeUntil(this.destroy$)).subscribe((value) => {
545
+ this.validateExpression(value);
546
+ });
547
+ // Try to register language eagerly if Monaco is already loaded
548
+ this.tryRegisterLanguage();
279
549
  }
280
550
  ngOnChanges(changes) {
281
- if (changes['value']) {
282
- this.inputMode = this.detectInputMode(this.value);
283
- this.browseDefault = normalizeToDots(this.value);
284
- this.selectedPv = extractPvName(this.value);
551
+ if (changes['expression'] && !this.suppressChange) {
552
+ this.editorModel = { value: this.expression || '', language: 'jsonata' };
553
+ this.validate$.next(this.expression);
285
554
  }
286
- if (changes['processVariables']) {
287
- this.cdr.markForCheck();
555
+ if (changes['suggestions']) {
556
+ jsonataCompletionData.suggestions = this.suggestions;
557
+ }
558
+ if (changes['functions']) {
559
+ jsonataCompletionData.functions = this.functions;
288
560
  }
289
561
  }
290
- setInputMode(mode) {
291
- this.inputMode = mode;
292
- }
293
- onBrowseValueChange(newValue) {
294
- this.valueChange.emit(normalizeToDots(newValue));
295
- }
296
- onPvChange(newValue) {
297
- this.selectedPv = newValue;
298
- this.valueChange.emit(newValue ? 'pv:' + newValue : '');
562
+ ngOnDestroy() {
563
+ this.destroy$.next();
564
+ this.destroy$.complete();
299
565
  }
300
- onExpressionValueChange(newValue) {
301
- this.valueChange.emit(newValue);
566
+ onEditorValueChange(value) {
567
+ // Register language on first editor event (Monaco is now loaded)
568
+ if (!this.languageRegistered) {
569
+ this.tryRegisterLanguage();
570
+ }
571
+ if (this.suppressChange)
572
+ return;
573
+ this.suppressChange = true;
574
+ this.expression = value;
575
+ this.expressionChange.emit(value);
576
+ this.validate$.next(value);
577
+ setTimeout(() => (this.suppressChange = false));
578
+ }
579
+ tryRegisterLanguage() {
580
+ const m = window.monaco;
581
+ if (m) {
582
+ registerJsonataLanguage(m);
583
+ this.languageRegistered = true;
584
+ jsonataCompletionData.suggestions = this.suggestions;
585
+ jsonataCompletionData.functions = this.functions;
586
+ }
302
587
  }
303
- detectInputMode(value) {
304
- if (!value)
305
- return 'browse';
306
- if (value.startsWith('doc:') || value.startsWith('case:'))
307
- return 'browse';
308
- if (value.startsWith('pv:'))
309
- return 'pv';
310
- if (value.length > 0)
311
- return 'expression';
312
- return 'browse';
588
+ validateExpression(value) {
589
+ if (!value || !value.trim()) {
590
+ this.error = null;
591
+ this.validChange.emit(true);
592
+ return;
593
+ }
594
+ try {
595
+ jsonata$1(value);
596
+ this.error = null;
597
+ this.validChange.emit(true);
598
+ }
599
+ catch (e) {
600
+ this.error = e.message || 'Invalid expression';
601
+ this.validChange.emit(false);
602
+ }
313
603
  }
314
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ValueInputComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
315
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ValueInputComponent, isStandalone: true, selector: "epistola-value-input", inputs: { name: "name", value: "value", pluginId: "pluginId", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables", disabled: "disabled", placeholder: "placeholder" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"value-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=\"input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"name\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"browseDefault\"\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=\"input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n [(ngModel)]=\"selectedPv\"\n (ngModelChange)=\"onPvChange($event)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\">{{ pv }}</option>\n </select>\n <ng-template #pvFallback>\n <v-input\n [name]=\"'pvfb_' + name\"\n [defaultValue]=\"selectedPv\"\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=\"input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + name\"\n [defaultValue]=\"value\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n (valueChange)=\"onExpressionValueChange($event)\"\n ></v-input>\n </div>\n</div>\n", styles: [".value-input{display:flex;align-items:flex-start;gap:.5rem;min-width:0}.value-input ::ng-deep [data-test-id=valuePathSelectorToggle]{display:none!important}.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}\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.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { 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: 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", "buildingBlockDefinitionKey", "buildingBlockDefinitionVersionTag", "prefixes", "label", "tooltip", "required", "showCaseDefinitionSelector", "notation", "dropUp", "defaultValue", "type", "parentItem", "filterItems"], outputs: ["valueChangeEvent", "collectionSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
604
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: JsonataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
605
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: JsonataEditorComponent, isStandalone: true, selector: "epistola-jsonata-editor", inputs: { expression: "expression", disabled: "disabled", suggestions: "suggestions", functions: "functions" }, outputs: { expressionChange: "expressionChange", validChange: "validChange" }, usesOnChanges: true, ngImport: i0, template: `
606
+ <div class="jsonata-editor">
607
+ <valtimo-editor
608
+ [model]="editorModel"
609
+ [editorOptions]="editorOptions"
610
+ [disabled]="disabled"
611
+ [heightPx]="250"
612
+ [formatOnLoad]="false"
613
+ (valueChangeEvent)="onEditorValueChange($event)"
614
+ ></valtimo-editor>
615
+ <div class="jsonata-editor__footer">
616
+ <span *ngIf="error" class="jsonata-editor__error">{{ error }}</span>
617
+ <span *ngIf="!error && expression" class="jsonata-editor__valid">&#x2713;</span>
618
+ <span class="jsonata-editor__variables">$doc · $pv · $case</span>
619
+ </div>
620
+ </div>
621
+ `, isInline: true, styles: [".jsonata-editor__footer{display:flex;align-items:center;gap:8px;margin-top:4px;font-size:.8em}.jsonata-editor__error{color:#da1e28}.jsonata-editor__valid{color:#198038}.jsonata-editor__variables{margin-left:auto;color:#8d8d8d;font-family:monospace}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "ngmodule", type: EditorModule }, { kind: "component", type: i3.EditorComponent, selector: "valtimo-editor", inputs: ["editorOptions", "model", "disabled", "formatOnLoad", "widthPx", "heightPx", "heightStyle", "jsonSchema", "fitPage", "fitPageSpaceAdjustment"], outputs: ["validEvent", "valueChangeEvent"] }] });
316
622
  }
317
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ValueInputComponent, decorators: [{
623
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: JsonataEditorComponent, decorators: [{
318
624
  type: Component,
319
- args: [{ selector: 'epistola-value-input', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
320
- CommonModule,
321
- FormsModule,
322
- PluginTranslatePipeModule,
323
- InputModule,
324
- ValuePathSelectorComponent
325
- ], template: "<div class=\"value-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=\"input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"name\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"browseDefault\"\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=\"input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n [(ngModel)]=\"selectedPv\"\n (ngModelChange)=\"onPvChange($event)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\">{{ pv }}</option>\n </select>\n <ng-template #pvFallback>\n <v-input\n [name]=\"'pvfb_' + name\"\n [defaultValue]=\"selectedPv\"\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=\"input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + name\"\n [defaultValue]=\"value\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n (valueChange)=\"onExpressionValueChange($event)\"\n ></v-input>\n </div>\n</div>\n", styles: [".value-input{display:flex;align-items:flex-start;gap:.5rem;min-width:0}.value-input ::ng-deep [data-test-id=valuePathSelectorToggle]{display:none!important}.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}\n"] }]
326
- }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { name: [{
327
- type: Input
328
- }], value: [{
329
- type: Input
330
- }], pluginId: [{
331
- type: Input
332
- }], caseDefinitionKey: [{
333
- type: Input
334
- }], processVariables: [{
625
+ args: [{ selector: 'epistola-jsonata-editor', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, EditorModule], template: `
626
+ <div class="jsonata-editor">
627
+ <valtimo-editor
628
+ [model]="editorModel"
629
+ [editorOptions]="editorOptions"
630
+ [disabled]="disabled"
631
+ [heightPx]="250"
632
+ [formatOnLoad]="false"
633
+ (valueChangeEvent)="onEditorValueChange($event)"
634
+ ></valtimo-editor>
635
+ <div class="jsonata-editor__footer">
636
+ <span *ngIf="error" class="jsonata-editor__error">{{ error }}</span>
637
+ <span *ngIf="!error && expression" class="jsonata-editor__valid">&#x2713;</span>
638
+ <span class="jsonata-editor__variables">$doc · $pv · $case</span>
639
+ </div>
640
+ </div>
641
+ `, styles: [".jsonata-editor__footer{display:flex;align-items:center;gap:8px;margin-top:4px;font-size:.8em}.jsonata-editor__error{color:#da1e28}.jsonata-editor__valid{color:#198038}.jsonata-editor__variables{margin-left:auto;color:#8d8d8d;font-family:monospace}\n"] }]
642
+ }], ctorParameters: () => [], propDecorators: { expression: [{
335
643
  type: Input
336
644
  }], disabled: [{
337
645
  type: Input
338
- }], placeholder: [{
646
+ }], suggestions: [{
339
647
  type: Input
340
- }], valueChange: [{
648
+ }], functions: [{
649
+ type: Input
650
+ }], expressionChange: [{
651
+ type: Output
652
+ }], validChange: [{
341
653
  type: Output
342
654
  }] } });
343
- /** Convert slash-notation paths (e.g. doc:/a/b) to dot notation (doc:a.b). */
344
- function normalizeToDots(value) {
345
- if (typeof value !== 'string')
346
- return value;
347
- const colonIndex = value.indexOf(':');
348
- if (colonIndex < 0)
349
- return value;
350
- const prefix = value.substring(0, colonIndex);
351
- const path = value.substring(colonIndex + 1);
352
- if (!path.includes('/'))
353
- return value;
354
- const normalized = path.split('/').filter(p => p.length > 0).join('.');
355
- return `${prefix}:${normalized}`;
356
- }
357
- function extractPvName(value) {
358
- if (typeof value === 'string' && value.startsWith('pv:')) {
359
- return value.substring(3);
655
+
656
+ class ExpectedStructureComponent {
657
+ templateFields = [];
658
+ structureText = '{}';
659
+ ngOnChanges(changes) {
660
+ if (changes['templateFields']) {
661
+ this.structureText = this.buildStructure(this.templateFields, 0);
662
+ }
360
663
  }
361
- return '';
664
+ buildStructure(fields, depth) {
665
+ if (!fields || fields.length === 0)
666
+ return '{}';
667
+ const indent = ' '.repeat(depth + 1);
668
+ const closing = ' '.repeat(depth);
669
+ const lines = fields.map((f) => {
670
+ const req = f.required ? ' (required)' : '';
671
+ if (f.fieldType === 'OBJECT' && f.children?.length) {
672
+ const nested = this.buildStructure(f.children, depth + 1);
673
+ return `${indent}"${f.name}": ${nested}${req}`;
674
+ }
675
+ if (f.fieldType === 'ARRAY') {
676
+ if (f.children?.length) {
677
+ const itemStructure = this.buildStructure(f.children, depth + 2);
678
+ return `${indent}"${f.name}": [${itemStructure}]${req}`;
679
+ }
680
+ return `${indent}"${f.name}": array${req}`;
681
+ }
682
+ return `${indent}"${f.name}": ${f.type || 'any'}${req}`;
683
+ });
684
+ return `{\n${lines.join(',\n')}\n${closing}}`;
685
+ }
686
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ExpectedStructureComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
687
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ExpectedStructureComponent, isStandalone: true, selector: "epistola-expected-structure", inputs: { templateFields: "templateFields" }, usesOnChanges: true, ngImport: i0, template: `
688
+ <div class="expected">
689
+ <div class="expected__header">
690
+ {{ 'expectedStructure' | pluginTranslate: 'epistola' | async }}
691
+ </div>
692
+ <div *ngIf="!templateFields || templateFields.length === 0" class="expected__empty">
693
+ {{ 'expectedStructureLoading' | pluginTranslate: 'epistola' | async }}
694
+ </div>
695
+ <pre *ngIf="templateFields && templateFields.length > 0" class="expected__code">{{
696
+ structureText
697
+ }}</pre>
698
+ </div>
699
+ `, isInline: true, styles: [".expected{border:1px solid #e0e0e0;border-radius:4px;overflow:hidden;height:100%;display:flex;flex-direction:column}.expected__header{padding:6px 12px;background:#f4f4f4;border-bottom:1px solid #e0e0e0;font-size:.75em;color:#6f6f6f;text-transform:uppercase;letter-spacing:.5px}.expected__code{flex:1;font-family:IBM Plex Mono,monospace;font-size:.8em;line-height:1.5;margin:0;padding:8px 12px;white-space:pre-wrap;overflow-y:auto}.expected__empty{padding:8px 12px;color:#8d8d8d;font-size:.85em;font-style:italic}\n"], 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" }] });
362
700
  }
701
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ExpectedStructureComponent, decorators: [{
702
+ type: Component,
703
+ args: [{ selector: 'epistola-expected-structure', standalone: true, imports: [CommonModule, PluginTranslatePipeModule], template: `
704
+ <div class="expected">
705
+ <div class="expected__header">
706
+ {{ 'expectedStructure' | pluginTranslate: 'epistola' | async }}
707
+ </div>
708
+ <div *ngIf="!templateFields || templateFields.length === 0" class="expected__empty">
709
+ {{ 'expectedStructureLoading' | pluginTranslate: 'epistola' | async }}
710
+ </div>
711
+ <pre *ngIf="templateFields && templateFields.length > 0" class="expected__code">{{
712
+ structureText
713
+ }}</pre>
714
+ </div>
715
+ `, styles: [".expected{border:1px solid #e0e0e0;border-radius:4px;overflow:hidden;height:100%;display:flex;flex-direction:column}.expected__header{padding:6px 12px;background:#f4f4f4;border-bottom:1px solid #e0e0e0;font-size:.75em;color:#6f6f6f;text-transform:uppercase;letter-spacing:.5px}.expected__code{flex:1;font-family:IBM Plex Mono,monospace;font-size:.8em;line-height:1.5;margin:0;padding:8px 12px;white-space:pre-wrap;overflow-y:auto}.expected__empty{padding:8px 12px;color:#8d8d8d;font-size:.85em;font-style:italic}\n"] }]
716
+ }], propDecorators: { templateFields: [{
717
+ type: Input
718
+ }] } });
363
719
 
364
- class ScalarFieldComponent {
720
+ class BuilderFieldComponent {
365
721
  field;
366
- value = undefined;
367
- pluginId;
368
- caseDefinitionKey = null;
369
- processVariables = [];
722
+ path = [];
723
+ suggestions = [];
370
724
  disabled = false;
725
+ collapsed = false;
726
+ required = false;
727
+ collapsedPaths = new Set();
371
728
  valueChange = new EventEmitter();
372
- get stringValue() {
373
- return typeof this.value === 'string' ? this.value : '';
374
- }
375
- onValueChange(newValue) {
376
- this.valueChange.emit(newValue || undefined);
377
- }
378
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ScalarFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
379
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ScalarFieldComponent, isStandalone: true, selector: "epistola-scalar-field", inputs: { field: "field", value: "value", pluginId: "pluginId", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables", disabled: "disabled" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"field-row\" [class.field-required-unmapped]=\"field.required && !stringValue\">\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 <epistola-value-input\n [name]=\"'field_' + field.path\"\n [value]=\"stringValue\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onValueChange($event)\"\n ></epistola-value-input>\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}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "component", type: ValueInputComponent, selector: "epistola-value-input", inputs: ["name", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled", "placeholder"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
729
+ modeToggle = new EventEmitter();
730
+ collapseToggle = new EventEmitter();
731
+ isChildCollapsed(childIndex) {
732
+ return this.collapsedPaths.has(this.path.concat(childIndex).join('.'));
733
+ }
734
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BuilderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
735
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: BuilderFieldComponent, isStandalone: true, selector: "epistola-builder-field", inputs: { field: "field", path: "path", suggestions: "suggestions", disabled: "disabled", collapsed: "collapsed", required: "required", collapsedPaths: "collapsedPaths" }, outputs: { valueChange: "valueChange", modeToggle: "modeToggle", collapseToggle: "collapseToggle" }, ngImport: i0, template: `
736
+ <div class="builder-field">
737
+ <div
738
+ class="builder-field__name"
739
+ [class.builder-field__name--clickable]="field.children"
740
+ (click)="field.children && collapseToggle.emit(path)"
741
+ >
742
+ <span *ngIf="field.children" class="builder-field__chevron">{{
743
+ collapsed ? '&#x25B6;' : '&#x25BC;'
744
+ }}</span>
745
+ <span class="builder-field__label">{{ field.name }}</span>
746
+ <span *ngIf="required" class="builder-field__required">*</span>
747
+ <span *ngIf="field.children" class="builder-field__type">(object)</span>
748
+ </div>
749
+
750
+ <div class="builder-field__value" *ngIf="!field.children">
751
+ <input
752
+ *ngIf="field.mode === 'ref'"
753
+ type="text"
754
+ class="builder-field__input"
755
+ [ngModel]="field.value"
756
+ (ngModelChange)="valueChange.emit({ path: path, value: $event })"
757
+ [disabled]="disabled"
758
+ placeholder="$doc.path.to.field"
759
+ [attr.list]="'suggestions-' + path.join('-')"
760
+ />
761
+ <datalist *ngIf="field.mode === 'ref'" [id]="'suggestions-' + path.join('-')">
762
+ <option *ngFor="let s of suggestions" [value]="s"></option>
763
+ </datalist>
764
+ <input
765
+ *ngIf="field.mode === 'raw'"
766
+ type="text"
767
+ class="builder-field__input builder-field__input--raw"
768
+ [ngModel]="field.value"
769
+ (ngModelChange)="valueChange.emit({ path: path, value: $event })"
770
+ [disabled]="disabled"
771
+ placeholder="JSONata expression"
772
+ />
773
+ <button
774
+ class="builder-field__mode-toggle"
775
+ (click)="modeToggle.emit(path)"
776
+ [disabled]="disabled"
777
+ [title]="field.mode === 'ref' ? 'Switch to raw JSONata' : 'Switch to reference'"
778
+ >
779
+ {{ field.mode === 'ref' ? 'fx' : '·' }}
780
+ </button>
781
+ </div>
782
+
783
+ <div *ngIf="field.children && !collapsed" class="builder-field__children">
784
+ <epistola-builder-field
785
+ *ngFor="let child of field.children; let j = index"
786
+ [field]="child"
787
+ [path]="path.concat(j)"
788
+ [suggestions]="suggestions"
789
+ [disabled]="disabled"
790
+ [collapsed]="isChildCollapsed(j)"
791
+ [collapsedPaths]="collapsedPaths"
792
+ [required]="false"
793
+ (valueChange)="valueChange.emit($event)"
794
+ (modeToggle)="modeToggle.emit($event)"
795
+ (collapseToggle)="collapseToggle.emit($event)"
796
+ ></epistola-builder-field>
797
+ </div>
798
+ </div>
799
+ `, isInline: true, styles: [".builder-field{margin-bottom:4px}.builder-field__name{margin-bottom:2px}.builder-field__name--clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.builder-field__name--clickable:hover{color:#0f62fe}.builder-field__chevron{font-size:.7em;margin-right:4px}.builder-field__label{font-weight:500;font-size:.9em}.builder-field__required{color:#da1e28;margin-left:2px}.builder-field__type{color:#8d8d8d;font-size:.8em;margin-left:4px}.builder-field__value{display:flex;align-items:center;gap:4px}.builder-field__input{flex:1;padding:6px 8px;border:1px solid #e0e0e0;border-radius:4px;font-size:.85em;font-family:IBM Plex Mono,monospace}.builder-field__input:focus{outline:2px solid #0f62fe;border-color:#0f62fe}.builder-field__input--raw{background:#f4f4f4}.builder-field__mode-toggle{width:28px;height:28px;border:1px solid #e0e0e0;border-radius:4px;background:#fff;cursor:pointer;font-family:monospace;font-size:.8em;display:flex;align-items:center;justify-content:center}.builder-field__mode-toggle:hover{background:#f4f4f4}.builder-field__children{border-left:2px solid #e0e0e0;padding-left:12px;margin-top:4px}\n"], dependencies: [{ kind: "component", type: BuilderFieldComponent, selector: "epistola-builder-field", inputs: ["field", "path", "suggestions", "disabled", "collapsed", "required", "collapsedPaths"], outputs: ["valueChange", "modeToggle", "collapseToggle"] }, { 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: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { 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"] }] });
380
800
  }
381
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ScalarFieldComponent, decorators: [{
801
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BuilderFieldComponent, decorators: [{
382
802
  type: Component,
383
- args: [{ selector: 'epistola-scalar-field', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, PluginTranslatePipeModule, ValueInputComponent], template: "<div class=\"field-row\" [class.field-required-unmapped]=\"field.required && !stringValue\">\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 <epistola-value-input\n [name]=\"'field_' + field.path\"\n [value]=\"stringValue\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onValueChange($event)\"\n ></epistola-value-input>\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}\n"] }]
803
+ args: [{ selector: 'epistola-builder-field', standalone: true, imports: [CommonModule, FormsModule], template: `
804
+ <div class="builder-field">
805
+ <div
806
+ class="builder-field__name"
807
+ [class.builder-field__name--clickable]="field.children"
808
+ (click)="field.children && collapseToggle.emit(path)"
809
+ >
810
+ <span *ngIf="field.children" class="builder-field__chevron">{{
811
+ collapsed ? '&#x25B6;' : '&#x25BC;'
812
+ }}</span>
813
+ <span class="builder-field__label">{{ field.name }}</span>
814
+ <span *ngIf="required" class="builder-field__required">*</span>
815
+ <span *ngIf="field.children" class="builder-field__type">(object)</span>
816
+ </div>
817
+
818
+ <div class="builder-field__value" *ngIf="!field.children">
819
+ <input
820
+ *ngIf="field.mode === 'ref'"
821
+ type="text"
822
+ class="builder-field__input"
823
+ [ngModel]="field.value"
824
+ (ngModelChange)="valueChange.emit({ path: path, value: $event })"
825
+ [disabled]="disabled"
826
+ placeholder="$doc.path.to.field"
827
+ [attr.list]="'suggestions-' + path.join('-')"
828
+ />
829
+ <datalist *ngIf="field.mode === 'ref'" [id]="'suggestions-' + path.join('-')">
830
+ <option *ngFor="let s of suggestions" [value]="s"></option>
831
+ </datalist>
832
+ <input
833
+ *ngIf="field.mode === 'raw'"
834
+ type="text"
835
+ class="builder-field__input builder-field__input--raw"
836
+ [ngModel]="field.value"
837
+ (ngModelChange)="valueChange.emit({ path: path, value: $event })"
838
+ [disabled]="disabled"
839
+ placeholder="JSONata expression"
840
+ />
841
+ <button
842
+ class="builder-field__mode-toggle"
843
+ (click)="modeToggle.emit(path)"
844
+ [disabled]="disabled"
845
+ [title]="field.mode === 'ref' ? 'Switch to raw JSONata' : 'Switch to reference'"
846
+ >
847
+ {{ field.mode === 'ref' ? 'fx' : '·' }}
848
+ </button>
849
+ </div>
850
+
851
+ <div *ngIf="field.children && !collapsed" class="builder-field__children">
852
+ <epistola-builder-field
853
+ *ngFor="let child of field.children; let j = index"
854
+ [field]="child"
855
+ [path]="path.concat(j)"
856
+ [suggestions]="suggestions"
857
+ [disabled]="disabled"
858
+ [collapsed]="isChildCollapsed(j)"
859
+ [collapsedPaths]="collapsedPaths"
860
+ [required]="false"
861
+ (valueChange)="valueChange.emit($event)"
862
+ (modeToggle)="modeToggle.emit($event)"
863
+ (collapseToggle)="collapseToggle.emit($event)"
864
+ ></epistola-builder-field>
865
+ </div>
866
+ </div>
867
+ `, styles: [".builder-field{margin-bottom:4px}.builder-field__name{margin-bottom:2px}.builder-field__name--clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.builder-field__name--clickable:hover{color:#0f62fe}.builder-field__chevron{font-size:.7em;margin-right:4px}.builder-field__label{font-weight:500;font-size:.9em}.builder-field__required{color:#da1e28;margin-left:2px}.builder-field__type{color:#8d8d8d;font-size:.8em;margin-left:4px}.builder-field__value{display:flex;align-items:center;gap:4px}.builder-field__input{flex:1;padding:6px 8px;border:1px solid #e0e0e0;border-radius:4px;font-size:.85em;font-family:IBM Plex Mono,monospace}.builder-field__input:focus{outline:2px solid #0f62fe;border-color:#0f62fe}.builder-field__input--raw{background:#f4f4f4}.builder-field__mode-toggle{width:28px;height:28px;border:1px solid #e0e0e0;border-radius:4px;background:#fff;cursor:pointer;font-family:monospace;font-size:.8em;display:flex;align-items:center;justify-content:center}.builder-field__mode-toggle:hover{background:#f4f4f4}.builder-field__children{border-left:2px solid #e0e0e0;padding-left:12px;margin-top:4px}\n"] }]
384
868
  }], propDecorators: { field: [{
385
869
  type: Input
386
- }], value: [{
870
+ }], path: [{
387
871
  type: Input
388
- }], pluginId: [{
872
+ }], suggestions: [{
389
873
  type: Input
390
- }], caseDefinitionKey: [{
874
+ }], disabled: [{
391
875
  type: Input
392
- }], processVariables: [{
876
+ }], collapsed: [{
393
877
  type: Input
394
- }], disabled: [{
878
+ }], required: [{
879
+ type: Input
880
+ }], collapsedPaths: [{
395
881
  type: Input
396
882
  }], valueChange: [{
397
883
  type: Output
884
+ }], modeToggle: [{
885
+ type: Output
886
+ }], collapseToggle: [{
887
+ type: Output
398
888
  }] } });
399
889
 
400
- class ArrayFieldComponent {
401
- field;
402
- value = undefined;
403
- pluginId;
404
- caseDefinitionKey = null;
405
- processVariables = [];
406
- disabled = false;
407
- valueChange = new EventEmitter();
408
- expanded = false;
409
- arrayPerFieldMode = false;
410
- mappedCount = 0;
411
- totalRequired = 0;
412
- ngOnChanges(changes) {
413
- if (changes['value'] || changes['field']) {
414
- this.updateCompleteness();
415
- if (!this.expanded && this.totalRequired > 0 && this.mappedCount < this.totalRequired) {
416
- this.expanded = true;
417
- }
418
- // Detect per-field mode from value shape
419
- if (changes['value'] && typeof this.value === 'object' && this.value !== null && '_source' in this.value) {
420
- this.arrayPerFieldMode = true;
421
- }
890
+ const jsonata = _jsonata.default || _jsonata;
891
+ /**
892
+ * Parse a JSONata expression into BuilderField array.
893
+ * Only supports top-level object literals with simple path references or nested objects.
894
+ * Anything else is stored as raw JSONata text.
895
+ */
896
+ function parseJsonataToBuilder(expression) {
897
+ if (!expression || !expression.trim()) {
898
+ return [];
899
+ }
900
+ try {
901
+ const ast = jsonata(expression).ast();
902
+ if (ast.type === 'unary' && ast.value === '{') {
903
+ return parseObjectEntries(ast.lhs, expression);
422
904
  }
905
+ // Not a top-level object — can't represent in builder
906
+ return [{ name: '_root', mode: 'raw', value: expression }];
423
907
  }
424
- toggleExpanded() {
425
- this.expanded = !this.expanded;
908
+ catch {
909
+ // Invalid JSONata — return as single raw field
910
+ return [{ name: '_root', mode: 'raw', value: expression }];
426
911
  }
427
- getSourceValue() {
428
- if (typeof this.value === 'string') {
429
- return normalizeToDots(this.value);
430
- }
431
- if (typeof this.value === 'object' && this.value !== null && '_source' in this.value) {
432
- return normalizeToDots(this.value['_source'] || '');
433
- }
912
+ }
913
+ /**
914
+ * Convert BuilderField array back to a JSONata expression string.
915
+ */
916
+ function builderToJsonata(fields) {
917
+ if (fields.length === 0) {
434
918
  return '';
435
919
  }
436
- onSourceValueChange(newValue) {
437
- if (this.arrayPerFieldMode) {
438
- const current = (typeof this.value === 'object' && this.value !== null) ? { ...this.value } : {};
439
- current['_source'] = newValue || '';
440
- this.valueChange.emit(current);
441
- }
442
- else {
443
- this.valueChange.emit(newValue || undefined);
920
+ // Special case: single _root raw field means the whole expression is raw
921
+ if (fields.length === 1 && fields[0].name === '_root' && fields[0].mode === 'raw') {
922
+ return fields[0].value;
923
+ }
924
+ const entries = fields.map((field) => formatFieldEntry(field)).filter(Boolean);
925
+ return `{\n${entries.join(',\n')}\n}`;
926
+ }
927
+ /**
928
+ * Check if a JSONata expression can be fully represented by the builder
929
+ * (i.e., all fields are simple refs or nested objects of simple refs).
930
+ */
931
+ function isBuilderCompatible(expression) {
932
+ const fields = parseJsonataToBuilder(expression);
933
+ return fields.every((f) => f.mode === 'ref' || (f.children && f.children.every(isFieldSimple)));
934
+ }
935
+ function isFieldSimple(field) {
936
+ if (field.mode === 'raw')
937
+ return false;
938
+ if (field.children)
939
+ return field.children.every(isFieldSimple);
940
+ return true;
941
+ }
942
+ function parseObjectEntries(entries, source) {
943
+ return entries.map(([keyNode, valueNode]) => {
944
+ const name = keyNode.value;
945
+ return classifyValue(name, valueNode, source);
946
+ });
947
+ }
948
+ function classifyValue(name, node, source) {
949
+ // Simple path reference: $doc.x.y, $pv.x, $case.x — store as JSONata directly
950
+ if (node.type === 'path' && node.steps?.length > 0 && node.steps[0].type === 'variable') {
951
+ const varName = node.steps[0].value; // doc, pv, case
952
+ if (['doc', 'pv', 'case'].includes(varName)) {
953
+ const path = node.steps
954
+ .slice(1)
955
+ .map((s) => s.value)
956
+ .join('.');
957
+ return { name, mode: 'ref', value: `$${varName}.${path}` };
444
958
  }
445
959
  }
446
- toggleArrayPerFieldMode() {
447
- this.arrayPerFieldMode = !this.arrayPerFieldMode;
448
- if (this.arrayPerFieldMode) {
449
- const currentSource = this.getSourceValue();
450
- this.valueChange.emit({ _source: currentSource });
960
+ // String literal
961
+ if (node.type === 'string') {
962
+ return { name, mode: 'ref', value: `"${node.value}"` };
963
+ }
964
+ // Number literal
965
+ if (node.type === 'number') {
966
+ return { name, mode: 'ref', value: String(node.value) };
967
+ }
968
+ // Boolean literal (value node)
969
+ if (node.type === 'value' && typeof node.value === 'boolean') {
970
+ return { name, mode: 'ref', value: String(node.value) };
971
+ }
972
+ // Nested object
973
+ if (node.type === 'unary' && node.value === '{' && node.lhs) {
974
+ const children = parseObjectEntries(node.lhs, source);
975
+ return { name, mode: 'ref', value: '', children };
976
+ }
977
+ // Anything else: extract raw source text
978
+ const raw = extractSourceFragment(node, source);
979
+ return { name, mode: 'raw', value: raw };
980
+ }
981
+ /**
982
+ * Extract the source text for a node using position info.
983
+ * Falls back to a generic representation if positions aren't useful.
984
+ */
985
+ function extractSourceFragment(node, source) {
986
+ // Try to reconstruct from AST for common patterns
987
+ if (node.type === 'condition') {
988
+ const cond = reconstructExpression(node.condition);
989
+ const then = reconstructExpression(node.then);
990
+ const els = reconstructExpression(node.else);
991
+ return `${cond} ? ${then} : ${els}`;
992
+ }
993
+ if (node.type === 'binary') {
994
+ const left = reconstructExpression(node.lhs);
995
+ const right = reconstructExpression(node.rhs);
996
+ return `${left} ${node.value} ${right}`;
997
+ }
998
+ if (node.type === 'function') {
999
+ const name = reconstructExpression(node.procedure);
1000
+ const args = (node.arguments || []).map(reconstructExpression).join(', ');
1001
+ return `${name}(${args})`;
1002
+ }
1003
+ // Generic fallback
1004
+ return reconstructExpression(node);
1005
+ }
1006
+ function reconstructExpression(node) {
1007
+ if (!node)
1008
+ return '';
1009
+ if (node.type === 'string')
1010
+ return `"${node.value}"`;
1011
+ if (node.type === 'number')
1012
+ return String(node.value);
1013
+ if (node.type === 'value')
1014
+ return String(node.value);
1015
+ if (node.type === 'path' && node.steps) {
1016
+ return node.steps.map((s) => (s.type === 'variable' ? `$${s.value}` : s.value)).join('.');
1017
+ }
1018
+ if (node.type === 'binary') {
1019
+ return `${reconstructExpression(node.lhs)} ${node.value} ${reconstructExpression(node.rhs)}`;
1020
+ }
1021
+ if (node.type === 'condition') {
1022
+ return `${reconstructExpression(node.condition)} ? ${reconstructExpression(node.then)} : ${reconstructExpression(node.else)}`;
1023
+ }
1024
+ if (node.type === 'function') {
1025
+ const proc = reconstructExpression(node.procedure);
1026
+ const args = (node.arguments || []).map(reconstructExpression).join(', ');
1027
+ return `${proc}(${args})`;
1028
+ }
1029
+ if (node.type === 'unary' && node.value === '{') {
1030
+ const entries = (node.lhs || [])
1031
+ .map(([k, v]) => `"${k.value}": ${reconstructExpression(v)}`)
1032
+ .join(', ');
1033
+ return `{${entries}}`;
1034
+ }
1035
+ return '...';
1036
+ }
1037
+ function formatFieldEntry(field, indent = ' ') {
1038
+ if (field.children && field.children.length > 0) {
1039
+ const childEntries = field.children.map((c) => formatFieldEntry(c, indent + ' ')).join(',\n');
1040
+ return `${indent}"${field.name}": {\n${childEntries}\n${indent}}`;
1041
+ }
1042
+ // Value is already valid JSONata (e.g. $doc.x.y, "string", 42, or raw expression)
1043
+ const value = field.value || 'null';
1044
+ return `${indent}"${field.name}": ${value}`;
1045
+ }
1046
+
1047
+ class MappingBuilderComponent {
1048
+ expression = '';
1049
+ templateFields = [];
1050
+ suggestions = null;
1051
+ disabled = false;
1052
+ expressionChange = new EventEmitter();
1053
+ fields = [];
1054
+ allSuggestions = [];
1055
+ collapsedPaths = new Set();
1056
+ initialCollapseApplied = false;
1057
+ ngOnChanges(changes) {
1058
+ // Skip re-parse only when expression alone changed (from our own emit)
1059
+ const expressionChanged = !!changes['expression'];
1060
+ const templateFieldsChanged = !!changes['templateFields'];
1061
+ if (expressionChanged && !templateFieldsChanged && !changes['expression'].firstChange) {
1062
+ return; // Don't re-parse when we emit changes ourselves
451
1063
  }
452
- else {
453
- const source = this.getSourceValue();
454
- this.valueChange.emit(source || undefined);
1064
+ if (expressionChanged || templateFieldsChanged) {
1065
+ this.rebuildFields();
1066
+ if (!this.initialCollapseApplied && this.fields.length > 0) {
1067
+ this.collapseAll();
1068
+ this.initialCollapseApplied = true;
1069
+ }
455
1070
  }
456
- }
457
- onItemFieldChange(childName, sourceFieldName) {
458
- const current = (typeof this.value === 'object' && this.value !== null) ? { ...this.value } : { _source: '' };
459
- if (sourceFieldName && sourceFieldName.trim().length > 0) {
460
- current[childName] = sourceFieldName;
1071
+ if (changes['suggestions']) {
1072
+ this.buildSuggestionList();
461
1073
  }
462
- else {
463
- delete current[childName];
1074
+ }
1075
+ onNestedValueChange(path, value) {
1076
+ const field = this.getFieldAtPath(path);
1077
+ if (field) {
1078
+ field.value = value;
1079
+ this.emit();
464
1080
  }
465
- this.valueChange.emit(current);
466
1081
  }
467
- getItemFieldValue(childName) {
468
- if (typeof this.value === 'object' && this.value !== null) {
469
- return this.value[childName] || '';
1082
+ onNestedModeToggle(path) {
1083
+ const field = this.getFieldAtPath(path);
1084
+ if (field) {
1085
+ field.mode = field.mode === 'ref' ? 'raw' : 'ref';
1086
+ this.emit();
470
1087
  }
471
- return '';
472
1088
  }
473
- hasArrayChildren() {
474
- return !!(this.field?.children && this.field.children.length > 0);
1089
+ isRequired(fieldName) {
1090
+ return this.templateFields?.find((tf) => tf.name === fieldName)?.required ?? false;
475
1091
  }
476
- updateCompleteness() {
477
- if (!this.field?.children || this.field.children.length === 0) {
478
- this.totalRequired = this.field?.required ? 1 : 0;
479
- this.mappedCount = this.getSourceValue() ? (this.field?.required ? 1 : 0) : 0;
480
- return;
481
- }
482
- if (typeof this.value === 'object' && this.value !== null && '_source' in this.value) {
483
- let total = 0;
484
- let mapped = 0;
485
- if (this.field?.required) {
486
- total++;
487
- if (this.value['_source'] && this.value['_source'].trim().length > 0) {
488
- mapped++;
489
- }
490
- }
491
- for (const child of this.field.children) {
492
- if (child.required) {
493
- total++;
494
- const val = this.value[child.name];
495
- if (typeof val === 'string' && val.trim().length > 0) {
496
- mapped++;
497
- }
498
- }
499
- }
500
- this.mappedCount = mapped;
501
- this.totalRequired = total;
1092
+ isCollapsed(path) {
1093
+ return this.collapsedPaths.has(path.join('.'));
1094
+ }
1095
+ toggleCollapse(path) {
1096
+ const key = path.join('.');
1097
+ if (this.collapsedPaths.has(key)) {
1098
+ this.collapsedPaths.delete(key);
502
1099
  }
503
1100
  else {
504
- this.totalRequired = this.field?.required ? 1 : 0;
505
- this.mappedCount = this.getSourceValue() ? (this.field?.required ? 1 : 0) : 0;
1101
+ this.collapsedPaths.add(key);
506
1102
  }
507
1103
  }
508
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
509
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ArrayFieldComponent, isStandalone: true, selector: "epistola-array-field", inputs: { field: "field", value: "value", pluginId: "pluginId", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables", disabled: "disabled" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div 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 <epistola-value-input\n [name]=\"'field_' + field.path\"\n [value]=\"getSourceValue()\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onSourceValueChange($event)\"\n ></epistola-value-input>\n </div>\n\n <!-- Per-item field mapping toggle -->\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-array{margin:.25rem 0}.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-array-header:hover{background:#f4f4f4}.field-array-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-array-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-array-header .field-name{font-weight:500}.field-array-header .field-meta{font-size:.8125rem;color:#6c757d}.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-array-header .mapped-indicator{margin-left:auto;color:#198754;font-weight:600}.field-array-content{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.field-row{display:flex;align-items:flex-start;gap:.75rem;padding:.5rem 0}.field-label{flex:0 0 200px;min-width:140px;padding-top:.5rem;word-break:break-word}.field-label .field-name{font-weight:500}.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: "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: "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: ValueInputComponent, selector: "epistola-value-input", inputs: ["name", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled", "placeholder"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
510
- }
511
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ArrayFieldComponent, decorators: [{
512
- type: Component,
513
- args: [{ selector: 'epistola-array-field', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, PluginTranslatePipeModule, InputModule, ValueInputComponent], template: "<div 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 <epistola-value-input\n [name]=\"'field_' + field.path\"\n [value]=\"getSourceValue()\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onSourceValueChange($event)\"\n ></epistola-value-input>\n </div>\n\n <!-- Per-item field mapping toggle -->\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-array{margin:.25rem 0}.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-array-header:hover{background:#f4f4f4}.field-array-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-array-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-array-header .field-name{font-weight:500}.field-array-header .field-meta{font-size:.8125rem;color:#6c757d}.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-array-header .mapped-indicator{margin-left:auto;color:#198754;font-weight:600}.field-array-content{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.field-row{display:flex;align-items:flex-start;gap:.75rem;padding:.5rem 0}.field-label{flex:0 0 200px;min-width:140px;padding-top:.5rem;word-break:break-word}.field-label .field-name{font-weight:500}.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"] }]
514
- }], propDecorators: { field: [{
515
- type: Input
516
- }], value: [{
517
- type: Input
518
- }], pluginId: [{
519
- type: Input
520
- }], caseDefinitionKey: [{
521
- type: Input
522
- }], processVariables: [{
523
- type: Input
524
- }], disabled: [{
525
- type: Input
526
- }], valueChange: [{
527
- type: Output
528
- }] } });
529
-
530
- /**
531
- * Recursive field tree component.
532
- * Dispatches SCALAR and ARRAY to dedicated sub-components.
533
- * Handles OBJECT inline to avoid circular import issues (OBJECT children recurse back to this component).
534
- * Uses forwardRef(() => FieldTreeComponent) in imports to allow self-referencing in the template.
535
- */
536
- class FieldTreeComponent {
537
- field;
538
- value = undefined;
539
- pluginId;
540
- caseDefinitionKey = null;
541
- processVariables = [];
542
- disabled = false;
543
- valueChange = new EventEmitter();
544
- // OBJECT-specific state
545
- expanded = false;
546
- mappedCount = 0;
547
- totalRequired = 0;
548
- ngOnChanges(changes) {
549
- if (this.field?.fieldType === 'OBJECT' && (changes['value'] || changes['field'])) {
550
- if (this.field.children) {
551
- const stats = countRequiredMapped(this.field.children, this.value || {});
552
- this.mappedCount = stats.mapped;
553
- this.totalRequired = stats.total;
1104
+ collapseAll() {
1105
+ this.collapsedPaths.clear();
1106
+ this.fields.forEach((field, i) => {
1107
+ if (field.children) {
1108
+ this.collapsedPaths.add(String(i));
1109
+ this.collapseChildren(field.children, [i]);
554
1110
  }
555
- if (!this.expanded && this.totalRequired > 0 && this.mappedCount < this.totalRequired) {
556
- this.expanded = true;
1111
+ });
1112
+ }
1113
+ collapseChildren(children, parentPath) {
1114
+ children.forEach((child, j) => {
1115
+ if (child.children) {
1116
+ this.collapsedPaths.add([...parentPath, j].join('.'));
1117
+ this.collapseChildren(child.children, [...parentPath, j]);
557
1118
  }
1119
+ });
1120
+ }
1121
+ getFieldAtPath(path) {
1122
+ if (path.length === 0)
1123
+ return null;
1124
+ let current = this.fields[path[0]];
1125
+ for (let i = 1; i < path.length; i++) {
1126
+ if (!current.children)
1127
+ return null;
1128
+ current = current.children[path[i]];
558
1129
  }
1130
+ return current;
559
1131
  }
560
- toggleExpanded() {
561
- this.expanded = !this.expanded;
1132
+ emit() {
1133
+ const jsonata = builderToJsonata(this.fields);
1134
+ this.expressionChange.emit(jsonata);
562
1135
  }
563
- onChildChange(childName, childValue) {
564
- const current = (typeof this.value === 'object' && this.value !== null) ? { ...this.value } : {};
565
- if (childValue === undefined || childValue === null || childValue === '') {
566
- delete current[childName];
567
- }
568
- else {
569
- current[childName] = childValue;
1136
+ /**
1137
+ * Ensure all template fields have a corresponding builder field.
1138
+ * Adds missing fields with empty values.
1139
+ */
1140
+ buildSuggestionList() {
1141
+ if (!this.suggestions) {
1142
+ this.allSuggestions = [];
1143
+ return;
570
1144
  }
571
- this.valueChange.emit(Object.keys(current).length > 0 ? current : undefined);
1145
+ const docSuggestions = (this.suggestions.doc || []).map((p) => `$doc.${p}`);
1146
+ const pvSuggestions = (this.suggestions.pv || []).map((p) => `$pv.${p}`);
1147
+ this.allSuggestions = [...docSuggestions, ...pvSuggestions];
572
1148
  }
573
- getChildValue(childName) {
574
- if (typeof this.value === 'object' && this.value !== null) {
575
- return this.value[childName];
1149
+ /**
1150
+ * Rebuild fields using template fields as the source of truth.
1151
+ * Expression values fill in where available; unmapped fields show empty.
1152
+ */
1153
+ rebuildFields() {
1154
+ const parsed = parseJsonataToBuilder(this.expression);
1155
+ const parsedByName = new Map(parsed.map((f) => [f.name, f]));
1156
+ if (!this.templateFields || this.templateFields.length === 0) {
1157
+ // No template fields yet — use whatever we parsed
1158
+ this.fields = parsed;
1159
+ return;
1160
+ }
1161
+ // Template fields drive the structure
1162
+ this.fields = this.templateFields.map((tf) => {
1163
+ const existing = parsedByName.get(tf.name);
1164
+ if (existing) {
1165
+ return existing;
1166
+ }
1167
+ if (tf.fieldType === 'OBJECT' && tf.children?.length) {
1168
+ return {
1169
+ name: tf.name,
1170
+ mode: 'ref',
1171
+ value: '',
1172
+ children: tf.children.map((c) => ({ name: c.name, mode: 'ref', value: '' })),
1173
+ };
1174
+ }
1175
+ return { name: tf.name, mode: 'ref', value: '' };
1176
+ });
1177
+ // Include extra fields from expression not in the template schema
1178
+ for (const p of parsed) {
1179
+ if (!this.templateFields.find((tf) => tf.name === p.name)) {
1180
+ this.fields.push(p);
1181
+ }
576
1182
  }
577
- return undefined;
578
1183
  }
579
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FieldTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
580
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", 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: "@switch (field.fieldType) {\n @case ('SCALAR') {\n <epistola-scalar-field\n [field]=\"field\"\n [value]=\"value\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"valueChange.emit($event)\"\n ></epistola-scalar-field>\n }\n @case ('OBJECT') {\n <div 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 @case ('ARRAY') {\n <epistola-array-field\n [field]=\"field\"\n [value]=\"value\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"valueChange.emit($event)\"\n ></epistola-array-field>\n }\n}\n", styles: [".field-object{margin:.25rem 0}.field-object-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{background:#f4f4f4}.field-object-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-object-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-object-header .field-name{font-weight:500}.field-object-header .field-meta{font-size:.8125rem;color:#6c757d}.field-object-header .completeness-badge{margin-left:auto;font-size:.75rem;padding:.125rem .5rem;border-radius:10px;background:#e0e0e0;color:#525252;font-weight:500}.field-object-children{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => FieldTreeComponent), selector: "epistola-field-tree", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "directive", type: i0.forwardRef(() => i1$1.NgForOf), selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i0.forwardRef(() => i1$1.NgIf), selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: i0.forwardRef(() => PluginTranslatePipeModule) }, { kind: "component", type: i0.forwardRef(() => ScalarFieldComponent), selector: "epistola-scalar-field", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => ArrayFieldComponent), selector: "epistola-array-field", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1184
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MappingBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1185
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: MappingBuilderComponent, isStandalone: true, selector: "epistola-mapping-builder", inputs: { expression: "expression", templateFields: "templateFields", suggestions: "suggestions", disabled: "disabled" }, outputs: { expressionChange: "expressionChange" }, usesOnChanges: true, ngImport: i0, template: `
1186
+ <div class="mapping-builder">
1187
+ <div
1188
+ *ngIf="fields.length === 0 && (!templateFields || templateFields.length === 0)"
1189
+ class="mapping-builder__empty"
1190
+ >
1191
+ {{ 'noTemplateFields' | pluginTranslate: 'epistola' | async }}
1192
+ </div>
1193
+
1194
+ <epistola-builder-field
1195
+ *ngFor="let field of fields; let i = index"
1196
+ [field]="field"
1197
+ [path]="[i]"
1198
+ [suggestions]="allSuggestions"
1199
+ [disabled]="disabled"
1200
+ [collapsed]="isCollapsed([i])"
1201
+ [collapsedPaths]="collapsedPaths"
1202
+ [required]="isRequired(field.name)"
1203
+ (valueChange)="onNestedValueChange($event.path, $event.value)"
1204
+ (modeToggle)="onNestedModeToggle($event)"
1205
+ (collapseToggle)="toggleCollapse($event)"
1206
+ ></epistola-builder-field>
1207
+ </div>
1208
+ `, isInline: true, styles: [".mapping-builder__empty{color:#6f6f6f;font-size:.9em;padding:12px 0}.mapping-builder__row{margin-bottom:8px}.mapping-builder__row--child{margin-left:20px;margin-bottom:4px}.mapping-builder__name{margin-bottom:2px}.mapping-builder__name--clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.mapping-builder__name--clickable:hover{color:#0f62fe}.mapping-builder__chevron{font-size:.7em;margin-right:4px}.mapping-builder__field-name{font-weight:500;font-size:.9em}.mapping-builder__required{color:#da1e28;margin-left:2px}.mapping-builder__type{color:#8d8d8d;font-size:.8em;margin-left:4px}.mapping-builder__value{display:flex;align-items:center;gap:4px}.mapping-builder__input{flex:1;padding:6px 8px;border:1px solid #e0e0e0;border-radius:4px;font-size:.85em;font-family:IBM Plex Mono,monospace}.mapping-builder__input:focus{outline:2px solid #0f62fe;border-color:#0f62fe}.mapping-builder__input--raw{background:#f4f4f4}.mapping-builder__mode-toggle{width:28px;height:28px;border:1px solid #e0e0e0;border-radius:4px;background:#fff;cursor:pointer;font-family:monospace;font-size:.8em;display:flex;align-items:center;justify-content:center}.mapping-builder__mode-toggle:hover{background:#f4f4f4}.mapping-builder__children{border-left:2px solid #e0e0e0;padding-left:12px;margin-top:4px}\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: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "component", type: BuilderFieldComponent, selector: "epistola-builder-field", inputs: ["field", "path", "suggestions", "disabled", "collapsed", "required", "collapsedPaths"], outputs: ["valueChange", "modeToggle", "collapseToggle"] }] });
581
1209
  }
582
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FieldTreeComponent, decorators: [{
1210
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MappingBuilderComponent, decorators: [{
583
1211
  type: Component,
584
- args: [{ selector: 'epistola-field-tree', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, PluginTranslatePipeModule, ScalarFieldComponent, ArrayFieldComponent, forwardRef(() => FieldTreeComponent)], template: "@switch (field.fieldType) {\n @case ('SCALAR') {\n <epistola-scalar-field\n [field]=\"field\"\n [value]=\"value\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"valueChange.emit($event)\"\n ></epistola-scalar-field>\n }\n @case ('OBJECT') {\n <div 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 @case ('ARRAY') {\n <epistola-array-field\n [field]=\"field\"\n [value]=\"value\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"valueChange.emit($event)\"\n ></epistola-array-field>\n }\n}\n", styles: [".field-object{margin:.25rem 0}.field-object-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{background:#f4f4f4}.field-object-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-object-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-object-header .field-name{font-weight:500}.field-object-header .field-meta{font-size:.8125rem;color:#6c757d}.field-object-header .completeness-badge{margin-left:auto;font-size:.75rem;padding:.125rem .5rem;border-radius:10px;background:#e0e0e0;color:#525252;font-weight:500}.field-object-children{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}\n"] }]
585
- }], propDecorators: { field: [{
586
- type: Input
587
- }], value: [{
588
- type: Input
589
- }], pluginId: [{
1212
+ args: [{ selector: 'epistola-mapping-builder', standalone: true, imports: [CommonModule, FormsModule, PluginTranslatePipeModule, BuilderFieldComponent], template: `
1213
+ <div class="mapping-builder">
1214
+ <div
1215
+ *ngIf="fields.length === 0 && (!templateFields || templateFields.length === 0)"
1216
+ class="mapping-builder__empty"
1217
+ >
1218
+ {{ 'noTemplateFields' | pluginTranslate: 'epistola' | async }}
1219
+ </div>
1220
+
1221
+ <epistola-builder-field
1222
+ *ngFor="let field of fields; let i = index"
1223
+ [field]="field"
1224
+ [path]="[i]"
1225
+ [suggestions]="allSuggestions"
1226
+ [disabled]="disabled"
1227
+ [collapsed]="isCollapsed([i])"
1228
+ [collapsedPaths]="collapsedPaths"
1229
+ [required]="isRequired(field.name)"
1230
+ (valueChange)="onNestedValueChange($event.path, $event.value)"
1231
+ (modeToggle)="onNestedModeToggle($event)"
1232
+ (collapseToggle)="toggleCollapse($event)"
1233
+ ></epistola-builder-field>
1234
+ </div>
1235
+ `, styles: [".mapping-builder__empty{color:#6f6f6f;font-size:.9em;padding:12px 0}.mapping-builder__row{margin-bottom:8px}.mapping-builder__row--child{margin-left:20px;margin-bottom:4px}.mapping-builder__name{margin-bottom:2px}.mapping-builder__name--clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.mapping-builder__name--clickable:hover{color:#0f62fe}.mapping-builder__chevron{font-size:.7em;margin-right:4px}.mapping-builder__field-name{font-weight:500;font-size:.9em}.mapping-builder__required{color:#da1e28;margin-left:2px}.mapping-builder__type{color:#8d8d8d;font-size:.8em;margin-left:4px}.mapping-builder__value{display:flex;align-items:center;gap:4px}.mapping-builder__input{flex:1;padding:6px 8px;border:1px solid #e0e0e0;border-radius:4px;font-size:.85em;font-family:IBM Plex Mono,monospace}.mapping-builder__input:focus{outline:2px solid #0f62fe;border-color:#0f62fe}.mapping-builder__input--raw{background:#f4f4f4}.mapping-builder__mode-toggle{width:28px;height:28px;border:1px solid #e0e0e0;border-radius:4px;background:#fff;cursor:pointer;font-family:monospace;font-size:.8em;display:flex;align-items:center;justify-content:center}.mapping-builder__mode-toggle:hover{background:#f4f4f4}.mapping-builder__children{border-left:2px solid #e0e0e0;padding-left:12px;margin-top:4px}\n"] }]
1236
+ }], propDecorators: { expression: [{
590
1237
  type: Input
591
- }], caseDefinitionKey: [{
1238
+ }], templateFields: [{
592
1239
  type: Input
593
- }], processVariables: [{
1240
+ }], suggestions: [{
594
1241
  type: Input
595
1242
  }], disabled: [{
596
1243
  type: Input
597
- }], valueChange: [{
1244
+ }], expressionChange: [{
598
1245
  type: Output
599
1246
  }] } });
600
1247
 
601
- /**
602
- * Top-level wrapper that hosts FieldTreeComponent instances for each top-level template field.
603
- * Manages the full nested mapping object, completeness tracking, and emits mapping changes.
604
- */
605
- class DataMappingTreeComponent {
606
- pluginId;
1248
+ class MappingPreviewComponent {
1249
+ epistolaPluginService;
1250
+ expression = '';
607
1251
  templateFields = [];
608
- prefillMapping = {};
609
- disabled = false;
610
1252
  caseDefinitionKey = null;
611
- processVariables = [];
612
- mappingChange = new EventEmitter();
613
- requiredFieldsStatus = new EventEmitter();
614
- mapping = {};
1253
+ documentId = '';
1254
+ loading = false;
1255
+ result = null;
1256
+ expectedJson = '';
1257
+ missingRequired = [];
1258
+ destroy$ = new Subject();
1259
+ evaluate$ = new Subject();
1260
+ constructor(epistolaPluginService) {
1261
+ this.epistolaPluginService = epistolaPluginService;
1262
+ this.evaluate$.pipe(debounceTime(300), takeUntil(this.destroy$)).subscribe(() => {
1263
+ this.doEvaluate();
1264
+ });
1265
+ }
615
1266
  ngOnChanges(changes) {
616
- if (changes['prefillMapping']) {
617
- const mapping = this.prefillMapping;
618
- if (mapping && Object.keys(mapping).length > 0) {
619
- this.mapping = { ...mapping };
620
- }
1267
+ if (changes['templateFields']) {
1268
+ this.expectedJson = this.buildExpectedJson();
621
1269
  }
622
- if (changes['templateFields'] || changes['prefillMapping']) {
623
- this.emitRequiredFieldsStatus();
1270
+ if (changes['expression'] || changes['templateFields']) {
1271
+ this.checkMissingRequired();
624
1272
  }
625
1273
  }
626
- onFieldValueChange(fieldName, value) {
627
- if (value === undefined || value === null || value === '') {
628
- const { [fieldName]: _, ...rest } = this.mapping;
629
- this.mapping = rest;
630
- }
631
- else {
632
- this.mapping = { ...this.mapping, [fieldName]: value };
633
- }
634
- this.mappingChange.emit(this.mapping);
635
- this.emitRequiredFieldsStatus();
1274
+ ngOnDestroy() {
1275
+ this.destroy$.next();
1276
+ this.destroy$.complete();
636
1277
  }
637
- getFieldValue(fieldName) {
638
- return this.mapping[fieldName];
1278
+ runPreview() {
1279
+ this.evaluate$.next();
639
1280
  }
640
- emitRequiredFieldsStatus() {
641
- const stats = countRequiredMapped(this.templateFields, this.mapping);
642
- this.requiredFieldsStatus.emit(stats);
1281
+ doEvaluate() {
1282
+ if (!this.documentId || !this.expression)
1283
+ return;
1284
+ this.loading = true;
1285
+ this.epistolaPluginService.evaluateMapping(this.expression, this.documentId).subscribe({
1286
+ next: (result) => {
1287
+ this.result = result;
1288
+ this.loading = false;
1289
+ if (result.success) {
1290
+ this.checkMissingRequired();
1291
+ }
1292
+ },
1293
+ error: (err) => {
1294
+ this.result = { success: false, result: null, error: err.message || 'Request failed' };
1295
+ this.loading = false;
1296
+ },
1297
+ });
643
1298
  }
644
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DataMappingTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
645
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", 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" }, usesOnChanges: true, 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1299
+ buildExpectedJson() {
1300
+ if (!this.templateFields || this.templateFields.length === 0)
1301
+ return '{}';
1302
+ const obj = {};
1303
+ for (const field of this.templateFields) {
1304
+ const type = field.type || 'any';
1305
+ obj[field.name] = field.required ? `${type} (required)` : type;
1306
+ }
1307
+ return JSON.stringify(obj, null, 2);
1308
+ }
1309
+ checkMissingRequired() {
1310
+ if (!this.templateFields) {
1311
+ this.missingRequired = [];
1312
+ return;
1313
+ }
1314
+ const requiredFields = this.templateFields.filter((f) => f.required).map((f) => f.name);
1315
+ if (!this.result?.success || !this.result.result) {
1316
+ // If no evaluation result yet, check statically from expression
1317
+ this.missingRequired = requiredFields;
1318
+ return;
1319
+ }
1320
+ const producedKeys = new Set(Object.keys(this.result.result));
1321
+ this.missingRequired = requiredFields.filter((f) => !producedKeys.has(f));
1322
+ }
1323
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MappingPreviewComponent, deps: [{ token: EpistolaPluginService }], target: i0.ɵɵFactoryTarget.Component });
1324
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: MappingPreviewComponent, isStandalone: true, selector: "epistola-mapping-preview", inputs: { expression: "expression", templateFields: "templateFields", caseDefinitionKey: "caseDefinitionKey" }, usesOnChanges: true, ngImport: i0, template: `
1325
+ <div class="preview">
1326
+ <div class="preview__header">
1327
+ <span class="preview__title">{{
1328
+ 'previewTitle' | pluginTranslate: 'epistola' | async
1329
+ }}</span>
1330
+ <div class="preview__controls">
1331
+ <input
1332
+ type="text"
1333
+ class="preview__doc-input"
1334
+ [ngModel]="documentId"
1335
+ (ngModelChange)="documentId = $event"
1336
+ [placeholder]="'previewDocPlaceholder' | pluginTranslate: 'epistola' | async"
1337
+ />
1338
+ <button
1339
+ class="preview__run-btn"
1340
+ (click)="runPreview()"
1341
+ [disabled]="!documentId || !expression || loading"
1342
+ >
1343
+ &#x25B6;
1344
+ </button>
1345
+ </div>
1346
+ </div>
1347
+
1348
+ <div class="preview__panels">
1349
+ <!-- Expected structure -->
1350
+ <div class="preview__panel">
1351
+ <div class="preview__panel-label">
1352
+ {{ 'previewExpected' | pluginTranslate: 'epistola' | async }}
1353
+ </div>
1354
+ <pre class="preview__code">{{ expectedJson }}</pre>
1355
+ </div>
1356
+
1357
+ <!-- Produced output -->
1358
+ <div class="preview__panel">
1359
+ <div class="preview__panel-label">
1360
+ {{ 'previewProduced' | pluginTranslate: 'epistola' | async }}
1361
+ </div>
1362
+ <div *ngIf="loading" class="preview__loading">...</div>
1363
+ <pre *ngIf="!loading && result?.success" class="preview__code">{{
1364
+ result.result | json
1365
+ }}</pre>
1366
+ <div *ngIf="!loading && result && !result.success" class="preview__error">
1367
+ {{ result.error }}
1368
+ </div>
1369
+ <div *ngIf="!loading && !result" class="preview__placeholder">
1370
+ {{ 'previewRunHint' | pluginTranslate: 'epistola' | async }}
1371
+ </div>
1372
+ </div>
1373
+ </div>
1374
+
1375
+ <!-- Missing fields warning -->
1376
+ <div *ngIf="missingRequired.length > 0" class="preview__warnings">
1377
+ <span class="preview__warning-icon">&#x26A0;</span>
1378
+ {{ 'previewMissing' | pluginTranslate: 'epistola' | async }}:
1379
+ <strong>{{ missingRequired.join(', ') }}</strong>
1380
+ </div>
1381
+ </div>
1382
+ `, isInline: true, styles: [".preview{border:1px solid #e0e0e0;border-radius:4px;margin-top:16px;overflow:hidden}.preview__header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#f4f4f4;border-bottom:1px solid #e0e0e0}.preview__title{font-weight:600;font-size:.85em}.preview__controls{display:flex;gap:4px}.preview__doc-input{padding:4px 8px;border:1px solid #e0e0e0;border-radius:4px;font-size:.8em;width:220px;font-family:monospace}.preview__run-btn{padding:4px 10px;border:1px solid #0f62fe;border-radius:4px;background:#0f62fe;color:#fff;cursor:pointer;font-size:.8em}.preview__run-btn:disabled{opacity:.4;cursor:not-allowed}.preview__panels{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:#e0e0e0}.preview__panel{background:#fff;padding:8px 12px;min-height:80px}.preview__panel-label{font-size:.75em;color:#6f6f6f;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}.preview__code{font-family:IBM Plex Mono,monospace;font-size:.8em;line-height:1.4;margin:0;white-space:pre-wrap;word-break:break-word}.preview__loading{color:#8d8d8d}.preview__error{color:#da1e28;font-size:.85em}.preview__placeholder{color:#8d8d8d;font-size:.85em;font-style:italic}.preview__warnings{padding:8px 12px;background:#fff8e1;border-top:1px solid #e0e0e0;font-size:.85em;color:#663c00}.preview__warning-icon{margin-right:4px}\n"], 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: "pipe", type: i1$1.JsonPipe, name: "json" }, { 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" }] });
646
1383
  }
647
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DataMappingTreeComponent, decorators: [{
1384
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MappingPreviewComponent, decorators: [{
648
1385
  type: Component,
649
- args: [{ selector: 'epistola-data-mapping-tree', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
650
- CommonModule,
651
- PluginTranslatePipeModule,
652
- FieldTreeComponent
653
- ], 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"] }]
654
- }], propDecorators: { pluginId: [{
1386
+ args: [{ selector: 'epistola-mapping-preview', standalone: true, imports: [CommonModule, FormsModule, PluginTranslatePipeModule], template: `
1387
+ <div class="preview">
1388
+ <div class="preview__header">
1389
+ <span class="preview__title">{{
1390
+ 'previewTitle' | pluginTranslate: 'epistola' | async
1391
+ }}</span>
1392
+ <div class="preview__controls">
1393
+ <input
1394
+ type="text"
1395
+ class="preview__doc-input"
1396
+ [ngModel]="documentId"
1397
+ (ngModelChange)="documentId = $event"
1398
+ [placeholder]="'previewDocPlaceholder' | pluginTranslate: 'epistola' | async"
1399
+ />
1400
+ <button
1401
+ class="preview__run-btn"
1402
+ (click)="runPreview()"
1403
+ [disabled]="!documentId || !expression || loading"
1404
+ >
1405
+ &#x25B6;
1406
+ </button>
1407
+ </div>
1408
+ </div>
1409
+
1410
+ <div class="preview__panels">
1411
+ <!-- Expected structure -->
1412
+ <div class="preview__panel">
1413
+ <div class="preview__panel-label">
1414
+ {{ 'previewExpected' | pluginTranslate: 'epistola' | async }}
1415
+ </div>
1416
+ <pre class="preview__code">{{ expectedJson }}</pre>
1417
+ </div>
1418
+
1419
+ <!-- Produced output -->
1420
+ <div class="preview__panel">
1421
+ <div class="preview__panel-label">
1422
+ {{ 'previewProduced' | pluginTranslate: 'epistola' | async }}
1423
+ </div>
1424
+ <div *ngIf="loading" class="preview__loading">...</div>
1425
+ <pre *ngIf="!loading && result?.success" class="preview__code">{{
1426
+ result.result | json
1427
+ }}</pre>
1428
+ <div *ngIf="!loading && result && !result.success" class="preview__error">
1429
+ {{ result.error }}
1430
+ </div>
1431
+ <div *ngIf="!loading && !result" class="preview__placeholder">
1432
+ {{ 'previewRunHint' | pluginTranslate: 'epistola' | async }}
1433
+ </div>
1434
+ </div>
1435
+ </div>
1436
+
1437
+ <!-- Missing fields warning -->
1438
+ <div *ngIf="missingRequired.length > 0" class="preview__warnings">
1439
+ <span class="preview__warning-icon">&#x26A0;</span>
1440
+ {{ 'previewMissing' | pluginTranslate: 'epistola' | async }}:
1441
+ <strong>{{ missingRequired.join(', ') }}</strong>
1442
+ </div>
1443
+ </div>
1444
+ `, styles: [".preview{border:1px solid #e0e0e0;border-radius:4px;margin-top:16px;overflow:hidden}.preview__header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#f4f4f4;border-bottom:1px solid #e0e0e0}.preview__title{font-weight:600;font-size:.85em}.preview__controls{display:flex;gap:4px}.preview__doc-input{padding:4px 8px;border:1px solid #e0e0e0;border-radius:4px;font-size:.8em;width:220px;font-family:monospace}.preview__run-btn{padding:4px 10px;border:1px solid #0f62fe;border-radius:4px;background:#0f62fe;color:#fff;cursor:pointer;font-size:.8em}.preview__run-btn:disabled{opacity:.4;cursor:not-allowed}.preview__panels{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:#e0e0e0}.preview__panel{background:#fff;padding:8px 12px;min-height:80px}.preview__panel-label{font-size:.75em;color:#6f6f6f;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}.preview__code{font-family:IBM Plex Mono,monospace;font-size:.8em;line-height:1.4;margin:0;white-space:pre-wrap;word-break:break-word}.preview__loading{color:#8d8d8d}.preview__error{color:#da1e28;font-size:.85em}.preview__placeholder{color:#8d8d8d;font-size:.85em;font-style:italic}.preview__warnings{padding:8px 12px;background:#fff8e1;border-top:1px solid #e0e0e0;font-size:.85em;color:#663c00}.preview__warning-icon{margin-right:4px}\n"] }]
1445
+ }], ctorParameters: () => [{ type: EpistolaPluginService }], propDecorators: { expression: [{
655
1446
  type: Input
656
1447
  }], templateFields: [{
657
1448
  type: Input
658
- }], prefillMapping: [{
659
- type: Input
660
- }], disabled: [{
661
- type: Input
662
1449
  }], caseDefinitionKey: [{
663
1450
  type: Input
664
- }], processVariables: [{
665
- type: Input
666
- }], mappingChange: [{
667
- type: Output
668
- }], requiredFieldsStatus: [{
669
- type: Output
670
1451
  }] } });
671
1452
 
672
1453
  class GenerateDocumentConfigurationComponent {
@@ -686,10 +1467,13 @@ class GenerateDocumentConfigurationComponent {
686
1467
  variants$ = new BehaviorSubject(initialResource([]));
687
1468
  environments$ = new BehaviorSubject(initialResource([]));
688
1469
  templateFields$ = new BehaviorSubject(initialResource([]));
689
- dataMapping$ = new BehaviorSubject({});
1470
+ dataMapping$ = new BehaviorSubject('');
1471
+ mappingMode = 'simple';
1472
+ toolsCollapsed = true;
1473
+ activeToolTab = 'preview';
690
1474
  outputFormatOptions = [
691
1475
  { id: 'PDF', text: 'PDF' },
692
- { id: 'HTML', text: 'HTML' }
1476
+ { id: 'HTML', text: 'HTML' },
693
1477
  ];
694
1478
  selectedCatalogId$ = new BehaviorSubject('');
695
1479
  /** Composite ID: "catalogId/templateId" */
@@ -700,6 +1484,8 @@ class GenerateDocumentConfigurationComponent {
700
1484
  availableAttributeKeys = [];
701
1485
  caseDefinitionKey = null;
702
1486
  processVariables = [];
1487
+ expressionFunctions = [];
1488
+ variableSuggestions = null;
703
1489
  requiredFieldsStatus = { mapped: 0, total: 0 };
704
1490
  prefillDataMapping = {};
705
1491
  destroy$ = new Subject();
@@ -719,6 +1505,7 @@ class GenerateDocumentConfigurationComponent {
719
1505
  this.initContext();
720
1506
  this.initPluginConfiguration();
721
1507
  this.initCascade();
1508
+ this.loadExpressionFunctions();
722
1509
  this.openSaveSubscription();
723
1510
  }
724
1511
  ngOnDestroy() {
@@ -745,8 +1532,8 @@ class GenerateDocumentConfigurationComponent {
745
1532
  }
746
1533
  this.handleValid(formValue);
747
1534
  }
748
- onDataMappingChange(mapping) {
749
- this.dataMapping$.next(mapping);
1535
+ onDataMappingChange(expression) {
1536
+ this.dataMapping$.next(expression);
750
1537
  const currentFormValue = this.formValue$.getValue();
751
1538
  if (currentFormValue) {
752
1539
  this.handleValid(currentFormValue);
@@ -767,7 +1554,10 @@ class GenerateDocumentConfigurationComponent {
767
1554
  this.revalidate();
768
1555
  }
769
1556
  addAttributeEntry() {
770
- this.variantAttributeEntries = [...this.variantAttributeEntries, { key: '', value: '', required: true }];
1557
+ this.variantAttributeEntries = [
1558
+ ...this.variantAttributeEntries,
1559
+ { key: '', value: '', required: true },
1560
+ ];
771
1561
  this.revalidate();
772
1562
  }
773
1563
  removeAttributeEntry(index) {
@@ -817,7 +1607,9 @@ class GenerateDocumentConfigurationComponent {
817
1607
  }
818
1608
  initContext() {
819
1609
  if (this.context$) {
820
- this.context$.pipe(takeUntil(this.destroy$), filter(([context]) => context === 'case')).subscribe(([, params]) => {
1610
+ this.context$
1611
+ .pipe(takeUntil$1(this.destroy$), filter(([context]) => context === 'case'))
1612
+ .subscribe(([, params]) => {
821
1613
  this.caseDefinitionKey = params.caseDefinitionKey;
822
1614
  this.cdr.markForCheck();
823
1615
  });
@@ -826,10 +1618,12 @@ class GenerateDocumentConfigurationComponent {
826
1618
  initPluginConfiguration() {
827
1619
  const sources = [];
828
1620
  if (this.selectedPluginConfigurationData$) {
829
- sources.push(this.selectedPluginConfigurationData$.pipe(filter(config => !!config?.configurationId), map(config => config.configurationId)));
1621
+ sources.push(this.selectedPluginConfigurationData$.pipe(filter((config) => !!config?.configurationId), map((config) => config.configurationId)));
830
1622
  }
831
- sources.push(this.processLinkStateService.selectedProcessLink$.pipe(filter(processLink => !!processLink?.pluginConfigurationId), map(processLink => processLink.pluginConfigurationId)));
832
- merge(...sources).pipe(takeUntil(this.destroy$)).subscribe(configurationId => {
1623
+ sources.push(this.processLinkStateService.selectedProcessLink$.pipe(filter((processLink) => !!processLink?.pluginConfigurationId), map((processLink) => processLink.pluginConfigurationId)));
1624
+ merge(...sources)
1625
+ .pipe(takeUntil$1(this.destroy$))
1626
+ .subscribe((configurationId) => {
833
1627
  this.pluginConfigurationId$.next(configurationId);
834
1628
  });
835
1629
  }
@@ -844,87 +1638,134 @@ class GenerateDocumentConfigurationComponent {
844
1638
  * prefill + templateFields loaded → seed dataMapping
845
1639
  */
846
1640
  initCascade() {
847
- const configId$ = this.pluginConfigurationId$.pipe(filter(id => !!id), distinctUntilChanged());
1641
+ const configId$ = this.pluginConfigurationId$.pipe(filter((id) => !!id), distinctUntilChanged());
848
1642
  // ── Catalogs: load when pluginConfigurationId changes ──
849
- configId$.pipe(takeUntil(this.destroy$), tap(() => this.catalogs$.next(loadingResource(this.catalogs$.getValue().data))), switchMap(configurationId => this.epistolaPluginService.getCatalogs(configurationId).pipe(map(catalogs => successResource(catalogs.map(c => ({ id: c.id, text: c.name })))), catchError(() => of(errorResource([], 'Failed to load catalogs')))))).subscribe(resource => this.catalogs$.next(resource));
1643
+ configId$
1644
+ .pipe(takeUntil$1(this.destroy$), tap(() => this.catalogs$.next(loadingResource(this.catalogs$.getValue().data))), switchMap((configurationId) => this.epistolaPluginService.getCatalogs(configurationId).pipe(map((catalogs) => successResource(catalogs.map((c) => ({ id: c.id, text: c.name })))), catchError(() => of(errorResource([], 'Failed to load catalogs'))))))
1645
+ .subscribe((resource) => this.catalogs$.next(resource));
850
1646
  // ── Environments: load when pluginConfigurationId changes (independent) ──
851
- configId$.pipe(takeUntil(this.destroy$), tap(() => this.environments$.next(loadingResource(this.environments$.getValue().data))), switchMap(configurationId => this.epistolaPluginService.getEnvironments(configurationId).pipe(map(envs => successResource(envs.map(e => ({ id: e.id, text: e.name })))), catchError(() => of(errorResource([], 'Failed to load environments')))))).subscribe(resource => this.environments$.next(resource));
1647
+ configId$
1648
+ .pipe(takeUntil$1(this.destroy$), tap(() => this.environments$.next(loadingResource(this.environments$.getValue().data))), switchMap((configurationId) => this.epistolaPluginService.getEnvironments(configurationId).pipe(map((envs) => successResource(envs.map((e) => ({ id: e.id, text: e.name })))), catchError(() => of(errorResource([], 'Failed to load environments'))))))
1649
+ .subscribe((resource) => this.environments$.next(resource));
852
1650
  // ── Seed selectedCatalogId$ from prefill once catalogs are loaded ──
853
1651
  combineLatest([
854
- this.prefill$.pipe(filter(config => !!config?.catalogId)),
855
- this.catalogs$.pipe(filter(c => !c.loading && c.data.length > 0))
856
- ]).pipe(takeUntil(this.destroy$), take$1(1)).subscribe(([config]) => {
1652
+ this.prefill$.pipe(filter((config) => !!config?.catalogId)),
1653
+ this.catalogs$.pipe(filter((c) => !c.loading && c.data.length > 0)),
1654
+ ])
1655
+ .pipe(takeUntil$1(this.destroy$), take$1(1))
1656
+ .subscribe(([config]) => {
857
1657
  this.selectedCatalogId$.next(config.catalogId);
858
1658
  });
859
1659
  // ── Templates: load when catalogId changes ──
860
- const catalogId$ = this.selectedCatalogId$.pipe(filter(id => !!id), distinctUntilChanged());
861
- combineLatest([configId$, catalogId$]).pipe(takeUntil(this.destroy$), tap(() => this.templates$.next(loadingResource(this.templates$.getValue().data))), switchMap(([configurationId, catalogId]) => this.epistolaPluginService.getTemplates(configurationId, catalogId).pipe(map(templates => successResource(templates.map(t => ({ id: t.id, text: t.name })))), catchError(() => of(errorResource([], 'Failed to load templates')))))).subscribe(resource => this.templates$.next(resource));
1660
+ const catalogId$ = this.selectedCatalogId$.pipe(filter((id) => !!id), distinctUntilChanged());
1661
+ combineLatest([configId$, catalogId$])
1662
+ .pipe(takeUntil$1(this.destroy$), tap(() => this.templates$.next(loadingResource(this.templates$.getValue().data))), switchMap(([configurationId, catalogId]) => this.epistolaPluginService.getTemplates(configurationId, catalogId).pipe(map((templates) => successResource(templates.map((t) => ({ id: t.id, text: t.name })))), catchError(() => of(errorResource([], 'Failed to load templates'))))))
1663
+ .subscribe((resource) => this.templates$.next(resource));
862
1664
  // ── Attributes: load when catalogId changes ──
863
- combineLatest([configId$, catalogId$]).pipe(takeUntil(this.destroy$), switchMap(([configurationId, catalogId]) => this.epistolaPluginService.getAttributes(configurationId, catalogId).pipe(catchError(() => of([]))))).subscribe(attributes => {
864
- this.availableAttributeKeys = attributes.map(a => a.key).sort();
1665
+ combineLatest([configId$, catalogId$])
1666
+ .pipe(takeUntil$1(this.destroy$), switchMap(([configurationId, catalogId]) => this.epistolaPluginService
1667
+ .getAttributes(configurationId, catalogId)
1668
+ .pipe(catchError(() => of([])))))
1669
+ .subscribe((attributes) => {
1670
+ this.availableAttributeKeys = attributes.map((a) => a.key).sort();
865
1671
  this.cdr.markForCheck();
866
1672
  });
867
1673
  // ── Seed selectedTemplateId$ from prefill once templates are loaded ──
868
1674
  combineLatest([
869
- this.prefill$.pipe(filter(config => !!config?.templateId)),
870
- this.templates$.pipe(filter(t => !t.loading && t.data.length > 0))
871
- ]).pipe(takeUntil(this.destroy$), take$1(1)).subscribe(([config]) => {
1675
+ this.prefill$.pipe(filter((config) => !!config?.templateId)),
1676
+ this.templates$.pipe(filter((t) => !t.loading && t.data.length > 0)),
1677
+ ])
1678
+ .pipe(takeUntil$1(this.destroy$), take$1(1))
1679
+ .subscribe(([config]) => {
872
1680
  this.selectedTemplateId$.next(config.templateId);
873
1681
  });
874
1682
  // ── Variants: load when templateId changes ──
875
- const templateId$ = this.selectedTemplateId$.pipe(filter(id => !!id), distinctUntilChanged());
876
- combineLatest([configId$, catalogId$, templateId$]).pipe(takeUntil(this.destroy$), tap(() => this.variants$.next(loadingResource(this.variants$.getValue().data))), switchMap(([configurationId, catalogId, templateId]) => this.epistolaPluginService.getVariants(configurationId, templateId, catalogId).pipe(map(variants => successResource(variants.map(v => ({ id: v.id, text: v.name + this.formatAttributes(v.attributes) })))), catchError(() => of(errorResource([], 'Failed to load variants')))))).subscribe(resource => this.variants$.next(resource));
1683
+ const templateId$ = this.selectedTemplateId$.pipe(filter((id) => !!id), distinctUntilChanged());
1684
+ combineLatest([configId$, catalogId$, templateId$])
1685
+ .pipe(takeUntil$1(this.destroy$), tap(() => this.variants$.next(loadingResource(this.variants$.getValue().data))), switchMap(([configurationId, catalogId, templateId]) => this.epistolaPluginService.getVariants(configurationId, templateId, catalogId).pipe(map((variants) => successResource(variants.map((v) => ({
1686
+ id: v.id,
1687
+ text: v.name + this.formatAttributes(v.attributes),
1688
+ })))), catchError(() => of(errorResource([], 'Failed to load variants'))))))
1689
+ .subscribe((resource) => this.variants$.next(resource));
877
1690
  // ── Template fields: load when templateId changes ──
878
- combineLatest([configId$, catalogId$, templateId$]).pipe(takeUntil(this.destroy$), tap(() => {
1691
+ combineLatest([configId$, catalogId$, templateId$])
1692
+ .pipe(takeUntil$1(this.destroy$), tap(() => {
879
1693
  this.templateFields$.next(loadingResource(this.templateFields$.getValue().data));
880
1694
  this.loadProcessVariables();
881
- }), switchMap(([configurationId, catalogId, templateId]) => this.epistolaPluginService.getTemplateDetails(configurationId, templateId, catalogId).pipe(map(details => successResource(details.fields || [])), catchError(() => of(errorResource([], 'Failed to load template fields')))))).subscribe(resource => this.templateFields$.next(resource));
1695
+ this.loadVariableSuggestions();
1696
+ }), switchMap(([configurationId, catalogId, templateId]) => this.epistolaPluginService
1697
+ .getTemplateDetails(configurationId, templateId, catalogId)
1698
+ .pipe(map((details) => successResource(details.fields || [])), catchError(() => of(errorResource([], 'Failed to load template fields'))))))
1699
+ .subscribe((resource) => this.templateFields$.next(resource));
882
1700
  // ── Seed variant + dataMapping from prefill once templateFields are loaded ──
883
1701
  combineLatest([
884
- this.prefill$.pipe(filter(config => !!config?.templateId)),
885
- this.templateFields$.pipe(filter(tf => !tf.loading && tf.data.length > 0))
886
- ]).pipe(takeUntil(this.destroy$), take$1(1)).subscribe(([config]) => {
1702
+ this.prefill$.pipe(filter((config) => !!config?.templateId)),
1703
+ this.templateFields$.pipe(filter((tf) => !tf.loading && tf.data.length > 0)),
1704
+ ])
1705
+ .pipe(takeUntil$1(this.destroy$), take$1(1))
1706
+ .subscribe(([config]) => {
887
1707
  if (!config)
888
1708
  return;
889
1709
  // Apply variant prefill
890
- if (config.variantAttributes && (Array.isArray(config.variantAttributes) ? config.variantAttributes.length > 0 : Object.keys(config.variantAttributes).length > 0)) {
1710
+ if (config.variantAttributes &&
1711
+ (Array.isArray(config.variantAttributes)
1712
+ ? config.variantAttributes.length > 0
1713
+ : Object.keys(config.variantAttributes).length > 0)) {
891
1714
  this.variantSelectionMode = 'attributes';
892
1715
  if (Array.isArray(config.variantAttributes)) {
893
- this.variantAttributeEntries = config.variantAttributes
894
- .map(e => ({ key: e.key, value: e.value, required: e.required !== false }));
1716
+ this.variantAttributeEntries = config.variantAttributes.map((e) => ({
1717
+ key: e.key,
1718
+ value: e.value,
1719
+ required: e.required !== false,
1720
+ }));
895
1721
  }
896
1722
  else {
897
- this.variantAttributeEntries = Object.entries(config.variantAttributes)
898
- .map(([key, value]) => ({ key, value: String(value), required: true }));
1723
+ this.variantAttributeEntries = Object.entries(config.variantAttributes).map(([key, value]) => ({ key, value: String(value), required: true }));
899
1724
  }
900
1725
  }
901
1726
  else if (config.variantId) {
902
1727
  this.variantSelectionMode = 'explicit';
903
1728
  this.selectedVariantId$.next(config.variantId);
904
1729
  }
905
- // Apply dataMapping prefill templateFields are guaranteed loaded at this point.
906
- // Use setTimeout to ensure the tree component exists in the DOM (after *ngIf resolves)
907
- // before setting the prefill, so ngOnChanges fires correctly on the child.
1730
+ // Apply dataMapping prefill (JSONata expression string)
908
1731
  if (config.dataMapping) {
909
- this.dataMapping$.next(config.dataMapping);
910
- setTimeout(() => {
911
- this.prefillDataMapping = { ...config.dataMapping };
912
- this.cdr.detectChanges();
913
- });
1732
+ const expr = typeof config.dataMapping === 'string' ? config.dataMapping : '';
1733
+ this.dataMapping$.next(expr);
914
1734
  }
915
1735
  else {
916
1736
  this.cdr.detectChanges();
917
1737
  }
918
1738
  });
919
1739
  }
1740
+ loadExpressionFunctions() {
1741
+ this.epistolaPluginService
1742
+ .getExpressionFunctions()
1743
+ .pipe(takeUntil$1(this.destroy$), catchError(() => of([])))
1744
+ .subscribe((functions) => {
1745
+ this.expressionFunctions = functions;
1746
+ this.cdr.markForCheck();
1747
+ });
1748
+ }
920
1749
  loadProcessVariables() {
921
1750
  if (this.caseDefinitionKey) {
922
- this.epistolaPluginService.getProcessVariables(this.caseDefinitionKey).pipe(takeUntil(this.destroy$), catchError(() => of([]))).subscribe(variables => {
1751
+ this.epistolaPluginService
1752
+ .getProcessVariables(this.caseDefinitionKey)
1753
+ .pipe(takeUntil$1(this.destroy$), catchError(() => of([])))
1754
+ .subscribe((variables) => {
923
1755
  this.processVariables = variables;
924
1756
  this.cdr.markForCheck();
925
1757
  });
926
1758
  }
927
1759
  }
1760
+ loadVariableSuggestions() {
1761
+ this.epistolaPluginService
1762
+ .getVariableSuggestions(this.caseDefinitionKey ?? undefined, this.caseDefinitionKey ?? undefined)
1763
+ .pipe(takeUntil$1(this.destroy$), catchError(() => of({ doc: [], pv: [] })))
1764
+ .subscribe((suggestions) => {
1765
+ this.variableSuggestions = suggestions;
1766
+ this.cdr.markForCheck();
1767
+ });
1768
+ }
928
1769
  handleValid(formValue) {
929
1770
  const baseComplete = !!(this.selectedCatalogId$.getValue() &&
930
1771
  formValue?.templateId &&
@@ -933,7 +1774,7 @@ class GenerateDocumentConfigurationComponent {
933
1774
  formValue?.resultProcessVariable);
934
1775
  let variantValid = true;
935
1776
  if (this.variantSelectionMode === 'attributes' && this.variantAttributeEntries.length > 0) {
936
- variantValid = this.variantAttributeEntries.every(e => !!e.key && !!e.value);
1777
+ variantValid = this.variantAttributeEntries.every((e) => !!e.key && !!e.value);
937
1778
  }
938
1779
  const requiredFieldsMapped = this.requiredFieldsStatus.total === 0 ||
939
1780
  this.requiredFieldsStatus.mapped === this.requiredFieldsStatus.total;
@@ -957,15 +1798,15 @@ class GenerateDocumentConfigurationComponent {
957
1798
  outputFormat: formValue.outputFormat,
958
1799
  filename: formValue.filename,
959
1800
  correlationId: formValue.correlationId || undefined,
960
- resultProcessVariable: formValue.resultProcessVariable
1801
+ resultProcessVariable: formValue.resultProcessVariable,
961
1802
  };
962
1803
  if (this.variantSelectionMode === 'explicit') {
963
1804
  config.variantId = formValue.variantId;
964
1805
  }
965
1806
  else {
966
1807
  config.variantAttributes = this.variantAttributeEntries
967
- .filter(e => e.key && e.value)
968
- .map(e => ({ key: e.key, value: e.value, required: e.required }));
1808
+ .filter((e) => e.key && e.value)
1809
+ .map((e) => ({ key: e.key, value: e.value, required: e.required }));
969
1810
  }
970
1811
  this.configuration.emit(config);
971
1812
  }
@@ -973,7 +1814,7 @@ class GenerateDocumentConfigurationComponent {
973
1814
  });
974
1815
  }
975
1816
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, deps: [{ token: EpistolaPluginService }, { token: i2$2.ProcessLinkStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
976
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", 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 catalogs: catalogs$ | async,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedCatalogId: selectedCatalogId$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"catalogId\"\n [title]=\"'catalogId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'catalogIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.catalogs.data\"\n [defaultSelectionId]=\"obs.prefill?.catalogId\"\n [disabled]=\"obs.disabled || obs.catalogs.loading\"\n [required]=\"true\"\n [loading]=\"obs.catalogs.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.catalogs.error\" class=\"loading-error\">{{ obs.catalogs.error }}</div>\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.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading || !obs.selectedCatalogId\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\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.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\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 <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>{{ 'attributeKey' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">{{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}</option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\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 <button type=\"button\" class=\"custom-key-cancel\" (click)=\"cancelCustomKey(entry)\" [disabled]=\"obs.disabled\">&times;</button>\n </div>\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 <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{ (entry.required ? 'attributeRequired' : 'attributePreferred') | pluginTranslate: pluginId | async }}</span>\n </label>\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.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\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<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">{{ templateFieldsError }}</div>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [disabled]=\"!!(disabled$ | async)\"\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: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.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}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.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.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { 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.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { 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", "warn", "warnText", "dataTestId"], outputs: ["selectedChange"] }, { kind: "component", type: DataMappingTreeComponent, selector: "epistola-data-mapping-tree", inputs: ["pluginId", "templateFields", "prefillMapping", "disabled", "caseDefinitionKey", "processVariables"], outputs: ["mappingChange", "requiredFieldsStatus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1817
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", 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 catalogs: catalogs$ | async,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedCatalogId: selectedCatalogId$ | async,\n selectedTemplateId: selectedTemplateId$ | async,\n } as obs\"\n>\n <v-select\n name=\"catalogId\"\n [title]=\"'catalogId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'catalogIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.catalogs.data\"\n [defaultSelectionId]=\"obs.prefill?.catalogId\"\n [disabled]=\"obs.disabled || obs.catalogs.loading\"\n [required]=\"true\"\n [loading]=\"obs.catalogs.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.catalogs.error\" class=\"loading-error\">{{ obs.catalogs.error }}</div>\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.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading || !obs.selectedCatalogId\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{\n 'variantSelectionMode' | pluginTranslate: pluginId | async\n }}</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 >\n {{ 'selectByVariant' | pluginTranslate: pluginId | async }}\n </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 >\n {{ 'selectByAttributes' | pluginTranslate: pluginId | async }}\n </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.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\n\n <!-- Attribute-based variant selection -->\n <div\n *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\"\n class=\"variant-attributes-section\"\n >\n <label class=\"variant-attributes-label\">{{\n 'variantAttributes' | pluginTranslate: pluginId | async\n }}</label>\n <div class=\"variant-attributes-list\">\n <div\n *ngFor=\"let entry of variantAttributeEntries; let i = index\"\n class=\"variant-attribute-row\"\n >\n <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>\n {{ 'attributeKey' | pluginTranslate: pluginId | async }}\n </option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">\n {{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}\n </option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\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 <button\n type=\"button\"\n class=\"custom-key-cancel\"\n (click)=\"cancelCustomKey(entry)\"\n [disabled]=\"obs.disabled\"\n >\n &times;\n </button>\n </div>\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 <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{\n (entry.required ? 'attributeRequired' : 'attributePreferred')\n | pluginTranslate: pluginId\n | async\n }}</span>\n </label>\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 >\n &times;\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >\n + {{ 'addAttribute' | pluginTranslate: pluginId | async }}\n </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.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\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<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">\n {{ templateFieldsError }}\n</div>\n\n<div *ngIf=\"selectedTemplateId$ | async\" class=\"mapping-section\">\n <h5 class=\"mapping-section__title\">\n {{ 'dataMappingTitle' | pluginTranslate: pluginId | async }}\n </h5>\n <p class=\"mapping-section__description\">\n {{ 'dataMappingDescription' | pluginTranslate: pluginId | async }}\n </p>\n <div class=\"mapping-mode-toggle\">\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'simple'\"\n (click)=\"mappingMode = 'simple'\"\n >\n {{ 'mappingModeSimple' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'advanced'\"\n (click)=\"mappingMode = 'advanced'\"\n >\n {{ 'mappingModeAdvanced' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <!-- Editor area (full width) -->\n <epistola-mapping-builder\n *ngIf=\"mappingMode === 'simple'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [suggestions]=\"variableSuggestions\"\n [disabled]=\"!!(disabled$ | async)\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-mapping-builder>\n\n <epistola-jsonata-editor\n *ngIf=\"mappingMode === 'advanced'\"\n [expression]=\"dataMapping$ | async\"\n [disabled]=\"!!(disabled$ | async)\"\n [suggestions]=\"variableSuggestions\"\n [functions]=\"expressionFunctions\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-jsonata-editor>\n\n <!-- Bottom tabs: Schema + Preview (collapsible) -->\n <div class=\"mapping-tools\" [class.mapping-tools--collapsed]=\"toolsCollapsed\">\n <div class=\"mapping-tools__header\" (click)=\"toolsCollapsed = !toolsCollapsed\">\n <span class=\"mapping-tools__chevron\">{{ toolsCollapsed ? '&#x25B6;' : '&#x25BC;' }}</span>\n <span>{{ 'mappingTools' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div *ngIf=\"!toolsCollapsed\" class=\"mapping-tools__content\">\n <div class=\"mapping-tools__tabs\">\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'schema'\"\n (click)=\"activeToolTab = 'schema'\"\n >\n {{ 'expectedStructure' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'preview'\"\n (click)=\"activeToolTab = 'preview'\"\n >\n {{ 'previewTitle' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <epistola-expected-structure\n *ngIf=\"activeToolTab === 'schema'\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n ></epistola-expected-structure>\n\n <epistola-mapping-preview\n *ngIf=\"activeToolTab === 'preview'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n ></epistola-mapping-preview>\n </div>\n </div>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.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}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.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}.mapping-section{margin-top:1rem}.mapping-section__title{font-size:1rem;font-weight:600;margin:0 0 4px}.mapping-section__description{font-size:.85em;color:#6f6f6f;margin:0 0 12px}.mapping-mode-toggle{display:flex;gap:0;margin-bottom:12px}.mapping-mode-toggle__btn{padding:6px 16px;border:1px solid #e0e0e0;background:#fff;font-size:.85em;cursor:pointer}.mapping-mode-toggle__btn:first-child{border-radius:4px 0 0 4px}.mapping-mode-toggle__btn:last-child{border-radius:0 4px 4px 0;border-left:none}.mapping-mode-toggle__btn--active{background:#0f62fe;color:#fff;border-color:#0f62fe}.mapping-tools{margin-top:12px;border:1px solid #e0e0e0;border-radius:4px;overflow:hidden}.mapping-tools__header{display:flex;align-items:center;gap:6px;padding:8px 12px;background:#f4f4f4;cursor:pointer;font-size:.85em;font-weight:500;-webkit-user-select:none;user-select:none}.mapping-tools__header:hover{background:#e8e8e8}.mapping-tools__chevron{font-size:.7em}.mapping-tools__content{border-top:1px solid #e0e0e0}.mapping-tools__tabs{display:flex;border-bottom:1px solid #e0e0e0}.mapping-tools__tab{padding:6px 16px;border:none;background:transparent;font-size:.8em;cursor:pointer;border-bottom:2px solid transparent}.mapping-tools__tab--active{border-bottom-color:#0f62fe;font-weight:500}\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.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { 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.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { 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", "warn", "warnText", "dataTestId"], outputs: ["selectedChange"] }, { kind: "component", type: ExpectedStructureComponent, selector: "epistola-expected-structure", inputs: ["templateFields"] }, { kind: "component", type: JsonataEditorComponent, selector: "epistola-jsonata-editor", inputs: ["expression", "disabled", "suggestions", "functions"], outputs: ["expressionChange", "validChange"] }, { kind: "component", type: MappingBuilderComponent, selector: "epistola-mapping-builder", inputs: ["expression", "templateFields", "suggestions", "disabled"], outputs: ["expressionChange"] }, { kind: "component", type: MappingPreviewComponent, selector: "epistola-mapping-preview", inputs: ["expression", "templateFields", "caseDefinitionKey"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
977
1818
  }
978
1819
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, decorators: [{
979
1820
  type: Component,
@@ -984,8 +1825,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
984
1825
  FormModule,
985
1826
  InputModule,
986
1827
  SelectModule,
987
- DataMappingTreeComponent
988
- ], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n catalogs: catalogs$ | async,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedCatalogId: selectedCatalogId$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"catalogId\"\n [title]=\"'catalogId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'catalogIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.catalogs.data\"\n [defaultSelectionId]=\"obs.prefill?.catalogId\"\n [disabled]=\"obs.disabled || obs.catalogs.loading\"\n [required]=\"true\"\n [loading]=\"obs.catalogs.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.catalogs.error\" class=\"loading-error\">{{ obs.catalogs.error }}</div>\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.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading || !obs.selectedCatalogId\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\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.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\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 <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>{{ 'attributeKey' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">{{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}</option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\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 <button type=\"button\" class=\"custom-key-cancel\" (click)=\"cancelCustomKey(entry)\" [disabled]=\"obs.disabled\">&times;</button>\n </div>\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 <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{ (entry.required ? 'attributeRequired' : 'attributePreferred') | pluginTranslate: pluginId | async }}</span>\n </label>\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.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\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<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">{{ templateFieldsError }}</div>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [disabled]=\"!!(disabled$ | async)\"\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: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.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}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.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"] }]
1828
+ ExpectedStructureComponent,
1829
+ JsonataEditorComponent,
1830
+ MappingBuilderComponent,
1831
+ MappingPreviewComponent,
1832
+ ], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n catalogs: catalogs$ | async,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedCatalogId: selectedCatalogId$ | async,\n selectedTemplateId: selectedTemplateId$ | async,\n } as obs\"\n>\n <v-select\n name=\"catalogId\"\n [title]=\"'catalogId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'catalogIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.catalogs.data\"\n [defaultSelectionId]=\"obs.prefill?.catalogId\"\n [disabled]=\"obs.disabled || obs.catalogs.loading\"\n [required]=\"true\"\n [loading]=\"obs.catalogs.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.catalogs.error\" class=\"loading-error\">{{ obs.catalogs.error }}</div>\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.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading || !obs.selectedCatalogId\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{\n 'variantSelectionMode' | pluginTranslate: pluginId | async\n }}</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 >\n {{ 'selectByVariant' | pluginTranslate: pluginId | async }}\n </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 >\n {{ 'selectByAttributes' | pluginTranslate: pluginId | async }}\n </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.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\n\n <!-- Attribute-based variant selection -->\n <div\n *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\"\n class=\"variant-attributes-section\"\n >\n <label class=\"variant-attributes-label\">{{\n 'variantAttributes' | pluginTranslate: pluginId | async\n }}</label>\n <div class=\"variant-attributes-list\">\n <div\n *ngFor=\"let entry of variantAttributeEntries; let i = index\"\n class=\"variant-attribute-row\"\n >\n <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>\n {{ 'attributeKey' | pluginTranslate: pluginId | async }}\n </option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">\n {{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}\n </option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\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 <button\n type=\"button\"\n class=\"custom-key-cancel\"\n (click)=\"cancelCustomKey(entry)\"\n [disabled]=\"obs.disabled\"\n >\n &times;\n </button>\n </div>\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 <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{\n (entry.required ? 'attributeRequired' : 'attributePreferred')\n | pluginTranslate: pluginId\n | async\n }}</span>\n </label>\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 >\n &times;\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >\n + {{ 'addAttribute' | pluginTranslate: pluginId | async }}\n </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.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\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<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">\n {{ templateFieldsError }}\n</div>\n\n<div *ngIf=\"selectedTemplateId$ | async\" class=\"mapping-section\">\n <h5 class=\"mapping-section__title\">\n {{ 'dataMappingTitle' | pluginTranslate: pluginId | async }}\n </h5>\n <p class=\"mapping-section__description\">\n {{ 'dataMappingDescription' | pluginTranslate: pluginId | async }}\n </p>\n <div class=\"mapping-mode-toggle\">\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'simple'\"\n (click)=\"mappingMode = 'simple'\"\n >\n {{ 'mappingModeSimple' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'advanced'\"\n (click)=\"mappingMode = 'advanced'\"\n >\n {{ 'mappingModeAdvanced' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <!-- Editor area (full width) -->\n <epistola-mapping-builder\n *ngIf=\"mappingMode === 'simple'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [suggestions]=\"variableSuggestions\"\n [disabled]=\"!!(disabled$ | async)\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-mapping-builder>\n\n <epistola-jsonata-editor\n *ngIf=\"mappingMode === 'advanced'\"\n [expression]=\"dataMapping$ | async\"\n [disabled]=\"!!(disabled$ | async)\"\n [suggestions]=\"variableSuggestions\"\n [functions]=\"expressionFunctions\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-jsonata-editor>\n\n <!-- Bottom tabs: Schema + Preview (collapsible) -->\n <div class=\"mapping-tools\" [class.mapping-tools--collapsed]=\"toolsCollapsed\">\n <div class=\"mapping-tools__header\" (click)=\"toolsCollapsed = !toolsCollapsed\">\n <span class=\"mapping-tools__chevron\">{{ toolsCollapsed ? '&#x25B6;' : '&#x25BC;' }}</span>\n <span>{{ 'mappingTools' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div *ngIf=\"!toolsCollapsed\" class=\"mapping-tools__content\">\n <div class=\"mapping-tools__tabs\">\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'schema'\"\n (click)=\"activeToolTab = 'schema'\"\n >\n {{ 'expectedStructure' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'preview'\"\n (click)=\"activeToolTab = 'preview'\"\n >\n {{ 'previewTitle' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <epistola-expected-structure\n *ngIf=\"activeToolTab === 'schema'\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n ></epistola-expected-structure>\n\n <epistola-mapping-preview\n *ngIf=\"activeToolTab === 'preview'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n ></epistola-mapping-preview>\n </div>\n </div>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.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}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.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}.mapping-section{margin-top:1rem}.mapping-section__title{font-size:1rem;font-weight:600;margin:0 0 4px}.mapping-section__description{font-size:.85em;color:#6f6f6f;margin:0 0 12px}.mapping-mode-toggle{display:flex;gap:0;margin-bottom:12px}.mapping-mode-toggle__btn{padding:6px 16px;border:1px solid #e0e0e0;background:#fff;font-size:.85em;cursor:pointer}.mapping-mode-toggle__btn:first-child{border-radius:4px 0 0 4px}.mapping-mode-toggle__btn:last-child{border-radius:0 4px 4px 0;border-left:none}.mapping-mode-toggle__btn--active{background:#0f62fe;color:#fff;border-color:#0f62fe}.mapping-tools{margin-top:12px;border:1px solid #e0e0e0;border-radius:4px;overflow:hidden}.mapping-tools__header{display:flex;align-items:center;gap:6px;padding:8px 12px;background:#f4f4f4;cursor:pointer;font-size:.85em;font-weight:500;-webkit-user-select:none;user-select:none}.mapping-tools__header:hover{background:#e8e8e8}.mapping-tools__chevron{font-size:.7em}.mapping-tools__content{border-top:1px solid #e0e0e0}.mapping-tools__tabs{display:flex;border-bottom:1px solid #e0e0e0}.mapping-tools__tab{padding:6px 16px;border:none;background:transparent;font-size:.8em;cursor:pointer;border-bottom:2px solid transparent}.mapping-tools__tab--active{border-bottom-color:#0f62fe;font-weight:500}\n"] }]
989
1833
  }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$2.ProcessLinkStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { save$: [{
990
1834
  type: Input
991
1835
  }], disabled$: [{
@@ -1028,8 +1872,7 @@ class CheckJobStatusConfigurationComponent {
1028
1872
  this.handleValid(formValue);
1029
1873
  }
1030
1874
  handleValid(formValue) {
1031
- const valid = !!(formValue?.requestIdVariable &&
1032
- formValue?.statusVariable);
1875
+ const valid = !!(formValue?.requestIdVariable && formValue?.statusVariable);
1033
1876
  this.valid$.next(valid);
1034
1877
  this.valid.emit(valid);
1035
1878
  }
@@ -1045,11 +1888,11 @@ class CheckJobStatusConfigurationComponent {
1045
1888
  });
1046
1889
  }
1047
1890
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CheckJobStatusConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1048
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", 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"] }] });
1891
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", 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"] }] });
1049
1892
  }
1050
1893
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CheckJobStatusConfigurationComponent, decorators: [{
1051
1894
  type: Component,
1052
- 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" }]
1895
+ 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" }]
1053
1896
  }], propDecorators: { save$: [{
1054
1897
  type: Input
1055
1898
  }], disabled$: [{
@@ -1088,8 +1931,7 @@ class DownloadDocumentConfigurationComponent {
1088
1931
  this.handleValid(formValue);
1089
1932
  }
1090
1933
  handleValid(formValue) {
1091
- const valid = !!(formValue?.documentIdVariable &&
1092
- formValue?.contentVariable);
1934
+ const valid = !!(formValue?.documentIdVariable && formValue?.contentVariable);
1093
1935
  this.valid$.next(valid);
1094
1936
  this.valid.emit(valid);
1095
1937
  }
@@ -1105,11 +1947,11 @@ class DownloadDocumentConfigurationComponent {
1105
1947
  });
1106
1948
  }
1107
1949
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1108
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", 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"] }] });
1950
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", 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"] }] });
1109
1951
  }
1110
1952
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, decorators: [{
1111
1953
  type: Component,
1112
- 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" }]
1954
+ 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" }]
1113
1955
  }], propDecorators: { save$: [{
1114
1956
  type: Input
1115
1957
  }], disabled$: [{
@@ -1149,9 +1991,9 @@ class EpistolaDownloadComponent {
1149
1991
  this.downloading = true;
1150
1992
  this.error = null;
1151
1993
  const { documentId, tenantId } = this.value;
1152
- const url = `/api/v1/plugin/epistola/documents/${encodeURIComponent(documentId)}/download`
1153
- + `?tenantId=${encodeURIComponent(tenantId)}`
1154
- + `&filename=${encodeURIComponent(this.filename)}`;
1994
+ const url = `/api/v1/plugin/epistola/documents/${encodeURIComponent(documentId)}/download` +
1995
+ `?tenantId=${encodeURIComponent(tenantId)}` +
1996
+ `&filename=${encodeURIComponent(this.filename)}`;
1155
1997
  this.http.get(url, { responseType: 'blob' }).subscribe({
1156
1998
  next: (blob) => {
1157
1999
  const objectUrl = URL.createObjectURL(blob);
@@ -1244,7 +2086,7 @@ class EpistolaRetryFormComponent {
1244
2086
  apiEndpoint;
1245
2087
  formOptions = {
1246
2088
  noAlerts: true,
1247
- buttonSettings: { showCancel: false, showSubmit: false, showPrevious: false, showNext: false }
2089
+ buttonSettings: { showCancel: false, showSubmit: false, showPrevious: false, showNext: false },
1248
2090
  };
1249
2091
  constructor(epistolaPluginService, formIoStateService, cdr, http, sanitizer, configService) {
1250
2092
  this.epistolaPluginService = epistolaPluginService;
@@ -1255,7 +2097,7 @@ class EpistolaRetryFormComponent {
1255
2097
  this.configService = configService;
1256
2098
  this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
1257
2099
  // Debounce preview calls
1258
- this.previewSubscription = this.previewSubject.pipe(debounceTime(1500)).subscribe(data => {
2100
+ this.previewSubscription = this.previewSubject.pipe(debounceTime$1(1500)).subscribe((data) => {
1259
2101
  this.loadPreview(data);
1260
2102
  });
1261
2103
  }
@@ -1298,12 +2140,14 @@ class EpistolaRetryFormComponent {
1298
2140
  URL.revokeObjectURL(this.currentBlobUrl);
1299
2141
  this.currentBlobUrl = null;
1300
2142
  }
1301
- this.http.post(`${this.apiEndpoint}/preview`, {
2143
+ this.http
2144
+ .post(`${this.apiEndpoint}/preview`, {
1302
2145
  documentId,
1303
2146
  processInstanceId,
1304
2147
  sourceActivityId: this.sourceActivityId || null,
1305
- overrides: formData
1306
- }, { responseType: 'blob', headers: new HttpHeaders().set('X-Skip-Interceptor', '422') }).subscribe({
2148
+ overrides: formData,
2149
+ }, { responseType: 'blob', headers: new HttpHeaders().set('X-Skip-Interceptor', '422') })
2150
+ .subscribe({
1307
2151
  next: (blob) => {
1308
2152
  this.currentBlobUrl = URL.createObjectURL(blob);
1309
2153
  this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
@@ -1332,7 +2176,7 @@ class EpistolaRetryFormComponent {
1332
2176
  this.previewLoading = false;
1333
2177
  this.cdr.markForCheck();
1334
2178
  }
1335
- }
2179
+ },
1336
2180
  });
1337
2181
  }
1338
2182
  loadForm() {
@@ -1344,7 +2188,9 @@ class EpistolaRetryFormComponent {
1344
2188
  this.cdr.markForCheck();
1345
2189
  return;
1346
2190
  }
1347
- this.loadSubscription = this.epistolaPluginService.getRetryForm(processInstanceId, documentId ?? undefined, this.sourceActivityId).subscribe({
2191
+ this.loadSubscription = this.epistolaPluginService
2192
+ .getRetryForm(processInstanceId, documentId ?? undefined, this.sourceActivityId)
2193
+ .subscribe({
1348
2194
  next: (form) => {
1349
2195
  this.formDefinition = form;
1350
2196
  if (this.value) {
@@ -1363,14 +2209,18 @@ class EpistolaRetryFormComponent {
1363
2209
  this.error = 'Failed to load the retry form. Please try again.';
1364
2210
  this.loading = false;
1365
2211
  this.cdr.markForCheck();
1366
- }
2212
+ },
1367
2213
  });
1368
2214
  }
1369
2215
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, deps: [{ token: EpistolaPluginService }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }, { token: i1.HttpClient }, { token: i4$1.DomSanitizer }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Component });
1370
2216
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaRetryFormComponent, isStandalone: true, selector: "epistola-retry-form-component", inputs: { value: "value", disabled: "disabled", label: "label", sourceActivityId: "sourceActivityId" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
1371
2217
  <div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
1372
2218
  <div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
1373
- <div *ngIf="formDefinition && !loading" class="epistola-retry-container" [class.preview-expanded]="previewExpanded">
2219
+ <div
2220
+ *ngIf="formDefinition && !loading"
2221
+ class="epistola-retry-container"
2222
+ [class.preview-expanded]="previewExpanded"
2223
+ >
1374
2224
  <div class="epistola-retry-form" [hidden]="previewExpanded">
1375
2225
  <formio
1376
2226
  [form]="formDefinition"
@@ -1387,7 +2237,12 @@ class EpistolaRetryFormComponent {
1387
2237
  </button>
1388
2238
  </div>
1389
2239
  <div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
1390
- <object *ngIf="previewUrl && !previewLoading" [data]="previewUrl" type="application/pdf" class="preview-pdf">
2240
+ <object
2241
+ *ngIf="previewUrl && !previewLoading"
2242
+ [data]="previewUrl"
2243
+ type="application/pdf"
2244
+ class="preview-pdf"
2245
+ >
1391
2246
  PDF preview not supported in this browser.
1392
2247
  </object>
1393
2248
  <div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
@@ -1403,7 +2258,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1403
2258
  args: [{ standalone: true, imports: [CommonModule, FormioModule], selector: 'epistola-retry-form-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
1404
2259
  <div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
1405
2260
  <div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
1406
- <div *ngIf="formDefinition && !loading" class="epistola-retry-container" [class.preview-expanded]="previewExpanded">
2261
+ <div
2262
+ *ngIf="formDefinition && !loading"
2263
+ class="epistola-retry-container"
2264
+ [class.preview-expanded]="previewExpanded"
2265
+ >
1407
2266
  <div class="epistola-retry-form" [hidden]="previewExpanded">
1408
2267
  <formio
1409
2268
  [form]="formDefinition"
@@ -1420,7 +2279,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1420
2279
  </button>
1421
2280
  </div>
1422
2281
  <div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
1423
- <object *ngIf="previewUrl && !previewLoading" [data]="previewUrl" type="application/pdf" class="preview-pdf">
2282
+ <object
2283
+ *ngIf="previewUrl && !previewLoading"
2284
+ [data]="previewUrl"
2285
+ type="application/pdf"
2286
+ class="preview-pdf"
2287
+ >
1424
2288
  PDF preview not supported in this browser.
1425
2289
  </object>
1426
2290
  <div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
@@ -1480,9 +2344,9 @@ class EpistolaPreviewButtonComponent {
1480
2344
  this.previewError = null;
1481
2345
  this.revokeBlobUrl();
1482
2346
  const { documentId, tenantId } = this.value;
1483
- const url = `${this.apiEndpoint}/documents/${encodeURIComponent(documentId)}/download`
1484
- + `?tenantId=${encodeURIComponent(tenantId)}`
1485
- + `&filename=preview.pdf`;
2347
+ const url = `${this.apiEndpoint}/documents/${encodeURIComponent(documentId)}/download` +
2348
+ `?tenantId=${encodeURIComponent(tenantId)}` +
2349
+ `&filename=preview.pdf`;
1486
2350
  this.http.get(url, { responseType: 'blob' }).subscribe({
1487
2351
  next: (blob) => {
1488
2352
  this.currentBlobUrl = URL.createObjectURL(blob);
@@ -1492,7 +2356,7 @@ class EpistolaPreviewButtonComponent {
1492
2356
  error: () => {
1493
2357
  this.previewError = 'Could not load the document.';
1494
2358
  this.previewLoading = false;
1495
- }
2359
+ },
1496
2360
  });
1497
2361
  }
1498
2362
  closePreview() {
@@ -1523,7 +2387,9 @@ class EpistolaPreviewButtonComponent {
1523
2387
  <div class="preview-modal-content" (click)="$event.stopPropagation()">
1524
2388
  <div class="preview-modal-header">
1525
2389
  <span>Document Preview</span>
1526
- <button type="button" class="preview-modal-close" (click)="closePreview()">&times;</button>
2390
+ <button type="button" class="preview-modal-close" (click)="closePreview()">
2391
+ &times;
2392
+ </button>
1527
2393
  </div>
1528
2394
  <div class="preview-modal-body">
1529
2395
  <div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
@@ -1558,7 +2424,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1558
2424
  <div class="preview-modal-content" (click)="$event.stopPropagation()">
1559
2425
  <div class="preview-modal-header">
1560
2426
  <span>Document Preview</span>
1561
- <button type="button" class="preview-modal-close" (click)="closePreview()">&times;</button>
2427
+ <button type="button" class="preview-modal-close" (click)="closePreview()">
2428
+ &times;
2429
+ </button>
1562
2430
  </div>
1563
2431
  <div class="preview-modal-body">
1564
2432
  <div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
@@ -1658,7 +2526,7 @@ class EpistolaDocumentPreviewComponent {
1658
2526
  this.error = err.error?.error || 'Failed to discover preview sources';
1659
2527
  this.discovering = false;
1660
2528
  this.cdr.markForCheck();
1661
- }
2529
+ },
1662
2530
  });
1663
2531
  }
1664
2532
  loadPreview() {
@@ -1673,15 +2541,17 @@ class EpistolaDocumentPreviewComponent {
1673
2541
  this.cdr.markForCheck();
1674
2542
  this.revokeBlobUrl();
1675
2543
  this.previewSubscription?.unsubscribe();
1676
- this.previewSubscription = this.http.post(`${this.apiEndpoint}/preview`, {
2544
+ this.previewSubscription = this.http
2545
+ .post(`${this.apiEndpoint}/preview`, {
1677
2546
  documentId,
1678
2547
  processInstanceId: source.processInstanceId,
1679
2548
  sourceActivityId: source.activityId,
1680
- overrides: null
2549
+ overrides: null,
1681
2550
  }, {
1682
2551
  responseType: 'blob',
1683
- headers: new HttpHeaders().set('X-Skip-Interceptor', '422')
1684
- }).subscribe({
2552
+ headers: new HttpHeaders().set('X-Skip-Interceptor', '422'),
2553
+ })
2554
+ .subscribe({
1685
2555
  next: (blob) => {
1686
2556
  this.currentBlobUrl = URL.createObjectURL(blob);
1687
2557
  this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
@@ -1709,7 +2579,7 @@ class EpistolaDocumentPreviewComponent {
1709
2579
  this.loading = false;
1710
2580
  this.cdr.markForCheck();
1711
2581
  }
1712
- }
2582
+ },
1713
2583
  });
1714
2584
  }
1715
2585
  revokeBlobUrl() {
@@ -1735,19 +2605,20 @@ class EpistolaDocumentPreviewComponent {
1735
2605
  {{ source.templateName }} ({{ source.activityId }})
1736
2606
  </option>
1737
2607
  </select>
1738
- <button type="button" class="preview-refresh" [disabled]="loading || discovering" (click)="refresh()">
2608
+ <button
2609
+ type="button"
2610
+ class="preview-refresh"
2611
+ [disabled]="loading || discovering"
2612
+ (click)="refresh()"
2613
+ >
1739
2614
  <i class="mdi mdi-refresh mr-1"></i>
1740
2615
  {{ loading ? 'Generating...' : 'Refresh' }}
1741
2616
  </button>
1742
2617
  </div>
1743
2618
  </div>
1744
2619
  <div class="preview-body">
1745
- <div *ngIf="discovering" class="preview-loading">
1746
- Discovering documents...
1747
- </div>
1748
- <div *ngIf="loading && !discovering" class="preview-loading">
1749
- Generating preview...
1750
- </div>
2620
+ <div *ngIf="discovering" class="preview-loading">Discovering documents...</div>
2621
+ <div *ngIf="loading && !discovering" class="preview-loading">Generating preview...</div>
1751
2622
  <div *ngIf="error && !loading && !discovering" class="preview-unavailable">
1752
2623
  <i class="mdi mdi-information-outline"></i>
1753
2624
  Preview is niet beschikbaar — niet alle gegevens zijn al ingevuld.
@@ -1760,7 +2631,10 @@ class EpistolaDocumentPreviewComponent {
1760
2631
  >
1761
2632
  PDF preview is not supported in this browser.
1762
2633
  </object>
1763
- <div *ngIf="!previewUrl && !loading && !discovering && !error && sources.length === 0" class="preview-empty">
2634
+ <div
2635
+ *ngIf="!previewUrl && !loading && !discovering && !error && sources.length === 0"
2636
+ class="preview-empty"
2637
+ >
1764
2638
  No previewable documents found
1765
2639
  </div>
1766
2640
  </div>
@@ -1784,19 +2658,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1784
2658
  {{ source.templateName }} ({{ source.activityId }})
1785
2659
  </option>
1786
2660
  </select>
1787
- <button type="button" class="preview-refresh" [disabled]="loading || discovering" (click)="refresh()">
2661
+ <button
2662
+ type="button"
2663
+ class="preview-refresh"
2664
+ [disabled]="loading || discovering"
2665
+ (click)="refresh()"
2666
+ >
1788
2667
  <i class="mdi mdi-refresh mr-1"></i>
1789
2668
  {{ loading ? 'Generating...' : 'Refresh' }}
1790
2669
  </button>
1791
2670
  </div>
1792
2671
  </div>
1793
2672
  <div class="preview-body">
1794
- <div *ngIf="discovering" class="preview-loading">
1795
- Discovering documents...
1796
- </div>
1797
- <div *ngIf="loading && !discovering" class="preview-loading">
1798
- Generating preview...
1799
- </div>
2673
+ <div *ngIf="discovering" class="preview-loading">Discovering documents...</div>
2674
+ <div *ngIf="loading && !discovering" class="preview-loading">Generating preview...</div>
1800
2675
  <div *ngIf="error && !loading && !discovering" class="preview-unavailable">
1801
2676
  <i class="mdi mdi-information-outline"></i>
1802
2677
  Preview is niet beschikbaar — niet alle gegevens zijn al ingevuld.
@@ -1809,7 +2684,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1809
2684
  >
1810
2685
  PDF preview is not supported in this browser.
1811
2686
  </object>
1812
- <div *ngIf="!previewUrl && !loading && !discovering && !error && sources.length === 0" class="preview-empty">
2687
+ <div
2688
+ *ngIf="!previewUrl && !loading && !discovering && !error && sources.length === 0"
2689
+ class="preview-empty"
2690
+ >
1813
2691
  No previewable documents found
1814
2692
  </div>
1815
2693
  </div>
@@ -1825,6 +2703,186 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1825
2703
  type: Input
1826
2704
  }] } });
1827
2705
 
2706
+ class EpistolaAdminPageComponent {
2707
+ adminService;
2708
+ route;
2709
+ router;
2710
+ cards = [];
2711
+ selectedCard = null;
2712
+ activeTab = 'actions';
2713
+ loading = false;
2714
+ pluginVersion = null;
2715
+ connectionStatuses = [];
2716
+ usageEntries = [];
2717
+ pendingJobs = [];
2718
+ connectionLoaded = false;
2719
+ usageLoaded = false;
2720
+ pendingLoaded = false;
2721
+ deepLinkConfigId = null;
2722
+ constructor(adminService, route, router) {
2723
+ this.adminService = adminService;
2724
+ this.route = route;
2725
+ this.router = router;
2726
+ }
2727
+ ngOnInit() {
2728
+ this.deepLinkConfigId = this.route.snapshot.queryParamMap.get('configurationId');
2729
+ const tab = this.route.snapshot.queryParamMap.get('tab');
2730
+ if (tab === 'pending' || tab === 'actions') {
2731
+ this.activeTab = tab;
2732
+ }
2733
+ this.loadData();
2734
+ this.loadPluginVersion();
2735
+ }
2736
+ selectConfiguration(card) {
2737
+ this.selectedCard = card;
2738
+ this.activeTab = 'actions';
2739
+ this.updateUrl(card.configurationId, this.activeTab);
2740
+ }
2741
+ backToOverview() {
2742
+ this.selectedCard = null;
2743
+ this.activeTab = 'actions';
2744
+ this.updateUrl(null, null);
2745
+ }
2746
+ setActiveTab(tab) {
2747
+ this.activeTab = tab;
2748
+ this.updateUrl(this.selectedCard?.configurationId ?? null, tab);
2749
+ }
2750
+ refresh() {
2751
+ this.selectedCard = null;
2752
+ this.loadData();
2753
+ }
2754
+ exportProcessLink(entry) {
2755
+ this.adminService.exportProcessLink(entry.processLinkId).subscribe({
2756
+ next: (blob) => {
2757
+ const url = URL.createObjectURL(blob);
2758
+ const anchor = document.createElement('a');
2759
+ anchor.href = url;
2760
+ anchor.download = `${entry.activityId}.process-link.json`;
2761
+ anchor.click();
2762
+ URL.revokeObjectURL(url);
2763
+ },
2764
+ });
2765
+ }
2766
+ updateUrl(configurationId, tab) {
2767
+ this.router.navigate([], {
2768
+ relativeTo: this.route,
2769
+ queryParams: {
2770
+ configurationId: configurationId ?? null,
2771
+ tab: tab ?? null,
2772
+ },
2773
+ replaceUrl: true,
2774
+ });
2775
+ }
2776
+ loadData() {
2777
+ this.loading = true;
2778
+ this.connectionLoaded = false;
2779
+ this.usageLoaded = false;
2780
+ this.pendingLoaded = false;
2781
+ this.adminService.getConnectionStatus().subscribe({
2782
+ next: (statuses) => {
2783
+ this.connectionStatuses = statuses;
2784
+ this.connectionLoaded = true;
2785
+ this.tryBuildCards();
2786
+ },
2787
+ error: () => {
2788
+ this.connectionStatuses = [];
2789
+ this.connectionLoaded = true;
2790
+ this.tryBuildCards();
2791
+ },
2792
+ });
2793
+ this.adminService.getPluginUsage().subscribe({
2794
+ next: (entries) => {
2795
+ this.usageEntries = entries;
2796
+ this.usageLoaded = true;
2797
+ this.tryBuildCards();
2798
+ },
2799
+ error: () => {
2800
+ this.usageEntries = [];
2801
+ this.usageLoaded = true;
2802
+ this.tryBuildCards();
2803
+ },
2804
+ });
2805
+ this.adminService.getPendingJobs().subscribe({
2806
+ next: (jobs) => {
2807
+ this.pendingJobs = jobs;
2808
+ this.pendingLoaded = true;
2809
+ this.tryBuildCards();
2810
+ },
2811
+ error: () => {
2812
+ this.pendingJobs = [];
2813
+ this.pendingLoaded = true;
2814
+ this.tryBuildCards();
2815
+ },
2816
+ });
2817
+ }
2818
+ tryBuildCards() {
2819
+ if (!this.connectionLoaded || !this.usageLoaded || !this.pendingLoaded) {
2820
+ return;
2821
+ }
2822
+ this.cards = this.connectionStatuses.map((status) => {
2823
+ const entries = this.usageEntries.filter((e) => e.configurationId === status.configurationId);
2824
+ const jobs = this.pendingJobs.filter((j) => j.tenantId === status.tenantId);
2825
+ const problemCount = entries.reduce((sum, e) => sum + e.problems.length, 0);
2826
+ return {
2827
+ configurationId: status.configurationId,
2828
+ configurationTitle: status.configurationTitle,
2829
+ tenantId: status.tenantId,
2830
+ reachable: status.reachable,
2831
+ latencyMs: status.latencyMs,
2832
+ errorMessage: status.errorMessage,
2833
+ serverVersion: status.serverVersion,
2834
+ usageCount: entries.length,
2835
+ problemCount,
2836
+ usageEntries: entries,
2837
+ pendingJobs: jobs,
2838
+ };
2839
+ });
2840
+ // Restore deep link selection
2841
+ if (this.deepLinkConfigId) {
2842
+ const match = this.cards.find((c) => c.configurationId === this.deepLinkConfigId);
2843
+ if (match) {
2844
+ this.selectedCard = match;
2845
+ }
2846
+ this.deepLinkConfigId = null;
2847
+ }
2848
+ this.loading = false;
2849
+ }
2850
+ loadPluginVersion() {
2851
+ this.adminService.getVersions().subscribe({
2852
+ next: (info) => {
2853
+ this.pluginVersion = info.pluginVersion;
2854
+ },
2855
+ });
2856
+ }
2857
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, deps: [{ token: EpistolaAdminService }, { token: i2$3.ActivatedRoute }, { token: i2$3.Router }], target: i0.ɵɵFactoryTarget.Component });
2858
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaAdminPageComponent, isStandalone: true, selector: "epistola-admin-page", ngImport: i0, template: "<div class=\"epistola-admin\">\n <!-- Overview: card grid (no configuration selected) -->\n <ng-container *ngIf=\"!selectedCard\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <div class=\"d-flex align-items-center\">\n <h5 class=\"mb-0\">{{ 'epistolaAdminOverview' | pluginTranslate: 'epistola' | async }}</h5>\n <span *ngIf=\"pluginVersion\" class=\"version-badge ms-2\">v{{ pluginVersion }}</span>\n </div>\n <button class=\"btn btn-outline-primary btn-sm\" (click)=\"refresh()\" [disabled]=\"loading\">\n {{ 'epistolaAdminRefresh' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div *ngIf=\"loading\" class=\"text-muted\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted\">\n {{ 'epistolaAdminNoConfigurations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length > 0\" class=\"card-grid\">\n <div\n *ngFor=\"let card of cards\"\n class=\"config-card\"\n [class.config-card--ok]=\"card.reachable && card.problemCount === 0\"\n [class.config-card--warning]=\"card.reachable && card.problemCount > 0\"\n [class.config-card--error]=\"!card.reachable\"\n (click)=\"selectConfiguration(card)\"\n >\n <div class=\"config-card__header\">\n <span\n class=\"status-dot\"\n [class.status-dot--ok]=\"card.reachable\"\n [class.status-dot--error]=\"!card.reachable\"\n >\n </span>\n <h5 class=\"config-card__title\">{{ card.configurationTitle }}</h5>\n </div>\n\n <div class=\"config-card__body\">\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async\n }}</span>\n <code class=\"config-card__value\">{{ card.tenantId }}</code>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" [type]=\"card.reachable ? 'green' : 'red'\">\n {{\n card.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n </div>\n <div *ngIf=\"card.serverVersion\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.serverVersion }}</span>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.usageCount }}</span>\n </div>\n <div *ngIf=\"card.pendingJobs.length > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">{{ card.pendingJobs.length }}</cds-tag>\n </div>\n <div *ngIf=\"card.problemCount > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"red\">{{ card.problemCount }}</cds-tag>\n </div>\n </div>\n\n <div class=\"config-card__footer\">\n <span class=\"config-card__latency\">{{ card.latencyMs }} ms</span>\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Detail view: selected configuration -->\n <ng-container *ngIf=\"selectedCard\">\n <div class=\"detail-header mb-3\">\n <button class=\"btn btn-link btn-sm p-0\" (click)=\"backToOverview()\">\n &larr; {{ 'epistolaAdminBackToOverview' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div class=\"detail-summary mb-4\">\n <h4>\n <span\n class=\"status-dot me-2\"\n [class.status-dot--ok]=\"selectedCard.reachable\"\n [class.status-dot--error]=\"!selectedCard.reachable\"\n >\n </span>\n {{ selectedCard.configurationTitle }}\n </h4>\n\n <table class=\"table table-sm detail-info-table\">\n <tbody>\n <tr>\n <th>{{ 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <code>{{ selectedCard.tenantId }}</code>\n </td>\n </tr>\n <tr>\n <th>{{ 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <cds-tag size=\"sm\" [type]=\"selectedCard.reachable ? 'green' : 'red'\">\n {{\n selectedCard.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n <span class=\"text-muted ms-2\">{{ selectedCard.latencyMs }} ms</span>\n </td>\n </tr>\n <tr *ngIf=\"selectedCard.serverVersion\">\n <th>{{ 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async }}</th>\n <td>{{ selectedCard.serverVersion }}</td>\n </tr>\n <tr *ngIf=\"selectedCard.errorMessage\">\n <th>{{ 'epistolaAdminError' | pluginTranslate: 'epistola' | async }}</th>\n <td class=\"text-danger\">{{ selectedCard.errorMessage }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Tabs -->\n <ng-template #actionsHeading>\n {{ 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ selectedCard.usageEntries.length }}</cds-tag>\n </ng-template>\n\n <ng-template #pendingHeading>\n {{ 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n size=\"sm\"\n [type]=\"selectedCard.pendingJobs.length > 0 ? 'blue' : 'gray'\"\n class=\"ms-1\"\n >\n {{ selectedCard.pendingJobs.length }}\n </cds-tag>\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"actionsHeading\"\n [active]=\"activeTab === 'actions'\"\n (selected)=\"setActiveTab('actions')\"\n >\n <div *ngIf=\"selectedCard.usageEntries.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoUsageForConfig' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.usageEntries.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCase' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminAction' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let entry of selectedCard.usageEntries\"\n [class.table-warning]=\"entry.problems.length > 0\"\n >\n <td>{{ entry.caseDefinitionKey || '-' }}</td>\n <td>\n <a\n *ngIf=\"entry.caseDefinitionKey && entry.caseDefinitionVersionTag\"\n [routerLink]=\"[\n '/case-management',\n 'case',\n entry.caseDefinitionKey,\n 'version',\n entry.caseDefinitionVersionTag,\n 'processes',\n entry.processDefinitionKey,\n ]\"\n class=\"usage-link\"\n >\n {{ entry.processDefinitionName }}\n </a>\n <span *ngIf=\"!entry.caseDefinitionKey || !entry.caseDefinitionVersionTag\">\n {{ entry.processDefinitionName }}\n </span>\n </td>\n <td>{{ entry.activityName }}</td>\n <td>\n <code>{{ entry.actionKey }}</code>\n </td>\n <td>\n <cds-tag *ngIf=\"entry.problems.length === 0\" size=\"sm\" type=\"green\">OK</cds-tag>\n <cds-tag\n *ngFor=\"let problem of entry.problems\"\n size=\"sm\"\n type=\"red\"\n class=\"d-block mb-1\"\n >\n {{ problem }}\n </cds-tag>\n </td>\n <td>\n <button\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"exportProcessLink(entry)\"\n [title]=\"'epistolaAdminExport' | pluginTranslate: 'epistola' | async\"\n >\n &#x2913;\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"pendingHeading\"\n [active]=\"activeTab === 'pending'\"\n (selected)=\"setActiveTab('pending')\"\n >\n <div *ngIf=\"selectedCard.pendingJobs.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoPendingJobs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.pendingJobs.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminRequestId' | pluginTranslate: 'epistola' | async }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let job of selectedCard.pendingJobs\">\n <td>{{ job.processDefinitionName }}</td>\n <td>{{ job.activityName }}</td>\n <td>\n <code>{{ job.requestId }}</code>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n </cds-tabs>\n </ng-container>\n</div>\n", styles: [".epistola-admin{padding:1.5rem}.epistola-admin .version-badge{font-size:.75rem;font-weight:500;padding:.2em .6em;border-radius:4px;background-color:#e8e8e8;color:#525252}.epistola-admin .badge{font-size:.85em;padding:.35em .65em}.epistola-admin code{font-size:.9em;color:#525252}.epistola-admin table th{font-weight:600;white-space:nowrap}.epistola-admin .status-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:.5rem;flex-shrink:0;background-color:#adb5bd}.epistola-admin .status-dot--ok{background-color:#198754}.epistola-admin .status-dot--error{background-color:#dc3545}.epistola-admin .card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}.epistola-admin .config-card{border:1px solid #dee2e6;border-radius:8px;padding:1.25rem;cursor:pointer;transition:box-shadow .15s ease,border-color .15s ease;background:#fff}.epistola-admin .config-card:hover{box-shadow:0 2px 8px #0000001a}.epistola-admin .config-card--ok{border-left:4px solid #198754}.epistola-admin .config-card--warning{border-left:4px solid #ffc107}.epistola-admin .config-card--error{border-left:4px solid #dc3545}.epistola-admin .config-card__header{display:flex;align-items:center;margin-bottom:1rem}.epistola-admin .config-card__title{margin:0;font-size:1.05rem;font-weight:600;color:#161616}.epistola-admin .config-card__body{display:flex;flex-direction:column;gap:.5rem}.epistola-admin .config-card__field{display:flex;justify-content:space-between;align-items:center}.epistola-admin .config-card__label{font-size:.875rem;color:#6c757d}.epistola-admin .config-card__value{font-size:.875rem;color:#161616}.epistola-admin .config-card__footer{margin-top:1rem;padding-top:.75rem;border-top:1px solid #f0f0f0;text-align:right}.epistola-admin .config-card__latency{font-size:.8rem;color:#adb5bd}.epistola-admin .usage-link{color:#0f62fe;text-decoration:none}.epistola-admin .usage-link:hover{text-decoration:underline}.epistola-admin .detail-info-table{max-width:500px}.epistola-admin .detail-info-table th{width:140px}.epistola-admin .detail-summary{padding:1rem 0;border-bottom:1px solid #dee2e6}\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: RouterModule }, { kind: "directive", type: i2$3.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i5.Tabs, selector: "cds-tabs, ibm-tabs", inputs: ["position", "cacheActive", "followFocus", "isNavigation", "ariaLabel", "ariaLabelledby", "type", "theme", "skeleton"] }, { kind: "component", type: i5.Tab, selector: "cds-tab, ibm-tab", inputs: ["heading", "title", "context", "active", "disabled", "tabIndex", "id", "cacheActive", "tabContent", "templateContext"], outputs: ["selected"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i6.Tag, selector: "cds-tag, ibm-tag", inputs: ["type", "size", "class", "skeleton"] }] });
2859
+ }
2860
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, decorators: [{
2861
+ type: Component,
2862
+ args: [{ selector: 'epistola-admin-page', standalone: true, imports: [CommonModule, RouterModule, PluginTranslatePipeModule, TabsModule, TagModule], template: "<div class=\"epistola-admin\">\n <!-- Overview: card grid (no configuration selected) -->\n <ng-container *ngIf=\"!selectedCard\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <div class=\"d-flex align-items-center\">\n <h5 class=\"mb-0\">{{ 'epistolaAdminOverview' | pluginTranslate: 'epistola' | async }}</h5>\n <span *ngIf=\"pluginVersion\" class=\"version-badge ms-2\">v{{ pluginVersion }}</span>\n </div>\n <button class=\"btn btn-outline-primary btn-sm\" (click)=\"refresh()\" [disabled]=\"loading\">\n {{ 'epistolaAdminRefresh' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div *ngIf=\"loading\" class=\"text-muted\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted\">\n {{ 'epistolaAdminNoConfigurations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length > 0\" class=\"card-grid\">\n <div\n *ngFor=\"let card of cards\"\n class=\"config-card\"\n [class.config-card--ok]=\"card.reachable && card.problemCount === 0\"\n [class.config-card--warning]=\"card.reachable && card.problemCount > 0\"\n [class.config-card--error]=\"!card.reachable\"\n (click)=\"selectConfiguration(card)\"\n >\n <div class=\"config-card__header\">\n <span\n class=\"status-dot\"\n [class.status-dot--ok]=\"card.reachable\"\n [class.status-dot--error]=\"!card.reachable\"\n >\n </span>\n <h5 class=\"config-card__title\">{{ card.configurationTitle }}</h5>\n </div>\n\n <div class=\"config-card__body\">\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async\n }}</span>\n <code class=\"config-card__value\">{{ card.tenantId }}</code>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" [type]=\"card.reachable ? 'green' : 'red'\">\n {{\n card.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n </div>\n <div *ngIf=\"card.serverVersion\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.serverVersion }}</span>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.usageCount }}</span>\n </div>\n <div *ngIf=\"card.pendingJobs.length > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">{{ card.pendingJobs.length }}</cds-tag>\n </div>\n <div *ngIf=\"card.problemCount > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"red\">{{ card.problemCount }}</cds-tag>\n </div>\n </div>\n\n <div class=\"config-card__footer\">\n <span class=\"config-card__latency\">{{ card.latencyMs }} ms</span>\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Detail view: selected configuration -->\n <ng-container *ngIf=\"selectedCard\">\n <div class=\"detail-header mb-3\">\n <button class=\"btn btn-link btn-sm p-0\" (click)=\"backToOverview()\">\n &larr; {{ 'epistolaAdminBackToOverview' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div class=\"detail-summary mb-4\">\n <h4>\n <span\n class=\"status-dot me-2\"\n [class.status-dot--ok]=\"selectedCard.reachable\"\n [class.status-dot--error]=\"!selectedCard.reachable\"\n >\n </span>\n {{ selectedCard.configurationTitle }}\n </h4>\n\n <table class=\"table table-sm detail-info-table\">\n <tbody>\n <tr>\n <th>{{ 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <code>{{ selectedCard.tenantId }}</code>\n </td>\n </tr>\n <tr>\n <th>{{ 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <cds-tag size=\"sm\" [type]=\"selectedCard.reachable ? 'green' : 'red'\">\n {{\n selectedCard.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n <span class=\"text-muted ms-2\">{{ selectedCard.latencyMs }} ms</span>\n </td>\n </tr>\n <tr *ngIf=\"selectedCard.serverVersion\">\n <th>{{ 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async }}</th>\n <td>{{ selectedCard.serverVersion }}</td>\n </tr>\n <tr *ngIf=\"selectedCard.errorMessage\">\n <th>{{ 'epistolaAdminError' | pluginTranslate: 'epistola' | async }}</th>\n <td class=\"text-danger\">{{ selectedCard.errorMessage }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Tabs -->\n <ng-template #actionsHeading>\n {{ 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ selectedCard.usageEntries.length }}</cds-tag>\n </ng-template>\n\n <ng-template #pendingHeading>\n {{ 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n size=\"sm\"\n [type]=\"selectedCard.pendingJobs.length > 0 ? 'blue' : 'gray'\"\n class=\"ms-1\"\n >\n {{ selectedCard.pendingJobs.length }}\n </cds-tag>\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"actionsHeading\"\n [active]=\"activeTab === 'actions'\"\n (selected)=\"setActiveTab('actions')\"\n >\n <div *ngIf=\"selectedCard.usageEntries.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoUsageForConfig' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.usageEntries.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCase' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminAction' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let entry of selectedCard.usageEntries\"\n [class.table-warning]=\"entry.problems.length > 0\"\n >\n <td>{{ entry.caseDefinitionKey || '-' }}</td>\n <td>\n <a\n *ngIf=\"entry.caseDefinitionKey && entry.caseDefinitionVersionTag\"\n [routerLink]=\"[\n '/case-management',\n 'case',\n entry.caseDefinitionKey,\n 'version',\n entry.caseDefinitionVersionTag,\n 'processes',\n entry.processDefinitionKey,\n ]\"\n class=\"usage-link\"\n >\n {{ entry.processDefinitionName }}\n </a>\n <span *ngIf=\"!entry.caseDefinitionKey || !entry.caseDefinitionVersionTag\">\n {{ entry.processDefinitionName }}\n </span>\n </td>\n <td>{{ entry.activityName }}</td>\n <td>\n <code>{{ entry.actionKey }}</code>\n </td>\n <td>\n <cds-tag *ngIf=\"entry.problems.length === 0\" size=\"sm\" type=\"green\">OK</cds-tag>\n <cds-tag\n *ngFor=\"let problem of entry.problems\"\n size=\"sm\"\n type=\"red\"\n class=\"d-block mb-1\"\n >\n {{ problem }}\n </cds-tag>\n </td>\n <td>\n <button\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"exportProcessLink(entry)\"\n [title]=\"'epistolaAdminExport' | pluginTranslate: 'epistola' | async\"\n >\n &#x2913;\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"pendingHeading\"\n [active]=\"activeTab === 'pending'\"\n (selected)=\"setActiveTab('pending')\"\n >\n <div *ngIf=\"selectedCard.pendingJobs.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoPendingJobs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.pendingJobs.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminRequestId' | pluginTranslate: 'epistola' | async }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let job of selectedCard.pendingJobs\">\n <td>{{ job.processDefinitionName }}</td>\n <td>{{ job.activityName }}</td>\n <td>\n <code>{{ job.requestId }}</code>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n </cds-tabs>\n </ng-container>\n</div>\n", styles: [".epistola-admin{padding:1.5rem}.epistola-admin .version-badge{font-size:.75rem;font-weight:500;padding:.2em .6em;border-radius:4px;background-color:#e8e8e8;color:#525252}.epistola-admin .badge{font-size:.85em;padding:.35em .65em}.epistola-admin code{font-size:.9em;color:#525252}.epistola-admin table th{font-weight:600;white-space:nowrap}.epistola-admin .status-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:.5rem;flex-shrink:0;background-color:#adb5bd}.epistola-admin .status-dot--ok{background-color:#198754}.epistola-admin .status-dot--error{background-color:#dc3545}.epistola-admin .card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}.epistola-admin .config-card{border:1px solid #dee2e6;border-radius:8px;padding:1.25rem;cursor:pointer;transition:box-shadow .15s ease,border-color .15s ease;background:#fff}.epistola-admin .config-card:hover{box-shadow:0 2px 8px #0000001a}.epistola-admin .config-card--ok{border-left:4px solid #198754}.epistola-admin .config-card--warning{border-left:4px solid #ffc107}.epistola-admin .config-card--error{border-left:4px solid #dc3545}.epistola-admin .config-card__header{display:flex;align-items:center;margin-bottom:1rem}.epistola-admin .config-card__title{margin:0;font-size:1.05rem;font-weight:600;color:#161616}.epistola-admin .config-card__body{display:flex;flex-direction:column;gap:.5rem}.epistola-admin .config-card__field{display:flex;justify-content:space-between;align-items:center}.epistola-admin .config-card__label{font-size:.875rem;color:#6c757d}.epistola-admin .config-card__value{font-size:.875rem;color:#161616}.epistola-admin .config-card__footer{margin-top:1rem;padding-top:.75rem;border-top:1px solid #f0f0f0;text-align:right}.epistola-admin .config-card__latency{font-size:.8rem;color:#adb5bd}.epistola-admin .usage-link{color:#0f62fe;text-decoration:none}.epistola-admin .usage-link:hover{text-decoration:underline}.epistola-admin .detail-info-table{max-width:500px}.epistola-admin .detail-info-table th{width:140px}.epistola-admin .detail-summary{padding:1rem 0;border-bottom:1px solid #dee2e6}\n"] }]
2863
+ }], ctorParameters: () => [{ type: EpistolaAdminService }, { type: i2$3.ActivatedRoute }, { type: i2$3.Router }] });
2864
+
2865
+ const routes = [
2866
+ {
2867
+ path: 'epistola',
2868
+ component: EpistolaAdminPageComponent,
2869
+ canActivate: [AuthGuardService],
2870
+ data: { title: 'Epistola', roles: ['ROLE_ADMIN'] },
2871
+ },
2872
+ ];
2873
+ class EpistolaAdminRoutingModule {
2874
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2875
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [i2$3.RouterModule], exports: [RouterModule] });
2876
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [RouterModule.forChild(routes), RouterModule] });
2877
+ }
2878
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, decorators: [{
2879
+ type: NgModule,
2880
+ args: [{
2881
+ imports: [RouterModule.forChild(routes)],
2882
+ exports: [RouterModule],
2883
+ }]
2884
+ }] });
2885
+
1828
2886
  const EPISTOLA_DOWNLOAD_OPTIONS = {
1829
2887
  type: 'epistola-download',
1830
2888
  selector: 'epistola-download-button',
@@ -1890,6 +2948,7 @@ class EpistolaPluginModule {
1890
2948
  return {
1891
2949
  ngModule: EpistolaPluginModule,
1892
2950
  providers: [
2951
+ EpistolaMenuService,
1893
2952
  {
1894
2953
  provide: ENVIRONMENT_INITIALIZER,
1895
2954
  multi: true,
@@ -1899,9 +2958,11 @@ class EpistolaPluginModule {
1899
2958
  registerEpistolaRetryFormComponent(injector);
1900
2959
  registerEpistolaPreviewButtonComponent(injector);
1901
2960
  registerEpistolaDocumentPreviewComponent(injector);
1902
- }
1903
- }
1904
- ]
2961
+ // Eagerly create EpistolaMenuService to trigger menu registration
2962
+ inject(EpistolaMenuService);
2963
+ },
2964
+ },
2965
+ ],
1905
2966
  };
1906
2967
  }
1907
2968
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
@@ -1911,52 +2972,40 @@ class EpistolaPluginModule {
1911
2972
  FormModule,
1912
2973
  InputModule,
1913
2974
  SelectModule,
2975
+ EpistolaAdminRoutingModule,
1914
2976
  EpistolaConfigurationComponent,
1915
2977
  GenerateDocumentConfigurationComponent,
1916
2978
  CheckJobStatusConfigurationComponent,
1917
2979
  DownloadDocumentConfigurationComponent,
1918
- DataMappingTreeComponent,
1919
- ValueInputComponent,
1920
- ScalarFieldComponent,
1921
- ArrayFieldComponent,
1922
- FieldTreeComponent,
1923
2980
  EpistolaDownloadComponent,
1924
2981
  EpistolaRetryFormComponent,
1925
2982
  EpistolaPreviewButtonComponent,
1926
- EpistolaDocumentPreviewComponent], exports: [EpistolaConfigurationComponent,
2983
+ EpistolaDocumentPreviewComponent,
2984
+ EpistolaAdminPageComponent], exports: [EpistolaConfigurationComponent,
1927
2985
  GenerateDocumentConfigurationComponent,
1928
2986
  CheckJobStatusConfigurationComponent,
1929
2987
  DownloadDocumentConfigurationComponent,
1930
- DataMappingTreeComponent,
1931
- ValueInputComponent,
1932
- ScalarFieldComponent,
1933
- ArrayFieldComponent,
1934
- FieldTreeComponent,
1935
2988
  EpistolaDownloadComponent,
1936
2989
  EpistolaRetryFormComponent,
1937
2990
  EpistolaPreviewButtonComponent,
1938
- EpistolaDocumentPreviewComponent] });
1939
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, providers: [
1940
- EpistolaPluginService
1941
- ], imports: [CommonModule,
2991
+ EpistolaDocumentPreviewComponent,
2992
+ EpistolaAdminPageComponent] });
2993
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, providers: [EpistolaPluginService, EpistolaAdminService], imports: [CommonModule,
1942
2994
  HttpClientModule,
1943
2995
  PluginTranslatePipeModule,
1944
2996
  FormModule,
1945
2997
  InputModule,
1946
2998
  SelectModule,
2999
+ EpistolaAdminRoutingModule,
1947
3000
  EpistolaConfigurationComponent,
1948
3001
  GenerateDocumentConfigurationComponent,
1949
3002
  CheckJobStatusConfigurationComponent,
1950
3003
  DownloadDocumentConfigurationComponent,
1951
- DataMappingTreeComponent,
1952
- ValueInputComponent,
1953
- ScalarFieldComponent,
1954
- ArrayFieldComponent,
1955
- FieldTreeComponent,
1956
3004
  EpistolaDownloadComponent,
1957
3005
  EpistolaRetryFormComponent,
1958
3006
  EpistolaPreviewButtonComponent,
1959
- EpistolaDocumentPreviewComponent] });
3007
+ EpistolaDocumentPreviewComponent,
3008
+ EpistolaAdminPageComponent] });
1960
3009
  }
1961
3010
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, decorators: [{
1962
3011
  type: NgModule,
@@ -1968,38 +3017,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1968
3017
  FormModule,
1969
3018
  InputModule,
1970
3019
  SelectModule,
3020
+ EpistolaAdminRoutingModule,
1971
3021
  EpistolaConfigurationComponent,
1972
3022
  GenerateDocumentConfigurationComponent,
1973
3023
  CheckJobStatusConfigurationComponent,
1974
3024
  DownloadDocumentConfigurationComponent,
1975
- DataMappingTreeComponent,
1976
- ValueInputComponent,
1977
- ScalarFieldComponent,
1978
- ArrayFieldComponent,
1979
- FieldTreeComponent,
1980
3025
  EpistolaDownloadComponent,
1981
3026
  EpistolaRetryFormComponent,
1982
3027
  EpistolaPreviewButtonComponent,
1983
- EpistolaDocumentPreviewComponent
3028
+ EpistolaDocumentPreviewComponent,
3029
+ EpistolaAdminPageComponent,
1984
3030
  ],
1985
3031
  exports: [
1986
3032
  EpistolaConfigurationComponent,
1987
3033
  GenerateDocumentConfigurationComponent,
1988
3034
  CheckJobStatusConfigurationComponent,
1989
3035
  DownloadDocumentConfigurationComponent,
1990
- DataMappingTreeComponent,
1991
- ValueInputComponent,
1992
- ScalarFieldComponent,
1993
- ArrayFieldComponent,
1994
- FieldTreeComponent,
1995
3036
  EpistolaDownloadComponent,
1996
3037
  EpistolaRetryFormComponent,
1997
3038
  EpistolaPreviewButtonComponent,
1998
- EpistolaDocumentPreviewComponent
3039
+ EpistolaDocumentPreviewComponent,
3040
+ EpistolaAdminPageComponent,
1999
3041
  ],
2000
- providers: [
2001
- EpistolaPluginService
2002
- ]
3042
+ providers: [EpistolaPluginService, EpistolaAdminService],
2003
3043
  }]
2004
3044
  }] });
2005
3045
 
@@ -2073,6 +3113,18 @@ const epistolaPluginSpecification = {
2073
3113
  // Data mapping builder translations
2074
3114
  dataMappingTitle: 'Data Mapping',
2075
3115
  dataMappingDescription: 'Koppel template velden aan Valtimo data bronnen',
3116
+ jsonataDescription: 'JSONata expressie die de template data genereert. Gebruik $doc, $pv en $case voor toegang tot document-, procesvariabelen- en zaakdata.',
3117
+ mappingModeSimple: 'Eenvoudig',
3118
+ mappingModeAdvanced: 'Geavanceerd',
3119
+ mappingTools: 'Schema & Voorbeeld',
3120
+ expectedStructure: 'Verwacht schema',
3121
+ expectedStructureLoading: 'Schema laden...',
3122
+ previewTitle: 'Voorbeeld',
3123
+ previewDocPlaceholder: 'Document ID',
3124
+ previewExpected: 'Verwacht',
3125
+ previewProduced: 'Geproduceerd',
3126
+ previewRunHint: 'Voer een document ID in en klik ▶ om een voorbeeld te zien',
3127
+ previewMissing: 'Ontbrekende verplichte velden',
2076
3128
  templateField: 'Template veld',
2077
3129
  dataSource: 'Data bron',
2078
3130
  addMapping: 'Mapping toevoegen',
@@ -2094,6 +3146,7 @@ const epistolaPluginSpecification = {
2094
3146
  pvMode: 'Procesvariabele modus',
2095
3147
  pvPlaceholder: 'Naam procesvariabele',
2096
3148
  expressionMode: 'Expressiemodus',
3149
+ availableFunctions: 'Beschikbare functies',
2097
3150
  itemFieldMapping: 'Veldnamen per item koppelen',
2098
3151
  itemFieldMappingTitle: 'Veldkoppeling per item:',
2099
3152
  sourceFieldPlaceholder: 'Bronveldnaam',
@@ -2111,7 +3164,31 @@ const epistolaPluginSpecification = {
2111
3164
  // Download document action
2112
3165
  'download-document': 'Download Document',
2113
3166
  contentVariable: 'Inhoud Variabele',
2114
- contentVariableTooltip: 'Naam van de procesvariabele waarin de documentinhoud (Base64) wordt opgeslagen'
3167
+ contentVariableTooltip: 'Naam van de procesvariabele waarin de documentinhoud (Base64) wordt opgeslagen',
3168
+ // Admin page
3169
+ epistolaAdminOverview: 'Overzicht',
3170
+ epistolaAdminRefresh: 'Vernieuwen',
3171
+ epistolaAdminLoading: 'Laden...',
3172
+ epistolaAdminNoConfigurations: 'Geen Epistola plugin configuraties gevonden.',
3173
+ epistolaAdminTenantId: 'Tenant ID',
3174
+ epistolaAdminStatus: 'Status',
3175
+ epistolaAdminConnected: 'Verbonden',
3176
+ epistolaAdminUnreachable: 'Onbereikbaar',
3177
+ epistolaAdminError: 'Fout',
3178
+ epistolaAdminPluginActions: 'Plugin acties',
3179
+ epistolaAdminProblems: 'Problemen',
3180
+ epistolaAdminBackToOverview: 'Terug naar overzicht',
3181
+ epistolaAdminNoUsageForConfig: 'Geen procesacties geconfigureerd voor deze verbinding.',
3182
+ epistolaAdminCase: 'Zaak',
3183
+ epistolaAdminProcess: 'Proces',
3184
+ epistolaAdminActivity: 'Activiteit',
3185
+ epistolaAdminAction: 'Actie',
3186
+ epistolaAdminServerVersion: 'Server versie',
3187
+ epistolaAdminExport: 'Exporteren',
3188
+ epistolaAdminPendingJobs: 'Wachtende taken',
3189
+ epistolaAdminNoPendingJobs: 'Geen wachtende taken voor deze verbinding.',
3190
+ epistolaAdminConfiguration: 'Configuratie',
3191
+ epistolaAdminRequestId: 'Request ID',
2115
3192
  },
2116
3193
  en: {
2117
3194
  title: 'Epistola Document Suite',
@@ -2164,6 +3241,18 @@ const epistolaPluginSpecification = {
2164
3241
  // Data mapping builder translations
2165
3242
  dataMappingTitle: 'Data Mapping',
2166
3243
  dataMappingDescription: 'Map template fields to Valtimo data sources',
3244
+ mappingModeSimple: 'Simple',
3245
+ mappingModeAdvanced: 'Advanced',
3246
+ mappingTools: 'Schema & Preview',
3247
+ expectedStructure: 'Expected schema',
3248
+ expectedStructureLoading: 'Loading schema...',
3249
+ previewTitle: 'Preview',
3250
+ previewDocPlaceholder: 'Document ID',
3251
+ previewExpected: 'Expected',
3252
+ previewProduced: 'Produced',
3253
+ previewRunHint: 'Enter a document ID and click ▶ to preview the output',
3254
+ previewMissing: 'Missing required fields',
3255
+ jsonataDescription: 'JSONata expression that generates the template data. Use $doc, $pv and $case to access document, process variable and case data.',
2167
3256
  templateField: 'Template field',
2168
3257
  dataSource: 'Data source',
2169
3258
  addMapping: 'Add mapping',
@@ -2185,6 +3274,7 @@ const epistolaPluginSpecification = {
2185
3274
  pvMode: 'Process variable mode',
2186
3275
  pvPlaceholder: 'Process variable name',
2187
3276
  expressionMode: 'Expression mode',
3277
+ availableFunctions: 'Available functions',
2188
3278
  itemFieldMapping: 'Map field names per item',
2189
3279
  itemFieldMappingTitle: 'Item field mapping:',
2190
3280
  sourceFieldPlaceholder: 'Source field name',
@@ -2202,9 +3292,33 @@ const epistolaPluginSpecification = {
2202
3292
  // Download document action
2203
3293
  'download-document': 'Download Document',
2204
3294
  contentVariable: 'Content Variable',
2205
- contentVariableTooltip: 'Name of the process variable to store the document content (Base64) in'
2206
- }
2207
- }
3295
+ contentVariableTooltip: 'Name of the process variable to store the document content (Base64) in',
3296
+ // Admin page
3297
+ epistolaAdminOverview: 'Overview',
3298
+ epistolaAdminRefresh: 'Refresh',
3299
+ epistolaAdminLoading: 'Loading...',
3300
+ epistolaAdminNoConfigurations: 'No Epistola plugin configurations found.',
3301
+ epistolaAdminTenantId: 'Tenant ID',
3302
+ epistolaAdminStatus: 'Status',
3303
+ epistolaAdminConnected: 'Connected',
3304
+ epistolaAdminUnreachable: 'Unreachable',
3305
+ epistolaAdminError: 'Error',
3306
+ epistolaAdminPluginActions: 'Plugin actions',
3307
+ epistolaAdminProblems: 'Problems',
3308
+ epistolaAdminBackToOverview: 'Back to overview',
3309
+ epistolaAdminNoUsageForConfig: 'No process actions configured for this connection.',
3310
+ epistolaAdminCase: 'Case',
3311
+ epistolaAdminProcess: 'Process',
3312
+ epistolaAdminActivity: 'Activity',
3313
+ epistolaAdminAction: 'Action',
3314
+ epistolaAdminServerVersion: 'Server version',
3315
+ epistolaAdminExport: 'Export',
3316
+ epistolaAdminPendingJobs: 'Pending jobs',
3317
+ epistolaAdminNoPendingJobs: 'No pending jobs for this connection.',
3318
+ epistolaAdminConfiguration: 'Configuration',
3319
+ epistolaAdminRequestId: 'Request ID',
3320
+ },
3321
+ },
2208
3322
  };
2209
3323
 
2210
3324
  /*
@@ -2215,5 +3329,5 @@ const epistolaPluginSpecification = {
2215
3329
  * Generated bundle index. Do not edit.
2216
3330
  */
2217
3331
 
2218
- export { ArrayFieldComponent, CheckJobStatusConfigurationComponent, DataMappingTreeComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EPISTOLA_DOWNLOAD_OPTIONS, EPISTOLA_PREVIEW_BUTTON_OPTIONS, EPISTOLA_RETRY_FORM_OPTIONS, EpistolaConfigurationComponent, EpistolaDocumentPreviewComponent, EpistolaDownloadComponent, EpistolaPluginModule, EpistolaPluginService, EpistolaPreviewButtonComponent, EpistolaRetryFormComponent, FieldTreeComponent, GenerateDocumentConfigurationComponent, ScalarFieldComponent, ValueInputComponent, epistolaPluginSpecification, errorResource, initialResource, loadingResource, normalizeToDots, registerEpistolaDocumentPreviewComponent, registerEpistolaDownloadComponent, registerEpistolaPreviewButtonComponent, registerEpistolaRetryFormComponent, successResource };
3332
+ export { CheckJobStatusConfigurationComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EPISTOLA_DOWNLOAD_OPTIONS, EPISTOLA_PREVIEW_BUTTON_OPTIONS, EPISTOLA_RETRY_FORM_OPTIONS, EpistolaAdminPageComponent, EpistolaAdminRoutingModule, EpistolaAdminService, EpistolaConfigurationComponent, EpistolaDocumentPreviewComponent, EpistolaDownloadComponent, EpistolaMenuService, EpistolaPluginModule, EpistolaPluginService, EpistolaPreviewButtonComponent, EpistolaRetryFormComponent, GenerateDocumentConfigurationComponent, JsonataEditorComponent, MappingBuilderComponent, epistolaPluginSpecification, errorResource, initialResource, loadingResource, registerEpistolaDocumentPreviewComponent, registerEpistolaDownloadComponent, registerEpistolaPreviewButtonComponent, registerEpistolaRetryFormComponent, successResource };
2219
3333
  //# sourceMappingURL=epistola.app-valtimo-plugin.mjs.map