@epistola.app/valtimo-plugin 0.5.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/epistola.app-valtimo-plugin.mjs +2531 -561
- package/fesm2022/epistola.app-valtimo-plugin.mjs.map +1 -1
- package/lib/assets/epistola-logo.d.ts +1 -1
- package/lib/components/epistola-admin-page/epistola-admin-page.component.d.ts +52 -0
- package/lib/components/epistola-document-preview/epistola-document-preview.component.d.ts +28 -5
- package/lib/components/epistola-document-preview/preview-utils.d.ts +18 -0
- package/lib/components/expected-structure/expected-structure.component.d.ts +11 -0
- package/lib/components/generate-document-configuration/generate-document-configuration.component.d.ts +29 -3
- package/lib/components/jsonata-editor/jsonata-editor.component.d.ts +29 -0
- package/lib/components/mapping-builder/builder-field/builder-field.component.d.ts +21 -0
- package/lib/components/mapping-builder/mapping-builder.component.d.ts +37 -0
- package/lib/components/mapping-preview/mapping-preview.component.d.ts +26 -0
- package/lib/components/override-builder/override-builder.component.d.ts +42 -0
- package/lib/components/override-builder/override-builder.formio.d.ts +4 -0
- package/lib/components/process-link-selector/process-link-selector.component.d.ts +31 -0
- package/lib/components/process-link-selector/process-link-selector.formio.d.ts +4 -0
- package/lib/epistola-admin-routing.module.d.ts +7 -0
- package/lib/epistola-enabled.guard.d.ts +2 -0
- package/lib/epistola-runtime-config.d.ts +28 -0
- package/lib/epistola.module.d.ts +11 -14
- package/lib/models/admin.d.ts +49 -0
- package/lib/models/config.d.ts +30 -2
- package/lib/models/expression.d.ts +13 -0
- package/lib/models/index.d.ts +2 -0
- package/lib/models/template.d.ts +0 -6
- package/lib/services/epistola-admin.service.d.ts +37 -0
- package/lib/services/epistola-menu.service.d.ts +13 -0
- package/lib/services/epistola-plugin.service.d.ts +16 -3
- package/lib/services/index.d.ts +2 -0
- package/lib/utils/jsonata-converter.d.ts +26 -0
- package/lib/utils/jsonata-monaco.d.ts +14 -0
- package/package.json +10 -6
- package/public_api.d.ts +9 -5
- package/lib/components/array-field/array-field.component.d.ts +0 -27
- package/lib/components/data-mapping-tree/data-mapping-tree.component.d.ts +0 -27
- package/lib/components/field-tree/field-tree.component.d.ts +0 -27
- package/lib/components/scalar-field/scalar-field.component.d.ts +0 -16
- package/lib/components/value-input/value-input.component.d.ts +0 -34
- 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,
|
|
2
|
+
import { Injectable, EventEmitter, Output, Input, Component, ChangeDetectionStrategy, inject, NgModule, ENVIRONMENT_INITIALIZER, 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
|
|
11
|
-
import
|
|
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 * as i4 from '@angular/forms';
|
|
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';
|
|
15
|
+
import * as i2$2 from '@angular/forms';
|
|
15
16
|
import { FormsModule } from '@angular/forms';
|
|
16
|
-
import * as
|
|
17
|
+
import * as _jsonata from 'jsonata';
|
|
18
|
+
import * as i2$3 from '@valtimo/process-link';
|
|
17
19
|
import * as i7 from '@formio/angular';
|
|
18
20
|
import { FormioModule } from '@formio/angular';
|
|
19
|
-
import * as i4
|
|
21
|
+
import * as i4 from '@angular/platform-browser';
|
|
22
|
+
import * as i2$4 from '@angular/router';
|
|
23
|
+
import { RouterModule, Router } 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`, {
|
|
194
|
+
return this.http.get(`${this.apiEndpoint}/process-variables`, {
|
|
195
|
+
params: { processDefinitionKey },
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get variable suggestions for JSONata autocompletion.
|
|
200
|
+
*/
|
|
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
|
+
});
|
|
98
210
|
}
|
|
99
211
|
/**
|
|
100
|
-
*
|
|
212
|
+
* Evaluate a JSONata expression against a real document.
|
|
101
213
|
*/
|
|
102
|
-
|
|
103
|
-
return this.http.post(`${this.apiEndpoint}/
|
|
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,26 @@ 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
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Validate the JSONata syntax of action-config expressions before save.
|
|
242
|
+
* Parse-only; runtime errors (missing variables, type mismatches) are not detected.
|
|
243
|
+
*/
|
|
244
|
+
validateJsonata(request) {
|
|
245
|
+
return this.http.post(`${this.apiEndpoint}/validate-jsonata`, request);
|
|
246
|
+
}
|
|
118
247
|
/**
|
|
119
248
|
* Discover all previewable document sources for a given Valtimo document.
|
|
120
249
|
*/
|
|
121
250
|
getPreviewSources(documentId) {
|
|
122
|
-
return this.http.get(`${this.apiEndpoint}/preview-sources`, {
|
|
251
|
+
return this.http.get(`${this.apiEndpoint}/preview-sources`, {
|
|
252
|
+
params: { documentId },
|
|
253
|
+
});
|
|
123
254
|
}
|
|
124
255
|
/**
|
|
125
256
|
* Preview a document by dry-running the generate-document process link.
|
|
@@ -131,7 +262,7 @@ class EpistolaPluginService {
|
|
|
131
262
|
processDefinitionKey,
|
|
132
263
|
sourceActivityId,
|
|
133
264
|
processInstanceId: processInstanceId || null,
|
|
134
|
-
overrides: overrides || null
|
|
265
|
+
overrides: overrides || null,
|
|
135
266
|
});
|
|
136
267
|
}
|
|
137
268
|
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 +333,11 @@ class EpistolaConfigurationComponent {
|
|
|
202
333
|
});
|
|
203
334
|
}
|
|
204
335
|
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
|
|
336
|
+
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
337
|
}
|
|
207
338
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaConfigurationComponent, decorators: [{
|
|
208
339
|
type: Component,
|
|
209
|
-
args: [{ selector: 'epistola-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form\n
|
|
340
|
+
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
341
|
}], propDecorators: { save$: [{
|
|
211
342
|
type: Input
|
|
212
343
|
}], disabled$: [{
|
|
@@ -221,453 +352,1164 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
221
352
|
type: Output
|
|
222
353
|
}] } });
|
|
223
354
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
355
|
+
/**
|
|
356
|
+
* Shared state for the JSONata completion provider.
|
|
357
|
+
* Updated by the editor component when suggestions/functions change.
|
|
358
|
+
*/
|
|
359
|
+
const jsonataCompletionData = {
|
|
360
|
+
suggestions: null,
|
|
361
|
+
functions: [],
|
|
362
|
+
};
|
|
363
|
+
/**
|
|
364
|
+
* Register the JSONata language in Monaco editor.
|
|
365
|
+
* Call this once when Monaco is available (e.g., in editor component OnInit).
|
|
366
|
+
*/
|
|
367
|
+
function registerJsonataLanguage(monaco) {
|
|
368
|
+
// Only register once
|
|
369
|
+
if (monaco.languages.getLanguages().some((lang) => lang.id === 'jsonata')) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
monaco.languages.register({ id: 'jsonata' });
|
|
373
|
+
// Syntax highlighting via Monarch tokenizer
|
|
374
|
+
monaco.languages.setMonarchTokensProvider('jsonata', {
|
|
375
|
+
defaultToken: '',
|
|
376
|
+
tokenPostfix: '.jsonata',
|
|
377
|
+
keywords: ['true', 'false', 'null', 'in', 'and', 'or', 'not'],
|
|
378
|
+
operators: ['&', '?', ':', '=', '!=', '>', '<', '>=', '<=', '+', '-', '*', '/', '%', '~>'],
|
|
379
|
+
symbols: /[=><!~?:&|+\-*/^%]+/,
|
|
380
|
+
tokenizer: {
|
|
381
|
+
root: [
|
|
382
|
+
// Variables: $identifier
|
|
383
|
+
[/\$[a-zA-Z_]\w*/, 'variable'],
|
|
384
|
+
// Identifiers and keywords
|
|
385
|
+
[
|
|
386
|
+
/[a-zA-Z_]\w*/,
|
|
387
|
+
{
|
|
388
|
+
cases: {
|
|
389
|
+
'@keywords': 'keyword',
|
|
390
|
+
'@default': 'identifier',
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
// Whitespace
|
|
395
|
+
{ include: '@whitespace' },
|
|
396
|
+
// Strings
|
|
397
|
+
[/"([^"\\]|\\.)*$/, 'string.invalid'],
|
|
398
|
+
[/"/, 'string', '@string_double'],
|
|
399
|
+
[/'([^'\\]|\\.)*$/, 'string.invalid'],
|
|
400
|
+
[/'/, 'string', '@string_single'],
|
|
401
|
+
// Numbers
|
|
402
|
+
[/\d+(\.\d+)?([eE][-+]?\d+)?/, 'number'],
|
|
403
|
+
// Delimiters and operators
|
|
404
|
+
[/[{}()\[\]]/, '@brackets'],
|
|
405
|
+
[/[,;.]/, 'delimiter'],
|
|
406
|
+
[
|
|
407
|
+
/@symbols/,
|
|
408
|
+
{
|
|
409
|
+
cases: {
|
|
410
|
+
'@operators': 'operator',
|
|
411
|
+
'@default': '',
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
],
|
|
416
|
+
string_double: [
|
|
417
|
+
[/[^\\"]+/, 'string'],
|
|
418
|
+
[/\\./, 'string.escape'],
|
|
419
|
+
[/"/, 'string', '@pop'],
|
|
420
|
+
],
|
|
421
|
+
string_single: [
|
|
422
|
+
[/[^\\']+/, 'string'],
|
|
423
|
+
[/\\./, 'string.escape'],
|
|
424
|
+
[/'/, 'string', '@pop'],
|
|
425
|
+
],
|
|
426
|
+
whitespace: [[/[ \t\r\n]+/, 'white']],
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
// Autocomplete provider
|
|
430
|
+
monaco.languages.registerCompletionItemProvider('jsonata', {
|
|
431
|
+
triggerCharacters: ['$', '.'],
|
|
432
|
+
provideCompletionItems: (model, position) => {
|
|
433
|
+
const textUntilPosition = model.getValueInRange({
|
|
434
|
+
startLineNumber: position.lineNumber,
|
|
435
|
+
startColumn: 1,
|
|
436
|
+
endLineNumber: position.lineNumber,
|
|
437
|
+
endColumn: position.column,
|
|
438
|
+
});
|
|
439
|
+
const suggestions = [];
|
|
440
|
+
const CompletionItemKind = monaco.languages.CompletionItemKind;
|
|
441
|
+
// After "$" — suggest variables and functions
|
|
442
|
+
if (textUntilPosition.endsWith('$')) {
|
|
443
|
+
suggestions.push(...['doc', 'pv', 'case'].map((v) => ({
|
|
444
|
+
label: `$${v}`,
|
|
445
|
+
kind: CompletionItemKind.Variable,
|
|
446
|
+
insertText: v,
|
|
447
|
+
detail: `Context variable`,
|
|
448
|
+
})));
|
|
449
|
+
// Custom functions
|
|
450
|
+
for (const func of jsonataCompletionData.functions) {
|
|
451
|
+
suggestions.push({
|
|
452
|
+
label: `$${func.name}`,
|
|
453
|
+
kind: CompletionItemKind.Function,
|
|
454
|
+
insertText: `${func.name}($0)`,
|
|
455
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
456
|
+
detail: func.description || 'Custom function',
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
// Built-in JSONata functions
|
|
460
|
+
const builtins = [
|
|
461
|
+
'string',
|
|
462
|
+
'number',
|
|
463
|
+
'boolean',
|
|
464
|
+
'length',
|
|
465
|
+
'substring',
|
|
466
|
+
'uppercase',
|
|
467
|
+
'lowercase',
|
|
468
|
+
'trim',
|
|
469
|
+
'contains',
|
|
470
|
+
'split',
|
|
471
|
+
'join',
|
|
472
|
+
'sum',
|
|
473
|
+
'count',
|
|
474
|
+
'max',
|
|
475
|
+
'min',
|
|
476
|
+
'average',
|
|
477
|
+
'append',
|
|
478
|
+
'sort',
|
|
479
|
+
'reverse',
|
|
480
|
+
'keys',
|
|
481
|
+
'values',
|
|
482
|
+
'lookup',
|
|
483
|
+
'now',
|
|
484
|
+
'exists',
|
|
485
|
+
'type',
|
|
486
|
+
'not',
|
|
487
|
+
];
|
|
488
|
+
for (const fn of builtins) {
|
|
489
|
+
suggestions.push({
|
|
490
|
+
label: `$${fn}`,
|
|
491
|
+
kind: CompletionItemKind.Function,
|
|
492
|
+
insertText: `${fn}($0)`,
|
|
493
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
494
|
+
detail: 'Built-in JSONata function',
|
|
495
|
+
});
|
|
496
|
+
}
|
|
233
497
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
498
|
+
// After "$doc." — suggest document paths
|
|
499
|
+
if (/\$doc\.\s*$/.test(textUntilPosition) || /\$doc\.[a-zA-Z_]*$/.test(textUntilPosition)) {
|
|
500
|
+
const docPaths = jsonataCompletionData.suggestions?.doc || [];
|
|
501
|
+
for (const path of docPaths) {
|
|
502
|
+
suggestions.push({
|
|
503
|
+
label: path,
|
|
504
|
+
kind: CompletionItemKind.Field,
|
|
505
|
+
insertText: path,
|
|
506
|
+
detail: 'Document field',
|
|
507
|
+
});
|
|
508
|
+
}
|
|
240
509
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
510
|
+
// After "$pv." — suggest process variables
|
|
511
|
+
if (/\$pv\.\s*$/.test(textUntilPosition) || /\$pv\.[a-zA-Z_]*$/.test(textUntilPosition)) {
|
|
512
|
+
const pvNames = jsonataCompletionData.suggestions?.pv || [];
|
|
513
|
+
for (const name of pvNames) {
|
|
514
|
+
suggestions.push({
|
|
515
|
+
label: name,
|
|
516
|
+
kind: CompletionItemKind.Variable,
|
|
517
|
+
insertText: name,
|
|
518
|
+
detail: 'Process variable',
|
|
519
|
+
});
|
|
244
520
|
}
|
|
245
521
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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 };
|
|
522
|
+
return { suggestions };
|
|
523
|
+
},
|
|
524
|
+
});
|
|
257
525
|
}
|
|
258
526
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
*/
|
|
263
|
-
class ValueInputComponent {
|
|
264
|
-
cdr;
|
|
265
|
-
name = '';
|
|
266
|
-
value = '';
|
|
267
|
-
pluginId = '';
|
|
268
|
-
caseDefinitionKey = null;
|
|
269
|
-
processVariables = [];
|
|
527
|
+
const jsonata$1 = _jsonata.default || _jsonata;
|
|
528
|
+
class JsonataEditorComponent {
|
|
529
|
+
expression = '';
|
|
270
530
|
disabled = false;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
531
|
+
suggestions = null;
|
|
532
|
+
functions = [];
|
|
533
|
+
expressionChange = new EventEmitter();
|
|
534
|
+
validChange = new EventEmitter();
|
|
535
|
+
editorModel = { value: '', language: 'jsonata' };
|
|
536
|
+
editorOptions = {
|
|
537
|
+
minimap: { enabled: false },
|
|
538
|
+
lineNumbers: 'on',
|
|
539
|
+
scrollBeyondLastLine: false,
|
|
540
|
+
fontSize: 13,
|
|
541
|
+
tabSize: 2,
|
|
542
|
+
wordWrap: 'on',
|
|
543
|
+
renderWhitespace: 'none',
|
|
544
|
+
};
|
|
545
|
+
error = null;
|
|
546
|
+
destroy$ = new Subject();
|
|
547
|
+
validate$ = new Subject();
|
|
548
|
+
suppressChange = false;
|
|
549
|
+
languageRegistered = false;
|
|
550
|
+
constructor() {
|
|
551
|
+
this.validate$.pipe(debounceTime(300), takeUntil(this.destroy$)).subscribe((value) => {
|
|
552
|
+
this.validateExpression(value);
|
|
553
|
+
});
|
|
554
|
+
// Try to register language eagerly if Monaco is already loaded
|
|
555
|
+
this.tryRegisterLanguage();
|
|
279
556
|
}
|
|
280
557
|
ngOnChanges(changes) {
|
|
281
|
-
if (changes['
|
|
282
|
-
this.
|
|
283
|
-
this.
|
|
284
|
-
this.selectedPv = extractPvName(this.value);
|
|
558
|
+
if (changes['expression'] && !this.suppressChange) {
|
|
559
|
+
this.editorModel = { value: this.expression || '', language: 'jsonata' };
|
|
560
|
+
this.validate$.next(this.expression);
|
|
285
561
|
}
|
|
286
|
-
if (changes['
|
|
287
|
-
this.
|
|
562
|
+
if (changes['suggestions']) {
|
|
563
|
+
jsonataCompletionData.suggestions = this.suggestions;
|
|
564
|
+
}
|
|
565
|
+
if (changes['functions']) {
|
|
566
|
+
jsonataCompletionData.functions = this.functions;
|
|
288
567
|
}
|
|
289
568
|
}
|
|
290
|
-
|
|
291
|
-
this.
|
|
292
|
-
|
|
293
|
-
onBrowseValueChange(newValue) {
|
|
294
|
-
this.valueChange.emit(normalizeToDots(newValue));
|
|
569
|
+
ngOnDestroy() {
|
|
570
|
+
this.destroy$.next();
|
|
571
|
+
this.destroy$.complete();
|
|
295
572
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
this.
|
|
573
|
+
onEditorValueChange(value) {
|
|
574
|
+
// Register language on first editor event (Monaco is now loaded)
|
|
575
|
+
if (!this.languageRegistered) {
|
|
576
|
+
this.tryRegisterLanguage();
|
|
577
|
+
}
|
|
578
|
+
if (this.suppressChange)
|
|
579
|
+
return;
|
|
580
|
+
this.suppressChange = true;
|
|
581
|
+
this.expression = value;
|
|
582
|
+
this.expressionChange.emit(value);
|
|
583
|
+
this.validate$.next(value);
|
|
584
|
+
setTimeout(() => (this.suppressChange = false));
|
|
299
585
|
}
|
|
300
|
-
|
|
301
|
-
|
|
586
|
+
tryRegisterLanguage() {
|
|
587
|
+
const m = window.monaco;
|
|
588
|
+
if (m) {
|
|
589
|
+
registerJsonataLanguage(m);
|
|
590
|
+
this.languageRegistered = true;
|
|
591
|
+
jsonataCompletionData.suggestions = this.suggestions;
|
|
592
|
+
jsonataCompletionData.functions = this.functions;
|
|
593
|
+
}
|
|
302
594
|
}
|
|
303
|
-
|
|
304
|
-
if (!value)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
return
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
595
|
+
validateExpression(value) {
|
|
596
|
+
if (!value || !value.trim()) {
|
|
597
|
+
this.error = null;
|
|
598
|
+
this.validChange.emit(true);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
jsonata$1(value);
|
|
603
|
+
this.error = null;
|
|
604
|
+
this.validChange.emit(true);
|
|
605
|
+
}
|
|
606
|
+
catch (e) {
|
|
607
|
+
this.error = e.message || 'Invalid expression';
|
|
608
|
+
this.validChange.emit(false);
|
|
609
|
+
}
|
|
313
610
|
}
|
|
314
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
315
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type:
|
|
611
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: JsonataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
612
|
+
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: `
|
|
613
|
+
<div class="jsonata-editor">
|
|
614
|
+
<valtimo-editor
|
|
615
|
+
[model]="editorModel"
|
|
616
|
+
[editorOptions]="editorOptions"
|
|
617
|
+
[disabled]="disabled"
|
|
618
|
+
[heightPx]="250"
|
|
619
|
+
[formatOnLoad]="false"
|
|
620
|
+
(valueChangeEvent)="onEditorValueChange($event)"
|
|
621
|
+
></valtimo-editor>
|
|
622
|
+
<div class="jsonata-editor__footer">
|
|
623
|
+
<span *ngIf="error" class="jsonata-editor__error">{{ error }}</span>
|
|
624
|
+
<span *ngIf="!error && expression" class="jsonata-editor__valid">✓</span>
|
|
625
|
+
<span class="jsonata-editor__variables">$doc · $pv · $case</span>
|
|
626
|
+
</div>
|
|
627
|
+
</div>
|
|
628
|
+
`, 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
629
|
}
|
|
317
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
630
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: JsonataEditorComponent, decorators: [{
|
|
318
631
|
type: Component,
|
|
319
|
-
args: [{ selector: 'epistola-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
632
|
+
args: [{ selector: 'epistola-jsonata-editor', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, EditorModule], template: `
|
|
633
|
+
<div class="jsonata-editor">
|
|
634
|
+
<valtimo-editor
|
|
635
|
+
[model]="editorModel"
|
|
636
|
+
[editorOptions]="editorOptions"
|
|
637
|
+
[disabled]="disabled"
|
|
638
|
+
[heightPx]="250"
|
|
639
|
+
[formatOnLoad]="false"
|
|
640
|
+
(valueChangeEvent)="onEditorValueChange($event)"
|
|
641
|
+
></valtimo-editor>
|
|
642
|
+
<div class="jsonata-editor__footer">
|
|
643
|
+
<span *ngIf="error" class="jsonata-editor__error">{{ error }}</span>
|
|
644
|
+
<span *ngIf="!error && expression" class="jsonata-editor__valid">✓</span>
|
|
645
|
+
<span class="jsonata-editor__variables">$doc · $pv · $case</span>
|
|
646
|
+
</div>
|
|
647
|
+
</div>
|
|
648
|
+
`, 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"] }]
|
|
649
|
+
}], ctorParameters: () => [], propDecorators: { expression: [{
|
|
335
650
|
type: Input
|
|
336
651
|
}], disabled: [{
|
|
337
652
|
type: Input
|
|
338
|
-
}],
|
|
653
|
+
}], suggestions: [{
|
|
339
654
|
type: Input
|
|
340
|
-
}],
|
|
655
|
+
}], functions: [{
|
|
656
|
+
type: Input
|
|
657
|
+
}], expressionChange: [{
|
|
658
|
+
type: Output
|
|
659
|
+
}], validChange: [{
|
|
341
660
|
type: Output
|
|
342
661
|
}] } });
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
662
|
+
|
|
663
|
+
class ExpectedStructureComponent {
|
|
664
|
+
templateFields = [];
|
|
665
|
+
structureText = '{}';
|
|
666
|
+
ngOnChanges(changes) {
|
|
667
|
+
if (changes['templateFields']) {
|
|
668
|
+
this.structureText = this.buildStructure(this.templateFields, 0);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
buildStructure(fields, depth) {
|
|
672
|
+
if (!fields || fields.length === 0)
|
|
673
|
+
return '{}';
|
|
674
|
+
const indent = ' '.repeat(depth + 1);
|
|
675
|
+
const closing = ' '.repeat(depth);
|
|
676
|
+
const lines = fields.map((f) => {
|
|
677
|
+
const req = f.required ? ' (required)' : '';
|
|
678
|
+
if (f.fieldType === 'OBJECT' && f.children?.length) {
|
|
679
|
+
const nested = this.buildStructure(f.children, depth + 1);
|
|
680
|
+
return `${indent}"${f.name}": ${nested}${req}`;
|
|
681
|
+
}
|
|
682
|
+
if (f.fieldType === 'ARRAY') {
|
|
683
|
+
if (f.children?.length) {
|
|
684
|
+
const itemStructure = this.buildStructure(f.children, depth + 2);
|
|
685
|
+
return `${indent}"${f.name}": [${itemStructure}]${req}`;
|
|
686
|
+
}
|
|
687
|
+
return `${indent}"${f.name}": array${req}`;
|
|
688
|
+
}
|
|
689
|
+
return `${indent}"${f.name}": ${f.type || 'any'}${req}`;
|
|
690
|
+
});
|
|
691
|
+
return `{\n${lines.join(',\n')}\n${closing}}`;
|
|
360
692
|
}
|
|
361
|
-
|
|
693
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ExpectedStructureComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
694
|
+
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: `
|
|
695
|
+
<div class="expected">
|
|
696
|
+
<div class="expected__header">
|
|
697
|
+
{{ 'expectedStructure' | pluginTranslate: 'epistola' | async }}
|
|
698
|
+
</div>
|
|
699
|
+
<div *ngIf="!templateFields || templateFields.length === 0" class="expected__empty">
|
|
700
|
+
{{ 'expectedStructureLoading' | pluginTranslate: 'epistola' | async }}
|
|
701
|
+
</div>
|
|
702
|
+
<pre *ngIf="templateFields && templateFields.length > 0" class="expected__code">{{
|
|
703
|
+
structureText
|
|
704
|
+
}}</pre>
|
|
705
|
+
</div>
|
|
706
|
+
`, 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
707
|
}
|
|
708
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ExpectedStructureComponent, decorators: [{
|
|
709
|
+
type: Component,
|
|
710
|
+
args: [{ selector: 'epistola-expected-structure', standalone: true, imports: [CommonModule, PluginTranslatePipeModule], template: `
|
|
711
|
+
<div class="expected">
|
|
712
|
+
<div class="expected__header">
|
|
713
|
+
{{ 'expectedStructure' | pluginTranslate: 'epistola' | async }}
|
|
714
|
+
</div>
|
|
715
|
+
<div *ngIf="!templateFields || templateFields.length === 0" class="expected__empty">
|
|
716
|
+
{{ 'expectedStructureLoading' | pluginTranslate: 'epistola' | async }}
|
|
717
|
+
</div>
|
|
718
|
+
<pre *ngIf="templateFields && templateFields.length > 0" class="expected__code">{{
|
|
719
|
+
structureText
|
|
720
|
+
}}</pre>
|
|
721
|
+
</div>
|
|
722
|
+
`, 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"] }]
|
|
723
|
+
}], propDecorators: { templateFields: [{
|
|
724
|
+
type: Input
|
|
725
|
+
}] } });
|
|
363
726
|
|
|
364
|
-
class
|
|
727
|
+
class BuilderFieldComponent {
|
|
365
728
|
field;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
caseDefinitionKey = null;
|
|
369
|
-
processVariables = [];
|
|
729
|
+
path = [];
|
|
730
|
+
suggestions = [];
|
|
370
731
|
disabled = false;
|
|
732
|
+
collapsed = false;
|
|
733
|
+
required = false;
|
|
734
|
+
collapsedPaths = new Set();
|
|
371
735
|
valueChange = new EventEmitter();
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
this.valueChange.emit(newValue || undefined);
|
|
736
|
+
modeToggle = new EventEmitter();
|
|
737
|
+
collapseToggle = new EventEmitter();
|
|
738
|
+
isChildCollapsed(childIndex) {
|
|
739
|
+
return this.collapsedPaths.has(this.path.concat(childIndex).join('.'));
|
|
377
740
|
}
|
|
378
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
379
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type:
|
|
741
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BuilderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
742
|
+
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: `
|
|
743
|
+
<div class="builder-field">
|
|
744
|
+
<div
|
|
745
|
+
class="builder-field__name"
|
|
746
|
+
[class.builder-field__name--clickable]="field.children"
|
|
747
|
+
(click)="field.children && collapseToggle.emit(path)"
|
|
748
|
+
>
|
|
749
|
+
<span *ngIf="field.children" class="builder-field__chevron">{{
|
|
750
|
+
collapsed ? '▶' : '▼'
|
|
751
|
+
}}</span>
|
|
752
|
+
<span class="builder-field__label">{{ field.name }}</span>
|
|
753
|
+
<span *ngIf="required" class="builder-field__required">*</span>
|
|
754
|
+
<span *ngIf="field.children" class="builder-field__type">(object)</span>
|
|
755
|
+
</div>
|
|
756
|
+
|
|
757
|
+
<div class="builder-field__value" *ngIf="!field.children">
|
|
758
|
+
<input
|
|
759
|
+
*ngIf="field.mode === 'ref'"
|
|
760
|
+
type="text"
|
|
761
|
+
class="builder-field__input"
|
|
762
|
+
[ngModel]="field.value"
|
|
763
|
+
(ngModelChange)="valueChange.emit({ path: path, value: $event })"
|
|
764
|
+
[disabled]="disabled"
|
|
765
|
+
placeholder="$doc.path.to.field"
|
|
766
|
+
[attr.list]="'suggestions-' + path.join('-')"
|
|
767
|
+
/>
|
|
768
|
+
<datalist *ngIf="field.mode === 'ref'" [id]="'suggestions-' + path.join('-')">
|
|
769
|
+
<option *ngFor="let s of suggestions" [value]="s"></option>
|
|
770
|
+
</datalist>
|
|
771
|
+
<input
|
|
772
|
+
*ngIf="field.mode === 'raw'"
|
|
773
|
+
type="text"
|
|
774
|
+
class="builder-field__input builder-field__input--raw"
|
|
775
|
+
[ngModel]="field.value"
|
|
776
|
+
(ngModelChange)="valueChange.emit({ path: path, value: $event })"
|
|
777
|
+
[disabled]="disabled"
|
|
778
|
+
placeholder="JSONata expression"
|
|
779
|
+
/>
|
|
780
|
+
<button
|
|
781
|
+
class="builder-field__mode-toggle"
|
|
782
|
+
(click)="modeToggle.emit(path)"
|
|
783
|
+
[disabled]="disabled"
|
|
784
|
+
[title]="field.mode === 'ref' ? 'Switch to raw JSONata' : 'Switch to reference'"
|
|
785
|
+
>
|
|
786
|
+
{{ field.mode === 'ref' ? 'fx' : '·' }}
|
|
787
|
+
</button>
|
|
788
|
+
</div>
|
|
789
|
+
|
|
790
|
+
<div *ngIf="field.children && !collapsed" class="builder-field__children">
|
|
791
|
+
<epistola-builder-field
|
|
792
|
+
*ngFor="let child of field.children; let j = index"
|
|
793
|
+
[field]="child"
|
|
794
|
+
[path]="path.concat(j)"
|
|
795
|
+
[suggestions]="suggestions"
|
|
796
|
+
[disabled]="disabled"
|
|
797
|
+
[collapsed]="isChildCollapsed(j)"
|
|
798
|
+
[collapsedPaths]="collapsedPaths"
|
|
799
|
+
[required]="false"
|
|
800
|
+
(valueChange)="valueChange.emit($event)"
|
|
801
|
+
(modeToggle)="modeToggle.emit($event)"
|
|
802
|
+
(collapseToggle)="collapseToggle.emit($event)"
|
|
803
|
+
></epistola-builder-field>
|
|
804
|
+
</div>
|
|
805
|
+
</div>
|
|
806
|
+
`, 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: i2$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.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: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
380
807
|
}
|
|
381
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
808
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BuilderFieldComponent, decorators: [{
|
|
382
809
|
type: Component,
|
|
383
|
-
args: [{ selector: 'epistola-
|
|
810
|
+
args: [{ selector: 'epistola-builder-field', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
811
|
+
<div class="builder-field">
|
|
812
|
+
<div
|
|
813
|
+
class="builder-field__name"
|
|
814
|
+
[class.builder-field__name--clickable]="field.children"
|
|
815
|
+
(click)="field.children && collapseToggle.emit(path)"
|
|
816
|
+
>
|
|
817
|
+
<span *ngIf="field.children" class="builder-field__chevron">{{
|
|
818
|
+
collapsed ? '▶' : '▼'
|
|
819
|
+
}}</span>
|
|
820
|
+
<span class="builder-field__label">{{ field.name }}</span>
|
|
821
|
+
<span *ngIf="required" class="builder-field__required">*</span>
|
|
822
|
+
<span *ngIf="field.children" class="builder-field__type">(object)</span>
|
|
823
|
+
</div>
|
|
824
|
+
|
|
825
|
+
<div class="builder-field__value" *ngIf="!field.children">
|
|
826
|
+
<input
|
|
827
|
+
*ngIf="field.mode === 'ref'"
|
|
828
|
+
type="text"
|
|
829
|
+
class="builder-field__input"
|
|
830
|
+
[ngModel]="field.value"
|
|
831
|
+
(ngModelChange)="valueChange.emit({ path: path, value: $event })"
|
|
832
|
+
[disabled]="disabled"
|
|
833
|
+
placeholder="$doc.path.to.field"
|
|
834
|
+
[attr.list]="'suggestions-' + path.join('-')"
|
|
835
|
+
/>
|
|
836
|
+
<datalist *ngIf="field.mode === 'ref'" [id]="'suggestions-' + path.join('-')">
|
|
837
|
+
<option *ngFor="let s of suggestions" [value]="s"></option>
|
|
838
|
+
</datalist>
|
|
839
|
+
<input
|
|
840
|
+
*ngIf="field.mode === 'raw'"
|
|
841
|
+
type="text"
|
|
842
|
+
class="builder-field__input builder-field__input--raw"
|
|
843
|
+
[ngModel]="field.value"
|
|
844
|
+
(ngModelChange)="valueChange.emit({ path: path, value: $event })"
|
|
845
|
+
[disabled]="disabled"
|
|
846
|
+
placeholder="JSONata expression"
|
|
847
|
+
/>
|
|
848
|
+
<button
|
|
849
|
+
class="builder-field__mode-toggle"
|
|
850
|
+
(click)="modeToggle.emit(path)"
|
|
851
|
+
[disabled]="disabled"
|
|
852
|
+
[title]="field.mode === 'ref' ? 'Switch to raw JSONata' : 'Switch to reference'"
|
|
853
|
+
>
|
|
854
|
+
{{ field.mode === 'ref' ? 'fx' : '·' }}
|
|
855
|
+
</button>
|
|
856
|
+
</div>
|
|
857
|
+
|
|
858
|
+
<div *ngIf="field.children && !collapsed" class="builder-field__children">
|
|
859
|
+
<epistola-builder-field
|
|
860
|
+
*ngFor="let child of field.children; let j = index"
|
|
861
|
+
[field]="child"
|
|
862
|
+
[path]="path.concat(j)"
|
|
863
|
+
[suggestions]="suggestions"
|
|
864
|
+
[disabled]="disabled"
|
|
865
|
+
[collapsed]="isChildCollapsed(j)"
|
|
866
|
+
[collapsedPaths]="collapsedPaths"
|
|
867
|
+
[required]="false"
|
|
868
|
+
(valueChange)="valueChange.emit($event)"
|
|
869
|
+
(modeToggle)="modeToggle.emit($event)"
|
|
870
|
+
(collapseToggle)="collapseToggle.emit($event)"
|
|
871
|
+
></epistola-builder-field>
|
|
872
|
+
</div>
|
|
873
|
+
</div>
|
|
874
|
+
`, 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
875
|
}], propDecorators: { field: [{
|
|
385
876
|
type: Input
|
|
386
|
-
}],
|
|
877
|
+
}], path: [{
|
|
387
878
|
type: Input
|
|
388
|
-
}],
|
|
879
|
+
}], suggestions: [{
|
|
389
880
|
type: Input
|
|
390
|
-
}],
|
|
881
|
+
}], disabled: [{
|
|
391
882
|
type: Input
|
|
392
|
-
}],
|
|
883
|
+
}], collapsed: [{
|
|
393
884
|
type: Input
|
|
394
|
-
}],
|
|
885
|
+
}], required: [{
|
|
886
|
+
type: Input
|
|
887
|
+
}], collapsedPaths: [{
|
|
395
888
|
type: Input
|
|
396
889
|
}], valueChange: [{
|
|
397
890
|
type: Output
|
|
891
|
+
}], modeToggle: [{
|
|
892
|
+
type: Output
|
|
893
|
+
}], collapseToggle: [{
|
|
894
|
+
type: Output
|
|
398
895
|
}] } });
|
|
399
896
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
}
|
|
897
|
+
const jsonata = _jsonata.default || _jsonata;
|
|
898
|
+
/**
|
|
899
|
+
* Parse a JSONata expression into BuilderField array.
|
|
900
|
+
* Only supports top-level object literals with simple path references or nested objects.
|
|
901
|
+
* Anything else is stored as raw JSONata text.
|
|
902
|
+
*/
|
|
903
|
+
function parseJsonataToBuilder(expression) {
|
|
904
|
+
if (!expression || !expression.trim()) {
|
|
905
|
+
return [];
|
|
906
|
+
}
|
|
907
|
+
try {
|
|
908
|
+
const ast = jsonata(expression).ast();
|
|
909
|
+
if (ast.type === 'unary' && ast.value === '{') {
|
|
910
|
+
return parseObjectEntries(ast.lhs, expression);
|
|
422
911
|
}
|
|
912
|
+
// Not a top-level object — can't represent in builder
|
|
913
|
+
return [{ name: '_root', mode: 'raw', value: expression }];
|
|
423
914
|
}
|
|
424
|
-
|
|
425
|
-
|
|
915
|
+
catch {
|
|
916
|
+
// Invalid JSONata — return as single raw field
|
|
917
|
+
return [{ name: '_root', mode: 'raw', value: expression }];
|
|
426
918
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Convert BuilderField array back to a JSONata expression string.
|
|
922
|
+
*/
|
|
923
|
+
function builderToJsonata(fields) {
|
|
924
|
+
if (fields.length === 0) {
|
|
925
|
+
return '';
|
|
926
|
+
}
|
|
927
|
+
// Special case: single _root raw field means the whole expression is raw
|
|
928
|
+
if (fields.length === 1 && fields[0].name === '_root' && fields[0].mode === 'raw') {
|
|
929
|
+
return fields[0].value;
|
|
930
|
+
}
|
|
931
|
+
const entries = fields.map((field) => formatFieldEntry(field)).filter(Boolean);
|
|
932
|
+
return `{\n${entries.join(',\n')}\n}`;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Check if a JSONata expression can be fully represented by the builder
|
|
936
|
+
* (i.e., all fields are simple refs or nested objects of simple refs).
|
|
937
|
+
*/
|
|
938
|
+
function isBuilderCompatible(expression) {
|
|
939
|
+
const fields = parseJsonataToBuilder(expression);
|
|
940
|
+
return fields.every((f) => f.mode === 'ref' || (f.children && f.children.every(isFieldSimple)));
|
|
941
|
+
}
|
|
942
|
+
function isFieldSimple(field) {
|
|
943
|
+
if (field.mode === 'raw')
|
|
944
|
+
return false;
|
|
945
|
+
if (field.children)
|
|
946
|
+
return field.children.every(isFieldSimple);
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
function parseObjectEntries(entries, source) {
|
|
950
|
+
return entries.map(([keyNode, valueNode]) => {
|
|
951
|
+
const name = keyNode.value;
|
|
952
|
+
return classifyValue(name, valueNode, source);
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
function classifyValue(name, node, source) {
|
|
956
|
+
// Simple path reference: $doc.x.y, $pv.x, $case.x — store as JSONata directly
|
|
957
|
+
if (node.type === 'path' && node.steps?.length > 0 && node.steps[0].type === 'variable') {
|
|
958
|
+
const varName = node.steps[0].value; // doc, pv, case
|
|
959
|
+
if (['doc', 'pv', 'case'].includes(varName)) {
|
|
960
|
+
const path = node.steps
|
|
961
|
+
.slice(1)
|
|
962
|
+
.map((s) => s.value)
|
|
963
|
+
.join('.');
|
|
964
|
+
return { name, mode: 'ref', value: `$${varName}.${path}` };
|
|
433
965
|
}
|
|
966
|
+
}
|
|
967
|
+
// String literal
|
|
968
|
+
if (node.type === 'string') {
|
|
969
|
+
return { name, mode: 'ref', value: `"${node.value}"` };
|
|
970
|
+
}
|
|
971
|
+
// Number literal
|
|
972
|
+
if (node.type === 'number') {
|
|
973
|
+
return { name, mode: 'ref', value: String(node.value) };
|
|
974
|
+
}
|
|
975
|
+
// Boolean literal (value node)
|
|
976
|
+
if (node.type === 'value' && typeof node.value === 'boolean') {
|
|
977
|
+
return { name, mode: 'ref', value: String(node.value) };
|
|
978
|
+
}
|
|
979
|
+
// Nested object
|
|
980
|
+
if (node.type === 'unary' && node.value === '{' && node.lhs) {
|
|
981
|
+
const children = parseObjectEntries(node.lhs, source);
|
|
982
|
+
return { name, mode: 'ref', value: '', children };
|
|
983
|
+
}
|
|
984
|
+
// Anything else: extract raw source text
|
|
985
|
+
const raw = extractSourceFragment(node, source);
|
|
986
|
+
return { name, mode: 'raw', value: raw };
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Extract the source text for a node using position info.
|
|
990
|
+
* Falls back to a generic representation if positions aren't useful.
|
|
991
|
+
*/
|
|
992
|
+
function extractSourceFragment(node, source) {
|
|
993
|
+
// Try to reconstruct from AST for common patterns
|
|
994
|
+
if (node.type === 'condition') {
|
|
995
|
+
const cond = reconstructExpression(node.condition);
|
|
996
|
+
const then = reconstructExpression(node.then);
|
|
997
|
+
const els = reconstructExpression(node.else);
|
|
998
|
+
return `${cond} ? ${then} : ${els}`;
|
|
999
|
+
}
|
|
1000
|
+
if (node.type === 'binary') {
|
|
1001
|
+
const left = reconstructExpression(node.lhs);
|
|
1002
|
+
const right = reconstructExpression(node.rhs);
|
|
1003
|
+
return `${left} ${node.value} ${right}`;
|
|
1004
|
+
}
|
|
1005
|
+
if (node.type === 'function') {
|
|
1006
|
+
const name = reconstructExpression(node.procedure);
|
|
1007
|
+
const args = (node.arguments || []).map(reconstructExpression).join(', ');
|
|
1008
|
+
return `${name}(${args})`;
|
|
1009
|
+
}
|
|
1010
|
+
// Generic fallback
|
|
1011
|
+
return reconstructExpression(node);
|
|
1012
|
+
}
|
|
1013
|
+
function reconstructExpression(node) {
|
|
1014
|
+
if (!node)
|
|
434
1015
|
return '';
|
|
1016
|
+
if (node.type === 'string')
|
|
1017
|
+
return `"${node.value}"`;
|
|
1018
|
+
if (node.type === 'number')
|
|
1019
|
+
return String(node.value);
|
|
1020
|
+
if (node.type === 'value')
|
|
1021
|
+
return String(node.value);
|
|
1022
|
+
if (node.type === 'path' && node.steps) {
|
|
1023
|
+
return node.steps.map((s) => (s.type === 'variable' ? `$${s.value}` : s.value)).join('.');
|
|
1024
|
+
}
|
|
1025
|
+
if (node.type === 'binary') {
|
|
1026
|
+
return `${reconstructExpression(node.lhs)} ${node.value} ${reconstructExpression(node.rhs)}`;
|
|
1027
|
+
}
|
|
1028
|
+
if (node.type === 'condition') {
|
|
1029
|
+
return `${reconstructExpression(node.condition)} ? ${reconstructExpression(node.then)} : ${reconstructExpression(node.else)}`;
|
|
1030
|
+
}
|
|
1031
|
+
if (node.type === 'function') {
|
|
1032
|
+
const proc = reconstructExpression(node.procedure);
|
|
1033
|
+
const args = (node.arguments || []).map(reconstructExpression).join(', ');
|
|
1034
|
+
return `${proc}(${args})`;
|
|
1035
|
+
}
|
|
1036
|
+
if (node.type === 'unary' && node.value === '{') {
|
|
1037
|
+
const entries = (node.lhs || [])
|
|
1038
|
+
.map(([k, v]) => `"${k.value}": ${reconstructExpression(v)}`)
|
|
1039
|
+
.join(', ');
|
|
1040
|
+
return `{${entries}}`;
|
|
435
1041
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
1042
|
+
return '...';
|
|
1043
|
+
}
|
|
1044
|
+
function formatFieldEntry(field, indent = ' ') {
|
|
1045
|
+
if (field.children && field.children.length > 0) {
|
|
1046
|
+
const childEntries = field.children.map((c) => formatFieldEntry(c, indent + ' ')).join(',\n');
|
|
1047
|
+
return `${indent}"${field.name}": {\n${childEntries}\n${indent}}`;
|
|
1048
|
+
}
|
|
1049
|
+
// Value is already valid JSONata (e.g. $doc.x.y, "string", 42, or raw expression)
|
|
1050
|
+
const value = field.value || 'null';
|
|
1051
|
+
return `${indent}"${field.name}": ${value}`;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
class MappingBuilderComponent {
|
|
1055
|
+
expression = '';
|
|
1056
|
+
templateFields = [];
|
|
1057
|
+
suggestions = null;
|
|
1058
|
+
disabled = false;
|
|
1059
|
+
expressionChange = new EventEmitter();
|
|
1060
|
+
fields = [];
|
|
1061
|
+
allSuggestions = [];
|
|
1062
|
+
collapsedPaths = new Set();
|
|
1063
|
+
initialCollapseApplied = false;
|
|
1064
|
+
ngOnChanges(changes) {
|
|
1065
|
+
// Skip re-parse only when expression alone changed (from our own emit)
|
|
1066
|
+
const expressionChanged = !!changes['expression'];
|
|
1067
|
+
const templateFieldsChanged = !!changes['templateFields'];
|
|
1068
|
+
if (expressionChanged && !templateFieldsChanged && !changes['expression'].firstChange) {
|
|
1069
|
+
return; // Don't re-parse when we emit changes ourselves
|
|
441
1070
|
}
|
|
442
|
-
|
|
443
|
-
this.
|
|
1071
|
+
if (expressionChanged || templateFieldsChanged) {
|
|
1072
|
+
this.rebuildFields();
|
|
1073
|
+
if (!this.initialCollapseApplied && this.fields.length > 0) {
|
|
1074
|
+
this.collapseAll();
|
|
1075
|
+
this.initialCollapseApplied = true;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
if (changes['suggestions']) {
|
|
1079
|
+
this.buildSuggestionList();
|
|
444
1080
|
}
|
|
445
1081
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (
|
|
449
|
-
|
|
450
|
-
this.
|
|
1082
|
+
onNestedValueChange(path, value) {
|
|
1083
|
+
const field = this.getFieldAtPath(path);
|
|
1084
|
+
if (field) {
|
|
1085
|
+
field.value = value;
|
|
1086
|
+
this.emit();
|
|
451
1087
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
1088
|
+
}
|
|
1089
|
+
onNestedModeToggle(path) {
|
|
1090
|
+
const field = this.getFieldAtPath(path);
|
|
1091
|
+
if (field) {
|
|
1092
|
+
field.mode = field.mode === 'ref' ? 'raw' : 'ref';
|
|
1093
|
+
this.emit();
|
|
455
1094
|
}
|
|
456
1095
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
1096
|
+
isRequired(fieldName) {
|
|
1097
|
+
return this.templateFields?.find((tf) => tf.name === fieldName)?.required ?? false;
|
|
1098
|
+
}
|
|
1099
|
+
isCollapsed(path) {
|
|
1100
|
+
return this.collapsedPaths.has(path.join('.'));
|
|
1101
|
+
}
|
|
1102
|
+
toggleCollapse(path) {
|
|
1103
|
+
const key = path.join('.');
|
|
1104
|
+
if (this.collapsedPaths.has(key)) {
|
|
1105
|
+
this.collapsedPaths.delete(key);
|
|
461
1106
|
}
|
|
462
1107
|
else {
|
|
463
|
-
|
|
1108
|
+
this.collapsedPaths.add(key);
|
|
464
1109
|
}
|
|
465
|
-
this.valueChange.emit(current);
|
|
466
1110
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
1111
|
+
collapseAll() {
|
|
1112
|
+
this.collapsedPaths.clear();
|
|
1113
|
+
this.fields.forEach((field, i) => {
|
|
1114
|
+
if (field.children) {
|
|
1115
|
+
this.collapsedPaths.add(String(i));
|
|
1116
|
+
this.collapseChildren(field.children, [i]);
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
collapseChildren(children, parentPath) {
|
|
1121
|
+
children.forEach((child, j) => {
|
|
1122
|
+
if (child.children) {
|
|
1123
|
+
this.collapsedPaths.add([...parentPath, j].join('.'));
|
|
1124
|
+
this.collapseChildren(child.children, [...parentPath, j]);
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
getFieldAtPath(path) {
|
|
1129
|
+
if (path.length === 0)
|
|
1130
|
+
return null;
|
|
1131
|
+
let current = this.fields[path[0]];
|
|
1132
|
+
for (let i = 1; i < path.length; i++) {
|
|
1133
|
+
if (!current.children)
|
|
1134
|
+
return null;
|
|
1135
|
+
current = current.children[path[i]];
|
|
470
1136
|
}
|
|
471
|
-
return
|
|
1137
|
+
return current;
|
|
472
1138
|
}
|
|
473
|
-
|
|
474
|
-
|
|
1139
|
+
emit() {
|
|
1140
|
+
const jsonata = builderToJsonata(this.fields);
|
|
1141
|
+
this.expressionChange.emit(jsonata);
|
|
475
1142
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
1143
|
+
/**
|
|
1144
|
+
* Ensure all template fields have a corresponding builder field.
|
|
1145
|
+
* Adds missing fields with empty values.
|
|
1146
|
+
*/
|
|
1147
|
+
buildSuggestionList() {
|
|
1148
|
+
if (!this.suggestions) {
|
|
1149
|
+
this.allSuggestions = [];
|
|
480
1150
|
return;
|
|
481
1151
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
1152
|
+
const docSuggestions = (this.suggestions.doc || []).map((p) => `$doc.${p}`);
|
|
1153
|
+
const pvSuggestions = (this.suggestions.pv || []).map((p) => `$pv.${p}`);
|
|
1154
|
+
this.allSuggestions = [...docSuggestions, ...pvSuggestions];
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Rebuild fields using template fields as the source of truth.
|
|
1158
|
+
* Expression values fill in where available; unmapped fields show empty.
|
|
1159
|
+
*/
|
|
1160
|
+
rebuildFields() {
|
|
1161
|
+
const parsed = parseJsonataToBuilder(this.expression);
|
|
1162
|
+
const parsedByName = new Map(parsed.map((f) => [f.name, f]));
|
|
1163
|
+
if (!this.templateFields || this.templateFields.length === 0) {
|
|
1164
|
+
// No template fields yet — use whatever we parsed
|
|
1165
|
+
this.fields = parsed;
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
// Template fields drive the structure
|
|
1169
|
+
this.fields = this.templateFields.map((tf) => {
|
|
1170
|
+
const existing = parsedByName.get(tf.name);
|
|
1171
|
+
if (existing) {
|
|
1172
|
+
return existing;
|
|
490
1173
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
1174
|
+
if (tf.fieldType === 'OBJECT' && tf.children?.length) {
|
|
1175
|
+
return {
|
|
1176
|
+
name: tf.name,
|
|
1177
|
+
mode: 'ref',
|
|
1178
|
+
value: '',
|
|
1179
|
+
children: tf.children.map((c) => ({ name: c.name, mode: 'ref', value: '' })),
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
return { name: tf.name, mode: 'ref', value: '' };
|
|
1183
|
+
});
|
|
1184
|
+
// Include extra fields from expression not in the template schema
|
|
1185
|
+
for (const p of parsed) {
|
|
1186
|
+
if (!this.templateFields.find((tf) => tf.name === p.name)) {
|
|
1187
|
+
this.fields.push(p);
|
|
499
1188
|
}
|
|
500
|
-
this.mappedCount = mapped;
|
|
501
|
-
this.totalRequired = total;
|
|
502
|
-
}
|
|
503
|
-
else {
|
|
504
|
-
this.totalRequired = this.field?.required ? 1 : 0;
|
|
505
|
-
this.mappedCount = this.getSourceValue() ? (this.field?.required ? 1 : 0) : 0;
|
|
506
1189
|
}
|
|
507
1190
|
}
|
|
508
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
509
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type:
|
|
1191
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MappingBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1192
|
+
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: `
|
|
1193
|
+
<div class="mapping-builder">
|
|
1194
|
+
<div
|
|
1195
|
+
*ngIf="fields.length === 0 && (!templateFields || templateFields.length === 0)"
|
|
1196
|
+
class="mapping-builder__empty"
|
|
1197
|
+
>
|
|
1198
|
+
{{ 'noTemplateFields' | pluginTranslate: 'epistola' | async }}
|
|
1199
|
+
</div>
|
|
1200
|
+
|
|
1201
|
+
<epistola-builder-field
|
|
1202
|
+
*ngFor="let field of fields; let i = index"
|
|
1203
|
+
[field]="field"
|
|
1204
|
+
[path]="[i]"
|
|
1205
|
+
[suggestions]="allSuggestions"
|
|
1206
|
+
[disabled]="disabled"
|
|
1207
|
+
[collapsed]="isCollapsed([i])"
|
|
1208
|
+
[collapsedPaths]="collapsedPaths"
|
|
1209
|
+
[required]="isRequired(field.name)"
|
|
1210
|
+
(valueChange)="onNestedValueChange($event.path, $event.value)"
|
|
1211
|
+
(modeToggle)="onNestedModeToggle($event)"
|
|
1212
|
+
(collapseToggle)="toggleCollapse($event)"
|
|
1213
|
+
></epistola-builder-field>
|
|
1214
|
+
</div>
|
|
1215
|
+
`, 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"] }] });
|
|
510
1216
|
}
|
|
511
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
1217
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MappingBuilderComponent, decorators: [{
|
|
512
1218
|
type: Component,
|
|
513
|
-
args: [{ selector: 'epistola-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
1219
|
+
args: [{ selector: 'epistola-mapping-builder', standalone: true, imports: [CommonModule, FormsModule, PluginTranslatePipeModule, BuilderFieldComponent], template: `
|
|
1220
|
+
<div class="mapping-builder">
|
|
1221
|
+
<div
|
|
1222
|
+
*ngIf="fields.length === 0 && (!templateFields || templateFields.length === 0)"
|
|
1223
|
+
class="mapping-builder__empty"
|
|
1224
|
+
>
|
|
1225
|
+
{{ 'noTemplateFields' | pluginTranslate: 'epistola' | async }}
|
|
1226
|
+
</div>
|
|
1227
|
+
|
|
1228
|
+
<epistola-builder-field
|
|
1229
|
+
*ngFor="let field of fields; let i = index"
|
|
1230
|
+
[field]="field"
|
|
1231
|
+
[path]="[i]"
|
|
1232
|
+
[suggestions]="allSuggestions"
|
|
1233
|
+
[disabled]="disabled"
|
|
1234
|
+
[collapsed]="isCollapsed([i])"
|
|
1235
|
+
[collapsedPaths]="collapsedPaths"
|
|
1236
|
+
[required]="isRequired(field.name)"
|
|
1237
|
+
(valueChange)="onNestedValueChange($event.path, $event.value)"
|
|
1238
|
+
(modeToggle)="onNestedModeToggle($event)"
|
|
1239
|
+
(collapseToggle)="toggleCollapse($event)"
|
|
1240
|
+
></epistola-builder-field>
|
|
1241
|
+
</div>
|
|
1242
|
+
`, 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"] }]
|
|
1243
|
+
}], propDecorators: { expression: [{
|
|
519
1244
|
type: Input
|
|
520
|
-
}],
|
|
1245
|
+
}], templateFields: [{
|
|
521
1246
|
type: Input
|
|
522
|
-
}],
|
|
1247
|
+
}], suggestions: [{
|
|
523
1248
|
type: Input
|
|
524
1249
|
}], disabled: [{
|
|
525
1250
|
type: Input
|
|
526
|
-
}],
|
|
1251
|
+
}], expressionChange: [{
|
|
527
1252
|
type: Output
|
|
528
1253
|
}] } });
|
|
529
1254
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
* Uses forwardRef(() => FieldTreeComponent) in imports to allow self-referencing in the template.
|
|
535
|
-
*/
|
|
536
|
-
class FieldTreeComponent {
|
|
537
|
-
field;
|
|
538
|
-
value = undefined;
|
|
539
|
-
pluginId;
|
|
1255
|
+
class MappingPreviewComponent {
|
|
1256
|
+
epistolaPluginService;
|
|
1257
|
+
expression = '';
|
|
1258
|
+
templateFields = [];
|
|
540
1259
|
caseDefinitionKey = null;
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1260
|
+
documentId = '';
|
|
1261
|
+
loading = false;
|
|
1262
|
+
result = null;
|
|
1263
|
+
expectedJson = '';
|
|
1264
|
+
missingRequired = [];
|
|
1265
|
+
destroy$ = new Subject();
|
|
1266
|
+
evaluate$ = new Subject();
|
|
1267
|
+
constructor(epistolaPluginService) {
|
|
1268
|
+
this.epistolaPluginService = epistolaPluginService;
|
|
1269
|
+
this.evaluate$.pipe(debounceTime(300), takeUntil(this.destroy$)).subscribe(() => {
|
|
1270
|
+
this.doEvaluate();
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
548
1273
|
ngOnChanges(changes) {
|
|
549
|
-
if (
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
}
|
|
555
|
-
if (!this.expanded && this.totalRequired > 0 && this.mappedCount < this.totalRequired) {
|
|
556
|
-
this.expanded = true;
|
|
557
|
-
}
|
|
1274
|
+
if (changes['templateFields']) {
|
|
1275
|
+
this.expectedJson = this.buildExpectedJson();
|
|
1276
|
+
}
|
|
1277
|
+
if (changes['expression'] || changes['templateFields']) {
|
|
1278
|
+
this.checkMissingRequired();
|
|
558
1279
|
}
|
|
559
1280
|
}
|
|
560
|
-
|
|
561
|
-
this.
|
|
1281
|
+
ngOnDestroy() {
|
|
1282
|
+
this.destroy$.next();
|
|
1283
|
+
this.destroy$.complete();
|
|
1284
|
+
}
|
|
1285
|
+
runPreview() {
|
|
1286
|
+
this.evaluate$.next();
|
|
562
1287
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
1288
|
+
doEvaluate() {
|
|
1289
|
+
if (!this.documentId || !this.expression)
|
|
1290
|
+
return;
|
|
1291
|
+
this.loading = true;
|
|
1292
|
+
this.epistolaPluginService.evaluateMapping(this.expression, this.documentId).subscribe({
|
|
1293
|
+
next: (result) => {
|
|
1294
|
+
this.result = result;
|
|
1295
|
+
this.loading = false;
|
|
1296
|
+
if (result.success) {
|
|
1297
|
+
this.checkMissingRequired();
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
error: (err) => {
|
|
1301
|
+
this.result = { success: false, result: null, error: err.message || 'Request failed' };
|
|
1302
|
+
this.loading = false;
|
|
1303
|
+
},
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
buildExpectedJson() {
|
|
1307
|
+
if (!this.templateFields || this.templateFields.length === 0)
|
|
1308
|
+
return '{}';
|
|
1309
|
+
const obj = {};
|
|
1310
|
+
for (const field of this.templateFields) {
|
|
1311
|
+
const type = field.type || 'any';
|
|
1312
|
+
obj[field.name] = field.required ? `${type} (required)` : type;
|
|
570
1313
|
}
|
|
571
|
-
|
|
1314
|
+
return JSON.stringify(obj, null, 2);
|
|
572
1315
|
}
|
|
573
|
-
|
|
574
|
-
if (
|
|
575
|
-
|
|
1316
|
+
checkMissingRequired() {
|
|
1317
|
+
if (!this.templateFields) {
|
|
1318
|
+
this.missingRequired = [];
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
const requiredFields = this.templateFields.filter((f) => f.required).map((f) => f.name);
|
|
1322
|
+
if (!this.result?.success || !this.result.result) {
|
|
1323
|
+
// If no evaluation result yet, check statically from expression
|
|
1324
|
+
this.missingRequired = requiredFields;
|
|
1325
|
+
return;
|
|
576
1326
|
}
|
|
577
|
-
|
|
1327
|
+
const producedKeys = new Set(Object.keys(this.result.result));
|
|
1328
|
+
this.missingRequired = requiredFields.filter((f) => !producedKeys.has(f));
|
|
578
1329
|
}
|
|
579
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
580
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
1330
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MappingPreviewComponent, deps: [{ token: EpistolaPluginService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1331
|
+
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: `
|
|
1332
|
+
<div class="preview">
|
|
1333
|
+
<div class="preview__header">
|
|
1334
|
+
<span class="preview__title">{{
|
|
1335
|
+
'previewTitle' | pluginTranslate: 'epistola' | async
|
|
1336
|
+
}}</span>
|
|
1337
|
+
<div class="preview__controls">
|
|
1338
|
+
<input
|
|
1339
|
+
type="text"
|
|
1340
|
+
class="preview__doc-input"
|
|
1341
|
+
[ngModel]="documentId"
|
|
1342
|
+
(ngModelChange)="documentId = $event"
|
|
1343
|
+
[placeholder]="'previewDocPlaceholder' | pluginTranslate: 'epistola' | async"
|
|
1344
|
+
/>
|
|
1345
|
+
<button
|
|
1346
|
+
class="preview__run-btn"
|
|
1347
|
+
(click)="runPreview()"
|
|
1348
|
+
[disabled]="!documentId || !expression || loading"
|
|
1349
|
+
>
|
|
1350
|
+
▶
|
|
1351
|
+
</button>
|
|
1352
|
+
</div>
|
|
1353
|
+
</div>
|
|
1354
|
+
|
|
1355
|
+
<div class="preview__panels">
|
|
1356
|
+
<!-- Expected structure -->
|
|
1357
|
+
<div class="preview__panel">
|
|
1358
|
+
<div class="preview__panel-label">
|
|
1359
|
+
{{ 'previewExpected' | pluginTranslate: 'epistola' | async }}
|
|
1360
|
+
</div>
|
|
1361
|
+
<pre class="preview__code">{{ expectedJson }}</pre>
|
|
1362
|
+
</div>
|
|
1363
|
+
|
|
1364
|
+
<!-- Produced output -->
|
|
1365
|
+
<div class="preview__panel">
|
|
1366
|
+
<div class="preview__panel-label">
|
|
1367
|
+
{{ 'previewProduced' | pluginTranslate: 'epistola' | async }}
|
|
1368
|
+
</div>
|
|
1369
|
+
<div *ngIf="loading" class="preview__loading">...</div>
|
|
1370
|
+
<pre *ngIf="!loading && result?.success" class="preview__code">{{
|
|
1371
|
+
result.result | json
|
|
1372
|
+
}}</pre>
|
|
1373
|
+
<div *ngIf="!loading && result && !result.success" class="preview__error">
|
|
1374
|
+
{{ result.error }}
|
|
1375
|
+
</div>
|
|
1376
|
+
<div *ngIf="!loading && !result" class="preview__placeholder">
|
|
1377
|
+
{{ 'previewRunHint' | pluginTranslate: 'epistola' | async }}
|
|
1378
|
+
</div>
|
|
1379
|
+
</div>
|
|
1380
|
+
</div>
|
|
1381
|
+
|
|
1382
|
+
<!-- Missing fields warning -->
|
|
1383
|
+
<div *ngIf="missingRequired.length > 0" class="preview__warnings">
|
|
1384
|
+
<span class="preview__warning-icon">⚠</span>
|
|
1385
|
+
{{ 'previewMissing' | pluginTranslate: 'epistola' | async }}:
|
|
1386
|
+
<strong>{{ missingRequired.join(', ') }}</strong>
|
|
1387
|
+
</div>
|
|
1388
|
+
</div>
|
|
1389
|
+
`, 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: i2$2.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: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.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" }] });
|
|
581
1390
|
}
|
|
582
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
1391
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MappingPreviewComponent, decorators: [{
|
|
583
1392
|
type: Component,
|
|
584
|
-
args: [{ selector: 'epistola-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
1393
|
+
args: [{ selector: 'epistola-mapping-preview', standalone: true, imports: [CommonModule, FormsModule, PluginTranslatePipeModule], template: `
|
|
1394
|
+
<div class="preview">
|
|
1395
|
+
<div class="preview__header">
|
|
1396
|
+
<span class="preview__title">{{
|
|
1397
|
+
'previewTitle' | pluginTranslate: 'epistola' | async
|
|
1398
|
+
}}</span>
|
|
1399
|
+
<div class="preview__controls">
|
|
1400
|
+
<input
|
|
1401
|
+
type="text"
|
|
1402
|
+
class="preview__doc-input"
|
|
1403
|
+
[ngModel]="documentId"
|
|
1404
|
+
(ngModelChange)="documentId = $event"
|
|
1405
|
+
[placeholder]="'previewDocPlaceholder' | pluginTranslate: 'epistola' | async"
|
|
1406
|
+
/>
|
|
1407
|
+
<button
|
|
1408
|
+
class="preview__run-btn"
|
|
1409
|
+
(click)="runPreview()"
|
|
1410
|
+
[disabled]="!documentId || !expression || loading"
|
|
1411
|
+
>
|
|
1412
|
+
▶
|
|
1413
|
+
</button>
|
|
1414
|
+
</div>
|
|
1415
|
+
</div>
|
|
1416
|
+
|
|
1417
|
+
<div class="preview__panels">
|
|
1418
|
+
<!-- Expected structure -->
|
|
1419
|
+
<div class="preview__panel">
|
|
1420
|
+
<div class="preview__panel-label">
|
|
1421
|
+
{{ 'previewExpected' | pluginTranslate: 'epistola' | async }}
|
|
1422
|
+
</div>
|
|
1423
|
+
<pre class="preview__code">{{ expectedJson }}</pre>
|
|
1424
|
+
</div>
|
|
1425
|
+
|
|
1426
|
+
<!-- Produced output -->
|
|
1427
|
+
<div class="preview__panel">
|
|
1428
|
+
<div class="preview__panel-label">
|
|
1429
|
+
{{ 'previewProduced' | pluginTranslate: 'epistola' | async }}
|
|
1430
|
+
</div>
|
|
1431
|
+
<div *ngIf="loading" class="preview__loading">...</div>
|
|
1432
|
+
<pre *ngIf="!loading && result?.success" class="preview__code">{{
|
|
1433
|
+
result.result | json
|
|
1434
|
+
}}</pre>
|
|
1435
|
+
<div *ngIf="!loading && result && !result.success" class="preview__error">
|
|
1436
|
+
{{ result.error }}
|
|
1437
|
+
</div>
|
|
1438
|
+
<div *ngIf="!loading && !result" class="preview__placeholder">
|
|
1439
|
+
{{ 'previewRunHint' | pluginTranslate: 'epistola' | async }}
|
|
1440
|
+
</div>
|
|
1441
|
+
</div>
|
|
1442
|
+
</div>
|
|
1443
|
+
|
|
1444
|
+
<!-- Missing fields warning -->
|
|
1445
|
+
<div *ngIf="missingRequired.length > 0" class="preview__warnings">
|
|
1446
|
+
<span class="preview__warning-icon">⚠</span>
|
|
1447
|
+
{{ 'previewMissing' | pluginTranslate: 'epistola' | async }}:
|
|
1448
|
+
<strong>{{ missingRequired.join(', ') }}</strong>
|
|
1449
|
+
</div>
|
|
1450
|
+
</div>
|
|
1451
|
+
`, 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"] }]
|
|
1452
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }], propDecorators: { expression: [{
|
|
588
1453
|
type: Input
|
|
589
|
-
}],
|
|
1454
|
+
}], templateFields: [{
|
|
590
1455
|
type: Input
|
|
591
1456
|
}], caseDefinitionKey: [{
|
|
592
1457
|
type: Input
|
|
593
|
-
}], processVariables: [{
|
|
594
|
-
type: Input
|
|
595
|
-
}], disabled: [{
|
|
596
|
-
type: Input
|
|
597
|
-
}], valueChange: [{
|
|
598
|
-
type: Output
|
|
599
1458
|
}] } });
|
|
600
1459
|
|
|
1460
|
+
const FORM_REF_PREFIX$1 = 'form:';
|
|
601
1461
|
/**
|
|
602
|
-
*
|
|
603
|
-
*
|
|
1462
|
+
* Detect if a string value is a JSONata expression (vs a plain literal).
|
|
1463
|
+
* Checks for characters that indicate JSONata operators: $, &, (, {, ?, [
|
|
604
1464
|
*/
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (
|
|
619
|
-
|
|
1465
|
+
function isExpression(value) {
|
|
1466
|
+
return /[$&({?\[]/.test(value);
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Expand dot-notation keys into nested objects.
|
|
1470
|
+
* e.g. { "beslissing.tekst": "value" } -> { beslissing: { tekst: "value" } }
|
|
1471
|
+
*/
|
|
1472
|
+
function expandDotNotation(flat) {
|
|
1473
|
+
const result = {};
|
|
1474
|
+
for (const [key, value] of Object.entries(flat)) {
|
|
1475
|
+
const parts = key.split('.');
|
|
1476
|
+
let current = result;
|
|
1477
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1478
|
+
if (!current[parts[i]] || typeof current[parts[i]] !== 'object') {
|
|
1479
|
+
current[parts[i]] = {};
|
|
620
1480
|
}
|
|
1481
|
+
current = current[parts[i]];
|
|
621
1482
|
}
|
|
622
|
-
|
|
623
|
-
this.emitRequiredFieldsStatus();
|
|
624
|
-
}
|
|
1483
|
+
current[parts[parts.length - 1]] = value;
|
|
625
1484
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
1485
|
+
return result;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Given an override mapping (scope -> { inputPath -> "form:<componentKey>" })
|
|
1489
|
+
* and form data, produce the inputOverrides object for the backend.
|
|
1490
|
+
* The "form:" prefix identifies form field references; the remainder is the Formio component key.
|
|
1491
|
+
*/
|
|
1492
|
+
function computeInputOverrides(mapping, formData) {
|
|
1493
|
+
const result = {};
|
|
1494
|
+
for (const [scope, fields] of Object.entries(mapping)) {
|
|
1495
|
+
if (scope !== 'doc' && scope !== 'pv')
|
|
1496
|
+
continue;
|
|
1497
|
+
const flatOverrides = {};
|
|
1498
|
+
for (const [inputPath, ref] of Object.entries(fields)) {
|
|
1499
|
+
const formFieldKey = String(ref).startsWith(FORM_REF_PREFIX$1)
|
|
1500
|
+
? String(ref).substring(FORM_REF_PREFIX$1.length)
|
|
1501
|
+
: String(ref);
|
|
1502
|
+
const value = formData[formFieldKey];
|
|
1503
|
+
if (value !== undefined) {
|
|
1504
|
+
flatOverrides[inputPath] = value;
|
|
1505
|
+
}
|
|
630
1506
|
}
|
|
631
|
-
|
|
632
|
-
|
|
1507
|
+
if (Object.keys(flatOverrides).length > 0) {
|
|
1508
|
+
result[scope] = expandDotNotation(flatOverrides);
|
|
633
1509
|
}
|
|
634
|
-
this.mappingChange.emit(this.mapping);
|
|
635
|
-
this.emitRequiredFieldsStatus();
|
|
636
|
-
}
|
|
637
|
-
getFieldValue(fieldName) {
|
|
638
|
-
return this.mapping[fieldName];
|
|
639
|
-
}
|
|
640
|
-
emitRequiredFieldsStatus() {
|
|
641
|
-
const stats = countRequiredMapped(this.templateFields, this.mapping);
|
|
642
|
-
this.requiredFieldsStatus.emit(stats);
|
|
643
1510
|
}
|
|
644
|
-
|
|
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 });
|
|
1511
|
+
return result;
|
|
646
1512
|
}
|
|
647
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DataMappingTreeComponent, decorators: [{
|
|
648
|
-
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: [{
|
|
655
|
-
type: Input
|
|
656
|
-
}], templateFields: [{
|
|
657
|
-
type: Input
|
|
658
|
-
}], prefillMapping: [{
|
|
659
|
-
type: Input
|
|
660
|
-
}], disabled: [{
|
|
661
|
-
type: Input
|
|
662
|
-
}], caseDefinitionKey: [{
|
|
663
|
-
type: Input
|
|
664
|
-
}], processVariables: [{
|
|
665
|
-
type: Input
|
|
666
|
-
}], mappingChange: [{
|
|
667
|
-
type: Output
|
|
668
|
-
}], requiredFieldsStatus: [{
|
|
669
|
-
type: Output
|
|
670
|
-
}] } });
|
|
671
1513
|
|
|
672
1514
|
class GenerateDocumentConfigurationComponent {
|
|
673
1515
|
epistolaPluginService;
|
|
@@ -686,22 +1528,32 @@ class GenerateDocumentConfigurationComponent {
|
|
|
686
1528
|
variants$ = new BehaviorSubject(initialResource([]));
|
|
687
1529
|
environments$ = new BehaviorSubject(initialResource([]));
|
|
688
1530
|
templateFields$ = new BehaviorSubject(initialResource([]));
|
|
689
|
-
dataMapping$ = new BehaviorSubject(
|
|
1531
|
+
dataMapping$ = new BehaviorSubject('');
|
|
1532
|
+
mappingMode = 'simple';
|
|
1533
|
+
toolsCollapsed = true;
|
|
1534
|
+
activeToolTab = 'preview';
|
|
690
1535
|
outputFormatOptions = [
|
|
691
1536
|
{ id: 'PDF', text: 'PDF' },
|
|
692
|
-
{ id: 'HTML', text: 'HTML' }
|
|
1537
|
+
{ id: 'HTML', text: 'HTML' },
|
|
693
1538
|
];
|
|
694
1539
|
selectedCatalogId$ = new BehaviorSubject('');
|
|
695
1540
|
/** Composite ID: "catalogId/templateId" */
|
|
696
1541
|
selectedTemplateId$ = new BehaviorSubject('');
|
|
697
1542
|
selectedVariantId$ = new BehaviorSubject('');
|
|
698
1543
|
variantSelectionMode = 'explicit';
|
|
1544
|
+
variantIdExpressionMode = false;
|
|
1545
|
+
variantIdExpression = '';
|
|
1546
|
+
filenameExpressionMode = false;
|
|
1547
|
+
filenameExpression = '';
|
|
699
1548
|
variantAttributeEntries = [];
|
|
700
1549
|
availableAttributeKeys = [];
|
|
701
1550
|
caseDefinitionKey = null;
|
|
702
1551
|
processVariables = [];
|
|
1552
|
+
expressionFunctions = [];
|
|
1553
|
+
variableSuggestions = null;
|
|
703
1554
|
requiredFieldsStatus = { mapped: 0, total: 0 };
|
|
704
1555
|
prefillDataMapping = {};
|
|
1556
|
+
validationErrors$ = new BehaviorSubject([]);
|
|
705
1557
|
destroy$ = new Subject();
|
|
706
1558
|
saveSubscription;
|
|
707
1559
|
formValue$ = new BehaviorSubject(null);
|
|
@@ -719,6 +1571,7 @@ class GenerateDocumentConfigurationComponent {
|
|
|
719
1571
|
this.initContext();
|
|
720
1572
|
this.initPluginConfiguration();
|
|
721
1573
|
this.initCascade();
|
|
1574
|
+
this.loadExpressionFunctions();
|
|
722
1575
|
this.openSaveSubscription();
|
|
723
1576
|
}
|
|
724
1577
|
ngOnDestroy() {
|
|
@@ -745,8 +1598,8 @@ class GenerateDocumentConfigurationComponent {
|
|
|
745
1598
|
}
|
|
746
1599
|
this.handleValid(formValue);
|
|
747
1600
|
}
|
|
748
|
-
onDataMappingChange(
|
|
749
|
-
this.dataMapping$.next(
|
|
1601
|
+
onDataMappingChange(expression) {
|
|
1602
|
+
this.dataMapping$.next(expression);
|
|
750
1603
|
const currentFormValue = this.formValue$.getValue();
|
|
751
1604
|
if (currentFormValue) {
|
|
752
1605
|
this.handleValid(currentFormValue);
|
|
@@ -767,7 +1620,10 @@ class GenerateDocumentConfigurationComponent {
|
|
|
767
1620
|
this.revalidate();
|
|
768
1621
|
}
|
|
769
1622
|
addAttributeEntry() {
|
|
770
|
-
this.variantAttributeEntries = [
|
|
1623
|
+
this.variantAttributeEntries = [
|
|
1624
|
+
...this.variantAttributeEntries,
|
|
1625
|
+
{ key: '', value: '', required: true },
|
|
1626
|
+
];
|
|
771
1627
|
this.revalidate();
|
|
772
1628
|
}
|
|
773
1629
|
removeAttributeEntry(index) {
|
|
@@ -777,6 +1633,12 @@ class GenerateDocumentConfigurationComponent {
|
|
|
777
1633
|
onAttributeEntryChange() {
|
|
778
1634
|
this.revalidate();
|
|
779
1635
|
}
|
|
1636
|
+
onVariantIdExpressionChange() {
|
|
1637
|
+
this.revalidate();
|
|
1638
|
+
}
|
|
1639
|
+
onFilenameExpressionChange() {
|
|
1640
|
+
this.revalidate();
|
|
1641
|
+
}
|
|
780
1642
|
onKeySelected(entry, value) {
|
|
781
1643
|
if (value === '__custom__') {
|
|
782
1644
|
entry._customKey = true;
|
|
@@ -817,7 +1679,9 @@ class GenerateDocumentConfigurationComponent {
|
|
|
817
1679
|
}
|
|
818
1680
|
initContext() {
|
|
819
1681
|
if (this.context$) {
|
|
820
|
-
this.context
|
|
1682
|
+
this.context$
|
|
1683
|
+
.pipe(takeUntil$1(this.destroy$), filter(([context]) => context === 'case'))
|
|
1684
|
+
.subscribe(([, params]) => {
|
|
821
1685
|
this.caseDefinitionKey = params.caseDefinitionKey;
|
|
822
1686
|
this.cdr.markForCheck();
|
|
823
1687
|
});
|
|
@@ -826,10 +1690,12 @@ class GenerateDocumentConfigurationComponent {
|
|
|
826
1690
|
initPluginConfiguration() {
|
|
827
1691
|
const sources = [];
|
|
828
1692
|
if (this.selectedPluginConfigurationData$) {
|
|
829
|
-
sources.push(this.selectedPluginConfigurationData$.pipe(filter(config => !!config?.configurationId), map(config => config.configurationId)));
|
|
1693
|
+
sources.push(this.selectedPluginConfigurationData$.pipe(filter((config) => !!config?.configurationId), map((config) => config.configurationId)));
|
|
830
1694
|
}
|
|
831
|
-
sources.push(this.processLinkStateService.selectedProcessLink$.pipe(filter(processLink => !!processLink?.pluginConfigurationId), map(processLink => processLink.pluginConfigurationId)));
|
|
832
|
-
merge(...sources)
|
|
1695
|
+
sources.push(this.processLinkStateService.selectedProcessLink$.pipe(filter((processLink) => !!processLink?.pluginConfigurationId), map((processLink) => processLink.pluginConfigurationId)));
|
|
1696
|
+
merge(...sources)
|
|
1697
|
+
.pipe(takeUntil$1(this.destroy$))
|
|
1698
|
+
.subscribe((configurationId) => {
|
|
833
1699
|
this.pluginConfigurationId$.next(configurationId);
|
|
834
1700
|
});
|
|
835
1701
|
}
|
|
@@ -844,87 +1710,146 @@ class GenerateDocumentConfigurationComponent {
|
|
|
844
1710
|
* prefill + templateFields loaded → seed dataMapping
|
|
845
1711
|
*/
|
|
846
1712
|
initCascade() {
|
|
847
|
-
const configId$ = this.pluginConfigurationId$.pipe(filter(id => !!id), distinctUntilChanged());
|
|
1713
|
+
const configId$ = this.pluginConfigurationId$.pipe(filter((id) => !!id), distinctUntilChanged());
|
|
848
1714
|
// ── Catalogs: load when pluginConfigurationId changes ──
|
|
849
|
-
configId
|
|
1715
|
+
configId$
|
|
1716
|
+
.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'))))))
|
|
1717
|
+
.subscribe((resource) => this.catalogs$.next(resource));
|
|
850
1718
|
// ── Environments: load when pluginConfigurationId changes (independent) ──
|
|
851
|
-
configId
|
|
1719
|
+
configId$
|
|
1720
|
+
.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'))))))
|
|
1721
|
+
.subscribe((resource) => this.environments$.next(resource));
|
|
852
1722
|
// ── Seed selectedCatalogId$ from prefill once catalogs are loaded ──
|
|
853
1723
|
combineLatest([
|
|
854
|
-
this.prefill$.pipe(filter(config => !!config?.catalogId)),
|
|
855
|
-
this.catalogs$.pipe(filter(c => !c.loading && c.data.length > 0))
|
|
856
|
-
])
|
|
1724
|
+
this.prefill$.pipe(filter((config) => !!config?.catalogId)),
|
|
1725
|
+
this.catalogs$.pipe(filter((c) => !c.loading && c.data.length > 0)),
|
|
1726
|
+
])
|
|
1727
|
+
.pipe(takeUntil$1(this.destroy$), take$1(1))
|
|
1728
|
+
.subscribe(([config]) => {
|
|
857
1729
|
this.selectedCatalogId$.next(config.catalogId);
|
|
858
1730
|
});
|
|
859
1731
|
// ── Templates: load when catalogId changes ──
|
|
860
|
-
const catalogId$ = this.selectedCatalogId$.pipe(filter(id => !!id), distinctUntilChanged());
|
|
861
|
-
combineLatest([configId$, catalogId$])
|
|
1732
|
+
const catalogId$ = this.selectedCatalogId$.pipe(filter((id) => !!id), distinctUntilChanged());
|
|
1733
|
+
combineLatest([configId$, catalogId$])
|
|
1734
|
+
.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'))))))
|
|
1735
|
+
.subscribe((resource) => this.templates$.next(resource));
|
|
862
1736
|
// ── Attributes: load when catalogId changes ──
|
|
863
|
-
combineLatest([configId$, catalogId$])
|
|
864
|
-
this.
|
|
1737
|
+
combineLatest([configId$, catalogId$])
|
|
1738
|
+
.pipe(takeUntil$1(this.destroy$), switchMap(([configurationId, catalogId]) => this.epistolaPluginService
|
|
1739
|
+
.getAttributes(configurationId, catalogId)
|
|
1740
|
+
.pipe(catchError(() => of([])))))
|
|
1741
|
+
.subscribe((attributes) => {
|
|
1742
|
+
this.availableAttributeKeys = attributes.map((a) => a.key).sort();
|
|
865
1743
|
this.cdr.markForCheck();
|
|
866
1744
|
});
|
|
867
1745
|
// ── Seed selectedTemplateId$ from prefill once templates are loaded ──
|
|
868
1746
|
combineLatest([
|
|
869
|
-
this.prefill$.pipe(filter(config => !!config?.templateId)),
|
|
870
|
-
this.templates$.pipe(filter(t => !t.loading && t.data.length > 0))
|
|
871
|
-
])
|
|
1747
|
+
this.prefill$.pipe(filter((config) => !!config?.templateId)),
|
|
1748
|
+
this.templates$.pipe(filter((t) => !t.loading && t.data.length > 0)),
|
|
1749
|
+
])
|
|
1750
|
+
.pipe(takeUntil$1(this.destroy$), take$1(1))
|
|
1751
|
+
.subscribe(([config]) => {
|
|
872
1752
|
this.selectedTemplateId$.next(config.templateId);
|
|
873
1753
|
});
|
|
874
1754
|
// ── Variants: load when templateId changes ──
|
|
875
|
-
const templateId$ = this.selectedTemplateId$.pipe(filter(id => !!id), distinctUntilChanged());
|
|
876
|
-
combineLatest([configId$, catalogId$, templateId$])
|
|
1755
|
+
const templateId$ = this.selectedTemplateId$.pipe(filter((id) => !!id), distinctUntilChanged());
|
|
1756
|
+
combineLatest([configId$, catalogId$, templateId$])
|
|
1757
|
+
.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) => ({
|
|
1758
|
+
id: v.id,
|
|
1759
|
+
text: v.name + this.formatAttributes(v.attributes),
|
|
1760
|
+
})))), catchError(() => of(errorResource([], 'Failed to load variants'))))))
|
|
1761
|
+
.subscribe((resource) => this.variants$.next(resource));
|
|
877
1762
|
// ── Template fields: load when templateId changes ──
|
|
878
|
-
combineLatest([configId$, catalogId$, templateId$])
|
|
1763
|
+
combineLatest([configId$, catalogId$, templateId$])
|
|
1764
|
+
.pipe(takeUntil$1(this.destroy$), tap(() => {
|
|
879
1765
|
this.templateFields$.next(loadingResource(this.templateFields$.getValue().data));
|
|
880
1766
|
this.loadProcessVariables();
|
|
881
|
-
|
|
1767
|
+
this.loadVariableSuggestions();
|
|
1768
|
+
}), switchMap(([configurationId, catalogId, templateId]) => this.epistolaPluginService
|
|
1769
|
+
.getTemplateDetails(configurationId, templateId, catalogId)
|
|
1770
|
+
.pipe(map((details) => successResource(details.fields || [])), catchError(() => of(errorResource([], 'Failed to load template fields'))))))
|
|
1771
|
+
.subscribe((resource) => this.templateFields$.next(resource));
|
|
882
1772
|
// ── Seed variant + dataMapping from prefill once templateFields are loaded ──
|
|
883
1773
|
combineLatest([
|
|
884
|
-
this.prefill$.pipe(filter(config => !!config?.templateId)),
|
|
885
|
-
this.templateFields$.pipe(filter(tf => !tf.loading && tf.data.length > 0))
|
|
886
|
-
])
|
|
1774
|
+
this.prefill$.pipe(filter((config) => !!config?.templateId)),
|
|
1775
|
+
this.templateFields$.pipe(filter((tf) => !tf.loading && tf.data.length > 0)),
|
|
1776
|
+
])
|
|
1777
|
+
.pipe(takeUntil$1(this.destroy$), take$1(1))
|
|
1778
|
+
.subscribe(([config]) => {
|
|
887
1779
|
if (!config)
|
|
888
1780
|
return;
|
|
889
1781
|
// Apply variant prefill
|
|
890
|
-
if (config.variantAttributes &&
|
|
1782
|
+
if (config.variantAttributes &&
|
|
1783
|
+
(Array.isArray(config.variantAttributes)
|
|
1784
|
+
? config.variantAttributes.length > 0
|
|
1785
|
+
: Object.keys(config.variantAttributes).length > 0)) {
|
|
891
1786
|
this.variantSelectionMode = 'attributes';
|
|
892
1787
|
if (Array.isArray(config.variantAttributes)) {
|
|
893
|
-
this.variantAttributeEntries = config.variantAttributes
|
|
894
|
-
|
|
1788
|
+
this.variantAttributeEntries = config.variantAttributes.map((e) => ({
|
|
1789
|
+
key: e.key,
|
|
1790
|
+
value: e.value,
|
|
1791
|
+
required: e.required !== false,
|
|
1792
|
+
_expressionMode: isExpression(e.value),
|
|
1793
|
+
}));
|
|
895
1794
|
}
|
|
896
1795
|
else {
|
|
897
|
-
this.variantAttributeEntries = Object.entries(config.variantAttributes)
|
|
898
|
-
.map(([key, value]) => ({ key, value: String(value), required: true }));
|
|
1796
|
+
this.variantAttributeEntries = Object.entries(config.variantAttributes).map(([key, value]) => ({ key, value: String(value), required: true }));
|
|
899
1797
|
}
|
|
900
1798
|
}
|
|
901
1799
|
else if (config.variantId) {
|
|
902
1800
|
this.variantSelectionMode = 'explicit';
|
|
903
|
-
|
|
1801
|
+
if (isExpression(config.variantId)) {
|
|
1802
|
+
this.variantIdExpressionMode = true;
|
|
1803
|
+
this.variantIdExpression = config.variantId;
|
|
1804
|
+
}
|
|
1805
|
+
else {
|
|
1806
|
+
this.selectedVariantId$.next(config.variantId);
|
|
1807
|
+
}
|
|
904
1808
|
}
|
|
905
|
-
//
|
|
906
|
-
|
|
907
|
-
|
|
1809
|
+
// Detect expression mode for filename
|
|
1810
|
+
if (config.filename && isExpression(config.filename)) {
|
|
1811
|
+
this.filenameExpressionMode = true;
|
|
1812
|
+
this.filenameExpression = config.filename;
|
|
1813
|
+
}
|
|
1814
|
+
// Apply dataMapping prefill (JSONata expression string)
|
|
908
1815
|
if (config.dataMapping) {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
this.prefillDataMapping = { ...config.dataMapping };
|
|
912
|
-
this.cdr.detectChanges();
|
|
913
|
-
});
|
|
1816
|
+
const expr = typeof config.dataMapping === 'string' ? config.dataMapping : '';
|
|
1817
|
+
this.dataMapping$.next(expr);
|
|
914
1818
|
}
|
|
915
1819
|
else {
|
|
916
1820
|
this.cdr.detectChanges();
|
|
917
1821
|
}
|
|
918
1822
|
});
|
|
919
1823
|
}
|
|
1824
|
+
loadExpressionFunctions() {
|
|
1825
|
+
this.epistolaPluginService
|
|
1826
|
+
.getExpressionFunctions()
|
|
1827
|
+
.pipe(takeUntil$1(this.destroy$), catchError(() => of([])))
|
|
1828
|
+
.subscribe((functions) => {
|
|
1829
|
+
this.expressionFunctions = functions;
|
|
1830
|
+
this.cdr.markForCheck();
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
920
1833
|
loadProcessVariables() {
|
|
921
1834
|
if (this.caseDefinitionKey) {
|
|
922
|
-
this.epistolaPluginService
|
|
1835
|
+
this.epistolaPluginService
|
|
1836
|
+
.getProcessVariables(this.caseDefinitionKey)
|
|
1837
|
+
.pipe(takeUntil$1(this.destroy$), catchError(() => of([])))
|
|
1838
|
+
.subscribe((variables) => {
|
|
923
1839
|
this.processVariables = variables;
|
|
924
1840
|
this.cdr.markForCheck();
|
|
925
1841
|
});
|
|
926
1842
|
}
|
|
927
1843
|
}
|
|
1844
|
+
loadVariableSuggestions() {
|
|
1845
|
+
this.epistolaPluginService
|
|
1846
|
+
.getVariableSuggestions(this.caseDefinitionKey ?? undefined, this.caseDefinitionKey ?? undefined)
|
|
1847
|
+
.pipe(takeUntil$1(this.destroy$), catchError(() => of({ doc: [], pv: [] })))
|
|
1848
|
+
.subscribe((suggestions) => {
|
|
1849
|
+
this.variableSuggestions = suggestions;
|
|
1850
|
+
this.cdr.markForCheck();
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
928
1853
|
handleValid(formValue) {
|
|
929
1854
|
const baseComplete = !!(this.selectedCatalogId$.getValue() &&
|
|
930
1855
|
formValue?.templateId &&
|
|
@@ -933,7 +1858,7 @@ class GenerateDocumentConfigurationComponent {
|
|
|
933
1858
|
formValue?.resultProcessVariable);
|
|
934
1859
|
let variantValid = true;
|
|
935
1860
|
if (this.variantSelectionMode === 'attributes' && this.variantAttributeEntries.length > 0) {
|
|
936
|
-
variantValid = this.variantAttributeEntries.every(e => !!e.key && !!e.value);
|
|
1861
|
+
variantValid = this.variantAttributeEntries.every((e) => !!e.key && !!e.value);
|
|
937
1862
|
}
|
|
938
1863
|
const requiredFieldsMapped = this.requiredFieldsStatus.total === 0 ||
|
|
939
1864
|
this.requiredFieldsStatus.mapped === this.requiredFieldsStatus.total;
|
|
@@ -955,25 +1880,66 @@ class GenerateDocumentConfigurationComponent {
|
|
|
955
1880
|
environmentId: formValue.environmentId || undefined,
|
|
956
1881
|
dataMapping: dataMapping,
|
|
957
1882
|
outputFormat: formValue.outputFormat,
|
|
958
|
-
filename: formValue.filename,
|
|
1883
|
+
filename: this.filenameExpressionMode ? this.filenameExpression : formValue.filename,
|
|
959
1884
|
correlationId: formValue.correlationId || undefined,
|
|
960
|
-
resultProcessVariable: formValue.resultProcessVariable
|
|
1885
|
+
resultProcessVariable: formValue.resultProcessVariable,
|
|
961
1886
|
};
|
|
962
1887
|
if (this.variantSelectionMode === 'explicit') {
|
|
963
|
-
config.variantId =
|
|
1888
|
+
config.variantId = this.variantIdExpressionMode
|
|
1889
|
+
? this.variantIdExpression
|
|
1890
|
+
: formValue.variantId;
|
|
964
1891
|
}
|
|
965
1892
|
else {
|
|
966
1893
|
config.variantAttributes = this.variantAttributeEntries
|
|
967
|
-
.filter(e => e.key && e.value)
|
|
968
|
-
.map(e => ({ key: e.key, value: e.value, required: e.required }));
|
|
1894
|
+
.filter((e) => e.key && e.value)
|
|
1895
|
+
.map((e) => ({ key: e.key, value: e.value, required: e.required }));
|
|
969
1896
|
}
|
|
970
|
-
this.
|
|
1897
|
+
this.validateAndEmit(config);
|
|
971
1898
|
}
|
|
972
1899
|
});
|
|
973
1900
|
});
|
|
974
1901
|
}
|
|
975
|
-
|
|
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\">×</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 >×</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 });
|
|
1902
|
+
/**
|
|
1903
|
+
* Build a JSONata validation request from the config and call the backend.
|
|
1904
|
+
* Only fields that are JSONata expressions get validated:
|
|
1905
|
+
* - dataMapping is always JSONata
|
|
1906
|
+
* - filename / variantId only when their `fx` toggle is on
|
|
1907
|
+
* - variant attribute values only when isExpression() reports true
|
|
1908
|
+
* On invalid response, surface errors and abort the emit.
|
|
1909
|
+
* If the validator endpoint itself fails (network/server), proceed with the
|
|
1910
|
+
* emit — the validation is a quality-of-life check, not a hard gate.
|
|
1911
|
+
*/
|
|
1912
|
+
validateAndEmit(config) {
|
|
1913
|
+
const variantAttributeValues = {};
|
|
1914
|
+
if (config.variantAttributes) {
|
|
1915
|
+
for (const attr of config.variantAttributes) {
|
|
1916
|
+
if (isExpression(attr.value)) {
|
|
1917
|
+
variantAttributeValues[attr.key] = attr.value;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
const request = {
|
|
1922
|
+
dataMapping: config.dataMapping || null,
|
|
1923
|
+
filename: this.filenameExpressionMode ? config.filename : null,
|
|
1924
|
+
variantId: this.variantIdExpressionMode ? config.variantId || null : null,
|
|
1925
|
+
variantAttributeValues: Object.keys(variantAttributeValues).length > 0 ? variantAttributeValues : null,
|
|
1926
|
+
};
|
|
1927
|
+
this.epistolaPluginService
|
|
1928
|
+
.validateJsonata(request)
|
|
1929
|
+
.pipe(take$1(1), catchError(() => of({ valid: true, errors: [] })))
|
|
1930
|
+
.subscribe((result) => {
|
|
1931
|
+
if (result.valid) {
|
|
1932
|
+
this.validationErrors$.next([]);
|
|
1933
|
+
this.configuration.emit(config);
|
|
1934
|
+
}
|
|
1935
|
+
else {
|
|
1936
|
+
this.validationErrors$.next(result.errors);
|
|
1937
|
+
this.cdr.markForCheck();
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, deps: [{ token: EpistolaPluginService }, { token: i2$3.ProcessLinkStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1942
|
+
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 validationErrors: validationErrors$ | async,\n } as obs\"\n>\n <div\n *ngIf=\"obs.validationErrors && obs.validationErrors.length > 0\"\n class=\"jsonata-validation-errors\"\n >\n <strong>{{ 'jsonataValidationErrorsHeading' | pluginTranslate: pluginId | async }}</strong>\n <ul>\n <li *ngFor=\"let err of obs.validationErrors\">\n <code>{{ err.field }}</code\n >: {{ err.message }}\n </li>\n </ul>\n </div>\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 or expression) -->\n <div *ngIf=\"variantSelectionMode === 'explicit'\" class=\"field-with-fx\">\n <v-select\n *ngIf=\"!variantIdExpressionMode\"\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=\"variantIdExpressionMode\" class=\"fx-input-group\">\n <label class=\"fx-input-label\">{{ 'variantId' | pluginTranslate: pluginId | async }}</label>\n <input\n type=\"text\"\n class=\"fx-input\"\n [ngModel]=\"variantIdExpression\"\n (ngModelChange)=\"variantIdExpression = $event; onVariantIdExpressionChange()\"\n [disabled]=\"obs.disabled\"\n placeholder=\"$pv.letterType\"\n />\n </div>\n <button\n type=\"button\"\n class=\"fx-toggle\"\n (click)=\"variantIdExpressionMode = !variantIdExpressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"variantIdExpressionMode ? 'Switch to dropdown' : 'Switch to expression'\"\n >\n {{ variantIdExpressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\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 ×\n </button>\n </div>\n <div class=\"attribute-value-with-fx\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [class.fx-input]=\"entry._expressionMode\"\n [placeholder]=\"\n entry._expressionMode\n ? '$pv.language'\n : ('attributeValue' | pluginTranslate: pluginId | async)\n \"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"fx-toggle fx-toggle--inline\"\n (click)=\"entry._expressionMode = !entry._expressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"entry._expressionMode ? 'Switch to plain value' : 'Switch to expression'\"\n >\n {{ entry._expressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\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 ×\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 <div class=\"field-with-fx\">\n <v-input\n *ngIf=\"!filenameExpressionMode\"\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 <div *ngIf=\"filenameExpressionMode\" class=\"fx-input-group\">\n <label class=\"fx-input-label\">{{ 'filename' | pluginTranslate: pluginId | async }}</label>\n <input\n type=\"text\"\n class=\"fx-input\"\n [ngModel]=\"filenameExpression\"\n (ngModelChange)=\"filenameExpression = $event; onFilenameExpressionChange()\"\n [disabled]=\"obs.disabled\"\n placeholder='\"besluit-\" & $doc.name & \".pdf\"'\n />\n </div>\n <button\n type=\"button\"\n class=\"fx-toggle\"\n (click)=\"filenameExpressionMode = !filenameExpressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"filenameExpressionMode ? 'Switch to plain input' : 'Switch to expression'\"\n >\n {{ filenameExpressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\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 ? '▶' : '▼' }}</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}.jsonata-validation-errors{margin-bottom:1rem;padding:.75rem 1rem;border:1px solid #dc3545;border-radius:4px;background:#fdf3f4;color:#dc3545;font-size:.875rem}.jsonata-validation-errors ul{margin:.5rem 0 0;padding-left:1.25rem}.jsonata-validation-errors code{background:#dc35451a;padding:0 .25rem;border-radius:2px;font-family:monospace}.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}.field-with-fx{display:flex;align-items:flex-start;gap:4px}.field-with-fx>*:first-child{flex:1;min-width:0}.fx-toggle{width:28px;height:28px;margin-top:22px;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;flex-shrink:0}.fx-toggle:hover{background:#f4f4f4}.fx-toggle--inline{margin-top:0}.fx-input-group{flex:1;min-width:0;margin-bottom:.75rem}.fx-input-label{display:block;font-size:.875rem;margin-bottom:.25rem;color:#525252}.fx-input{width:100%;border:1px solid #8d8d8d;border-radius:0;padding:.4rem .75rem;font-family:monospace;font-size:.875rem;background:#f4f4f4}.attribute-value-with-fx{display:flex;align-items:center;gap:4px;flex:1;min-width:0}.attribute-value-with-fx>input{flex:1;min-width:0}.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: i2$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.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: i2$2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.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
1943
|
}
|
|
978
1944
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, decorators: [{
|
|
979
1945
|
type: Component,
|
|
@@ -984,9 +1950,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
984
1950
|
FormModule,
|
|
985
1951
|
InputModule,
|
|
986
1952
|
SelectModule,
|
|
987
|
-
|
|
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\">×</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 >×</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"] }]
|
|
989
|
-
|
|
1953
|
+
ExpectedStructureComponent,
|
|
1954
|
+
JsonataEditorComponent,
|
|
1955
|
+
MappingBuilderComponent,
|
|
1956
|
+
MappingPreviewComponent,
|
|
1957
|
+
], 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 validationErrors: validationErrors$ | async,\n } as obs\"\n>\n <div\n *ngIf=\"obs.validationErrors && obs.validationErrors.length > 0\"\n class=\"jsonata-validation-errors\"\n >\n <strong>{{ 'jsonataValidationErrorsHeading' | pluginTranslate: pluginId | async }}</strong>\n <ul>\n <li *ngFor=\"let err of obs.validationErrors\">\n <code>{{ err.field }}</code\n >: {{ err.message }}\n </li>\n </ul>\n </div>\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 or expression) -->\n <div *ngIf=\"variantSelectionMode === 'explicit'\" class=\"field-with-fx\">\n <v-select\n *ngIf=\"!variantIdExpressionMode\"\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=\"variantIdExpressionMode\" class=\"fx-input-group\">\n <label class=\"fx-input-label\">{{ 'variantId' | pluginTranslate: pluginId | async }}</label>\n <input\n type=\"text\"\n class=\"fx-input\"\n [ngModel]=\"variantIdExpression\"\n (ngModelChange)=\"variantIdExpression = $event; onVariantIdExpressionChange()\"\n [disabled]=\"obs.disabled\"\n placeholder=\"$pv.letterType\"\n />\n </div>\n <button\n type=\"button\"\n class=\"fx-toggle\"\n (click)=\"variantIdExpressionMode = !variantIdExpressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"variantIdExpressionMode ? 'Switch to dropdown' : 'Switch to expression'\"\n >\n {{ variantIdExpressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\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 ×\n </button>\n </div>\n <div class=\"attribute-value-with-fx\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [class.fx-input]=\"entry._expressionMode\"\n [placeholder]=\"\n entry._expressionMode\n ? '$pv.language'\n : ('attributeValue' | pluginTranslate: pluginId | async)\n \"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"fx-toggle fx-toggle--inline\"\n (click)=\"entry._expressionMode = !entry._expressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"entry._expressionMode ? 'Switch to plain value' : 'Switch to expression'\"\n >\n {{ entry._expressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\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 ×\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 <div class=\"field-with-fx\">\n <v-input\n *ngIf=\"!filenameExpressionMode\"\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 <div *ngIf=\"filenameExpressionMode\" class=\"fx-input-group\">\n <label class=\"fx-input-label\">{{ 'filename' | pluginTranslate: pluginId | async }}</label>\n <input\n type=\"text\"\n class=\"fx-input\"\n [ngModel]=\"filenameExpression\"\n (ngModelChange)=\"filenameExpression = $event; onFilenameExpressionChange()\"\n [disabled]=\"obs.disabled\"\n placeholder='\"besluit-\" & $doc.name & \".pdf\"'\n />\n </div>\n <button\n type=\"button\"\n class=\"fx-toggle\"\n (click)=\"filenameExpressionMode = !filenameExpressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"filenameExpressionMode ? 'Switch to plain input' : 'Switch to expression'\"\n >\n {{ filenameExpressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\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 ? '▶' : '▼' }}</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}.jsonata-validation-errors{margin-bottom:1rem;padding:.75rem 1rem;border:1px solid #dc3545;border-radius:4px;background:#fdf3f4;color:#dc3545;font-size:.875rem}.jsonata-validation-errors ul{margin:.5rem 0 0;padding-left:1.25rem}.jsonata-validation-errors code{background:#dc35451a;padding:0 .25rem;border-radius:2px;font-family:monospace}.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}.field-with-fx{display:flex;align-items:flex-start;gap:4px}.field-with-fx>*:first-child{flex:1;min-width:0}.fx-toggle{width:28px;height:28px;margin-top:22px;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;flex-shrink:0}.fx-toggle:hover{background:#f4f4f4}.fx-toggle--inline{margin-top:0}.fx-input-group{flex:1;min-width:0;margin-bottom:.75rem}.fx-input-label{display:block;font-size:.875rem;margin-bottom:.25rem;color:#525252}.fx-input{width:100%;border:1px solid #8d8d8d;border-radius:0;padding:.4rem .75rem;font-family:monospace;font-size:.875rem;background:#f4f4f4}.attribute-value-with-fx{display:flex;align-items:center;gap:4px;flex:1;min-width:0}.attribute-value-with-fx>input{flex:1;min-width:0}.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"] }]
|
|
1958
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$3.ProcessLinkStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { save$: [{
|
|
990
1959
|
type: Input
|
|
991
1960
|
}], disabled$: [{
|
|
992
1961
|
type: Input
|
|
@@ -1028,8 +1997,7 @@ class CheckJobStatusConfigurationComponent {
|
|
|
1028
1997
|
this.handleValid(formValue);
|
|
1029
1998
|
}
|
|
1030
1999
|
handleValid(formValue) {
|
|
1031
|
-
const valid = !!(formValue?.requestIdVariable &&
|
|
1032
|
-
formValue?.statusVariable);
|
|
2000
|
+
const valid = !!(formValue?.requestIdVariable && formValue?.statusVariable);
|
|
1033
2001
|
this.valid$.next(valid);
|
|
1034
2002
|
this.valid.emit(valid);
|
|
1035
2003
|
}
|
|
@@ -1045,11 +2013,11 @@ class CheckJobStatusConfigurationComponent {
|
|
|
1045
2013
|
});
|
|
1046
2014
|
}
|
|
1047
2015
|
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
|
|
2016
|
+
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
2017
|
}
|
|
1050
2018
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CheckJobStatusConfigurationComponent, decorators: [{
|
|
1051
2019
|
type: Component,
|
|
1052
|
-
args: [{ selector: 'epistola-check-job-status-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form\n
|
|
2020
|
+
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
2021
|
}], propDecorators: { save$: [{
|
|
1054
2022
|
type: Input
|
|
1055
2023
|
}], disabled$: [{
|
|
@@ -1088,8 +2056,7 @@ class DownloadDocumentConfigurationComponent {
|
|
|
1088
2056
|
this.handleValid(formValue);
|
|
1089
2057
|
}
|
|
1090
2058
|
handleValid(formValue) {
|
|
1091
|
-
const valid = !!(formValue?.documentIdVariable &&
|
|
1092
|
-
formValue?.contentVariable);
|
|
2059
|
+
const valid = !!(formValue?.documentIdVariable && formValue?.contentVariable);
|
|
1093
2060
|
this.valid$.next(valid);
|
|
1094
2061
|
this.valid.emit(valid);
|
|
1095
2062
|
}
|
|
@@ -1105,11 +2072,11 @@ class DownloadDocumentConfigurationComponent {
|
|
|
1105
2072
|
});
|
|
1106
2073
|
}
|
|
1107
2074
|
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
|
|
2075
|
+
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
2076
|
}
|
|
1110
2077
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, decorators: [{
|
|
1111
2078
|
type: Component,
|
|
1112
|
-
args: [{ selector: 'epistola-download-document-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form\n
|
|
2079
|
+
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
2080
|
}], propDecorators: { save$: [{
|
|
1114
2081
|
type: Input
|
|
1115
2082
|
}], disabled$: [{
|
|
@@ -1149,9 +2116,9 @@ class EpistolaDownloadComponent {
|
|
|
1149
2116
|
this.downloading = true;
|
|
1150
2117
|
this.error = null;
|
|
1151
2118
|
const { documentId, tenantId } = this.value;
|
|
1152
|
-
const url = `/api/v1/plugin/epistola/documents/${encodeURIComponent(documentId)}/download`
|
|
1153
|
-
|
|
1154
|
-
|
|
2119
|
+
const url = `/api/v1/plugin/epistola/documents/${encodeURIComponent(documentId)}/download` +
|
|
2120
|
+
`?tenantId=${encodeURIComponent(tenantId)}` +
|
|
2121
|
+
`&filename=${encodeURIComponent(this.filename)}`;
|
|
1155
2122
|
this.http.get(url, { responseType: 'blob' }).subscribe({
|
|
1156
2123
|
next: (blob) => {
|
|
1157
2124
|
const objectUrl = URL.createObjectURL(blob);
|
|
@@ -1244,7 +2211,7 @@ class EpistolaRetryFormComponent {
|
|
|
1244
2211
|
apiEndpoint;
|
|
1245
2212
|
formOptions = {
|
|
1246
2213
|
noAlerts: true,
|
|
1247
|
-
buttonSettings: { showCancel: false, showSubmit: false, showPrevious: false, showNext: false }
|
|
2214
|
+
buttonSettings: { showCancel: false, showSubmit: false, showPrevious: false, showNext: false },
|
|
1248
2215
|
};
|
|
1249
2216
|
constructor(epistolaPluginService, formIoStateService, cdr, http, sanitizer, configService) {
|
|
1250
2217
|
this.epistolaPluginService = epistolaPluginService;
|
|
@@ -1255,7 +2222,7 @@ class EpistolaRetryFormComponent {
|
|
|
1255
2222
|
this.configService = configService;
|
|
1256
2223
|
this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
|
|
1257
2224
|
// Debounce preview calls
|
|
1258
|
-
this.previewSubscription = this.previewSubject.pipe(debounceTime(1500)).subscribe(data => {
|
|
2225
|
+
this.previewSubscription = this.previewSubject.pipe(debounceTime$1(1500)).subscribe((data) => {
|
|
1259
2226
|
this.loadPreview(data);
|
|
1260
2227
|
});
|
|
1261
2228
|
}
|
|
@@ -1298,12 +2265,14 @@ class EpistolaRetryFormComponent {
|
|
|
1298
2265
|
URL.revokeObjectURL(this.currentBlobUrl);
|
|
1299
2266
|
this.currentBlobUrl = null;
|
|
1300
2267
|
}
|
|
1301
|
-
this.http
|
|
2268
|
+
this.http
|
|
2269
|
+
.post(`${this.apiEndpoint}/preview`, {
|
|
1302
2270
|
documentId,
|
|
1303
2271
|
processInstanceId,
|
|
1304
2272
|
sourceActivityId: this.sourceActivityId || null,
|
|
1305
|
-
overrides: formData
|
|
1306
|
-
}, { responseType: 'blob', headers: new HttpHeaders().set('X-Skip-Interceptor', '422') })
|
|
2273
|
+
overrides: formData,
|
|
2274
|
+
}, { responseType: 'blob', headers: new HttpHeaders().set('X-Skip-Interceptor', '422') })
|
|
2275
|
+
.subscribe({
|
|
1307
2276
|
next: (blob) => {
|
|
1308
2277
|
this.currentBlobUrl = URL.createObjectURL(blob);
|
|
1309
2278
|
this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
|
|
@@ -1332,7 +2301,7 @@ class EpistolaRetryFormComponent {
|
|
|
1332
2301
|
this.previewLoading = false;
|
|
1333
2302
|
this.cdr.markForCheck();
|
|
1334
2303
|
}
|
|
1335
|
-
}
|
|
2304
|
+
},
|
|
1336
2305
|
});
|
|
1337
2306
|
}
|
|
1338
2307
|
loadForm() {
|
|
@@ -1344,7 +2313,9 @@ class EpistolaRetryFormComponent {
|
|
|
1344
2313
|
this.cdr.markForCheck();
|
|
1345
2314
|
return;
|
|
1346
2315
|
}
|
|
1347
|
-
this.loadSubscription = this.epistolaPluginService
|
|
2316
|
+
this.loadSubscription = this.epistolaPluginService
|
|
2317
|
+
.getRetryForm(processInstanceId, documentId ?? undefined, this.sourceActivityId)
|
|
2318
|
+
.subscribe({
|
|
1348
2319
|
next: (form) => {
|
|
1349
2320
|
this.formDefinition = form;
|
|
1350
2321
|
if (this.value) {
|
|
@@ -1363,14 +2334,18 @@ class EpistolaRetryFormComponent {
|
|
|
1363
2334
|
this.error = 'Failed to load the retry form. Please try again.';
|
|
1364
2335
|
this.loading = false;
|
|
1365
2336
|
this.cdr.markForCheck();
|
|
1366
|
-
}
|
|
2337
|
+
},
|
|
1367
2338
|
});
|
|
1368
2339
|
}
|
|
1369
|
-
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
|
|
2340
|
+
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.DomSanitizer }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1370
2341
|
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
2342
|
<div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
|
|
1372
2343
|
<div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
|
|
1373
|
-
<div
|
|
2344
|
+
<div
|
|
2345
|
+
*ngIf="formDefinition && !loading"
|
|
2346
|
+
class="epistola-retry-container"
|
|
2347
|
+
[class.preview-expanded]="previewExpanded"
|
|
2348
|
+
>
|
|
1374
2349
|
<div class="epistola-retry-form" [hidden]="previewExpanded">
|
|
1375
2350
|
<formio
|
|
1376
2351
|
[form]="formDefinition"
|
|
@@ -1387,7 +2362,12 @@ class EpistolaRetryFormComponent {
|
|
|
1387
2362
|
</button>
|
|
1388
2363
|
</div>
|
|
1389
2364
|
<div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
|
|
1390
|
-
<object
|
|
2365
|
+
<object
|
|
2366
|
+
*ngIf="previewUrl && !previewLoading"
|
|
2367
|
+
[data]="previewUrl"
|
|
2368
|
+
type="application/pdf"
|
|
2369
|
+
class="preview-pdf"
|
|
2370
|
+
>
|
|
1391
2371
|
PDF preview not supported in this browser.
|
|
1392
2372
|
</object>
|
|
1393
2373
|
<div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
|
|
@@ -1403,7 +2383,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1403
2383
|
args: [{ standalone: true, imports: [CommonModule, FormioModule], selector: 'epistola-retry-form-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1404
2384
|
<div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
|
|
1405
2385
|
<div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
|
|
1406
|
-
<div
|
|
2386
|
+
<div
|
|
2387
|
+
*ngIf="formDefinition && !loading"
|
|
2388
|
+
class="epistola-retry-container"
|
|
2389
|
+
[class.preview-expanded]="previewExpanded"
|
|
2390
|
+
>
|
|
1407
2391
|
<div class="epistola-retry-form" [hidden]="previewExpanded">
|
|
1408
2392
|
<formio
|
|
1409
2393
|
[form]="formDefinition"
|
|
@@ -1420,7 +2404,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1420
2404
|
</button>
|
|
1421
2405
|
</div>
|
|
1422
2406
|
<div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
|
|
1423
|
-
<object
|
|
2407
|
+
<object
|
|
2408
|
+
*ngIf="previewUrl && !previewLoading"
|
|
2409
|
+
[data]="previewUrl"
|
|
2410
|
+
type="application/pdf"
|
|
2411
|
+
class="preview-pdf"
|
|
2412
|
+
>
|
|
1424
2413
|
PDF preview not supported in this browser.
|
|
1425
2414
|
</object>
|
|
1426
2415
|
<div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
|
|
@@ -1430,7 +2419,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1430
2419
|
</div>
|
|
1431
2420
|
</div>
|
|
1432
2421
|
`, styles: [".epistola-retry-loading{padding:1rem;color:#6c757d}.epistola-retry-error{padding:.5rem;color:#dc3545}.epistola-retry-container{display:flex;gap:1rem}.epistola-retry-form{flex:2;min-width:0}.epistola-retry-preview{flex:1;min-width:0;border:1px solid #dee2e6;border-radius:4px;padding:1rem;background:#f8f9fa;display:flex;flex-direction:column}.preview-expanded .epistola-retry-preview{flex:1}.preview-header{display:flex;justify-content:space-between;align-items:center;font-weight:700;margin-bottom:.5rem;color:#495057}.preview-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.2rem .5rem;font-size:.75rem;cursor:pointer}.preview-toggle:hover{background:#e9ecef}.preview-loading{color:#6c757d;font-style:italic}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-expanded .preview-pdf{min-height:80vh}.preview-error{color:#dc3545}.preview-empty{color:#6c757d;font-style:italic}\n"] }]
|
|
1433
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }, { type: i1.HttpClient }, { type: i4
|
|
2422
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }, { type: i1.HttpClient }, { type: i4.DomSanitizer }, { type: i2.ConfigService }], propDecorators: { value: [{
|
|
1434
2423
|
type: Input
|
|
1435
2424
|
}], valueChange: [{
|
|
1436
2425
|
type: Output
|
|
@@ -1480,9 +2469,9 @@ class EpistolaPreviewButtonComponent {
|
|
|
1480
2469
|
this.previewError = null;
|
|
1481
2470
|
this.revokeBlobUrl();
|
|
1482
2471
|
const { documentId, tenantId } = this.value;
|
|
1483
|
-
const url = `${this.apiEndpoint}/documents/${encodeURIComponent(documentId)}/download`
|
|
1484
|
-
|
|
1485
|
-
|
|
2472
|
+
const url = `${this.apiEndpoint}/documents/${encodeURIComponent(documentId)}/download` +
|
|
2473
|
+
`?tenantId=${encodeURIComponent(tenantId)}` +
|
|
2474
|
+
`&filename=preview.pdf`;
|
|
1486
2475
|
this.http.get(url, { responseType: 'blob' }).subscribe({
|
|
1487
2476
|
next: (blob) => {
|
|
1488
2477
|
this.currentBlobUrl = URL.createObjectURL(blob);
|
|
@@ -1492,7 +2481,7 @@ class EpistolaPreviewButtonComponent {
|
|
|
1492
2481
|
error: () => {
|
|
1493
2482
|
this.previewError = 'Could not load the document.';
|
|
1494
2483
|
this.previewLoading = false;
|
|
1495
|
-
}
|
|
2484
|
+
},
|
|
1496
2485
|
});
|
|
1497
2486
|
}
|
|
1498
2487
|
closePreview() {
|
|
@@ -1507,7 +2496,7 @@ class EpistolaPreviewButtonComponent {
|
|
|
1507
2496
|
this.currentBlobUrl = null;
|
|
1508
2497
|
}
|
|
1509
2498
|
}
|
|
1510
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPreviewButtonComponent, deps: [{ token: i1.HttpClient }, { token: i4
|
|
2499
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPreviewButtonComponent, deps: [{ token: i1.HttpClient }, { token: i4.DomSanitizer }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1511
2500
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaPreviewButtonComponent, isStandalone: true, selector: "epistola-preview-button-component", inputs: { value: "value", disabled: "disabled", label: "label" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: `
|
|
1512
2501
|
<button
|
|
1513
2502
|
type="button"
|
|
@@ -1523,7 +2512,9 @@ class EpistolaPreviewButtonComponent {
|
|
|
1523
2512
|
<div class="preview-modal-content" (click)="$event.stopPropagation()">
|
|
1524
2513
|
<div class="preview-modal-header">
|
|
1525
2514
|
<span>Document Preview</span>
|
|
1526
|
-
<button type="button" class="preview-modal-close" (click)="closePreview()"
|
|
2515
|
+
<button type="button" class="preview-modal-close" (click)="closePreview()">
|
|
2516
|
+
×
|
|
2517
|
+
</button>
|
|
1527
2518
|
</div>
|
|
1528
2519
|
<div class="preview-modal-body">
|
|
1529
2520
|
<div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
|
|
@@ -1558,7 +2549,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1558
2549
|
<div class="preview-modal-content" (click)="$event.stopPropagation()">
|
|
1559
2550
|
<div class="preview-modal-header">
|
|
1560
2551
|
<span>Document Preview</span>
|
|
1561
|
-
<button type="button" class="preview-modal-close" (click)="closePreview()"
|
|
2552
|
+
<button type="button" class="preview-modal-close" (click)="closePreview()">
|
|
2553
|
+
×
|
|
2554
|
+
</button>
|
|
1562
2555
|
</div>
|
|
1563
2556
|
<div class="preview-modal-body">
|
|
1564
2557
|
<div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
|
|
@@ -1575,7 +2568,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1575
2568
|
</div>
|
|
1576
2569
|
</div>
|
|
1577
2570
|
`, styles: [".preview-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:10000}.preview-modal-content{background:#fff;border-radius:8px;width:90vw;height:90vh;max-width:1200px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 32px #0000004d}.preview-modal-header{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;font-size:1rem}.preview-modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6c757d;line-height:1;padding:0 .25rem}.preview-modal-close:hover{color:#333}.preview-modal-body{flex:1;overflow:hidden;display:flex;flex-direction:column}.preview-loading,.preview-error{padding:2rem;text-align:center}.preview-error{color:#dc3545}.preview-pdf{width:100%;flex:1}\n"] }]
|
|
1578
|
-
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i4
|
|
2571
|
+
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i4.DomSanitizer }, { type: i2.ConfigService }], propDecorators: { value: [{
|
|
1579
2572
|
type: Input
|
|
1580
2573
|
}], valueChange: [{
|
|
1581
2574
|
type: Output
|
|
@@ -1596,17 +2589,25 @@ class EpistolaDocumentPreviewComponent {
|
|
|
1596
2589
|
valueChange = new EventEmitter();
|
|
1597
2590
|
disabled = false;
|
|
1598
2591
|
label = 'Document Preview';
|
|
2592
|
+
processDefinitionKey;
|
|
2593
|
+
sourceActivityId;
|
|
2594
|
+
overrideMapping;
|
|
1599
2595
|
sources = [];
|
|
1600
2596
|
selectedIndex = 0;
|
|
1601
2597
|
discovering = false;
|
|
1602
2598
|
loading = false;
|
|
1603
2599
|
error = null;
|
|
1604
2600
|
previewUrl = null;
|
|
2601
|
+
designMode = false;
|
|
1605
2602
|
initialized = false;
|
|
1606
2603
|
currentBlobUrl = null;
|
|
1607
2604
|
discoverSubscription;
|
|
1608
2605
|
previewSubscription;
|
|
1609
2606
|
apiEndpoint;
|
|
2607
|
+
/** Whether the component is in configured mode (explicit process link) vs auto-discover mode */
|
|
2608
|
+
get configuredMode() {
|
|
2609
|
+
return !!this.sourceActivityId;
|
|
2610
|
+
}
|
|
1610
2611
|
constructor(epistolaPluginService, http, sanitizer, configService, formIoStateService, cdr) {
|
|
1611
2612
|
this.epistolaPluginService = epistolaPluginService;
|
|
1612
2613
|
this.http = http;
|
|
@@ -1616,10 +2617,36 @@ class EpistolaDocumentPreviewComponent {
|
|
|
1616
2617
|
this.cdr = cdr;
|
|
1617
2618
|
this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
|
|
1618
2619
|
}
|
|
2620
|
+
get overrideMappingScopes() {
|
|
2621
|
+
return this.overrideMapping ? Object.keys(this.overrideMapping) : [];
|
|
2622
|
+
}
|
|
2623
|
+
overrideMappingEntries(scope) {
|
|
2624
|
+
const fields = this.overrideMapping?.[scope];
|
|
2625
|
+
if (!fields || typeof fields !== 'object')
|
|
2626
|
+
return [];
|
|
2627
|
+
return Object.entries(fields).map(([path, field]) => ({ path, field: String(field) }));
|
|
2628
|
+
}
|
|
1619
2629
|
ngOnChanges(changes) {
|
|
1620
2630
|
if (!this.initialized) {
|
|
1621
2631
|
this.initialized = true;
|
|
1622
|
-
|
|
2632
|
+
// Detect design mode: no runtime context (Formio builder)
|
|
2633
|
+
const documentId = this.formIoStateService.documentId;
|
|
2634
|
+
if (!documentId) {
|
|
2635
|
+
this.designMode = true;
|
|
2636
|
+
this.cdr.markForCheck();
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
if (this.configuredMode) {
|
|
2640
|
+
this.loadConfiguredPreview();
|
|
2641
|
+
}
|
|
2642
|
+
else {
|
|
2643
|
+
this.discoverSources();
|
|
2644
|
+
}
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
// In configured mode, react to value changes (input overrides from Formio wrapper)
|
|
2648
|
+
if (this.configuredMode && changes['value']) {
|
|
2649
|
+
this.loadConfiguredPreview();
|
|
1623
2650
|
}
|
|
1624
2651
|
}
|
|
1625
2652
|
ngOnDestroy() {
|
|
@@ -1629,11 +2656,51 @@ class EpistolaDocumentPreviewComponent {
|
|
|
1629
2656
|
}
|
|
1630
2657
|
onSourceChange(event) {
|
|
1631
2658
|
this.selectedIndex = +event.target.value;
|
|
1632
|
-
this.
|
|
2659
|
+
this.loadDiscoveredPreview();
|
|
1633
2660
|
}
|
|
1634
2661
|
refresh() {
|
|
1635
|
-
this.
|
|
2662
|
+
if (this.configuredMode) {
|
|
2663
|
+
this.loadConfiguredPreview();
|
|
2664
|
+
}
|
|
2665
|
+
else {
|
|
2666
|
+
this.loadDiscoveredPreview();
|
|
2667
|
+
}
|
|
1636
2668
|
}
|
|
2669
|
+
/**
|
|
2670
|
+
* Configured mode: preview using the explicitly configured process link + input overrides.
|
|
2671
|
+
*/
|
|
2672
|
+
loadConfiguredPreview() {
|
|
2673
|
+
const documentId = this.formIoStateService.documentId;
|
|
2674
|
+
if (!documentId) {
|
|
2675
|
+
this.error = 'Could not determine document ID from context.';
|
|
2676
|
+
this.cdr.markForCheck();
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
this.loading = true;
|
|
2680
|
+
this.error = null;
|
|
2681
|
+
this.cdr.markForCheck();
|
|
2682
|
+
this.revokeBlobUrl();
|
|
2683
|
+
this.previewSubscription?.unsubscribe();
|
|
2684
|
+
this.previewSubscription = this.http
|
|
2685
|
+
.post(`${this.apiEndpoint}/preview`, {
|
|
2686
|
+
documentId,
|
|
2687
|
+
processDefinitionKey: this.processDefinitionKey || null,
|
|
2688
|
+
processInstanceId: this.formIoStateService.processInstanceId || null,
|
|
2689
|
+
sourceActivityId: this.sourceActivityId,
|
|
2690
|
+
inputOverrides: this.value || null,
|
|
2691
|
+
overrides: null,
|
|
2692
|
+
}, {
|
|
2693
|
+
responseType: 'blob',
|
|
2694
|
+
headers: new HttpHeaders().set('X-Skip-Interceptor', '422'),
|
|
2695
|
+
})
|
|
2696
|
+
.subscribe({
|
|
2697
|
+
next: (blob) => this.handlePreviewSuccess(blob),
|
|
2698
|
+
error: (err) => this.handlePreviewError(err),
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
/**
|
|
2702
|
+
* Auto-discover mode: discover sources from running process instances.
|
|
2703
|
+
*/
|
|
1637
2704
|
discoverSources() {
|
|
1638
2705
|
const documentId = this.formIoStateService.documentId;
|
|
1639
2706
|
if (!documentId) {
|
|
@@ -1651,17 +2718,20 @@ class EpistolaDocumentPreviewComponent {
|
|
|
1651
2718
|
this.cdr.markForCheck();
|
|
1652
2719
|
if (sources.length > 0) {
|
|
1653
2720
|
this.selectedIndex = 0;
|
|
1654
|
-
this.
|
|
2721
|
+
this.loadDiscoveredPreview();
|
|
1655
2722
|
}
|
|
1656
2723
|
},
|
|
1657
2724
|
error: (err) => {
|
|
1658
2725
|
this.error = err.error?.error || 'Failed to discover preview sources';
|
|
1659
2726
|
this.discovering = false;
|
|
1660
2727
|
this.cdr.markForCheck();
|
|
1661
|
-
}
|
|
2728
|
+
},
|
|
1662
2729
|
});
|
|
1663
2730
|
}
|
|
1664
|
-
|
|
2731
|
+
/**
|
|
2732
|
+
* Auto-discover mode: load preview for the selected discovered source.
|
|
2733
|
+
*/
|
|
2734
|
+
loadDiscoveredPreview() {
|
|
1665
2735
|
const source = this.sources[this.selectedIndex];
|
|
1666
2736
|
if (!source)
|
|
1667
2737
|
return;
|
|
@@ -1673,44 +2743,48 @@ class EpistolaDocumentPreviewComponent {
|
|
|
1673
2743
|
this.cdr.markForCheck();
|
|
1674
2744
|
this.revokeBlobUrl();
|
|
1675
2745
|
this.previewSubscription?.unsubscribe();
|
|
1676
|
-
this.previewSubscription = this.http
|
|
2746
|
+
this.previewSubscription = this.http
|
|
2747
|
+
.post(`${this.apiEndpoint}/preview`, {
|
|
1677
2748
|
documentId,
|
|
1678
2749
|
processInstanceId: source.processInstanceId,
|
|
1679
2750
|
sourceActivityId: source.activityId,
|
|
1680
|
-
overrides: null
|
|
2751
|
+
overrides: null,
|
|
1681
2752
|
}, {
|
|
1682
2753
|
responseType: 'blob',
|
|
1683
|
-
headers: new HttpHeaders().set('X-Skip-Interceptor', '422')
|
|
1684
|
-
})
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
this.cdr.markForCheck();
|
|
1705
|
-
});
|
|
2754
|
+
headers: new HttpHeaders().set('X-Skip-Interceptor', '422'),
|
|
2755
|
+
})
|
|
2756
|
+
.subscribe({
|
|
2757
|
+
next: (blob) => this.handlePreviewSuccess(blob),
|
|
2758
|
+
error: (err) => this.handlePreviewError(err),
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
handlePreviewSuccess(blob) {
|
|
2762
|
+
this.currentBlobUrl = URL.createObjectURL(blob);
|
|
2763
|
+
this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
|
|
2764
|
+
this.error = null;
|
|
2765
|
+
this.loading = false;
|
|
2766
|
+
this.cdr.markForCheck();
|
|
2767
|
+
}
|
|
2768
|
+
handlePreviewError(err) {
|
|
2769
|
+
this.previewUrl = null;
|
|
2770
|
+
if (err.error instanceof Blob) {
|
|
2771
|
+
err.error.text().then((text) => {
|
|
2772
|
+
try {
|
|
2773
|
+
const body = JSON.parse(text);
|
|
2774
|
+
this.error = body.details || body.error || 'Preview could not be generated';
|
|
1706
2775
|
}
|
|
1707
|
-
|
|
1708
|
-
this.error =
|
|
1709
|
-
this.loading = false;
|
|
1710
|
-
this.cdr.markForCheck();
|
|
2776
|
+
catch {
|
|
2777
|
+
this.error = 'Preview could not be generated';
|
|
1711
2778
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
2779
|
+
this.loading = false;
|
|
2780
|
+
this.cdr.markForCheck();
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
else {
|
|
2784
|
+
this.error = err.error?.error || 'Preview could not be generated';
|
|
2785
|
+
this.loading = false;
|
|
2786
|
+
this.cdr.markForCheck();
|
|
2787
|
+
}
|
|
1714
2788
|
}
|
|
1715
2789
|
revokeBlobUrl() {
|
|
1716
2790
|
if (this.currentBlobUrl) {
|
|
@@ -1719,14 +2793,44 @@ class EpistolaDocumentPreviewComponent {
|
|
|
1719
2793
|
this.previewUrl = null;
|
|
1720
2794
|
}
|
|
1721
2795
|
}
|
|
1722
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, deps: [{ token: EpistolaPluginService }, { token: i1.HttpClient }, { token: i4
|
|
1723
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaDocumentPreviewComponent, isStandalone: true, selector: "epistola-document-preview-component", inputs: { value: "value", disabled: "disabled", label: "label" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
1724
|
-
|
|
2796
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, deps: [{ token: EpistolaPluginService }, { token: i1.HttpClient }, { token: i4.DomSanitizer }, { token: i2.ConfigService }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
2797
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaDocumentPreviewComponent, isStandalone: true, selector: "epistola-document-preview-component", inputs: { value: "value", disabled: "disabled", label: "label", processDefinitionKey: "processDefinitionKey", sourceActivityId: "sourceActivityId", overrideMapping: "overrideMapping" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2798
|
+
<!-- Design-time view: show configuration summary when no runtime context -->
|
|
2799
|
+
<div *ngIf="designMode" class="epistola-preview-panel">
|
|
2800
|
+
<div class="preview-header">
|
|
2801
|
+
<span>{{ label || 'Document Preview' }}</span>
|
|
2802
|
+
</div>
|
|
2803
|
+
<div class="preview-body design-info">
|
|
2804
|
+
<div class="design-section" *ngIf="sourceActivityId">
|
|
2805
|
+
<div class="design-label">Process</div>
|
|
2806
|
+
<div class="design-value">{{ processDefinitionKey || '(any)' }}</div>
|
|
2807
|
+
<div class="design-label">Activity</div>
|
|
2808
|
+
<div class="design-value">{{ sourceActivityId }}</div>
|
|
2809
|
+
</div>
|
|
2810
|
+
<div class="design-section" *ngIf="overrideMapping">
|
|
2811
|
+
<div class="design-label">Input Overrides</div>
|
|
2812
|
+
<div *ngFor="let scope of overrideMappingScopes" class="design-mapping">
|
|
2813
|
+
<div *ngFor="let entry of overrideMappingEntries(scope)" class="design-entry">
|
|
2814
|
+
<span class="design-scope">{{ scope }}</span
|
|
2815
|
+
>.{{ entry.path }}
|
|
2816
|
+
<i class="mdi mdi-arrow-left"></i>
|
|
2817
|
+
<span class="design-field">{{ entry.field }}</span>
|
|
2818
|
+
</div>
|
|
2819
|
+
</div>
|
|
2820
|
+
</div>
|
|
2821
|
+
<div *ngIf="!sourceActivityId" class="design-unconfigured">
|
|
2822
|
+
Auto-discover mode (no process link configured)
|
|
2823
|
+
</div>
|
|
2824
|
+
</div>
|
|
2825
|
+
</div>
|
|
2826
|
+
|
|
2827
|
+
<!-- Runtime view: actual preview -->
|
|
2828
|
+
<div *ngIf="!designMode" class="epistola-preview-panel">
|
|
1725
2829
|
<div class="preview-header">
|
|
1726
2830
|
<span>{{ label || 'Document Preview' }}</span>
|
|
1727
2831
|
<div class="preview-controls">
|
|
1728
2832
|
<select
|
|
1729
|
-
*ngIf="sources.length > 1"
|
|
2833
|
+
*ngIf="!sourceActivityId && sources.length > 1"
|
|
1730
2834
|
class="preview-select"
|
|
1731
2835
|
[value]="selectedIndex"
|
|
1732
2836
|
(change)="onSourceChange($event)"
|
|
@@ -1735,19 +2839,20 @@ class EpistolaDocumentPreviewComponent {
|
|
|
1735
2839
|
{{ source.templateName }} ({{ source.activityId }})
|
|
1736
2840
|
</option>
|
|
1737
2841
|
</select>
|
|
1738
|
-
<button
|
|
2842
|
+
<button
|
|
2843
|
+
type="button"
|
|
2844
|
+
class="preview-refresh"
|
|
2845
|
+
[disabled]="loading || discovering"
|
|
2846
|
+
(click)="refresh()"
|
|
2847
|
+
>
|
|
1739
2848
|
<i class="mdi mdi-refresh mr-1"></i>
|
|
1740
2849
|
{{ loading ? 'Generating...' : 'Refresh' }}
|
|
1741
2850
|
</button>
|
|
1742
2851
|
</div>
|
|
1743
2852
|
</div>
|
|
1744
2853
|
<div class="preview-body">
|
|
1745
|
-
<div *ngIf="discovering" class="preview-loading">
|
|
1746
|
-
|
|
1747
|
-
</div>
|
|
1748
|
-
<div *ngIf="loading && !discovering" class="preview-loading">
|
|
1749
|
-
Generating preview...
|
|
1750
|
-
</div>
|
|
2854
|
+
<div *ngIf="discovering" class="preview-loading">Discovering documents...</div>
|
|
2855
|
+
<div *ngIf="loading && !discovering" class="preview-loading">Generating preview...</div>
|
|
1751
2856
|
<div *ngIf="error && !loading && !discovering" class="preview-unavailable">
|
|
1752
2857
|
<i class="mdi mdi-information-outline"></i>
|
|
1753
2858
|
Preview is niet beschikbaar — niet alle gegevens zijn al ingevuld.
|
|
@@ -1760,22 +2865,62 @@ class EpistolaDocumentPreviewComponent {
|
|
|
1760
2865
|
>
|
|
1761
2866
|
PDF preview is not supported in this browser.
|
|
1762
2867
|
</object>
|
|
1763
|
-
<div
|
|
2868
|
+
<div
|
|
2869
|
+
*ngIf="
|
|
2870
|
+
!previewUrl &&
|
|
2871
|
+
!loading &&
|
|
2872
|
+
!discovering &&
|
|
2873
|
+
!error &&
|
|
2874
|
+
!sourceActivityId &&
|
|
2875
|
+
sources.length === 0
|
|
2876
|
+
"
|
|
2877
|
+
class="preview-empty"
|
|
2878
|
+
>
|
|
1764
2879
|
No previewable documents found
|
|
1765
2880
|
</div>
|
|
1766
2881
|
</div>
|
|
1767
2882
|
</div>
|
|
1768
|
-
`, isInline: true, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}\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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2883
|
+
`, isInline: true, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.design-info{padding:1rem;min-height:auto}.design-section{margin-bottom:.75rem}.design-label{font-size:.7rem;text-transform:uppercase;color:#868e96;font-weight:600;letter-spacing:.05em}.design-value{font-family:monospace;font-size:.85rem;color:#212529;margin-bottom:.25rem}.design-mapping{margin-top:.25rem}.design-entry{font-family:monospace;font-size:.8rem;color:#495057;padding:.15rem 0}.design-scope{color:#0d6efd}.design-field{color:#198754}.design-entry i{font-size:.7rem;margin:0 .25rem;color:#adb5bd}.design-unconfigured{color:#6c757d;font-style:italic;font-size:.85rem}\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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1769
2884
|
}
|
|
1770
2885
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, decorators: [{
|
|
1771
2886
|
type: Component,
|
|
1772
2887
|
args: [{ standalone: true, imports: [CommonModule], selector: 'epistola-document-preview-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1773
|
-
|
|
2888
|
+
<!-- Design-time view: show configuration summary when no runtime context -->
|
|
2889
|
+
<div *ngIf="designMode" class="epistola-preview-panel">
|
|
2890
|
+
<div class="preview-header">
|
|
2891
|
+
<span>{{ label || 'Document Preview' }}</span>
|
|
2892
|
+
</div>
|
|
2893
|
+
<div class="preview-body design-info">
|
|
2894
|
+
<div class="design-section" *ngIf="sourceActivityId">
|
|
2895
|
+
<div class="design-label">Process</div>
|
|
2896
|
+
<div class="design-value">{{ processDefinitionKey || '(any)' }}</div>
|
|
2897
|
+
<div class="design-label">Activity</div>
|
|
2898
|
+
<div class="design-value">{{ sourceActivityId }}</div>
|
|
2899
|
+
</div>
|
|
2900
|
+
<div class="design-section" *ngIf="overrideMapping">
|
|
2901
|
+
<div class="design-label">Input Overrides</div>
|
|
2902
|
+
<div *ngFor="let scope of overrideMappingScopes" class="design-mapping">
|
|
2903
|
+
<div *ngFor="let entry of overrideMappingEntries(scope)" class="design-entry">
|
|
2904
|
+
<span class="design-scope">{{ scope }}</span
|
|
2905
|
+
>.{{ entry.path }}
|
|
2906
|
+
<i class="mdi mdi-arrow-left"></i>
|
|
2907
|
+
<span class="design-field">{{ entry.field }}</span>
|
|
2908
|
+
</div>
|
|
2909
|
+
</div>
|
|
2910
|
+
</div>
|
|
2911
|
+
<div *ngIf="!sourceActivityId" class="design-unconfigured">
|
|
2912
|
+
Auto-discover mode (no process link configured)
|
|
2913
|
+
</div>
|
|
2914
|
+
</div>
|
|
2915
|
+
</div>
|
|
2916
|
+
|
|
2917
|
+
<!-- Runtime view: actual preview -->
|
|
2918
|
+
<div *ngIf="!designMode" class="epistola-preview-panel">
|
|
1774
2919
|
<div class="preview-header">
|
|
1775
2920
|
<span>{{ label || 'Document Preview' }}</span>
|
|
1776
2921
|
<div class="preview-controls">
|
|
1777
2922
|
<select
|
|
1778
|
-
*ngIf="sources.length > 1"
|
|
2923
|
+
*ngIf="!sourceActivityId && sources.length > 1"
|
|
1779
2924
|
class="preview-select"
|
|
1780
2925
|
[value]="selectedIndex"
|
|
1781
2926
|
(change)="onSourceChange($event)"
|
|
@@ -1784,19 +2929,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1784
2929
|
{{ source.templateName }} ({{ source.activityId }})
|
|
1785
2930
|
</option>
|
|
1786
2931
|
</select>
|
|
1787
|
-
<button
|
|
2932
|
+
<button
|
|
2933
|
+
type="button"
|
|
2934
|
+
class="preview-refresh"
|
|
2935
|
+
[disabled]="loading || discovering"
|
|
2936
|
+
(click)="refresh()"
|
|
2937
|
+
>
|
|
1788
2938
|
<i class="mdi mdi-refresh mr-1"></i>
|
|
1789
2939
|
{{ loading ? 'Generating...' : 'Refresh' }}
|
|
1790
2940
|
</button>
|
|
1791
2941
|
</div>
|
|
1792
2942
|
</div>
|
|
1793
2943
|
<div class="preview-body">
|
|
1794
|
-
<div *ngIf="discovering" class="preview-loading">
|
|
1795
|
-
|
|
1796
|
-
</div>
|
|
1797
|
-
<div *ngIf="loading && !discovering" class="preview-loading">
|
|
1798
|
-
Generating preview...
|
|
1799
|
-
</div>
|
|
2944
|
+
<div *ngIf="discovering" class="preview-loading">Discovering documents...</div>
|
|
2945
|
+
<div *ngIf="loading && !discovering" class="preview-loading">Generating preview...</div>
|
|
1800
2946
|
<div *ngIf="error && !loading && !discovering" class="preview-unavailable">
|
|
1801
2947
|
<i class="mdi mdi-information-outline"></i>
|
|
1802
2948
|
Preview is niet beschikbaar — niet alle gegevens zijn al ingevuld.
|
|
@@ -1809,13 +2955,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1809
2955
|
>
|
|
1810
2956
|
PDF preview is not supported in this browser.
|
|
1811
2957
|
</object>
|
|
1812
|
-
<div
|
|
2958
|
+
<div
|
|
2959
|
+
*ngIf="
|
|
2960
|
+
!previewUrl &&
|
|
2961
|
+
!loading &&
|
|
2962
|
+
!discovering &&
|
|
2963
|
+
!error &&
|
|
2964
|
+
!sourceActivityId &&
|
|
2965
|
+
sources.length === 0
|
|
2966
|
+
"
|
|
2967
|
+
class="preview-empty"
|
|
2968
|
+
>
|
|
1813
2969
|
No previewable documents found
|
|
1814
2970
|
</div>
|
|
1815
2971
|
</div>
|
|
1816
2972
|
</div>
|
|
1817
|
-
`, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}\n"] }]
|
|
1818
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i1.HttpClient }, { type: i4
|
|
2973
|
+
`, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.design-info{padding:1rem;min-height:auto}.design-section{margin-bottom:.75rem}.design-label{font-size:.7rem;text-transform:uppercase;color:#868e96;font-weight:600;letter-spacing:.05em}.design-value{font-family:monospace;font-size:.85rem;color:#212529;margin-bottom:.25rem}.design-mapping{margin-top:.25rem}.design-entry{font-family:monospace;font-size:.8rem;color:#495057;padding:.15rem 0}.design-scope{color:#0d6efd}.design-field{color:#198754}.design-entry i{font-size:.7rem;margin:0 .25rem;color:#adb5bd}.design-unconfigured{color:#6c757d;font-style:italic;font-size:.85rem}\n"] }]
|
|
2974
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i1.HttpClient }, { type: i4.DomSanitizer }, { type: i2.ConfigService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
1819
2975
|
type: Input
|
|
1820
2976
|
}], valueChange: [{
|
|
1821
2977
|
type: Output
|
|
@@ -1823,8 +2979,229 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1823
2979
|
type: Input
|
|
1824
2980
|
}], label: [{
|
|
1825
2981
|
type: Input
|
|
2982
|
+
}], processDefinitionKey: [{
|
|
2983
|
+
type: Input
|
|
2984
|
+
}], sourceActivityId: [{
|
|
2985
|
+
type: Input
|
|
2986
|
+
}], overrideMapping: [{
|
|
2987
|
+
type: Input
|
|
1826
2988
|
}] } });
|
|
1827
2989
|
|
|
2990
|
+
class EpistolaAdminPageComponent {
|
|
2991
|
+
adminService;
|
|
2992
|
+
route;
|
|
2993
|
+
router;
|
|
2994
|
+
cards = [];
|
|
2995
|
+
selectedCard = null;
|
|
2996
|
+
activeTab = 'actions';
|
|
2997
|
+
loading = false;
|
|
2998
|
+
pluginVersion = null;
|
|
2999
|
+
connectionStatuses = [];
|
|
3000
|
+
usageEntries = [];
|
|
3001
|
+
pendingJobs = [];
|
|
3002
|
+
connectionLoaded = false;
|
|
3003
|
+
usageLoaded = false;
|
|
3004
|
+
pendingLoaded = false;
|
|
3005
|
+
deepLinkConfigId = null;
|
|
3006
|
+
constructor(adminService, route, router) {
|
|
3007
|
+
this.adminService = adminService;
|
|
3008
|
+
this.route = route;
|
|
3009
|
+
this.router = router;
|
|
3010
|
+
}
|
|
3011
|
+
ngOnInit() {
|
|
3012
|
+
this.deepLinkConfigId = this.route.snapshot.queryParamMap.get('configurationId');
|
|
3013
|
+
const tab = this.route.snapshot.queryParamMap.get('tab');
|
|
3014
|
+
if (tab === 'pending' || tab === 'actions') {
|
|
3015
|
+
this.activeTab = tab;
|
|
3016
|
+
}
|
|
3017
|
+
this.loadData();
|
|
3018
|
+
this.loadPluginVersion();
|
|
3019
|
+
}
|
|
3020
|
+
selectConfiguration(card) {
|
|
3021
|
+
this.selectedCard = card;
|
|
3022
|
+
this.activeTab = 'actions';
|
|
3023
|
+
this.updateUrl(card.configurationId, this.activeTab);
|
|
3024
|
+
}
|
|
3025
|
+
backToOverview() {
|
|
3026
|
+
this.selectedCard = null;
|
|
3027
|
+
this.activeTab = 'actions';
|
|
3028
|
+
this.updateUrl(null, null);
|
|
3029
|
+
}
|
|
3030
|
+
setActiveTab(tab) {
|
|
3031
|
+
this.activeTab = tab;
|
|
3032
|
+
this.updateUrl(this.selectedCard?.configurationId ?? null, tab);
|
|
3033
|
+
}
|
|
3034
|
+
refresh() {
|
|
3035
|
+
this.selectedCard = null;
|
|
3036
|
+
this.loadData();
|
|
3037
|
+
}
|
|
3038
|
+
exportProcessLink(entry) {
|
|
3039
|
+
this.adminService.exportProcessLink(entry.processLinkId).subscribe({
|
|
3040
|
+
next: (blob) => {
|
|
3041
|
+
const url = URL.createObjectURL(blob);
|
|
3042
|
+
const anchor = document.createElement('a');
|
|
3043
|
+
anchor.href = url;
|
|
3044
|
+
anchor.download = `${entry.activityId}.process-link.json`;
|
|
3045
|
+
anchor.click();
|
|
3046
|
+
URL.revokeObjectURL(url);
|
|
3047
|
+
},
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
updateUrl(configurationId, tab) {
|
|
3051
|
+
this.router.navigate([], {
|
|
3052
|
+
relativeTo: this.route,
|
|
3053
|
+
queryParams: {
|
|
3054
|
+
configurationId: configurationId ?? null,
|
|
3055
|
+
tab: tab ?? null,
|
|
3056
|
+
},
|
|
3057
|
+
replaceUrl: true,
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
loadData() {
|
|
3061
|
+
this.loading = true;
|
|
3062
|
+
this.connectionLoaded = false;
|
|
3063
|
+
this.usageLoaded = false;
|
|
3064
|
+
this.pendingLoaded = false;
|
|
3065
|
+
this.adminService.getConnectionStatus().subscribe({
|
|
3066
|
+
next: (statuses) => {
|
|
3067
|
+
this.connectionStatuses = statuses;
|
|
3068
|
+
this.connectionLoaded = true;
|
|
3069
|
+
this.tryBuildCards();
|
|
3070
|
+
},
|
|
3071
|
+
error: () => {
|
|
3072
|
+
this.connectionStatuses = [];
|
|
3073
|
+
this.connectionLoaded = true;
|
|
3074
|
+
this.tryBuildCards();
|
|
3075
|
+
},
|
|
3076
|
+
});
|
|
3077
|
+
this.adminService.getPluginUsage().subscribe({
|
|
3078
|
+
next: (entries) => {
|
|
3079
|
+
this.usageEntries = entries;
|
|
3080
|
+
this.usageLoaded = true;
|
|
3081
|
+
this.tryBuildCards();
|
|
3082
|
+
},
|
|
3083
|
+
error: () => {
|
|
3084
|
+
this.usageEntries = [];
|
|
3085
|
+
this.usageLoaded = true;
|
|
3086
|
+
this.tryBuildCards();
|
|
3087
|
+
},
|
|
3088
|
+
});
|
|
3089
|
+
this.adminService.getPendingJobs().subscribe({
|
|
3090
|
+
next: (jobs) => {
|
|
3091
|
+
this.pendingJobs = jobs;
|
|
3092
|
+
this.pendingLoaded = true;
|
|
3093
|
+
this.tryBuildCards();
|
|
3094
|
+
},
|
|
3095
|
+
error: () => {
|
|
3096
|
+
this.pendingJobs = [];
|
|
3097
|
+
this.pendingLoaded = true;
|
|
3098
|
+
this.tryBuildCards();
|
|
3099
|
+
},
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
tryBuildCards() {
|
|
3103
|
+
if (!this.connectionLoaded || !this.usageLoaded || !this.pendingLoaded) {
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
this.cards = this.connectionStatuses.map((status) => {
|
|
3107
|
+
const entries = this.usageEntries.filter((e) => e.configurationId === status.configurationId);
|
|
3108
|
+
const jobs = this.pendingJobs.filter((j) => j.tenantId === status.tenantId);
|
|
3109
|
+
const problemCount = entries.reduce((sum, e) => sum + e.problems.length, 0);
|
|
3110
|
+
return {
|
|
3111
|
+
configurationId: status.configurationId,
|
|
3112
|
+
configurationTitle: status.configurationTitle,
|
|
3113
|
+
tenantId: status.tenantId,
|
|
3114
|
+
reachable: status.reachable,
|
|
3115
|
+
latencyMs: status.latencyMs,
|
|
3116
|
+
errorMessage: status.errorMessage,
|
|
3117
|
+
serverVersion: status.serverVersion,
|
|
3118
|
+
usageCount: entries.length,
|
|
3119
|
+
problemCount,
|
|
3120
|
+
usageEntries: entries,
|
|
3121
|
+
pendingJobs: jobs,
|
|
3122
|
+
};
|
|
3123
|
+
});
|
|
3124
|
+
// Restore deep link selection
|
|
3125
|
+
if (this.deepLinkConfigId) {
|
|
3126
|
+
const match = this.cards.find((c) => c.configurationId === this.deepLinkConfigId);
|
|
3127
|
+
if (match) {
|
|
3128
|
+
this.selectedCard = match;
|
|
3129
|
+
}
|
|
3130
|
+
this.deepLinkConfigId = null;
|
|
3131
|
+
}
|
|
3132
|
+
this.loading = false;
|
|
3133
|
+
}
|
|
3134
|
+
loadPluginVersion() {
|
|
3135
|
+
this.adminService.getVersions().subscribe({
|
|
3136
|
+
next: (info) => {
|
|
3137
|
+
this.pluginVersion = info.pluginVersion;
|
|
3138
|
+
},
|
|
3139
|
+
});
|
|
3140
|
+
}
|
|
3141
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, deps: [{ token: EpistolaAdminService }, { token: i2$4.ActivatedRoute }, { token: i2$4.Router }], target: i0.ɵɵFactoryTarget.Component });
|
|
3142
|
+
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 ← {{ '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 ⤓\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$4.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"] }] });
|
|
3143
|
+
}
|
|
3144
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, decorators: [{
|
|
3145
|
+
type: Component,
|
|
3146
|
+
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 ← {{ '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 ⤓\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"] }]
|
|
3147
|
+
}], ctorParameters: () => [{ type: EpistolaAdminService }, { type: i2$4.ActivatedRoute }, { type: i2$4.Router }] });
|
|
3148
|
+
|
|
3149
|
+
function isRuntimeWindow(value) {
|
|
3150
|
+
return typeof value === 'object' && value !== null;
|
|
3151
|
+
}
|
|
3152
|
+
/**
|
|
3153
|
+
* Reads the runtime feature flag that decides whether the Epistola plugin
|
|
3154
|
+
* surfaces (admin menu, /epistola route, plugin specification, Formio
|
|
3155
|
+
* components) should activate in the host Valtimo app.
|
|
3156
|
+
*
|
|
3157
|
+
* The flag is sourced from `window['env']['epistolaEnabled']`, populated at
|
|
3158
|
+
* container start by `envsubst` against `assets/config.template.js` (the
|
|
3159
|
+
* standard Valtimo runtime-config pattern). Defaults to enabled — only the
|
|
3160
|
+
* literal `false` or string `'false'` disables the plugin, matching the
|
|
3161
|
+
* backend's `epistola.enabled` `matchIfMissing = true` semantics.
|
|
3162
|
+
*
|
|
3163
|
+
* Exposed as a runtime helper rather than evaluated directly in `@NgModule`
|
|
3164
|
+
* decorator metadata because Angular's AOT compiler cannot statically resolve
|
|
3165
|
+
* `window` accesses (NG1010). Read from runtime code such as specification
|
|
3166
|
+
* property getters, route guards, or the environment initializer instead.
|
|
3167
|
+
*/
|
|
3168
|
+
function isEpistolaEnabled() {
|
|
3169
|
+
const runtimeWindow = Reflect.get(globalThis, 'window');
|
|
3170
|
+
if (!runtimeWindow)
|
|
3171
|
+
return true;
|
|
3172
|
+
if (!isRuntimeWindow(runtimeWindow))
|
|
3173
|
+
return true;
|
|
3174
|
+
const flag = runtimeWindow.env ? runtimeWindow.env.epistolaEnabled : undefined;
|
|
3175
|
+
return flag !== false && flag !== 'false';
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
const epistolaEnabledGuard = () => {
|
|
3179
|
+
if (isEpistolaEnabled())
|
|
3180
|
+
return true;
|
|
3181
|
+
return inject(Router).parseUrl('/');
|
|
3182
|
+
};
|
|
3183
|
+
|
|
3184
|
+
const routes = [
|
|
3185
|
+
{
|
|
3186
|
+
path: 'epistola',
|
|
3187
|
+
component: EpistolaAdminPageComponent,
|
|
3188
|
+
canActivate: [epistolaEnabledGuard, AuthGuardService],
|
|
3189
|
+
data: { title: 'Epistola', roles: ['ROLE_ADMIN'] },
|
|
3190
|
+
},
|
|
3191
|
+
];
|
|
3192
|
+
class EpistolaAdminRoutingModule {
|
|
3193
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
3194
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [i2$4.RouterModule], exports: [RouterModule] });
|
|
3195
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [RouterModule.forChild(routes), RouterModule] });
|
|
3196
|
+
}
|
|
3197
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, decorators: [{
|
|
3198
|
+
type: NgModule,
|
|
3199
|
+
args: [{
|
|
3200
|
+
imports: [RouterModule.forChild(routes)],
|
|
3201
|
+
exports: [RouterModule],
|
|
3202
|
+
}]
|
|
3203
|
+
}] });
|
|
3204
|
+
|
|
1828
3205
|
const EPISTOLA_DOWNLOAD_OPTIONS = {
|
|
1829
3206
|
type: 'epistola-download',
|
|
1830
3207
|
selector: 'epistola-download-button',
|
|
@@ -1877,11 +3254,541 @@ const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
|
|
|
1877
3254
|
group: 'basic',
|
|
1878
3255
|
icon: 'file-pdf-o',
|
|
1879
3256
|
emptyValue: null,
|
|
1880
|
-
fieldOptions: ['label'],
|
|
3257
|
+
fieldOptions: ['label', 'processDefinitionKey', 'sourceActivityId', 'overrideMapping'],
|
|
3258
|
+
editForm: () => ({
|
|
3259
|
+
components: [
|
|
3260
|
+
{
|
|
3261
|
+
type: 'epistola-process-link-selector',
|
|
3262
|
+
key: 'processLinkSelection',
|
|
3263
|
+
label: 'Process Link',
|
|
3264
|
+
weight: 10,
|
|
3265
|
+
validate: { required: true },
|
|
3266
|
+
},
|
|
3267
|
+
{
|
|
3268
|
+
type: 'epistola-override-builder',
|
|
3269
|
+
key: 'overrideMapping',
|
|
3270
|
+
label: 'Input Overrides',
|
|
3271
|
+
weight: 20,
|
|
3272
|
+
},
|
|
3273
|
+
],
|
|
3274
|
+
}),
|
|
1881
3275
|
};
|
|
1882
3276
|
function registerEpistolaDocumentPreviewComponent(injector) {
|
|
1883
|
-
if (
|
|
1884
|
-
|
|
3277
|
+
if (customElements.get(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS.selector)) {
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
// Register the base component (Angular element + Formio component class)
|
|
3281
|
+
registerCustomFormioComponent(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EpistolaDocumentPreviewComponent, injector);
|
|
3282
|
+
// Get the Formio Components registry and the registered base class
|
|
3283
|
+
const Formio = window.Formio;
|
|
3284
|
+
if (!Formio?.Components)
|
|
3285
|
+
return;
|
|
3286
|
+
const BasePreviewComponent = Formio.Components.components[EPISTOLA_DOCUMENT_PREVIEW_OPTIONS.type];
|
|
3287
|
+
if (!BasePreviewComponent)
|
|
3288
|
+
return;
|
|
3289
|
+
// Extend the base class to listen for form data changes and compute input overrides
|
|
3290
|
+
class PreviewWithOverrides extends BasePreviewComponent {
|
|
3291
|
+
_debounceTimer = null;
|
|
3292
|
+
_changeListenerAttached = false;
|
|
3293
|
+
attach(element) {
|
|
3294
|
+
// Bidirectional sync between processLinkSelection object and separate properties.
|
|
3295
|
+
// The editForm uses processLinkSelection (single field), while the component
|
|
3296
|
+
// config and Angular inputs use processDefinitionKey + sourceActivityId.
|
|
3297
|
+
if (this.component?.processLinkSelection) {
|
|
3298
|
+
const sel = this.component.processLinkSelection;
|
|
3299
|
+
this.component.processDefinitionKey = sel.processDefinitionKey || '';
|
|
3300
|
+
this.component.sourceActivityId = sel.sourceActivityId || '';
|
|
3301
|
+
}
|
|
3302
|
+
else if (this.component?.processDefinitionKey && this.component?.sourceActivityId) {
|
|
3303
|
+
this.component.processLinkSelection = {
|
|
3304
|
+
processDefinitionKey: this.component.processDefinitionKey,
|
|
3305
|
+
sourceActivityId: this.component.sourceActivityId,
|
|
3306
|
+
};
|
|
3307
|
+
}
|
|
3308
|
+
const result = super.attach(element);
|
|
3309
|
+
if (this._customAngularElement) {
|
|
3310
|
+
this._customAngularElement['processDefinitionKey'] =
|
|
3311
|
+
this.component.processDefinitionKey || '';
|
|
3312
|
+
this._customAngularElement['sourceActivityId'] = this.component.sourceActivityId || '';
|
|
3313
|
+
}
|
|
3314
|
+
// Listen to form changes and compute input overrides from the mapping
|
|
3315
|
+
if (this.root && this.component?.overrideMapping && !this._changeListenerAttached) {
|
|
3316
|
+
this._changeListenerAttached = true;
|
|
3317
|
+
this.root.on('change', () => {
|
|
3318
|
+
this._computeAndSetOverrides();
|
|
3319
|
+
});
|
|
3320
|
+
// Compute initial value
|
|
3321
|
+
this._computeAndSetOverrides();
|
|
3322
|
+
}
|
|
3323
|
+
return result;
|
|
3324
|
+
}
|
|
3325
|
+
_computeAndSetOverrides() {
|
|
3326
|
+
if (this._debounceTimer) {
|
|
3327
|
+
clearTimeout(this._debounceTimer);
|
|
3328
|
+
}
|
|
3329
|
+
this._debounceTimer = setTimeout(() => {
|
|
3330
|
+
const mapping = this.component?.overrideMapping;
|
|
3331
|
+
const formData = this.root?.data;
|
|
3332
|
+
if (mapping && formData) {
|
|
3333
|
+
const overrides = computeInputOverrides(mapping, formData);
|
|
3334
|
+
if (Object.keys(overrides).length > 0) {
|
|
3335
|
+
this.setValue(overrides);
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
}, 1500);
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
// Re-register with the extended class
|
|
3342
|
+
Formio.Components.setComponent(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS.type, PreviewWithOverrides);
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
const FORM_REF_PREFIX = 'form:';
|
|
3346
|
+
class EpistolaOverrideBuilderComponent {
|
|
3347
|
+
cdr;
|
|
3348
|
+
value;
|
|
3349
|
+
valueChange = new EventEmitter();
|
|
3350
|
+
disabled = false;
|
|
3351
|
+
label = 'Input Overrides';
|
|
3352
|
+
availableFields = [];
|
|
3353
|
+
rows = [];
|
|
3354
|
+
advancedMode = false;
|
|
3355
|
+
jsonText = '';
|
|
3356
|
+
jsonError = null;
|
|
3357
|
+
initialized = false;
|
|
3358
|
+
constructor(cdr) {
|
|
3359
|
+
this.cdr = cdr;
|
|
3360
|
+
}
|
|
3361
|
+
ngOnChanges() {
|
|
3362
|
+
if (!this.initialized && this.value) {
|
|
3363
|
+
this.initialized = true;
|
|
3364
|
+
this.rows = this.mappingToRows(this.value);
|
|
3365
|
+
this.jsonText = JSON.stringify(this.value, null, 2);
|
|
3366
|
+
}
|
|
3367
|
+
this.cdr.markForCheck();
|
|
3368
|
+
}
|
|
3369
|
+
toggleMode() {
|
|
3370
|
+
this.advancedMode = !this.advancedMode;
|
|
3371
|
+
if (this.advancedMode) {
|
|
3372
|
+
const mapping = this.rowsToMapping();
|
|
3373
|
+
this.jsonText = Object.keys(mapping).length > 0 ? JSON.stringify(mapping, null, 2) : '';
|
|
3374
|
+
this.jsonError = null;
|
|
3375
|
+
}
|
|
3376
|
+
else {
|
|
3377
|
+
try {
|
|
3378
|
+
const parsed = this.jsonText.trim() ? JSON.parse(this.jsonText) : {};
|
|
3379
|
+
this.rows = this.mappingToRows(parsed);
|
|
3380
|
+
this.jsonError = null;
|
|
3381
|
+
}
|
|
3382
|
+
catch {
|
|
3383
|
+
// Keep current rows if JSON is invalid
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
addRow() {
|
|
3388
|
+
this.rows.push({ scope: 'doc', inputPath: '', formFieldKey: '' });
|
|
3389
|
+
}
|
|
3390
|
+
removeRow(index) {
|
|
3391
|
+
this.rows.splice(index, 1);
|
|
3392
|
+
this.emitChange();
|
|
3393
|
+
}
|
|
3394
|
+
emitChange() {
|
|
3395
|
+
const mapping = this.rowsToMapping();
|
|
3396
|
+
this.value = Object.keys(mapping).length > 0 ? mapping : null;
|
|
3397
|
+
this.valueChange.emit(this.value);
|
|
3398
|
+
}
|
|
3399
|
+
onJsonChange(text) {
|
|
3400
|
+
this.jsonText = text;
|
|
3401
|
+
if (!text.trim()) {
|
|
3402
|
+
this.jsonError = null;
|
|
3403
|
+
this.value = null;
|
|
3404
|
+
this.valueChange.emit(null);
|
|
3405
|
+
return;
|
|
3406
|
+
}
|
|
3407
|
+
try {
|
|
3408
|
+
const parsed = JSON.parse(text);
|
|
3409
|
+
this.jsonError = null;
|
|
3410
|
+
this.value = parsed;
|
|
3411
|
+
this.valueChange.emit(parsed);
|
|
3412
|
+
}
|
|
3413
|
+
catch (e) {
|
|
3414
|
+
this.jsonError = 'Invalid JSON';
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
rowsToMapping() {
|
|
3418
|
+
const mapping = {};
|
|
3419
|
+
for (const row of this.rows) {
|
|
3420
|
+
if (row.inputPath && row.formFieldKey) {
|
|
3421
|
+
if (!mapping[row.scope]) {
|
|
3422
|
+
mapping[row.scope] = {};
|
|
3423
|
+
}
|
|
3424
|
+
mapping[row.scope][row.inputPath] = FORM_REF_PREFIX + row.formFieldKey;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
return mapping;
|
|
3428
|
+
}
|
|
3429
|
+
mappingToRows(mapping) {
|
|
3430
|
+
const rows = [];
|
|
3431
|
+
for (const [scope, fields] of Object.entries(mapping)) {
|
|
3432
|
+
if (scope === 'doc' || scope === 'pv') {
|
|
3433
|
+
for (const [path, ref] of Object.entries(fields)) {
|
|
3434
|
+
const formFieldKey = String(ref).startsWith(FORM_REF_PREFIX)
|
|
3435
|
+
? String(ref).substring(FORM_REF_PREFIX.length)
|
|
3436
|
+
: String(ref);
|
|
3437
|
+
rows.push({ scope, inputPath: path, formFieldKey });
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
return rows;
|
|
3442
|
+
}
|
|
3443
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaOverrideBuilderComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3444
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaOverrideBuilderComponent, isStandalone: true, selector: "epistola-override-builder-component", inputs: { value: "value", disabled: "disabled", label: "label", availableFields: "availableFields" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
3445
|
+
<div class="override-builder">
|
|
3446
|
+
<div class="builder-header">
|
|
3447
|
+
<span class="builder-label">{{ label || 'Input Overrides' }}</span>
|
|
3448
|
+
<button type="button" class="mode-toggle" (click)="toggleMode()">
|
|
3449
|
+
{{ advancedMode ? 'Simple' : 'Advanced' }}
|
|
3450
|
+
</button>
|
|
3451
|
+
</div>
|
|
3452
|
+
|
|
3453
|
+
<!-- Simple mode: table -->
|
|
3454
|
+
<div *ngIf="!advancedMode" class="builder-table">
|
|
3455
|
+
<div *ngIf="rows.length > 0" class="table-header">
|
|
3456
|
+
<span class="col-scope">Scope</span>
|
|
3457
|
+
<span class="col-path">Input Path</span>
|
|
3458
|
+
<span class="col-field">Form Field</span>
|
|
3459
|
+
<span class="col-action"></span>
|
|
3460
|
+
</div>
|
|
3461
|
+
<div *ngFor="let row of rows; let i = index" class="table-row">
|
|
3462
|
+
<select class="col-scope" [(ngModel)]="row.scope" (ngModelChange)="emitChange()">
|
|
3463
|
+
<option value="doc">doc</option>
|
|
3464
|
+
<option value="pv">pv</option>
|
|
3465
|
+
</select>
|
|
3466
|
+
<input
|
|
3467
|
+
class="col-path"
|
|
3468
|
+
type="text"
|
|
3469
|
+
[(ngModel)]="row.inputPath"
|
|
3470
|
+
(ngModelChange)="emitChange()"
|
|
3471
|
+
placeholder="e.g. beslissing.tekst"
|
|
3472
|
+
/>
|
|
3473
|
+
<!-- Dropdown when form fields are available, text input as fallback -->
|
|
3474
|
+
<select
|
|
3475
|
+
*ngIf="availableFields.length > 0"
|
|
3476
|
+
class="col-field"
|
|
3477
|
+
[(ngModel)]="row.formFieldKey"
|
|
3478
|
+
(ngModelChange)="emitChange()"
|
|
3479
|
+
>
|
|
3480
|
+
<option value="">-- Select field --</option>
|
|
3481
|
+
<option *ngFor="let field of availableFields" [value]="field.key">
|
|
3482
|
+
{{ field.label }}
|
|
3483
|
+
</option>
|
|
3484
|
+
</select>
|
|
3485
|
+
<input
|
|
3486
|
+
*ngIf="availableFields.length === 0"
|
|
3487
|
+
class="col-field"
|
|
3488
|
+
type="text"
|
|
3489
|
+
[(ngModel)]="row.formFieldKey"
|
|
3490
|
+
(ngModelChange)="emitChange()"
|
|
3491
|
+
placeholder="form field key"
|
|
3492
|
+
/>
|
|
3493
|
+
<button type="button" class="col-action remove-btn" (click)="removeRow(i)">
|
|
3494
|
+
<i class="mdi mdi-close"></i>
|
|
3495
|
+
</button>
|
|
3496
|
+
</div>
|
|
3497
|
+
<button type="button" class="add-btn" (click)="addRow()">
|
|
3498
|
+
<i class="mdi mdi-plus mr-1"></i> Add override
|
|
3499
|
+
</button>
|
|
3500
|
+
</div>
|
|
3501
|
+
|
|
3502
|
+
<!-- Advanced mode: JSON editor -->
|
|
3503
|
+
<div *ngIf="advancedMode" class="builder-advanced">
|
|
3504
|
+
<textarea
|
|
3505
|
+
class="json-editor"
|
|
3506
|
+
[ngModel]="jsonText"
|
|
3507
|
+
(ngModelChange)="onJsonChange($event)"
|
|
3508
|
+
placeholder='{ "pv": { "motivation": "form:pv:motivation" } }'
|
|
3509
|
+
rows="6"
|
|
3510
|
+
></textarea>
|
|
3511
|
+
<div *ngIf="jsonError" class="json-error">{{ jsonError }}</div>
|
|
3512
|
+
</div>
|
|
3513
|
+
</div>
|
|
3514
|
+
`, isInline: true, styles: [".override-builder{border:1px solid #dee2e6;border-radius:4px;padding:.75rem;background:#f8f9fa}.builder-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem}.builder-label{font-weight:600;font-size:.85rem;color:#495057}.mode-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.15rem .5rem;font-size:.75rem;cursor:pointer}.mode-toggle:hover{background:#e9ecef}.table-header{display:flex;gap:.5rem;padding:.25rem 0;font-size:.75rem;color:#6c757d;font-weight:600}.table-row{display:flex;gap:.5rem;margin-bottom:.25rem;align-items:center}.col-scope{width:70px;flex-shrink:0}.col-path,.col-field{flex:1;min-width:0}.col-action{width:30px;flex-shrink:0}.table-row select,.table-row input{border:1px solid #ced4da;border-radius:4px;padding:.25rem .4rem;font-size:.8rem;background:#fff}.remove-btn{background:none;border:none;color:#dc3545;cursor:pointer;padding:.25rem;font-size:.9rem}.remove-btn:hover{color:#a71d2a}.add-btn{background:none;border:1px dashed #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;margin-top:.25rem;display:flex;align-items:center}.add-btn:hover{background:#e9ecef;border-color:#495057}.json-editor{width:100%;border:1px solid #ced4da;border-radius:4px;padding:.5rem;font-family:monospace;font-size:.8rem;resize:vertical;background:#fff}.json-error{color:#dc3545;font-size:.75rem;margin-top:.25rem}\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: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.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: i2$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3515
|
+
}
|
|
3516
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaOverrideBuilderComponent, decorators: [{
|
|
3517
|
+
type: Component,
|
|
3518
|
+
args: [{ standalone: true, imports: [CommonModule, FormsModule], selector: 'epistola-override-builder-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3519
|
+
<div class="override-builder">
|
|
3520
|
+
<div class="builder-header">
|
|
3521
|
+
<span class="builder-label">{{ label || 'Input Overrides' }}</span>
|
|
3522
|
+
<button type="button" class="mode-toggle" (click)="toggleMode()">
|
|
3523
|
+
{{ advancedMode ? 'Simple' : 'Advanced' }}
|
|
3524
|
+
</button>
|
|
3525
|
+
</div>
|
|
3526
|
+
|
|
3527
|
+
<!-- Simple mode: table -->
|
|
3528
|
+
<div *ngIf="!advancedMode" class="builder-table">
|
|
3529
|
+
<div *ngIf="rows.length > 0" class="table-header">
|
|
3530
|
+
<span class="col-scope">Scope</span>
|
|
3531
|
+
<span class="col-path">Input Path</span>
|
|
3532
|
+
<span class="col-field">Form Field</span>
|
|
3533
|
+
<span class="col-action"></span>
|
|
3534
|
+
</div>
|
|
3535
|
+
<div *ngFor="let row of rows; let i = index" class="table-row">
|
|
3536
|
+
<select class="col-scope" [(ngModel)]="row.scope" (ngModelChange)="emitChange()">
|
|
3537
|
+
<option value="doc">doc</option>
|
|
3538
|
+
<option value="pv">pv</option>
|
|
3539
|
+
</select>
|
|
3540
|
+
<input
|
|
3541
|
+
class="col-path"
|
|
3542
|
+
type="text"
|
|
3543
|
+
[(ngModel)]="row.inputPath"
|
|
3544
|
+
(ngModelChange)="emitChange()"
|
|
3545
|
+
placeholder="e.g. beslissing.tekst"
|
|
3546
|
+
/>
|
|
3547
|
+
<!-- Dropdown when form fields are available, text input as fallback -->
|
|
3548
|
+
<select
|
|
3549
|
+
*ngIf="availableFields.length > 0"
|
|
3550
|
+
class="col-field"
|
|
3551
|
+
[(ngModel)]="row.formFieldKey"
|
|
3552
|
+
(ngModelChange)="emitChange()"
|
|
3553
|
+
>
|
|
3554
|
+
<option value="">-- Select field --</option>
|
|
3555
|
+
<option *ngFor="let field of availableFields" [value]="field.key">
|
|
3556
|
+
{{ field.label }}
|
|
3557
|
+
</option>
|
|
3558
|
+
</select>
|
|
3559
|
+
<input
|
|
3560
|
+
*ngIf="availableFields.length === 0"
|
|
3561
|
+
class="col-field"
|
|
3562
|
+
type="text"
|
|
3563
|
+
[(ngModel)]="row.formFieldKey"
|
|
3564
|
+
(ngModelChange)="emitChange()"
|
|
3565
|
+
placeholder="form field key"
|
|
3566
|
+
/>
|
|
3567
|
+
<button type="button" class="col-action remove-btn" (click)="removeRow(i)">
|
|
3568
|
+
<i class="mdi mdi-close"></i>
|
|
3569
|
+
</button>
|
|
3570
|
+
</div>
|
|
3571
|
+
<button type="button" class="add-btn" (click)="addRow()">
|
|
3572
|
+
<i class="mdi mdi-plus mr-1"></i> Add override
|
|
3573
|
+
</button>
|
|
3574
|
+
</div>
|
|
3575
|
+
|
|
3576
|
+
<!-- Advanced mode: JSON editor -->
|
|
3577
|
+
<div *ngIf="advancedMode" class="builder-advanced">
|
|
3578
|
+
<textarea
|
|
3579
|
+
class="json-editor"
|
|
3580
|
+
[ngModel]="jsonText"
|
|
3581
|
+
(ngModelChange)="onJsonChange($event)"
|
|
3582
|
+
placeholder='{ "pv": { "motivation": "form:pv:motivation" } }'
|
|
3583
|
+
rows="6"
|
|
3584
|
+
></textarea>
|
|
3585
|
+
<div *ngIf="jsonError" class="json-error">{{ jsonError }}</div>
|
|
3586
|
+
</div>
|
|
3587
|
+
</div>
|
|
3588
|
+
`, styles: [".override-builder{border:1px solid #dee2e6;border-radius:4px;padding:.75rem;background:#f8f9fa}.builder-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem}.builder-label{font-weight:600;font-size:.85rem;color:#495057}.mode-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.15rem .5rem;font-size:.75rem;cursor:pointer}.mode-toggle:hover{background:#e9ecef}.table-header{display:flex;gap:.5rem;padding:.25rem 0;font-size:.75rem;color:#6c757d;font-weight:600}.table-row{display:flex;gap:.5rem;margin-bottom:.25rem;align-items:center}.col-scope{width:70px;flex-shrink:0}.col-path,.col-field{flex:1;min-width:0}.col-action{width:30px;flex-shrink:0}.table-row select,.table-row input{border:1px solid #ced4da;border-radius:4px;padding:.25rem .4rem;font-size:.8rem;background:#fff}.remove-btn{background:none;border:none;color:#dc3545;cursor:pointer;padding:.25rem;font-size:.9rem}.remove-btn:hover{color:#a71d2a}.add-btn{background:none;border:1px dashed #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;margin-top:.25rem;display:flex;align-items:center}.add-btn:hover{background:#e9ecef;border-color:#495057}.json-editor{width:100%;border:1px solid #ced4da;border-radius:4px;padding:.5rem;font-family:monospace;font-size:.8rem;resize:vertical;background:#fff}.json-error{color:#dc3545;font-size:.75rem;margin-top:.25rem}\n"] }]
|
|
3589
|
+
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
3590
|
+
type: Input
|
|
3591
|
+
}], valueChange: [{
|
|
3592
|
+
type: Output
|
|
3593
|
+
}], disabled: [{
|
|
3594
|
+
type: Input
|
|
3595
|
+
}], label: [{
|
|
3596
|
+
type: Input
|
|
3597
|
+
}], availableFields: [{
|
|
3598
|
+
type: Input
|
|
3599
|
+
}] } });
|
|
3600
|
+
|
|
3601
|
+
const EPISTOLA_OVERRIDE_BUILDER_OPTIONS = {
|
|
3602
|
+
type: 'epistola-override-builder',
|
|
3603
|
+
selector: 'epistola-override-builder-element',
|
|
3604
|
+
title: 'Epistola Override Builder',
|
|
3605
|
+
group: 'basic',
|
|
3606
|
+
icon: 'list',
|
|
3607
|
+
emptyValue: null,
|
|
3608
|
+
fieldOptions: ['label', 'availableFields'],
|
|
3609
|
+
};
|
|
3610
|
+
/**
|
|
3611
|
+
* Recursively collect input field keys and labels from a Formio component tree.
|
|
3612
|
+
* Skips epistola custom components (which are builder UI, not form fields).
|
|
3613
|
+
*/
|
|
3614
|
+
function collectFormFields(components) {
|
|
3615
|
+
const fields = [];
|
|
3616
|
+
for (const comp of components) {
|
|
3617
|
+
if (comp.input && comp.key && comp.type !== 'button' && !comp.type?.startsWith('epistola-')) {
|
|
3618
|
+
fields.push({ key: comp.key, label: comp.label || comp.key });
|
|
3619
|
+
}
|
|
3620
|
+
if (comp.components) {
|
|
3621
|
+
fields.push(...collectFormFields(comp.components));
|
|
3622
|
+
}
|
|
3623
|
+
if (comp.columns) {
|
|
3624
|
+
for (const col of comp.columns) {
|
|
3625
|
+
if (col.components) {
|
|
3626
|
+
fields.push(...collectFormFields(col.components));
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
return fields;
|
|
3632
|
+
}
|
|
3633
|
+
function registerEpistolaOverrideBuilderComponent(injector) {
|
|
3634
|
+
if (customElements.get(EPISTOLA_OVERRIDE_BUILDER_OPTIONS.selector)) {
|
|
3635
|
+
return;
|
|
3636
|
+
}
|
|
3637
|
+
// Register the base component (Angular element + Formio component class)
|
|
3638
|
+
registerCustomFormioComponent(EPISTOLA_OVERRIDE_BUILDER_OPTIONS, EpistolaOverrideBuilderComponent, injector);
|
|
3639
|
+
// Get the Formio Components registry and the registered base class
|
|
3640
|
+
const Formio = window.Formio;
|
|
3641
|
+
if (!Formio?.Components)
|
|
3642
|
+
return;
|
|
3643
|
+
const BaseComponent = Formio.Components.components[EPISTOLA_OVERRIDE_BUILDER_OPTIONS.type];
|
|
3644
|
+
if (!BaseComponent)
|
|
3645
|
+
return;
|
|
3646
|
+
// Extend the base class to pass available form fields to the Angular component
|
|
3647
|
+
class OverrideBuilderWithFields extends BaseComponent {
|
|
3648
|
+
attach(element) {
|
|
3649
|
+
// Set form fields on the component BEFORE super.attach() reads fieldOptions
|
|
3650
|
+
this.component.availableFields = this._extractFormFields();
|
|
3651
|
+
return super.attach(element);
|
|
3652
|
+
}
|
|
3653
|
+
_extractFormFields() {
|
|
3654
|
+
// The Formio builder passes the main form schema as options.editForm
|
|
3655
|
+
// when opening the edit dialog (editFormOptions.editForm = this.form).
|
|
3656
|
+
const components = this.options?.editForm?.components;
|
|
3657
|
+
if (Array.isArray(components)) {
|
|
3658
|
+
return collectFormFields(components);
|
|
3659
|
+
}
|
|
3660
|
+
return [];
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
// Re-register with the extended class
|
|
3664
|
+
Formio.Components.setComponent(EPISTOLA_OVERRIDE_BUILDER_OPTIONS.type, OverrideBuilderWithFields);
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
class EpistolaProcessLinkSelectorComponent {
|
|
3668
|
+
adminService;
|
|
3669
|
+
cdr;
|
|
3670
|
+
value;
|
|
3671
|
+
valueChange = new EventEmitter();
|
|
3672
|
+
disabled = false;
|
|
3673
|
+
label = 'Process Link';
|
|
3674
|
+
filteredEntries = [];
|
|
3675
|
+
selectedKey = '';
|
|
3676
|
+
loading = false;
|
|
3677
|
+
error = null;
|
|
3678
|
+
initialized = false;
|
|
3679
|
+
loadSubscription;
|
|
3680
|
+
constructor(adminService, cdr) {
|
|
3681
|
+
this.adminService = adminService;
|
|
3682
|
+
this.cdr = cdr;
|
|
3683
|
+
}
|
|
3684
|
+
ngOnChanges(changes) {
|
|
3685
|
+
if (!this.initialized) {
|
|
3686
|
+
this.initialized = true;
|
|
3687
|
+
this.loadEntries();
|
|
3688
|
+
}
|
|
3689
|
+
// Restore selection whenever value changes (Formio may set it after init)
|
|
3690
|
+
if (changes['value'] && this.value) {
|
|
3691
|
+
this.selectedKey = `${this.value.processDefinitionKey}::${this.value.sourceActivityId}`;
|
|
3692
|
+
this.cdr.markForCheck();
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
ngOnDestroy() {
|
|
3696
|
+
this.loadSubscription?.unsubscribe();
|
|
3697
|
+
}
|
|
3698
|
+
onSelect(key) {
|
|
3699
|
+
this.selectedKey = key;
|
|
3700
|
+
if (!key) {
|
|
3701
|
+
this.value = null;
|
|
3702
|
+
this.valueChange.emit(null);
|
|
3703
|
+
return;
|
|
3704
|
+
}
|
|
3705
|
+
const [processDefinitionKey, sourceActivityId] = key.split('::');
|
|
3706
|
+
this.value = { processDefinitionKey, sourceActivityId };
|
|
3707
|
+
this.valueChange.emit(this.value);
|
|
3708
|
+
}
|
|
3709
|
+
entryKey(entry) {
|
|
3710
|
+
return `${entry.processDefinitionKey}::${entry.activityId}`;
|
|
3711
|
+
}
|
|
3712
|
+
loadEntries() {
|
|
3713
|
+
this.loading = true;
|
|
3714
|
+
this.cdr.markForCheck();
|
|
3715
|
+
this.loadSubscription = this.adminService.getPluginUsage().subscribe({
|
|
3716
|
+
next: (entries) => {
|
|
3717
|
+
this.filteredEntries = entries.filter((e) => e.actionKey === 'generate-document');
|
|
3718
|
+
this.loading = false;
|
|
3719
|
+
// Restore selection from value
|
|
3720
|
+
if (this.value) {
|
|
3721
|
+
this.selectedKey = `${this.value.processDefinitionKey}::${this.value.sourceActivityId}`;
|
|
3722
|
+
}
|
|
3723
|
+
this.cdr.markForCheck();
|
|
3724
|
+
},
|
|
3725
|
+
error: (err) => {
|
|
3726
|
+
this.error = 'Failed to load process links';
|
|
3727
|
+
this.loading = false;
|
|
3728
|
+
this.cdr.markForCheck();
|
|
3729
|
+
},
|
|
3730
|
+
});
|
|
3731
|
+
}
|
|
3732
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaProcessLinkSelectorComponent, deps: [{ token: EpistolaAdminService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3733
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaProcessLinkSelectorComponent, isStandalone: true, selector: "epistola-process-link-selector-component", inputs: { value: "value", disabled: "disabled", label: "label" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
3734
|
+
<div class="process-link-selector">
|
|
3735
|
+
<label class="selector-label">{{ label || 'Process Link' }}</label>
|
|
3736
|
+
<select
|
|
3737
|
+
class="selector-dropdown"
|
|
3738
|
+
[ngModel]="selectedKey"
|
|
3739
|
+
(ngModelChange)="onSelect($event)"
|
|
3740
|
+
[disabled]="disabled || loading"
|
|
3741
|
+
>
|
|
3742
|
+
<option value="">{{ loading ? 'Loading...' : '-- Select a process link --' }}</option>
|
|
3743
|
+
<option *ngFor="let entry of filteredEntries" [value]="entryKey(entry)">
|
|
3744
|
+
{{ entry.processDefinitionName }} / {{ entry.activityName }} ({{ entry.activityId }})
|
|
3745
|
+
</option>
|
|
3746
|
+
</select>
|
|
3747
|
+
<div *ngIf="error" class="selector-error">{{ error }}</div>
|
|
3748
|
+
</div>
|
|
3749
|
+
`, isInline: true, styles: [".process-link-selector{margin-bottom:.5rem}.selector-label{display:block;font-weight:600;font-size:.85rem;color:#495057;margin-bottom:.25rem}.selector-dropdown{width:100%;border:1px solid #ced4da;border-radius:4px;padding:.4rem .5rem;font-size:.85rem;background:#fff}.selector-error{color:#dc3545;font-size:.75rem;margin-top:.25rem}\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: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3750
|
+
}
|
|
3751
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaProcessLinkSelectorComponent, decorators: [{
|
|
3752
|
+
type: Component,
|
|
3753
|
+
args: [{ standalone: true, imports: [CommonModule, FormsModule], selector: 'epistola-process-link-selector-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3754
|
+
<div class="process-link-selector">
|
|
3755
|
+
<label class="selector-label">{{ label || 'Process Link' }}</label>
|
|
3756
|
+
<select
|
|
3757
|
+
class="selector-dropdown"
|
|
3758
|
+
[ngModel]="selectedKey"
|
|
3759
|
+
(ngModelChange)="onSelect($event)"
|
|
3760
|
+
[disabled]="disabled || loading"
|
|
3761
|
+
>
|
|
3762
|
+
<option value="">{{ loading ? 'Loading...' : '-- Select a process link --' }}</option>
|
|
3763
|
+
<option *ngFor="let entry of filteredEntries" [value]="entryKey(entry)">
|
|
3764
|
+
{{ entry.processDefinitionName }} / {{ entry.activityName }} ({{ entry.activityId }})
|
|
3765
|
+
</option>
|
|
3766
|
+
</select>
|
|
3767
|
+
<div *ngIf="error" class="selector-error">{{ error }}</div>
|
|
3768
|
+
</div>
|
|
3769
|
+
`, styles: [".process-link-selector{margin-bottom:.5rem}.selector-label{display:block;font-weight:600;font-size:.85rem;color:#495057;margin-bottom:.25rem}.selector-dropdown{width:100%;border:1px solid #ced4da;border-radius:4px;padding:.4rem .5rem;font-size:.85rem;background:#fff}.selector-error{color:#dc3545;font-size:.75rem;margin-top:.25rem}\n"] }]
|
|
3770
|
+
}], ctorParameters: () => [{ type: EpistolaAdminService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
3771
|
+
type: Input
|
|
3772
|
+
}], valueChange: [{
|
|
3773
|
+
type: Output
|
|
3774
|
+
}], disabled: [{
|
|
3775
|
+
type: Input
|
|
3776
|
+
}], label: [{
|
|
3777
|
+
type: Input
|
|
3778
|
+
}] } });
|
|
3779
|
+
|
|
3780
|
+
const EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS = {
|
|
3781
|
+
type: 'epistola-process-link-selector',
|
|
3782
|
+
selector: 'epistola-process-link-selector-element',
|
|
3783
|
+
title: 'Epistola Process Link Selector',
|
|
3784
|
+
group: 'basic',
|
|
3785
|
+
icon: 'link',
|
|
3786
|
+
emptyValue: null,
|
|
3787
|
+
fieldOptions: ['label'],
|
|
3788
|
+
};
|
|
3789
|
+
function registerEpistolaProcessLinkSelectorComponent(injector) {
|
|
3790
|
+
if (!customElements.get(EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS.selector)) {
|
|
3791
|
+
registerCustomFormioComponent(EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS, EpistolaProcessLinkSelectorComponent, injector);
|
|
1885
3792
|
}
|
|
1886
3793
|
}
|
|
1887
3794
|
|
|
@@ -1890,18 +3797,25 @@ class EpistolaPluginModule {
|
|
|
1890
3797
|
return {
|
|
1891
3798
|
ngModule: EpistolaPluginModule,
|
|
1892
3799
|
providers: [
|
|
3800
|
+
EpistolaMenuService,
|
|
1893
3801
|
{
|
|
1894
3802
|
provide: ENVIRONMENT_INITIALIZER,
|
|
1895
3803
|
multi: true,
|
|
1896
3804
|
useValue: () => {
|
|
3805
|
+
if (!isEpistolaEnabled())
|
|
3806
|
+
return;
|
|
1897
3807
|
const injector = inject(Injector);
|
|
1898
3808
|
registerEpistolaDownloadComponent(injector);
|
|
1899
3809
|
registerEpistolaRetryFormComponent(injector);
|
|
1900
3810
|
registerEpistolaPreviewButtonComponent(injector);
|
|
3811
|
+
registerEpistolaOverrideBuilderComponent(injector);
|
|
3812
|
+
registerEpistolaProcessLinkSelectorComponent(injector);
|
|
1901
3813
|
registerEpistolaDocumentPreviewComponent(injector);
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
3814
|
+
// Eagerly create EpistolaMenuService to trigger menu registration
|
|
3815
|
+
inject(EpistolaMenuService);
|
|
3816
|
+
},
|
|
3817
|
+
},
|
|
3818
|
+
],
|
|
1905
3819
|
};
|
|
1906
3820
|
}
|
|
1907
3821
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
@@ -1911,52 +3825,40 @@ class EpistolaPluginModule {
|
|
|
1911
3825
|
FormModule,
|
|
1912
3826
|
InputModule,
|
|
1913
3827
|
SelectModule,
|
|
3828
|
+
EpistolaAdminRoutingModule,
|
|
1914
3829
|
EpistolaConfigurationComponent,
|
|
1915
3830
|
GenerateDocumentConfigurationComponent,
|
|
1916
3831
|
CheckJobStatusConfigurationComponent,
|
|
1917
3832
|
DownloadDocumentConfigurationComponent,
|
|
1918
|
-
DataMappingTreeComponent,
|
|
1919
|
-
ValueInputComponent,
|
|
1920
|
-
ScalarFieldComponent,
|
|
1921
|
-
ArrayFieldComponent,
|
|
1922
|
-
FieldTreeComponent,
|
|
1923
3833
|
EpistolaDownloadComponent,
|
|
1924
3834
|
EpistolaRetryFormComponent,
|
|
1925
3835
|
EpistolaPreviewButtonComponent,
|
|
1926
|
-
EpistolaDocumentPreviewComponent
|
|
3836
|
+
EpistolaDocumentPreviewComponent,
|
|
3837
|
+
EpistolaAdminPageComponent], exports: [EpistolaConfigurationComponent,
|
|
1927
3838
|
GenerateDocumentConfigurationComponent,
|
|
1928
3839
|
CheckJobStatusConfigurationComponent,
|
|
1929
3840
|
DownloadDocumentConfigurationComponent,
|
|
1930
|
-
DataMappingTreeComponent,
|
|
1931
|
-
ValueInputComponent,
|
|
1932
|
-
ScalarFieldComponent,
|
|
1933
|
-
ArrayFieldComponent,
|
|
1934
|
-
FieldTreeComponent,
|
|
1935
3841
|
EpistolaDownloadComponent,
|
|
1936
3842
|
EpistolaRetryFormComponent,
|
|
1937
3843
|
EpistolaPreviewButtonComponent,
|
|
1938
|
-
EpistolaDocumentPreviewComponent
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
], imports: [CommonModule,
|
|
3844
|
+
EpistolaDocumentPreviewComponent,
|
|
3845
|
+
EpistolaAdminPageComponent] });
|
|
3846
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, providers: [EpistolaPluginService, EpistolaAdminService], imports: [CommonModule,
|
|
1942
3847
|
HttpClientModule,
|
|
1943
3848
|
PluginTranslatePipeModule,
|
|
1944
3849
|
FormModule,
|
|
1945
3850
|
InputModule,
|
|
1946
3851
|
SelectModule,
|
|
3852
|
+
EpistolaAdminRoutingModule,
|
|
1947
3853
|
EpistolaConfigurationComponent,
|
|
1948
3854
|
GenerateDocumentConfigurationComponent,
|
|
1949
3855
|
CheckJobStatusConfigurationComponent,
|
|
1950
3856
|
DownloadDocumentConfigurationComponent,
|
|
1951
|
-
DataMappingTreeComponent,
|
|
1952
|
-
ValueInputComponent,
|
|
1953
|
-
ScalarFieldComponent,
|
|
1954
|
-
ArrayFieldComponent,
|
|
1955
|
-
FieldTreeComponent,
|
|
1956
3857
|
EpistolaDownloadComponent,
|
|
1957
3858
|
EpistolaRetryFormComponent,
|
|
1958
3859
|
EpistolaPreviewButtonComponent,
|
|
1959
|
-
EpistolaDocumentPreviewComponent
|
|
3860
|
+
EpistolaDocumentPreviewComponent,
|
|
3861
|
+
EpistolaAdminPageComponent] });
|
|
1960
3862
|
}
|
|
1961
3863
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, decorators: [{
|
|
1962
3864
|
type: NgModule,
|
|
@@ -1968,48 +3870,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1968
3870
|
FormModule,
|
|
1969
3871
|
InputModule,
|
|
1970
3872
|
SelectModule,
|
|
3873
|
+
EpistolaAdminRoutingModule,
|
|
1971
3874
|
EpistolaConfigurationComponent,
|
|
1972
3875
|
GenerateDocumentConfigurationComponent,
|
|
1973
3876
|
CheckJobStatusConfigurationComponent,
|
|
1974
3877
|
DownloadDocumentConfigurationComponent,
|
|
1975
|
-
DataMappingTreeComponent,
|
|
1976
|
-
ValueInputComponent,
|
|
1977
|
-
ScalarFieldComponent,
|
|
1978
|
-
ArrayFieldComponent,
|
|
1979
|
-
FieldTreeComponent,
|
|
1980
3878
|
EpistolaDownloadComponent,
|
|
1981
3879
|
EpistolaRetryFormComponent,
|
|
1982
3880
|
EpistolaPreviewButtonComponent,
|
|
1983
|
-
EpistolaDocumentPreviewComponent
|
|
3881
|
+
EpistolaDocumentPreviewComponent,
|
|
3882
|
+
EpistolaAdminPageComponent,
|
|
1984
3883
|
],
|
|
1985
3884
|
exports: [
|
|
1986
3885
|
EpistolaConfigurationComponent,
|
|
1987
3886
|
GenerateDocumentConfigurationComponent,
|
|
1988
3887
|
CheckJobStatusConfigurationComponent,
|
|
1989
3888
|
DownloadDocumentConfigurationComponent,
|
|
1990
|
-
DataMappingTreeComponent,
|
|
1991
|
-
ValueInputComponent,
|
|
1992
|
-
ScalarFieldComponent,
|
|
1993
|
-
ArrayFieldComponent,
|
|
1994
|
-
FieldTreeComponent,
|
|
1995
3889
|
EpistolaDownloadComponent,
|
|
1996
3890
|
EpistolaRetryFormComponent,
|
|
1997
3891
|
EpistolaPreviewButtonComponent,
|
|
1998
|
-
EpistolaDocumentPreviewComponent
|
|
3892
|
+
EpistolaDocumentPreviewComponent,
|
|
3893
|
+
EpistolaAdminPageComponent,
|
|
1999
3894
|
],
|
|
2000
|
-
providers: [
|
|
2001
|
-
EpistolaPluginService
|
|
2002
|
-
]
|
|
3895
|
+
providers: [EpistolaPluginService, EpistolaAdminService],
|
|
2003
3896
|
}]
|
|
2004
3897
|
}] });
|
|
2005
3898
|
|
|
2006
|
-
|
|
2007
|
-
// TODO: Replace with actual Epistola logo
|
|
2008
|
-
const EPISTOLA_PLUGIN_LOGO_BASE64 = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzMzNjZjYyI+PHBhdGggZD0iTTE0IDJINmMtMS4xIDAtMiAuOS0yIDJ2MTZjMCAxLjEuOSAyIDIgMmgxMmMxLjEgMCAyLS45IDItMlY4bC02LTZ6bTQgMThINlY0aDd2NWg1djExeiIvPjxwYXRoIGQ9Ik04IDEyaDh2Mkg4em0wIDRoOHYtMkg4em0wLThWNmg0djJ6Ii8+PC9zdmc+';
|
|
3899
|
+
const EPISTOLA_PLUGIN_LOGO_BASE64 = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAgMTIwIiB3aWR0aD0iMTIwIiBoZWlnaHQ9IjEyMCI+CiAgPCEtLSBTdGFjayBiYXNlIC0tPgogIDxyZWN0IHg9IjM2IiB5PSIxNiIgd2lkdGg9IjU0IiBoZWlnaHQ9IjcwIiByeD0iMyIgZmlsbD0iI2U2YzJiMCIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIiIHRyYW5zZm9ybT0icm90YXRlKDUgNjMgNTEpIi8+CiAgPHJlY3QgeD0iMzIiIHk9IjIyIiB3aWR0aD0iNTQiIGhlaWdodD0iNzAiIHJ4PSIzIiBmaWxsPSIjZjBkOGM4IiBzdHJva2U9IiM0ZjJmMmIiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxyZWN0IHg9IjI4IiB5PSIyOCIgd2lkdGg9IjU0IiBoZWlnaHQ9IjcwIiByeD0iMyIgZmlsbD0iI2Y1ZWJlMyIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIuNSIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjQ0IiB4Mj0iNzIiIHkyPSI0NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjU0IiB4Mj0iNzIiIHkyPSI1NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjY0IiB4Mj0iNTgiIHkyPSI2NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDwhLS0gV2F4IHNlYWwgd2l0aCBjaGVja21hcmsgLS0+CiAgPGNpcmNsZSBjeD0iNTUiIGN5PSI4NCIgcj0iMTUiIGZpbGw9IiNiODVjM2MiIHN0cm9rZT0iIzRmMmYyYiIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPGNpcmNsZSBjeD0iNTUiIGN5PSI4NCIgcj0iMTAuNSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDQ4MzZhIiBzdHJva2Utd2lkdGg9IjEiLz4KICA8cG9seWxpbmUgcG9pbnRzPSI0OSw4NCA1Myw4OSA2Miw3OCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo=';
|
|
2009
3900
|
|
|
3901
|
+
const EPISTOLA_PLUGIN_ID = 'epistola';
|
|
3902
|
+
const DISABLED_EPISTOLA_PLUGIN_ID = '__epistola_disabled__';
|
|
2010
3903
|
const epistolaPluginSpecification = {
|
|
2011
|
-
|
|
2012
|
-
|
|
3904
|
+
get pluginId() {
|
|
3905
|
+
return isEpistolaEnabled() ? EPISTOLA_PLUGIN_ID : DISABLED_EPISTOLA_PLUGIN_ID;
|
|
3906
|
+
},
|
|
2013
3907
|
// Component for plugin-level configuration (tenantId)
|
|
2014
3908
|
pluginConfigurationComponent: EpistolaConfigurationComponent,
|
|
2015
3909
|
// Plugin logo
|
|
@@ -2073,6 +3967,18 @@ const epistolaPluginSpecification = {
|
|
|
2073
3967
|
// Data mapping builder translations
|
|
2074
3968
|
dataMappingTitle: 'Data Mapping',
|
|
2075
3969
|
dataMappingDescription: 'Koppel template velden aan Valtimo data bronnen',
|
|
3970
|
+
jsonataDescription: 'JSONata expressie die de template data genereert. Gebruik $doc, $pv en $case voor toegang tot document-, procesvariabelen- en zaakdata.',
|
|
3971
|
+
mappingModeSimple: 'Eenvoudig',
|
|
3972
|
+
mappingModeAdvanced: 'Geavanceerd',
|
|
3973
|
+
mappingTools: 'Schema & Voorbeeld',
|
|
3974
|
+
expectedStructure: 'Verwacht schema',
|
|
3975
|
+
expectedStructureLoading: 'Schema laden...',
|
|
3976
|
+
previewTitle: 'Voorbeeld',
|
|
3977
|
+
previewDocPlaceholder: 'Document ID',
|
|
3978
|
+
previewExpected: 'Verwacht',
|
|
3979
|
+
previewProduced: 'Geproduceerd',
|
|
3980
|
+
previewRunHint: 'Voer een document ID in en klik ▶ om een voorbeeld te zien',
|
|
3981
|
+
previewMissing: 'Ontbrekende verplichte velden',
|
|
2076
3982
|
templateField: 'Template veld',
|
|
2077
3983
|
dataSource: 'Data bron',
|
|
2078
3984
|
addMapping: 'Mapping toevoegen',
|
|
@@ -2087,6 +3993,7 @@ const epistolaPluginSpecification = {
|
|
|
2087
3993
|
requiredFieldsMissing: 'Niet alle verplichte velden zijn gekoppeld',
|
|
2088
3994
|
requiredFieldsComplete: 'Alle verplichte velden zijn gekoppeld',
|
|
2089
3995
|
validationSummary: 'verplichte velden gekoppeld',
|
|
3996
|
+
jsonataValidationErrorsHeading: 'Ongeldige JSONata-expressies:',
|
|
2090
3997
|
fieldRequired: 'Verplicht',
|
|
2091
3998
|
fieldOptional: 'Optioneel',
|
|
2092
3999
|
mapCollectionTo: 'Koppel collectie aan',
|
|
@@ -2094,6 +4001,7 @@ const epistolaPluginSpecification = {
|
|
|
2094
4001
|
pvMode: 'Procesvariabele modus',
|
|
2095
4002
|
pvPlaceholder: 'Naam procesvariabele',
|
|
2096
4003
|
expressionMode: 'Expressiemodus',
|
|
4004
|
+
availableFunctions: 'Beschikbare functies',
|
|
2097
4005
|
itemFieldMapping: 'Veldnamen per item koppelen',
|
|
2098
4006
|
itemFieldMappingTitle: 'Veldkoppeling per item:',
|
|
2099
4007
|
sourceFieldPlaceholder: 'Bronveldnaam',
|
|
@@ -2111,7 +4019,31 @@ const epistolaPluginSpecification = {
|
|
|
2111
4019
|
// Download document action
|
|
2112
4020
|
'download-document': 'Download Document',
|
|
2113
4021
|
contentVariable: 'Inhoud Variabele',
|
|
2114
|
-
contentVariableTooltip: 'Naam van de procesvariabele waarin de documentinhoud (Base64) wordt opgeslagen'
|
|
4022
|
+
contentVariableTooltip: 'Naam van de procesvariabele waarin de documentinhoud (Base64) wordt opgeslagen',
|
|
4023
|
+
// Admin page
|
|
4024
|
+
epistolaAdminOverview: 'Overzicht',
|
|
4025
|
+
epistolaAdminRefresh: 'Vernieuwen',
|
|
4026
|
+
epistolaAdminLoading: 'Laden...',
|
|
4027
|
+
epistolaAdminNoConfigurations: 'Geen Epistola plugin configuraties gevonden.',
|
|
4028
|
+
epistolaAdminTenantId: 'Tenant ID',
|
|
4029
|
+
epistolaAdminStatus: 'Status',
|
|
4030
|
+
epistolaAdminConnected: 'Verbonden',
|
|
4031
|
+
epistolaAdminUnreachable: 'Onbereikbaar',
|
|
4032
|
+
epistolaAdminError: 'Fout',
|
|
4033
|
+
epistolaAdminPluginActions: 'Plugin acties',
|
|
4034
|
+
epistolaAdminProblems: 'Problemen',
|
|
4035
|
+
epistolaAdminBackToOverview: 'Terug naar overzicht',
|
|
4036
|
+
epistolaAdminNoUsageForConfig: 'Geen procesacties geconfigureerd voor deze verbinding.',
|
|
4037
|
+
epistolaAdminCase: 'Zaak',
|
|
4038
|
+
epistolaAdminProcess: 'Proces',
|
|
4039
|
+
epistolaAdminActivity: 'Activiteit',
|
|
4040
|
+
epistolaAdminAction: 'Actie',
|
|
4041
|
+
epistolaAdminServerVersion: 'Server versie',
|
|
4042
|
+
epistolaAdminExport: 'Exporteren',
|
|
4043
|
+
epistolaAdminPendingJobs: 'Wachtende taken',
|
|
4044
|
+
epistolaAdminNoPendingJobs: 'Geen wachtende taken voor deze verbinding.',
|
|
4045
|
+
epistolaAdminConfiguration: 'Configuratie',
|
|
4046
|
+
epistolaAdminRequestId: 'Request ID',
|
|
2115
4047
|
},
|
|
2116
4048
|
en: {
|
|
2117
4049
|
title: 'Epistola Document Suite',
|
|
@@ -2164,6 +4096,18 @@ const epistolaPluginSpecification = {
|
|
|
2164
4096
|
// Data mapping builder translations
|
|
2165
4097
|
dataMappingTitle: 'Data Mapping',
|
|
2166
4098
|
dataMappingDescription: 'Map template fields to Valtimo data sources',
|
|
4099
|
+
mappingModeSimple: 'Simple',
|
|
4100
|
+
mappingModeAdvanced: 'Advanced',
|
|
4101
|
+
mappingTools: 'Schema & Preview',
|
|
4102
|
+
expectedStructure: 'Expected schema',
|
|
4103
|
+
expectedStructureLoading: 'Loading schema...',
|
|
4104
|
+
previewTitle: 'Preview',
|
|
4105
|
+
previewDocPlaceholder: 'Document ID',
|
|
4106
|
+
previewExpected: 'Expected',
|
|
4107
|
+
previewProduced: 'Produced',
|
|
4108
|
+
previewRunHint: 'Enter a document ID and click ▶ to preview the output',
|
|
4109
|
+
previewMissing: 'Missing required fields',
|
|
4110
|
+
jsonataDescription: 'JSONata expression that generates the template data. Use $doc, $pv and $case to access document, process variable and case data.',
|
|
2167
4111
|
templateField: 'Template field',
|
|
2168
4112
|
dataSource: 'Data source',
|
|
2169
4113
|
addMapping: 'Add mapping',
|
|
@@ -2178,6 +4122,7 @@ const epistolaPluginSpecification = {
|
|
|
2178
4122
|
requiredFieldsMissing: 'Not all required fields are mapped',
|
|
2179
4123
|
requiredFieldsComplete: 'All required fields are mapped',
|
|
2180
4124
|
validationSummary: 'required fields mapped',
|
|
4125
|
+
jsonataValidationErrorsHeading: 'Invalid JSONata expressions:',
|
|
2181
4126
|
fieldRequired: 'Required',
|
|
2182
4127
|
fieldOptional: 'Optional',
|
|
2183
4128
|
mapCollectionTo: 'Map collection to',
|
|
@@ -2185,6 +4130,7 @@ const epistolaPluginSpecification = {
|
|
|
2185
4130
|
pvMode: 'Process variable mode',
|
|
2186
4131
|
pvPlaceholder: 'Process variable name',
|
|
2187
4132
|
expressionMode: 'Expression mode',
|
|
4133
|
+
availableFunctions: 'Available functions',
|
|
2188
4134
|
itemFieldMapping: 'Map field names per item',
|
|
2189
4135
|
itemFieldMappingTitle: 'Item field mapping:',
|
|
2190
4136
|
sourceFieldPlaceholder: 'Source field name',
|
|
@@ -2202,9 +4148,33 @@ const epistolaPluginSpecification = {
|
|
|
2202
4148
|
// Download document action
|
|
2203
4149
|
'download-document': 'Download Document',
|
|
2204
4150
|
contentVariable: 'Content Variable',
|
|
2205
|
-
contentVariableTooltip: 'Name of the process variable to store the document content (Base64) in'
|
|
2206
|
-
|
|
2207
|
-
|
|
4151
|
+
contentVariableTooltip: 'Name of the process variable to store the document content (Base64) in',
|
|
4152
|
+
// Admin page
|
|
4153
|
+
epistolaAdminOverview: 'Overview',
|
|
4154
|
+
epistolaAdminRefresh: 'Refresh',
|
|
4155
|
+
epistolaAdminLoading: 'Loading...',
|
|
4156
|
+
epistolaAdminNoConfigurations: 'No Epistola plugin configurations found.',
|
|
4157
|
+
epistolaAdminTenantId: 'Tenant ID',
|
|
4158
|
+
epistolaAdminStatus: 'Status',
|
|
4159
|
+
epistolaAdminConnected: 'Connected',
|
|
4160
|
+
epistolaAdminUnreachable: 'Unreachable',
|
|
4161
|
+
epistolaAdminError: 'Error',
|
|
4162
|
+
epistolaAdminPluginActions: 'Plugin actions',
|
|
4163
|
+
epistolaAdminProblems: 'Problems',
|
|
4164
|
+
epistolaAdminBackToOverview: 'Back to overview',
|
|
4165
|
+
epistolaAdminNoUsageForConfig: 'No process actions configured for this connection.',
|
|
4166
|
+
epistolaAdminCase: 'Case',
|
|
4167
|
+
epistolaAdminProcess: 'Process',
|
|
4168
|
+
epistolaAdminActivity: 'Activity',
|
|
4169
|
+
epistolaAdminAction: 'Action',
|
|
4170
|
+
epistolaAdminServerVersion: 'Server version',
|
|
4171
|
+
epistolaAdminExport: 'Export',
|
|
4172
|
+
epistolaAdminPendingJobs: 'Pending jobs',
|
|
4173
|
+
epistolaAdminNoPendingJobs: 'No pending jobs for this connection.',
|
|
4174
|
+
epistolaAdminConfiguration: 'Configuration',
|
|
4175
|
+
epistolaAdminRequestId: 'Request ID',
|
|
4176
|
+
},
|
|
4177
|
+
},
|
|
2208
4178
|
};
|
|
2209
4179
|
|
|
2210
4180
|
/*
|
|
@@ -2215,5 +4185,5 @@ const epistolaPluginSpecification = {
|
|
|
2215
4185
|
* Generated bundle index. Do not edit.
|
|
2216
4186
|
*/
|
|
2217
4187
|
|
|
2218
|
-
export {
|
|
4188
|
+
export { CheckJobStatusConfigurationComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EPISTOLA_DOWNLOAD_OPTIONS, EPISTOLA_OVERRIDE_BUILDER_OPTIONS, EPISTOLA_PREVIEW_BUTTON_OPTIONS, EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS, EPISTOLA_RETRY_FORM_OPTIONS, EpistolaAdminPageComponent, EpistolaAdminRoutingModule, EpistolaAdminService, EpistolaConfigurationComponent, EpistolaDocumentPreviewComponent, EpistolaDownloadComponent, EpistolaMenuService, EpistolaOverrideBuilderComponent, EpistolaPluginModule, EpistolaPluginService, EpistolaPreviewButtonComponent, EpistolaProcessLinkSelectorComponent, EpistolaRetryFormComponent, GenerateDocumentConfigurationComponent, JsonataEditorComponent, MappingBuilderComponent, epistolaPluginSpecification, errorResource, initialResource, isEpistolaEnabled, loadingResource, registerEpistolaDocumentPreviewComponent, registerEpistolaDownloadComponent, registerEpistolaOverrideBuilderComponent, registerEpistolaPreviewButtonComponent, registerEpistolaProcessLinkSelectorComponent, registerEpistolaRetryFormComponent, successResource };
|
|
2219
4189
|
//# sourceMappingURL=epistola.app-valtimo-plugin.mjs.map
|