@epistola.app/valtimo-plugin 0.9.4 → 0.11.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 +2430 -395
- package/fesm2022/epistola.app-valtimo-plugin.mjs.map +1 -1
- package/lib/components/check-job-status-configuration/check-job-status-configuration.component.d.ts +2 -2
- package/lib/components/download-document-configuration/download-document-config.util.d.ts +15 -0
- package/lib/components/download-document-configuration/download-document-configuration.component.d.ts +12 -3
- package/lib/components/epistola-admin-page/epistola-admin-page.component.d.ts +28 -4
- package/lib/components/epistola-configuration/epistola-configuration.component.d.ts +2 -2
- package/lib/components/epistola-document/epistola-document.component.d.ts +11 -7
- package/lib/components/epistola-document-preview/epistola-document-preview.component.d.ts +57 -13
- package/lib/components/epistola-document-preview/preview-utils.d.ts +39 -6
- package/lib/components/epistola-retry-form/epistola-retry-form.component.d.ts +9 -7
- package/lib/components/formio-builder-utils.d.ts +14 -0
- package/lib/components/generate-document-configuration/generate-document-configuration.component.d.ts +4 -2
- package/lib/components/jsonata-editor/jsonata-editor.component.d.ts +11 -3
- package/lib/components/override-builder/legacy-override-converter.d.ts +23 -0
- package/lib/components/override-builder/override-builder.component.d.ts +52 -20
- package/lib/components/override-builder/override-jsonata.d.ts +25 -0
- package/lib/models/admin.d.ts +57 -0
- package/lib/models/config.d.ts +25 -1
- package/lib/services/epistola-admin.service.d.ts +13 -4
- package/lib/services/epistola-plugin.service.d.ts +16 -10
- package/lib/services/index.d.ts +1 -2
- package/lib/services/prefilled-task-id.d.ts +52 -0
- package/lib/utils/extract-referenced-paths.d.ts +19 -0
- package/lib/utils/jsonata-monaco.d.ts +2 -2
- package/package.json +2 -1
- package/sbom.json +1 -1
- package/lib/services/epistola-task-context.interceptor.d.ts +0 -29
- package/lib/services/epistola-task-context.matcher.d.ts +0 -19
- package/lib/services/epistola-task-context.service.d.ts +0 -26
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
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
|
-
import { HttpHeaders, HttpClientModule
|
|
4
|
+
import { HttpHeaders, HttpClientModule } from '@angular/common/http';
|
|
5
5
|
import * as i2 from '@valtimo/shared';
|
|
6
6
|
import { ROLE_ADMIN } from '@valtimo/shared';
|
|
7
7
|
import { of, BehaviorSubject, combineLatest, take, Subject, debounceTime, takeUntil, merge } from 'rxjs';
|
|
@@ -12,21 +12,56 @@ import { CommonModule } from '@angular/common';
|
|
|
12
12
|
import * as i2$1 from '@valtimo/plugin';
|
|
13
13
|
import { PluginTranslatePipeModule } from '@valtimo/plugin';
|
|
14
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
|
|
15
|
+
import * as i3$1 from '@angular/forms';
|
|
16
16
|
import { FormsModule } from '@angular/forms';
|
|
17
17
|
import * as _jsonata from 'jsonata';
|
|
18
|
-
import * as i2$
|
|
19
|
-
import * as i2$
|
|
20
|
-
import * as
|
|
18
|
+
import * as i2$2 from '@valtimo/process-link';
|
|
19
|
+
import * as i2$3 from '@angular/platform-browser';
|
|
20
|
+
import * as i4 from '@formio/angular';
|
|
21
21
|
import { FormioModule } from '@formio/angular';
|
|
22
|
-
import * as i2$
|
|
22
|
+
import * as i2$4 from '@angular/router';
|
|
23
23
|
import { RouterModule, Router } from '@angular/router';
|
|
24
|
-
import * as i5
|
|
24
|
+
import * as i5 from 'carbon-components-angular/tabs';
|
|
25
25
|
import { TabsModule } from 'carbon-components-angular/tabs';
|
|
26
26
|
import * as i6 from 'carbon-components-angular/tag';
|
|
27
27
|
import { TagModule } from 'carbon-components-angular/tag';
|
|
28
28
|
import { AuthGuardService } from '@valtimo/security';
|
|
29
29
|
|
|
30
|
+
/*
|
|
31
|
+
* Copyright 2025 Epistola.
|
|
32
|
+
*
|
|
33
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
34
|
+
* you may not use this file except in compliance with the License.
|
|
35
|
+
* You may obtain a copy of the License at
|
|
36
|
+
*
|
|
37
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
38
|
+
*
|
|
39
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
40
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
41
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
42
|
+
* See the License for the specific language governing permissions and
|
|
43
|
+
* limitations under the License.
|
|
44
|
+
*
|
|
45
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
* Copyright 2025 Epistola.
|
|
50
|
+
*
|
|
51
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
52
|
+
* you may not use this file except in compliance with the License.
|
|
53
|
+
* You may obtain a copy of the License at
|
|
54
|
+
*
|
|
55
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
56
|
+
*
|
|
57
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
58
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
59
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
60
|
+
* See the License for the specific language governing permissions and
|
|
61
|
+
* limitations under the License.
|
|
62
|
+
*
|
|
63
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
64
|
+
*/
|
|
30
65
|
function initialResource(empty) {
|
|
31
66
|
return { data: empty, loading: false, error: null };
|
|
32
67
|
}
|
|
@@ -40,6 +75,95 @@ function errorResource(current, error) {
|
|
|
40
75
|
return { data: current, loading: false, error };
|
|
41
76
|
}
|
|
42
77
|
|
|
78
|
+
/*
|
|
79
|
+
* Copyright 2025 Epistola.
|
|
80
|
+
*
|
|
81
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
82
|
+
* you may not use this file except in compliance with the License.
|
|
83
|
+
* You may obtain a copy of the License at
|
|
84
|
+
*
|
|
85
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
86
|
+
*
|
|
87
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
88
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
89
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
90
|
+
* See the License for the specific language governing permissions and
|
|
91
|
+
* limitations under the License.
|
|
92
|
+
*
|
|
93
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
* Copyright 2025 Epistola.
|
|
98
|
+
*
|
|
99
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
100
|
+
* you may not use this file except in compliance with the License.
|
|
101
|
+
* You may obtain a copy of the License at
|
|
102
|
+
*
|
|
103
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
104
|
+
*
|
|
105
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
106
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
107
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
108
|
+
* See the License for the specific language governing permissions and
|
|
109
|
+
* limitations under the License.
|
|
110
|
+
*
|
|
111
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
* Copyright 2025 Epistola.
|
|
116
|
+
*
|
|
117
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
118
|
+
* you may not use this file except in compliance with the License.
|
|
119
|
+
* You may obtain a copy of the License at
|
|
120
|
+
*
|
|
121
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
122
|
+
*
|
|
123
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
124
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
125
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
126
|
+
* See the License for the specific language governing permissions and
|
|
127
|
+
* limitations under the License.
|
|
128
|
+
*
|
|
129
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
/*
|
|
133
|
+
* Copyright 2025 Epistola.
|
|
134
|
+
*
|
|
135
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
136
|
+
* you may not use this file except in compliance with the License.
|
|
137
|
+
* You may obtain a copy of the License at
|
|
138
|
+
*
|
|
139
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
140
|
+
*
|
|
141
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
142
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
143
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
144
|
+
* See the License for the specific language governing permissions and
|
|
145
|
+
* limitations under the License.
|
|
146
|
+
*
|
|
147
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* Copyright 2025 Epistola.
|
|
152
|
+
*
|
|
153
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
154
|
+
* you may not use this file except in compliance with the License.
|
|
155
|
+
* You may obtain a copy of the License at
|
|
156
|
+
*
|
|
157
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
158
|
+
*
|
|
159
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
160
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
161
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
162
|
+
* See the License for the specific language governing permissions and
|
|
163
|
+
* limitations under the License.
|
|
164
|
+
*
|
|
165
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
166
|
+
*/
|
|
43
167
|
/**
|
|
44
168
|
* Service for Epistola plugin administrative operations.
|
|
45
169
|
* Provides health checks, version info, and usage overview.
|
|
@@ -88,10 +212,11 @@ class EpistolaAdminService {
|
|
|
88
212
|
return this.http.post(`${this.apiEndpoint}/pending/${encodeURIComponent(executionId)}/reconcile`, null);
|
|
89
213
|
}
|
|
90
214
|
/**
|
|
91
|
-
* Get the latest BPMN race-safety validation
|
|
92
|
-
*
|
|
215
|
+
* Get the latest BPMN race-safety validation report across deployed process
|
|
216
|
+
* definitions: the violations (empty = healthy) plus when it was last checked
|
|
217
|
+
* and how often it refreshes.
|
|
93
218
|
*/
|
|
94
|
-
|
|
219
|
+
getValidationReport() {
|
|
95
220
|
return this.http.get(`${this.apiEndpoint}/validations`);
|
|
96
221
|
}
|
|
97
222
|
/**
|
|
@@ -126,6 +251,24 @@ class EpistolaAdminService {
|
|
|
126
251
|
responseType: 'blob',
|
|
127
252
|
});
|
|
128
253
|
}
|
|
254
|
+
// ---- TEMPORARY (removed in 1.0.0): task-id carrier detection + repair ----
|
|
255
|
+
/** Forms whose Epistola components are missing the task-id carrier. */
|
|
256
|
+
getFormCarrierIssues() {
|
|
257
|
+
return this.http.get(`${this.apiEndpoint}/forms/carrier-issues`);
|
|
258
|
+
}
|
|
259
|
+
/** Inject the task-id carrier into a single form's Epistola components. */
|
|
260
|
+
repairFormCarrier(formId) {
|
|
261
|
+
return this.http.post(`${this.apiEndpoint}/forms/${encodeURIComponent(formId)}/repair-carrier`, null);
|
|
262
|
+
}
|
|
263
|
+
/** Repair every flagged form. */
|
|
264
|
+
repairAllFormCarriers() {
|
|
265
|
+
return this.http.post(`${this.apiEndpoint}/forms/repair-carrier`, null);
|
|
266
|
+
}
|
|
267
|
+
// ---- TEMPORARY: legacy override-mapping format detection ----
|
|
268
|
+
/** Forms whose preview components still use the legacy override-mapping object format. */
|
|
269
|
+
getLegacyOverrideForms() {
|
|
270
|
+
return this.http.get(`${this.apiEndpoint}/forms/legacy-override`);
|
|
271
|
+
}
|
|
129
272
|
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 });
|
|
130
273
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminService });
|
|
131
274
|
}
|
|
@@ -133,6 +276,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
133
276
|
type: Injectable
|
|
134
277
|
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.ConfigService }] });
|
|
135
278
|
|
|
279
|
+
/*
|
|
280
|
+
* Copyright 2025 Epistola.
|
|
281
|
+
*
|
|
282
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
283
|
+
* you may not use this file except in compliance with the License.
|
|
284
|
+
* You may obtain a copy of the License at
|
|
285
|
+
*
|
|
286
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
287
|
+
*
|
|
288
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
289
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
290
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
291
|
+
* See the License for the specific language governing permissions and
|
|
292
|
+
* limitations under the License.
|
|
293
|
+
*
|
|
294
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
295
|
+
*/
|
|
136
296
|
/**
|
|
137
297
|
* Registers the Epistola admin page menu item under the Admin > Other section.
|
|
138
298
|
* Instantiated eagerly via ENVIRONMENT_INITIALIZER so the menu item
|
|
@@ -169,6 +329,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
169
329
|
type: Injectable
|
|
170
330
|
}], ctorParameters: () => [{ type: i3.MenuService }] });
|
|
171
331
|
|
|
332
|
+
/*
|
|
333
|
+
* Copyright 2025 Epistola.
|
|
334
|
+
*
|
|
335
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
336
|
+
* you may not use this file except in compliance with the License.
|
|
337
|
+
* You may obtain a copy of the License at
|
|
338
|
+
*
|
|
339
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
340
|
+
*
|
|
341
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
342
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
343
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
344
|
+
* See the License for the specific language governing permissions and
|
|
345
|
+
* limitations under the License.
|
|
346
|
+
*
|
|
347
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
348
|
+
*/
|
|
172
349
|
/**
|
|
173
350
|
* Service for interacting with Epistola plugin API endpoints.
|
|
174
351
|
* Provides methods to fetch templates, environments, variants,
|
|
@@ -236,6 +413,16 @@ class EpistolaPluginService {
|
|
|
236
413
|
params: { processDefinitionKey },
|
|
237
414
|
});
|
|
238
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Get the raw `dataMapping` JSONata of a generate-document process link, identified by its
|
|
418
|
+
* process definition key and activity id. The override builder extracts the referenced
|
|
419
|
+
* `$doc`/`$pv` paths from it to guide the author. Returns an empty mapping when unresolved.
|
|
420
|
+
*/
|
|
421
|
+
getProcessLinkMapping(processDefinitionKey, activityId) {
|
|
422
|
+
return this.http.get(`${this.apiEndpoint}/process-link-mapping`, {
|
|
423
|
+
params: { processDefinitionKey, activityId },
|
|
424
|
+
});
|
|
425
|
+
}
|
|
239
426
|
/**
|
|
240
427
|
* Get variable suggestions for JSONata autocompletion.
|
|
241
428
|
*/
|
|
@@ -262,13 +449,13 @@ class EpistolaPluginService {
|
|
|
262
449
|
/**
|
|
263
450
|
* Get a dynamically generated Formio form for retrying a failed document generation.
|
|
264
451
|
*
|
|
452
|
+
* The backend derives the process instance and case document from the authorized task,
|
|
453
|
+
* so only the task id (and optionally the source activity) is sent.
|
|
454
|
+
*
|
|
265
455
|
* @param taskId Operaton user task id (required — backend authorizes via OperatonTask:VIEW)
|
|
266
456
|
*/
|
|
267
|
-
getRetryForm(taskId,
|
|
268
|
-
const params = { taskId
|
|
269
|
-
if (documentId) {
|
|
270
|
-
params['documentId'] = documentId;
|
|
271
|
-
}
|
|
457
|
+
getRetryForm(taskId, sourceActivityId) {
|
|
458
|
+
const params = { taskId };
|
|
272
459
|
if (sourceActivityId) {
|
|
273
460
|
params['sourceActivityId'] = sourceActivityId;
|
|
274
461
|
}
|
|
@@ -327,107 +514,158 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
327
514
|
type: Injectable
|
|
328
515
|
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.ConfigService }] });
|
|
329
516
|
|
|
330
|
-
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
517
|
+
/*
|
|
518
|
+
* Copyright 2025 Epistola.
|
|
519
|
+
*
|
|
520
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
521
|
+
* you may not use this file except in compliance with the License.
|
|
522
|
+
* You may obtain a copy of the License at
|
|
523
|
+
*
|
|
524
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
525
|
+
*
|
|
526
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
527
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
528
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
529
|
+
* See the License for the specific language governing permissions and
|
|
530
|
+
* limitations under the License.
|
|
531
|
+
*
|
|
532
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
345
533
|
*/
|
|
346
|
-
class EpistolaTaskContextService {
|
|
347
|
-
_taskInstanceId$ = new BehaviorSubject(null);
|
|
348
|
-
taskInstanceId$ = this._taskInstanceId$.asObservable();
|
|
349
|
-
get taskInstanceId() {
|
|
350
|
-
return this._taskInstanceId$.value;
|
|
351
|
-
}
|
|
352
|
-
setTaskInstanceId(id) {
|
|
353
|
-
this._taskInstanceId$.next(id);
|
|
354
|
-
}
|
|
355
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
356
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, providedIn: 'root' });
|
|
357
|
-
}
|
|
358
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, decorators: [{
|
|
359
|
-
type: Injectable,
|
|
360
|
-
args: [{ providedIn: 'root' }]
|
|
361
|
-
}] });
|
|
362
|
-
|
|
363
534
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
535
|
+
* Helpers for reading the active user task's id out of a Valtimo task form that was
|
|
536
|
+
* prefilled server-side by the {@code epistola:} value resolver (see the backend
|
|
537
|
+
* {@code EpistolaTaskValueResolverFactory}).
|
|
538
|
+
*
|
|
539
|
+
* <p>Background: the Epistola Formio components need the id of the user task whose form
|
|
540
|
+
* they're rendered in, to authorize their backend requests ({@code OperatonTask:VIEW}).
|
|
541
|
+
* Valtimo exposes no service that carries the task id to a custom Formio component at
|
|
542
|
+
* runtime, and earlier URL-sniffing only worked in the direct task-open flow (the
|
|
543
|
+
* task-list / case-detail flow bulk-fetches process links and never fires the per-task
|
|
544
|
+
* call).
|
|
545
|
+
*
|
|
546
|
+
* <p>Form prefill, however, runs server-side in every flow. A form field with
|
|
547
|
+
* {@code properties.sourceKey = "epistola:taskId"} is filled with the task id at prefill
|
|
548
|
+
* time (by the backend {@code EpistolaTaskValueResolverFactory}); this helper reads it back
|
|
549
|
+
* from the Formio root — robustly, regardless of how the task was opened.
|
|
367
550
|
*/
|
|
551
|
+
/** The value-resolver source key that yields the current task id at prefill time. */
|
|
552
|
+
const PREFILLED_TASK_ID_SOURCE_KEY = 'epistola:taskId';
|
|
553
|
+
/** Conventional key of the hidden carrier field that holds the prefilled task id. */
|
|
554
|
+
const PREFILLED_TASK_ID_DATA_KEY = 'epistolaTaskId';
|
|
368
555
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
556
|
+
* Hidden Formio child component that carries the prefilled task id. It is embedded as a
|
|
557
|
+
* nested component inside each Epistola task component's schema, so dropping that component
|
|
558
|
+
* brings the carrier with it — the form author never adds a separate field. Valtimo's
|
|
559
|
+
* server-side prefill fills its {@code defaultValue} from the {@code epistola:taskId}
|
|
560
|
+
* value resolver; {@link readPrefilledTaskId} reads it back from the form definition.
|
|
371
561
|
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
* so we don't accidentally match a longer trailing segment.
|
|
562
|
+
* {@code persistent: false} keeps the value out of the submission, so the task id never
|
|
563
|
+
* lands in the case document / process variables.
|
|
375
564
|
*/
|
|
376
|
-
const
|
|
565
|
+
const PREFILLED_TASK_ID_CARRIER = {
|
|
566
|
+
type: 'hidden',
|
|
567
|
+
key: PREFILLED_TASK_ID_DATA_KEY,
|
|
568
|
+
input: true,
|
|
569
|
+
persistent: false,
|
|
570
|
+
label: 'Epistola Task Id',
|
|
571
|
+
properties: { sourceKey: PREFILLED_TASK_ID_SOURCE_KEY },
|
|
572
|
+
};
|
|
377
573
|
/**
|
|
378
|
-
*
|
|
379
|
-
*
|
|
574
|
+
* Reads the prefilled task id from a Formio webform/wizard root, or null when absent.
|
|
575
|
+
*
|
|
576
|
+
* Looks in two places, in order:
|
|
577
|
+
* 1. The (prefilled) form definition — any component whose {@code properties.sourceKey}
|
|
578
|
+
* is {@code epistola:taskId} carries the task id in its {@code defaultValue}. This works
|
|
579
|
+
* even when the carrier is a hidden field that Formio doesn't surface into submission data.
|
|
580
|
+
* 2. The submission data under {@link PREFILLED_TASK_ID_DATA_KEY}, for a rendered sibling
|
|
581
|
+
* hidden field whose value Formio copied into {@code root.data}.
|
|
380
582
|
*/
|
|
381
|
-
function
|
|
382
|
-
if (
|
|
583
|
+
function readPrefilledTaskId(root) {
|
|
584
|
+
if (!root) {
|
|
383
585
|
return null;
|
|
384
|
-
|
|
385
|
-
|
|
586
|
+
}
|
|
587
|
+
const fromForm = findSourceKeyDefaultValue(root.form, PREFILLED_TASK_ID_SOURCE_KEY);
|
|
588
|
+
if (typeof fromForm === 'string' && fromForm.length > 0) {
|
|
589
|
+
return fromForm;
|
|
590
|
+
}
|
|
591
|
+
const fromData = root.data?.[PREFILLED_TASK_ID_DATA_KEY];
|
|
592
|
+
if (typeof fromData === 'string' && fromData.length > 0) {
|
|
593
|
+
return fromData;
|
|
594
|
+
}
|
|
595
|
+
return null;
|
|
386
596
|
}
|
|
387
|
-
|
|
388
597
|
/**
|
|
389
|
-
*
|
|
390
|
-
* {@
|
|
391
|
-
* {@code
|
|
392
|
-
* {@code TaskDetailContentComponent.loadTaskDetails(...)} fires unconditionally
|
|
393
|
-
* before any task form is rendered (see @valtimo/task internals).
|
|
394
|
-
*
|
|
395
|
-
* <p>This interceptor does <b>not</b> modify the outgoing request. It only
|
|
396
|
-
* captures the taskId from the URL.
|
|
397
|
-
*
|
|
398
|
-
* <p>Workaround for Valtimo 13.21 not exposing taskInstanceId through any
|
|
399
|
-
* injectable service. Remove once upstream adds e.g.
|
|
400
|
-
* {@code FormIoStateService.setTaskInstanceId(...)}.
|
|
401
|
-
*
|
|
402
|
-
* <p>The actual URL-matching logic lives in
|
|
403
|
-
* {@link extractTaskInstanceIdFromUrl} so it can be unit-tested without an
|
|
404
|
-
* Angular harness.
|
|
598
|
+
* Deep-walks a form definition node looking for a component whose
|
|
599
|
+
* {@code properties.sourceKey} equals {@code sourceKey}, and returns its
|
|
600
|
+
* {@code defaultValue} (the prefilled value). Returns null when not found.
|
|
405
601
|
*/
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
602
|
+
function findSourceKeyDefaultValue(node, sourceKey) {
|
|
603
|
+
if (Array.isArray(node)) {
|
|
604
|
+
for (const item of node) {
|
|
605
|
+
const found = findSourceKeyDefaultValue(item, sourceKey);
|
|
606
|
+
if (found != null) {
|
|
607
|
+
return found;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
if (node && typeof node === 'object') {
|
|
613
|
+
if (node.properties?.sourceKey === sourceKey && typeof node.defaultValue === 'string') {
|
|
614
|
+
return node.defaultValue;
|
|
615
|
+
}
|
|
616
|
+
for (const key of Object.keys(node)) {
|
|
617
|
+
const found = findSourceKeyDefaultValue(node[key], sourceKey);
|
|
618
|
+
if (found != null) {
|
|
619
|
+
return found;
|
|
620
|
+
}
|
|
415
621
|
}
|
|
416
|
-
return next.handle(request);
|
|
417
622
|
}
|
|
418
|
-
|
|
419
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextInterceptor });
|
|
623
|
+
return null;
|
|
420
624
|
}
|
|
421
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextInterceptor, decorators: [{
|
|
422
|
-
type: Injectable
|
|
423
|
-
}], ctorParameters: () => [{ type: EpistolaTaskContextService }] });
|
|
424
625
|
|
|
626
|
+
/*
|
|
627
|
+
* Copyright 2025 Epistola.
|
|
628
|
+
*
|
|
629
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
630
|
+
* you may not use this file except in compliance with the License.
|
|
631
|
+
* You may obtain a copy of the License at
|
|
632
|
+
*
|
|
633
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
634
|
+
*
|
|
635
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
636
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
637
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
638
|
+
* See the License for the specific language governing permissions and
|
|
639
|
+
* limitations under the License.
|
|
640
|
+
*
|
|
641
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
642
|
+
*/
|
|
643
|
+
|
|
644
|
+
/*
|
|
645
|
+
* Copyright 2025 Epistola.
|
|
646
|
+
*
|
|
647
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
648
|
+
* you may not use this file except in compliance with the License.
|
|
649
|
+
* You may obtain a copy of the License at
|
|
650
|
+
*
|
|
651
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
652
|
+
*
|
|
653
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
654
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
655
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
656
|
+
* See the License for the specific language governing permissions and
|
|
657
|
+
* limitations under the License.
|
|
658
|
+
*
|
|
659
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
660
|
+
*/
|
|
425
661
|
class EpistolaConfigurationComponent {
|
|
426
662
|
save$;
|
|
427
663
|
disabled$;
|
|
428
664
|
pluginId;
|
|
429
665
|
prefillConfiguration$;
|
|
430
666
|
valid = new EventEmitter();
|
|
667
|
+
// Framework's PluginConfigurationData (index type) to satisfy the invariant
|
|
668
|
+
// EventEmitter contract under strict mode; emitted values remain the typed config.
|
|
431
669
|
configuration = new EventEmitter();
|
|
432
670
|
/** Epistola slug pattern: lowercase alphanumeric with hyphens, no leading/trailing hyphens. */
|
|
433
671
|
static SLUG_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
@@ -476,7 +714,7 @@ class EpistolaConfigurationComponent {
|
|
|
476
714
|
combineLatest([this.formValue$, this.valid$])
|
|
477
715
|
.pipe(take(1))
|
|
478
716
|
.subscribe(([formValue, valid]) => {
|
|
479
|
-
if (valid) {
|
|
717
|
+
if (valid && formValue) {
|
|
480
718
|
this.configuration.emit(formValue);
|
|
481
719
|
}
|
|
482
720
|
});
|
|
@@ -502,12 +740,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
502
740
|
type: Output
|
|
503
741
|
}] } });
|
|
504
742
|
|
|
743
|
+
/*
|
|
744
|
+
* Copyright 2025 Epistola.
|
|
745
|
+
*
|
|
746
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
747
|
+
* you may not use this file except in compliance with the License.
|
|
748
|
+
* You may obtain a copy of the License at
|
|
749
|
+
*
|
|
750
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
751
|
+
*
|
|
752
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
753
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
754
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
755
|
+
* See the License for the specific language governing permissions and
|
|
756
|
+
* limitations under the License.
|
|
757
|
+
*
|
|
758
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
759
|
+
*/
|
|
505
760
|
/**
|
|
506
761
|
* Shared state for the JSONata completion provider.
|
|
507
762
|
* Updated by the editor component when suggestions/functions change.
|
|
508
763
|
*/
|
|
509
764
|
const jsonataCompletionData = {
|
|
510
|
-
|
|
765
|
+
// Context variables in scope, keyed by name (without the `$`), each mapping to
|
|
766
|
+
// its known field/path suggestions. The completion provider derives both the
|
|
767
|
+
// `$`-variable list and the `$<name>.` field list from this — adding a new
|
|
768
|
+
// context variable needs no provider change, just another key here.
|
|
769
|
+
// e.g. { doc: ['name', 'address.street'], pv: ['amount'], form: ['voornaam'] }
|
|
770
|
+
variables: {},
|
|
511
771
|
functions: [],
|
|
512
772
|
};
|
|
513
773
|
/**
|
|
@@ -590,7 +850,8 @@ function registerJsonataLanguage(monaco) {
|
|
|
590
850
|
const CompletionItemKind = monaco.languages.CompletionItemKind;
|
|
591
851
|
// After "$" — suggest variables and functions
|
|
592
852
|
if (textUntilPosition.endsWith('$')) {
|
|
593
|
-
|
|
853
|
+
// Variables are whatever the host put in scope (doc/pv/case/form/…).
|
|
854
|
+
suggestions.push(...Object.keys(jsonataCompletionData.variables).map((v) => ({
|
|
594
855
|
label: `$${v}`,
|
|
595
856
|
kind: CompletionItemKind.Variable,
|
|
596
857
|
insertText: v,
|
|
@@ -645,27 +906,20 @@ function registerJsonataLanguage(monaco) {
|
|
|
645
906
|
});
|
|
646
907
|
}
|
|
647
908
|
}
|
|
648
|
-
// After "
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
909
|
+
// After "$<name>." — suggest that variable's fields. The variable name is
|
|
910
|
+
// captured generically, so doc/pv/case/form/… all work from one branch.
|
|
911
|
+
const fieldMatch = textUntilPosition.match(/\$([a-zA-Z_]\w*)\.[a-zA-Z_]*$/);
|
|
912
|
+
if (fieldMatch) {
|
|
913
|
+
const fields = jsonataCompletionData.variables[fieldMatch[1]] || [];
|
|
914
|
+
for (const field of fields) {
|
|
652
915
|
suggestions.push({
|
|
653
|
-
label:
|
|
916
|
+
label: field,
|
|
654
917
|
kind: CompletionItemKind.Field,
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
// After "$pv." — suggest process variables
|
|
661
|
-
if (/\$pv\.\s*$/.test(textUntilPosition) || /\$pv\.[a-zA-Z_]*$/.test(textUntilPosition)) {
|
|
662
|
-
const pvNames = jsonataCompletionData.suggestions?.pv || [];
|
|
663
|
-
for (const name of pvNames) {
|
|
664
|
-
suggestions.push({
|
|
665
|
-
label: name,
|
|
666
|
-
kind: CompletionItemKind.Variable,
|
|
667
|
-
insertText: name,
|
|
668
|
-
detail: 'Process variable',
|
|
918
|
+
// Path-style suggestions (doc/pv: `a.b`, `a[].b`) insert as-is; keys
|
|
919
|
+
// with characters invalid in a bare JSONata name (e.g. "pv:motivation")
|
|
920
|
+
// are backtick-quoted so they resolve as a single property.
|
|
921
|
+
insertText: /[^A-Za-z0-9_.[\]]/.test(field) ? '`' + field + '`' : field,
|
|
922
|
+
detail: `$${fieldMatch[1]} field`,
|
|
669
923
|
});
|
|
670
924
|
}
|
|
671
925
|
}
|
|
@@ -674,12 +928,37 @@ function registerJsonataLanguage(monaco) {
|
|
|
674
928
|
});
|
|
675
929
|
}
|
|
676
930
|
|
|
677
|
-
|
|
931
|
+
/*
|
|
932
|
+
* Copyright 2025 Epistola.
|
|
933
|
+
*
|
|
934
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
935
|
+
* you may not use this file except in compliance with the License.
|
|
936
|
+
* You may obtain a copy of the License at
|
|
937
|
+
*
|
|
938
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
939
|
+
*
|
|
940
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
941
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
942
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
943
|
+
* See the License for the specific language governing permissions and
|
|
944
|
+
* limitations under the License.
|
|
945
|
+
*
|
|
946
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
947
|
+
*/
|
|
948
|
+
const jsonata$4 = _jsonata.default || _jsonata;
|
|
678
949
|
class JsonataEditorComponent {
|
|
679
950
|
expression = '';
|
|
680
951
|
disabled = false;
|
|
681
|
-
|
|
952
|
+
/**
|
|
953
|
+
* Context variables in scope, keyed by name (without `$`), each mapping to its
|
|
954
|
+
* field/path suggestions — e.g. `{ doc: [...], pv: [...] }` for the data
|
|
955
|
+
* mapping, `{ form: [...] }` for the override builder. Drives both the
|
|
956
|
+
* `$`-variable list and `$<name>.` field completion.
|
|
957
|
+
*/
|
|
958
|
+
contextVariables = {};
|
|
682
959
|
functions = [];
|
|
960
|
+
/** Footer hint listing the context variables in scope. */
|
|
961
|
+
variablesHint = '$doc · $pv · $case';
|
|
683
962
|
expressionChange = new EventEmitter();
|
|
684
963
|
validChange = new EventEmitter();
|
|
685
964
|
editorModel = { value: '', language: 'jsonata' };
|
|
@@ -709,8 +988,8 @@ class JsonataEditorComponent {
|
|
|
709
988
|
this.editorModel = { value: this.expression || '', language: 'jsonata' };
|
|
710
989
|
this.validate$.next(this.expression);
|
|
711
990
|
}
|
|
712
|
-
if (changes['
|
|
713
|
-
jsonataCompletionData.
|
|
991
|
+
if (changes['contextVariables']) {
|
|
992
|
+
jsonataCompletionData.variables = this.contextVariables || {};
|
|
714
993
|
}
|
|
715
994
|
if (changes['functions']) {
|
|
716
995
|
jsonataCompletionData.functions = this.functions;
|
|
@@ -738,7 +1017,7 @@ class JsonataEditorComponent {
|
|
|
738
1017
|
if (m) {
|
|
739
1018
|
registerJsonataLanguage(m);
|
|
740
1019
|
this.languageRegistered = true;
|
|
741
|
-
jsonataCompletionData.
|
|
1020
|
+
jsonataCompletionData.variables = this.contextVariables || {};
|
|
742
1021
|
jsonataCompletionData.functions = this.functions;
|
|
743
1022
|
}
|
|
744
1023
|
}
|
|
@@ -749,7 +1028,7 @@ class JsonataEditorComponent {
|
|
|
749
1028
|
return;
|
|
750
1029
|
}
|
|
751
1030
|
try {
|
|
752
|
-
jsonata$
|
|
1031
|
+
jsonata$4(value);
|
|
753
1032
|
this.error = null;
|
|
754
1033
|
this.validChange.emit(true);
|
|
755
1034
|
}
|
|
@@ -759,7 +1038,7 @@ class JsonataEditorComponent {
|
|
|
759
1038
|
}
|
|
760
1039
|
}
|
|
761
1040
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: JsonataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
762
|
-
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",
|
|
1041
|
+
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", contextVariables: "contextVariables", functions: "functions", variablesHint: "variablesHint" }, outputs: { expressionChange: "expressionChange", validChange: "validChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
763
1042
|
<div class="jsonata-editor">
|
|
764
1043
|
<valtimo-editor
|
|
765
1044
|
[model]="editorModel"
|
|
@@ -772,7 +1051,7 @@ class JsonataEditorComponent {
|
|
|
772
1051
|
<div class="jsonata-editor__footer">
|
|
773
1052
|
<span *ngIf="error" class="jsonata-editor__error">{{ error }}</span>
|
|
774
1053
|
<span *ngIf="!error && expression" class="jsonata-editor__valid">✓</span>
|
|
775
|
-
<span class="jsonata-editor__variables"
|
|
1054
|
+
<span class="jsonata-editor__variables">{{ variablesHint }}</span>
|
|
776
1055
|
</div>
|
|
777
1056
|
</div>
|
|
778
1057
|
`, 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"] }] });
|
|
@@ -792,7 +1071,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
792
1071
|
<div class="jsonata-editor__footer">
|
|
793
1072
|
<span *ngIf="error" class="jsonata-editor__error">{{ error }}</span>
|
|
794
1073
|
<span *ngIf="!error && expression" class="jsonata-editor__valid">✓</span>
|
|
795
|
-
<span class="jsonata-editor__variables"
|
|
1074
|
+
<span class="jsonata-editor__variables">{{ variablesHint }}</span>
|
|
796
1075
|
</div>
|
|
797
1076
|
</div>
|
|
798
1077
|
`, 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"] }]
|
|
@@ -800,16 +1079,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
800
1079
|
type: Input
|
|
801
1080
|
}], disabled: [{
|
|
802
1081
|
type: Input
|
|
803
|
-
}],
|
|
1082
|
+
}], contextVariables: [{
|
|
804
1083
|
type: Input
|
|
805
1084
|
}], functions: [{
|
|
806
1085
|
type: Input
|
|
1086
|
+
}], variablesHint: [{
|
|
1087
|
+
type: Input
|
|
807
1088
|
}], expressionChange: [{
|
|
808
1089
|
type: Output
|
|
809
1090
|
}], validChange: [{
|
|
810
1091
|
type: Output
|
|
811
1092
|
}] } });
|
|
812
1093
|
|
|
1094
|
+
/*
|
|
1095
|
+
* Copyright 2025 Epistola.
|
|
1096
|
+
*
|
|
1097
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
1098
|
+
* you may not use this file except in compliance with the License.
|
|
1099
|
+
* You may obtain a copy of the License at
|
|
1100
|
+
*
|
|
1101
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
1102
|
+
*
|
|
1103
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1104
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
1105
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1106
|
+
* See the License for the specific language governing permissions and
|
|
1107
|
+
* limitations under the License.
|
|
1108
|
+
*
|
|
1109
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
1110
|
+
*/
|
|
813
1111
|
class ExpectedStructureComponent {
|
|
814
1112
|
templateFields = [];
|
|
815
1113
|
structureText = '{}';
|
|
@@ -874,6 +1172,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
874
1172
|
type: Input
|
|
875
1173
|
}] } });
|
|
876
1174
|
|
|
1175
|
+
/*
|
|
1176
|
+
* Copyright 2025 Epistola.
|
|
1177
|
+
*
|
|
1178
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
1179
|
+
* you may not use this file except in compliance with the License.
|
|
1180
|
+
* You may obtain a copy of the License at
|
|
1181
|
+
*
|
|
1182
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
1183
|
+
*
|
|
1184
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1185
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
1186
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1187
|
+
* See the License for the specific language governing permissions and
|
|
1188
|
+
* limitations under the License.
|
|
1189
|
+
*
|
|
1190
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
1191
|
+
*/
|
|
877
1192
|
class BuilderFieldComponent {
|
|
878
1193
|
field;
|
|
879
1194
|
path = [];
|
|
@@ -953,7 +1268,7 @@ class BuilderFieldComponent {
|
|
|
953
1268
|
></epistola-builder-field>
|
|
954
1269
|
</div>
|
|
955
1270
|
</div>
|
|
956
|
-
`, 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:
|
|
1271
|
+
`, 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: i3$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$1.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: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
957
1272
|
}
|
|
958
1273
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BuilderFieldComponent, decorators: [{
|
|
959
1274
|
type: Component,
|
|
@@ -1044,7 +1359,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1044
1359
|
type: Output
|
|
1045
1360
|
}] } });
|
|
1046
1361
|
|
|
1047
|
-
|
|
1362
|
+
/*
|
|
1363
|
+
* Copyright 2025 Epistola.
|
|
1364
|
+
*
|
|
1365
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
1366
|
+
* you may not use this file except in compliance with the License.
|
|
1367
|
+
* You may obtain a copy of the License at
|
|
1368
|
+
*
|
|
1369
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
1370
|
+
*
|
|
1371
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1372
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
1373
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1374
|
+
* See the License for the specific language governing permissions and
|
|
1375
|
+
* limitations under the License.
|
|
1376
|
+
*
|
|
1377
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
1378
|
+
*/
|
|
1379
|
+
const jsonata$3 = _jsonata.default || _jsonata;
|
|
1048
1380
|
/**
|
|
1049
1381
|
* Parse a JSONata expression into BuilderField array.
|
|
1050
1382
|
* Only supports top-level object literals with simple path references or nested objects.
|
|
@@ -1055,7 +1387,7 @@ function parseJsonataToBuilder(expression) {
|
|
|
1055
1387
|
return [];
|
|
1056
1388
|
}
|
|
1057
1389
|
try {
|
|
1058
|
-
const ast = jsonata(expression).ast();
|
|
1390
|
+
const ast = jsonata$3(expression).ast();
|
|
1059
1391
|
if (ast.type === 'unary' && ast.value === '{') {
|
|
1060
1392
|
return parseObjectEntries(ast.lhs, expression);
|
|
1061
1393
|
}
|
|
@@ -1201,6 +1533,23 @@ function formatFieldEntry(field, indent = ' ') {
|
|
|
1201
1533
|
return `${indent}"${field.name}": ${value}`;
|
|
1202
1534
|
}
|
|
1203
1535
|
|
|
1536
|
+
/*
|
|
1537
|
+
* Copyright 2025 Epistola.
|
|
1538
|
+
*
|
|
1539
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
1540
|
+
* you may not use this file except in compliance with the License.
|
|
1541
|
+
* You may obtain a copy of the License at
|
|
1542
|
+
*
|
|
1543
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
1544
|
+
*
|
|
1545
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1546
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
1547
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1548
|
+
* See the License for the specific language governing permissions and
|
|
1549
|
+
* limitations under the License.
|
|
1550
|
+
*
|
|
1551
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
1552
|
+
*/
|
|
1204
1553
|
class MappingBuilderComponent {
|
|
1205
1554
|
expression = '';
|
|
1206
1555
|
templateFields = [];
|
|
@@ -1402,6 +1751,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1402
1751
|
type: Output
|
|
1403
1752
|
}] } });
|
|
1404
1753
|
|
|
1754
|
+
/*
|
|
1755
|
+
* Copyright 2025 Epistola.
|
|
1756
|
+
*
|
|
1757
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
1758
|
+
* you may not use this file except in compliance with the License.
|
|
1759
|
+
* You may obtain a copy of the License at
|
|
1760
|
+
*
|
|
1761
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
1762
|
+
*
|
|
1763
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1764
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
1765
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1766
|
+
* See the License for the specific language governing permissions and
|
|
1767
|
+
* limitations under the License.
|
|
1768
|
+
*
|
|
1769
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
1770
|
+
*/
|
|
1405
1771
|
class MappingPreviewComponent {
|
|
1406
1772
|
epistolaPluginService;
|
|
1407
1773
|
expression = '';
|
|
@@ -1536,7 +1902,7 @@ class MappingPreviewComponent {
|
|
|
1536
1902
|
<strong>{{ missingRequired.join(', ') }}</strong>
|
|
1537
1903
|
</div>
|
|
1538
1904
|
</div>
|
|
1539
|
-
`, 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:
|
|
1905
|
+
`, 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: i3$1.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: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.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" }] });
|
|
1540
1906
|
}
|
|
1541
1907
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MappingPreviewComponent, decorators: [{
|
|
1542
1908
|
type: Component,
|
|
@@ -1607,7 +1973,227 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1607
1973
|
type: Input
|
|
1608
1974
|
}] } });
|
|
1609
1975
|
|
|
1610
|
-
|
|
1976
|
+
/*
|
|
1977
|
+
* Copyright 2025 Epistola.
|
|
1978
|
+
*
|
|
1979
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
1980
|
+
* you may not use this file except in compliance with the License.
|
|
1981
|
+
* You may obtain a copy of the License at
|
|
1982
|
+
*
|
|
1983
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
1984
|
+
*
|
|
1985
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1986
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
1987
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1988
|
+
* See the License for the specific language governing permissions and
|
|
1989
|
+
* limitations under the License.
|
|
1990
|
+
*
|
|
1991
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
1992
|
+
*/
|
|
1993
|
+
const jsonata$2 = _jsonata.default || _jsonata;
|
|
1994
|
+
const BARE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1995
|
+
/**
|
|
1996
|
+
* Render a `$form` reference for a form-field key. Keys that aren't bare
|
|
1997
|
+
* identifiers (e.g. `pv:motivation`) are backtick-quoted so JSONata treats the
|
|
1998
|
+
* whole key as a single property name — matching the old flat `formData[key]`
|
|
1999
|
+
* lookup rather than a nested path traversal.
|
|
2000
|
+
*/
|
|
2001
|
+
function formRef(key) {
|
|
2002
|
+
return BARE_IDENTIFIER.test(key) ? `$form.${key}` : '$form.`' + key + '`';
|
|
2003
|
+
}
|
|
2004
|
+
function insert(tree, segments, leaf) {
|
|
2005
|
+
let node = tree;
|
|
2006
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
2007
|
+
const seg = segments[i];
|
|
2008
|
+
if (typeof node[seg] !== 'object') {
|
|
2009
|
+
node[seg] = {};
|
|
2010
|
+
}
|
|
2011
|
+
node = node[seg];
|
|
2012
|
+
}
|
|
2013
|
+
node[segments[segments.length - 1]] = leaf;
|
|
2014
|
+
}
|
|
2015
|
+
function emit(node, indent) {
|
|
2016
|
+
if (typeof node === 'string') {
|
|
2017
|
+
return node;
|
|
2018
|
+
}
|
|
2019
|
+
const inner = indent + ' ';
|
|
2020
|
+
const entries = Object.entries(node).map(([key, value]) => `${inner}"${key}": ${emit(value, inner)}`);
|
|
2021
|
+
return `{\n${entries.join(',\n')}\n${indent}}`;
|
|
2022
|
+
}
|
|
2023
|
+
/**
|
|
2024
|
+
* Serialize simple-table rows into a JSONata expression that maps `$form` onto
|
|
2025
|
+
* a `{ doc, pv }` overlay. Dot-notation input paths expand into nested object
|
|
2026
|
+
* literals (so `beslissing.tekst` becomes `{ "beslissing": { "tekst": ... } }`),
|
|
2027
|
+
* preserving the legacy override semantics.
|
|
2028
|
+
*/
|
|
2029
|
+
function serializeOverrideRows(rows) {
|
|
2030
|
+
const scopes = {};
|
|
2031
|
+
for (const row of rows) {
|
|
2032
|
+
if (!row.inputPath || !row.formFieldKey)
|
|
2033
|
+
continue;
|
|
2034
|
+
if (row.scope !== 'doc' && row.scope !== 'pv')
|
|
2035
|
+
continue;
|
|
2036
|
+
if (typeof scopes[row.scope] !== 'object') {
|
|
2037
|
+
scopes[row.scope] = {};
|
|
2038
|
+
}
|
|
2039
|
+
insert(scopes[row.scope], row.inputPath.split('.'), formRef(row.formFieldKey));
|
|
2040
|
+
}
|
|
2041
|
+
if (Object.keys(scopes).length === 0) {
|
|
2042
|
+
return '';
|
|
2043
|
+
}
|
|
2044
|
+
return emit(scopes, '');
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Parse a JSONata override expression back into simple-table rows, or `null`
|
|
2048
|
+
* when the expression is richer than the simple table can represent (anything
|
|
2049
|
+
* beyond `doc`/`pv` objects whose leaves are plain `$form.<key>` references).
|
|
2050
|
+
* A `null` result is the builder's signal to fall back to advanced mode.
|
|
2051
|
+
*/
|
|
2052
|
+
function parseOverrideJsonata(expression) {
|
|
2053
|
+
if (!expression || !expression.trim()) {
|
|
2054
|
+
return [];
|
|
2055
|
+
}
|
|
2056
|
+
let ast;
|
|
2057
|
+
try {
|
|
2058
|
+
ast = jsonata$2(expression).ast();
|
|
2059
|
+
}
|
|
2060
|
+
catch {
|
|
2061
|
+
return null;
|
|
2062
|
+
}
|
|
2063
|
+
if (!(ast?.type === 'unary' && ast.value === '{')) {
|
|
2064
|
+
return null;
|
|
2065
|
+
}
|
|
2066
|
+
const rows = [];
|
|
2067
|
+
for (const entry of ast.lhs || []) {
|
|
2068
|
+
const scope = entry?.[0]?.value;
|
|
2069
|
+
const valueNode = entry?.[1];
|
|
2070
|
+
if (scope !== 'doc' && scope !== 'pv') {
|
|
2071
|
+
return null;
|
|
2072
|
+
}
|
|
2073
|
+
if (!(valueNode?.type === 'unary' && valueNode.value === '{')) {
|
|
2074
|
+
return null;
|
|
2075
|
+
}
|
|
2076
|
+
if (!collectLeaves(valueNode.lhs || [], [], scope, rows)) {
|
|
2077
|
+
return null;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
return rows;
|
|
2081
|
+
}
|
|
2082
|
+
function collectLeaves(entries, prefix, scope, rows) {
|
|
2083
|
+
for (const [keyNode, valueNode] of entries) {
|
|
2084
|
+
const segment = keyNode?.value;
|
|
2085
|
+
if (typeof segment !== 'string') {
|
|
2086
|
+
return false;
|
|
2087
|
+
}
|
|
2088
|
+
const path = [...prefix, segment];
|
|
2089
|
+
if (valueNode?.type === 'unary' && valueNode.value === '{') {
|
|
2090
|
+
if (!collectLeaves(valueNode.lhs || [], path, scope, rows)) {
|
|
2091
|
+
return false;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
else {
|
|
2095
|
+
const formFieldKey = formKeyOf(valueNode);
|
|
2096
|
+
if (formFieldKey === null) {
|
|
2097
|
+
return false;
|
|
2098
|
+
}
|
|
2099
|
+
rows.push({ scope, inputPath: path.join('.'), formFieldKey });
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
return true;
|
|
2103
|
+
}
|
|
2104
|
+
/** Extract the form-field key from a `$form.<key>` path node, or null. */
|
|
2105
|
+
function formKeyOf(node) {
|
|
2106
|
+
if (node?.type === 'path' &&
|
|
2107
|
+
node.steps?.length === 2 &&
|
|
2108
|
+
node.steps[0].type === 'variable' &&
|
|
2109
|
+
node.steps[0].value === 'form' &&
|
|
2110
|
+
typeof node.steps[1]?.value === 'string') {
|
|
2111
|
+
return node.steps[1].value;
|
|
2112
|
+
}
|
|
2113
|
+
return null;
|
|
2114
|
+
}
|
|
2115
|
+
/** Whether the expression can be edited in the simple table (round-trippable). */
|
|
2116
|
+
function isRoundTrippable(expression) {
|
|
2117
|
+
return parseOverrideJsonata(expression) !== null;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
/*
|
|
2121
|
+
* Copyright 2025 Epistola.
|
|
2122
|
+
*
|
|
2123
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
2124
|
+
* you may not use this file except in compliance with the License.
|
|
2125
|
+
* You may obtain a copy of the License at
|
|
2126
|
+
*
|
|
2127
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
2128
|
+
*
|
|
2129
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2130
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
2131
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2132
|
+
* See the License for the specific language governing permissions and
|
|
2133
|
+
* limitations under the License.
|
|
2134
|
+
*
|
|
2135
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
2136
|
+
*/
|
|
2137
|
+
/**
|
|
2138
|
+
* Prefix that marked a form-field reference in the legacy override-mapping
|
|
2139
|
+
* object format (e.g. `"form:motivationField"`).
|
|
2140
|
+
*/
|
|
2141
|
+
const FORM_REF_PREFIX = 'form:';
|
|
2142
|
+
/**
|
|
2143
|
+
* Whether a stored override-mapping value is in the legacy **object** format
|
|
2144
|
+
* (`{ scope: { inputPath: "form:fieldKey" } }`) rather than the new JSONata
|
|
2145
|
+
* **string** format.
|
|
2146
|
+
*/
|
|
2147
|
+
function isLegacyOverrideMapping(value) {
|
|
2148
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
2149
|
+
}
|
|
2150
|
+
/**
|
|
2151
|
+
* TEMPORARY migration shim.
|
|
2152
|
+
*
|
|
2153
|
+
* Converts a legacy override-mapping object into the equivalent JSONata string
|
|
2154
|
+
* over `$form`. Funnelling every legacy value through this one function keeps
|
|
2155
|
+
* the rest of the codebase JSONata-only.
|
|
2156
|
+
*
|
|
2157
|
+
* @deprecated Remove once all deployed forms have been re-saved in the JSONata
|
|
2158
|
+
* format. The admin page's "legacy override format" warning tracks which
|
|
2159
|
+
* forms still need migrating.
|
|
2160
|
+
*/
|
|
2161
|
+
function legacyOverrideToJsonata(mapping) {
|
|
2162
|
+
const rows = [];
|
|
2163
|
+
for (const [scope, fields] of Object.entries(mapping || {})) {
|
|
2164
|
+
if (scope !== 'doc' && scope !== 'pv')
|
|
2165
|
+
continue;
|
|
2166
|
+
if (!fields || typeof fields !== 'object')
|
|
2167
|
+
continue;
|
|
2168
|
+
for (const [inputPath, ref] of Object.entries(fields)) {
|
|
2169
|
+
const raw = String(ref);
|
|
2170
|
+
const formFieldKey = raw.startsWith(FORM_REF_PREFIX)
|
|
2171
|
+
? raw.substring(FORM_REF_PREFIX.length)
|
|
2172
|
+
: raw;
|
|
2173
|
+
rows.push({ scope, inputPath, formFieldKey });
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
return serializeOverrideRows(rows);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
/*
|
|
2180
|
+
* Copyright 2025 Epistola.
|
|
2181
|
+
*
|
|
2182
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
2183
|
+
* you may not use this file except in compliance with the License.
|
|
2184
|
+
* You may obtain a copy of the License at
|
|
2185
|
+
*
|
|
2186
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
2187
|
+
*
|
|
2188
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2189
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
2190
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2191
|
+
* See the License for the specific language governing permissions and
|
|
2192
|
+
* limitations under the License.
|
|
2193
|
+
*
|
|
2194
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
2195
|
+
*/
|
|
2196
|
+
const jsonata$1 = _jsonata.default || _jsonata;
|
|
1611
2197
|
/**
|
|
1612
2198
|
* Detect if a string value is a JSONata expression (vs a plain literal).
|
|
1613
2199
|
* Checks for characters that indicate JSONata operators: $, &, (, {, ?, [
|
|
@@ -1635,32 +2221,98 @@ function expandDotNotation(flat) {
|
|
|
1635
2221
|
return result;
|
|
1636
2222
|
}
|
|
1637
2223
|
/**
|
|
1638
|
-
*
|
|
1639
|
-
*
|
|
1640
|
-
*
|
|
2224
|
+
* A preview is "override-driven" when it has a non-empty override mapping: its
|
|
2225
|
+
* input data comes from the form via the mapping, so it must wait for that data
|
|
2226
|
+
* before it can render. Previews without a mapping load straight from the base
|
|
2227
|
+
* doc/case data.
|
|
1641
2228
|
*/
|
|
1642
|
-
function
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
2229
|
+
function isOverrideDriven(mapping) {
|
|
2230
|
+
if (!mapping)
|
|
2231
|
+
return false;
|
|
2232
|
+
if (typeof mapping === 'string')
|
|
2233
|
+
return mapping.trim().length > 0;
|
|
2234
|
+
return Object.keys(mapping).length > 0;
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Whether the computed input overrides carry any usable data yet.
|
|
2238
|
+
*/
|
|
2239
|
+
function hasUsableOverrides(overrides) {
|
|
2240
|
+
return !!overrides && Object.keys(overrides).length > 0;
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Decide whether a preview request should fire given the configured override
|
|
2244
|
+
* mapping and the currently computed overrides.
|
|
2245
|
+
*
|
|
2246
|
+
* - Override-driven previews only load once the mapped form data is present;
|
|
2247
|
+
* before that they show a "complete the form" placeholder and fire nothing
|
|
2248
|
+
* (avoids a doomed request that Epistola rejects with a 400 for missing
|
|
2249
|
+
* required fields).
|
|
2250
|
+
* - Previews without a mapping always load (base data is the whole input).
|
|
2251
|
+
*/
|
|
2252
|
+
function shouldLoadPreview(mapping, overrides) {
|
|
2253
|
+
if (isOverrideDriven(mapping)) {
|
|
2254
|
+
return hasUsableOverrides(overrides);
|
|
2255
|
+
}
|
|
2256
|
+
return true;
|
|
2257
|
+
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Given an override mapping and the live form data, produce the inputOverrides
|
|
2260
|
+
* object (`{ doc, pv }`) the backend overlays onto the real document / process
|
|
2261
|
+
* variables before the data mapping runs.
|
|
2262
|
+
*
|
|
2263
|
+
* The mapping is a JSONata expression over `$form`; legacy `form:`-ref objects
|
|
2264
|
+
* are converted on the fly via {@link legacyOverrideToJsonata}. Evaluation is
|
|
2265
|
+
* asynchronous because `jsonata().evaluate()` returns a Promise. Only `doc` and
|
|
2266
|
+
* `pv` scopes (with at least one resolved field) are kept — matching what the
|
|
2267
|
+
* backend consumes.
|
|
2268
|
+
*/
|
|
2269
|
+
async function computeInputOverrides(mapping, formData) {
|
|
2270
|
+
if (!mapping) {
|
|
2271
|
+
return {};
|
|
2272
|
+
}
|
|
2273
|
+
const expression = isLegacyOverrideMapping(mapping)
|
|
2274
|
+
? legacyOverrideToJsonata(mapping)
|
|
2275
|
+
: String(mapping);
|
|
2276
|
+
if (!expression.trim()) {
|
|
2277
|
+
return {};
|
|
2278
|
+
}
|
|
2279
|
+
let evaluated;
|
|
2280
|
+
try {
|
|
2281
|
+
evaluated = await jsonata$1(expression).evaluate({}, { form: formData ?? {} });
|
|
2282
|
+
}
|
|
2283
|
+
catch {
|
|
2284
|
+
return {};
|
|
2285
|
+
}
|
|
2286
|
+
if (!evaluated || typeof evaluated !== 'object' || Array.isArray(evaluated)) {
|
|
2287
|
+
return {};
|
|
2288
|
+
}
|
|
2289
|
+
const result = {};
|
|
2290
|
+
for (const scope of ['doc', 'pv']) {
|
|
2291
|
+
const value = evaluated[scope];
|
|
2292
|
+
if (value && typeof value === 'object' && Object.keys(value).length > 0) {
|
|
2293
|
+
result[scope] = value;
|
|
1659
2294
|
}
|
|
1660
2295
|
}
|
|
1661
2296
|
return result;
|
|
1662
2297
|
}
|
|
1663
2298
|
|
|
2299
|
+
/*
|
|
2300
|
+
* Copyright 2025 Epistola.
|
|
2301
|
+
*
|
|
2302
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
2303
|
+
* you may not use this file except in compliance with the License.
|
|
2304
|
+
* You may obtain a copy of the License at
|
|
2305
|
+
*
|
|
2306
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
2307
|
+
*
|
|
2308
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2309
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
2310
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2311
|
+
* See the License for the specific language governing permissions and
|
|
2312
|
+
* limitations under the License.
|
|
2313
|
+
*
|
|
2314
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
2315
|
+
*/
|
|
1664
2316
|
class GenerateDocumentConfigurationComponent {
|
|
1665
2317
|
epistolaPluginService;
|
|
1666
2318
|
processLinkStateService;
|
|
@@ -1672,6 +2324,8 @@ class GenerateDocumentConfigurationComponent {
|
|
|
1672
2324
|
selectedPluginConfigurationData$;
|
|
1673
2325
|
context$;
|
|
1674
2326
|
valid = new EventEmitter();
|
|
2327
|
+
// Framework's FunctionConfigurationData (index type) to satisfy the invariant
|
|
2328
|
+
// EventEmitter contract under strict mode; emitted values remain the typed config.
|
|
1675
2329
|
configuration = new EventEmitter();
|
|
1676
2330
|
catalogs$ = new BehaviorSubject(initialResource([]));
|
|
1677
2331
|
templates$ = new BehaviorSubject(initialResource([]));
|
|
@@ -1718,6 +2372,8 @@ class GenerateDocumentConfigurationComponent {
|
|
|
1718
2372
|
processVariables = [];
|
|
1719
2373
|
expressionFunctions = [];
|
|
1720
2374
|
variableSuggestions = null;
|
|
2375
|
+
/** Context variables for the JSONata editor's autocomplete ($doc/$pv/$case). */
|
|
2376
|
+
editorContextVariables = { doc: [], pv: [], case: [] };
|
|
1721
2377
|
prefillDataMapping = {};
|
|
1722
2378
|
validationErrors$ = new BehaviorSubject([]);
|
|
1723
2379
|
destroy$ = new Subject();
|
|
@@ -2031,6 +2687,12 @@ class GenerateDocumentConfigurationComponent {
|
|
|
2031
2687
|
.pipe(takeUntil$1(this.destroy$), catchError(() => of({ doc: [], pv: [] })))
|
|
2032
2688
|
.subscribe((suggestions) => {
|
|
2033
2689
|
this.variableSuggestions = suggestions;
|
|
2690
|
+
// `$case` is a valid (currently-empty) binding — keep it offered.
|
|
2691
|
+
this.editorContextVariables = {
|
|
2692
|
+
doc: suggestions.doc || [],
|
|
2693
|
+
pv: suggestions.pv || [],
|
|
2694
|
+
case: [],
|
|
2695
|
+
};
|
|
2034
2696
|
this.cdr.markForCheck();
|
|
2035
2697
|
});
|
|
2036
2698
|
}
|
|
@@ -2123,8 +2785,8 @@ class GenerateDocumentConfigurationComponent {
|
|
|
2123
2785
|
}
|
|
2124
2786
|
});
|
|
2125
2787
|
}
|
|
2126
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, deps: [{ token: EpistolaPluginService }, { token: i2$
|
|
2127
|
-
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 [clearSelectionSubject$]=\"clearTemplateId$\"\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 <!--\n The v-select / v-input here is intentionally NOT named into the v-form: v-form's\n @ContentChildren queries default to descendants:false and would skip anything nested\n in this `field-with-fx` wrapper. The value is tracked via (selectedChange)/(valueChange)\n on the component, see variantIdValue / filenameValue.\n -->\n <div *ngIf=\"variantSelectionMode === 'explicit'\" class=\"field-with-fx\">\n <v-select\n *ngIf=\"!variantIdExpressionMode\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"variantIdValue\"\n [clearSelectionSubject$]=\"clearVariantId$\"\n (selectedChange)=\"onVariantIdValueChange($event)\"\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)=\"toggleVariantIdExpressionMode()\"\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 [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"filenameValue\"\n (valueChange)=\"onFilenameValueChange($event)\"\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)=\"toggleFilenameExpressionMode()\"\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 });
|
|
2788
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, deps: [{ token: EpistolaPluginService }, { token: i2$2.ProcessLinkStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
2789
|
+
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 [clearSelectionSubject$]=\"clearTemplateId$\"\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 <!--\n The v-select / v-input here is intentionally NOT named into the v-form: v-form's\n @ContentChildren queries default to descendants:false and would skip anything nested\n in this `field-with-fx` wrapper. The value is tracked via (selectedChange)/(valueChange)\n on the component, see variantIdValue / filenameValue.\n -->\n <div *ngIf=\"variantSelectionMode === 'explicit'\" class=\"field-with-fx\">\n <v-select\n *ngIf=\"!variantIdExpressionMode\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"variantIdValue\"\n [clearSelectionSubject$]=\"clearVariantId$\"\n (selectedChange)=\"onVariantIdValueChange($event)\"\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)=\"toggleVariantIdExpressionMode()\"\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 [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"filenameValue\"\n (valueChange)=\"onFilenameValueChange($event)\"\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)=\"toggleFilenameExpressionMode()\"\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 [contextVariables]=\"editorContextVariables\"\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: i3$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$1.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: i3$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i3$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.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", "contextVariables", "functions", "variablesHint"], 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 });
|
|
2128
2790
|
}
|
|
2129
2791
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, decorators: [{
|
|
2130
2792
|
type: Component,
|
|
@@ -2139,8 +2801,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2139
2801
|
JsonataEditorComponent,
|
|
2140
2802
|
MappingBuilderComponent,
|
|
2141
2803
|
MappingPreviewComponent,
|
|
2142
|
-
], 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 [clearSelectionSubject$]=\"clearTemplateId$\"\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 <!--\n The v-select / v-input here is intentionally NOT named into the v-form: v-form's\n @ContentChildren queries default to descendants:false and would skip anything nested\n in this `field-with-fx` wrapper. The value is tracked via (selectedChange)/(valueChange)\n on the component, see variantIdValue / filenameValue.\n -->\n <div *ngIf=\"variantSelectionMode === 'explicit'\" class=\"field-with-fx\">\n <v-select\n *ngIf=\"!variantIdExpressionMode\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"variantIdValue\"\n [clearSelectionSubject$]=\"clearVariantId$\"\n (selectedChange)=\"onVariantIdValueChange($event)\"\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)=\"toggleVariantIdExpressionMode()\"\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 [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"filenameValue\"\n (valueChange)=\"onFilenameValueChange($event)\"\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)=\"toggleFilenameExpressionMode()\"\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"] }]
|
|
2143
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$
|
|
2804
|
+
], 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 [clearSelectionSubject$]=\"clearTemplateId$\"\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 <!--\n The v-select / v-input here is intentionally NOT named into the v-form: v-form's\n @ContentChildren queries default to descendants:false and would skip anything nested\n in this `field-with-fx` wrapper. The value is tracked via (selectedChange)/(valueChange)\n on the component, see variantIdValue / filenameValue.\n -->\n <div *ngIf=\"variantSelectionMode === 'explicit'\" class=\"field-with-fx\">\n <v-select\n *ngIf=\"!variantIdExpressionMode\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"variantIdValue\"\n [clearSelectionSubject$]=\"clearVariantId$\"\n (selectedChange)=\"onVariantIdValueChange($event)\"\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)=\"toggleVariantIdExpressionMode()\"\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 [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"filenameValue\"\n (valueChange)=\"onFilenameValueChange($event)\"\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)=\"toggleFilenameExpressionMode()\"\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 [contextVariables]=\"editorContextVariables\"\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"] }]
|
|
2805
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$2.ProcessLinkStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { save$: [{
|
|
2144
2806
|
type: Input
|
|
2145
2807
|
}], disabled$: [{
|
|
2146
2808
|
type: Input
|
|
@@ -2158,12 +2820,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2158
2820
|
type: Output
|
|
2159
2821
|
}] } });
|
|
2160
2822
|
|
|
2823
|
+
/*
|
|
2824
|
+
* Copyright 2025 Epistola.
|
|
2825
|
+
*
|
|
2826
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
2827
|
+
* you may not use this file except in compliance with the License.
|
|
2828
|
+
* You may obtain a copy of the License at
|
|
2829
|
+
*
|
|
2830
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
2831
|
+
*
|
|
2832
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2833
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
2834
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2835
|
+
* See the License for the specific language governing permissions and
|
|
2836
|
+
* limitations under the License.
|
|
2837
|
+
*
|
|
2838
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
2839
|
+
*/
|
|
2161
2840
|
class CheckJobStatusConfigurationComponent {
|
|
2162
2841
|
save$;
|
|
2163
2842
|
disabled$;
|
|
2164
2843
|
pluginId;
|
|
2165
2844
|
prefillConfiguration$;
|
|
2166
2845
|
valid = new EventEmitter();
|
|
2846
|
+
// Typed as the framework's FunctionConfigurationData (an index type) to match the
|
|
2847
|
+
// FunctionConfigurationComponent contract under strict mode — EventEmitter is invariant,
|
|
2848
|
+
// so a narrower generic isn't assignable. Emitted values are still the typed config.
|
|
2167
2849
|
configuration = new EventEmitter();
|
|
2168
2850
|
saveSubscription;
|
|
2169
2851
|
formValue$ = new BehaviorSubject(null);
|
|
@@ -2225,12 +2907,66 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2225
2907
|
type: Output
|
|
2226
2908
|
}] } });
|
|
2227
2909
|
|
|
2910
|
+
/*
|
|
2911
|
+
* Copyright 2025 Epistola.
|
|
2912
|
+
*
|
|
2913
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
2914
|
+
* you may not use this file except in compliance with the License.
|
|
2915
|
+
* You may obtain a copy of the License at
|
|
2916
|
+
*
|
|
2917
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
2918
|
+
*
|
|
2919
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2920
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
2921
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2922
|
+
* See the License for the specific language governing permissions and
|
|
2923
|
+
* limitations under the License.
|
|
2924
|
+
*
|
|
2925
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
2926
|
+
*/
|
|
2927
|
+
const DEFAULT_STORAGE_TARGET = 'TEMPORARY_RESOURCE';
|
|
2928
|
+
/** Normalize a (possibly undefined) storageTarget to a concrete target, applying the default. */
|
|
2929
|
+
function resolveStorageTarget(target) {
|
|
2930
|
+
return target === 'PROCESS_VARIABLE' ? 'PROCESS_VARIABLE' : DEFAULT_STORAGE_TARGET;
|
|
2931
|
+
}
|
|
2932
|
+
/**
|
|
2933
|
+
* A config is valid when the input variable and the output variable that matches the chosen
|
|
2934
|
+
* storage target are both set.
|
|
2935
|
+
*/
|
|
2936
|
+
function isDownloadDocumentConfigValid(config) {
|
|
2937
|
+
if (!config?.documentVariable) {
|
|
2938
|
+
return false;
|
|
2939
|
+
}
|
|
2940
|
+
return resolveStorageTarget(config.storageTarget) === 'PROCESS_VARIABLE'
|
|
2941
|
+
? !!config.contentVariable
|
|
2942
|
+
: !!config.resourceIdVariable;
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
/*
|
|
2946
|
+
* Copyright 2025 Epistola.
|
|
2947
|
+
*
|
|
2948
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
2949
|
+
* you may not use this file except in compliance with the License.
|
|
2950
|
+
* You may obtain a copy of the License at
|
|
2951
|
+
*
|
|
2952
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
2953
|
+
*
|
|
2954
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2955
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
2956
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2957
|
+
* See the License for the specific language governing permissions and
|
|
2958
|
+
* limitations under the License.
|
|
2959
|
+
*
|
|
2960
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
2961
|
+
*/
|
|
2228
2962
|
class DownloadDocumentConfigurationComponent {
|
|
2229
2963
|
save$;
|
|
2230
2964
|
disabled$;
|
|
2231
2965
|
pluginId;
|
|
2232
2966
|
prefillConfiguration$;
|
|
2233
2967
|
valid = new EventEmitter();
|
|
2968
|
+
// Framework's FunctionConfigurationData (index type) to satisfy the invariant
|
|
2969
|
+
// EventEmitter contract under strict mode; emitted values remain the typed config.
|
|
2234
2970
|
configuration = new EventEmitter();
|
|
2235
2971
|
saveSubscription;
|
|
2236
2972
|
formValue$ = new BehaviorSubject(null);
|
|
@@ -2243,11 +2979,24 @@ class DownloadDocumentConfigurationComponent {
|
|
|
2243
2979
|
resolvedPrefill = {};
|
|
2244
2980
|
prefillResolved$ = new BehaviorSubject(false);
|
|
2245
2981
|
safeDisabled$;
|
|
2982
|
+
/**
|
|
2983
|
+
* Static option set for the storage target. Values match the backend
|
|
2984
|
+
* {@code DocumentStorageTarget} enum constants; labels are explained further via the
|
|
2985
|
+
* translated field title/tooltip.
|
|
2986
|
+
*/
|
|
2987
|
+
storageTargetOptions = [
|
|
2988
|
+
{ id: 'TEMPORARY_RESOURCE', text: 'Temporary resource storage' },
|
|
2989
|
+
{ id: 'PROCESS_VARIABLE', text: 'Process variable (inline bytes)' },
|
|
2990
|
+
];
|
|
2991
|
+
defaultStorageTarget = DEFAULT_STORAGE_TARGET;
|
|
2992
|
+
/** Drives which output-variable field is shown (resource id vs inline content). */
|
|
2993
|
+
selectedTarget$ = new BehaviorSubject(this.defaultStorageTarget);
|
|
2246
2994
|
ngOnInit() {
|
|
2247
2995
|
this.safeDisabled$ = this.disabled$.pipe(startWith(true), delay(0));
|
|
2248
2996
|
const prefill$ = this.prefillConfiguration$ ?? of({});
|
|
2249
2997
|
prefill$.pipe(take(1)).subscribe((prefill) => {
|
|
2250
2998
|
this.resolvedPrefill = prefill ?? {};
|
|
2999
|
+
this.selectedTarget$.next(resolveStorageTarget(this.resolvedPrefill.storageTarget));
|
|
2251
3000
|
this.prefillResolved$.next(true);
|
|
2252
3001
|
});
|
|
2253
3002
|
this.openSaveSubscription();
|
|
@@ -2257,11 +3006,14 @@ class DownloadDocumentConfigurationComponent {
|
|
|
2257
3006
|
}
|
|
2258
3007
|
formValueChange(formOutput) {
|
|
2259
3008
|
const formValue = formOutput;
|
|
3009
|
+
if (formValue?.storageTarget) {
|
|
3010
|
+
this.selectedTarget$.next(formValue.storageTarget);
|
|
3011
|
+
}
|
|
2260
3012
|
this.formValue$.next(formValue);
|
|
2261
3013
|
this.handleValid(formValue);
|
|
2262
3014
|
}
|
|
2263
3015
|
handleValid(formValue) {
|
|
2264
|
-
const valid =
|
|
3016
|
+
const valid = isDownloadDocumentConfigValid(formValue);
|
|
2265
3017
|
this.valid$.next(valid);
|
|
2266
3018
|
this.valid.emit(valid);
|
|
2267
3019
|
}
|
|
@@ -2277,11 +3029,11 @@ class DownloadDocumentConfigurationComponent {
|
|
|
2277
3029
|
});
|
|
2278
3030
|
}
|
|
2279
3031
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2280
|
-
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 (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"documentVariable\"\n [title]=\"'documentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentVariable\"\n [disabled]=\"safeDisabled$ | async\"\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]=\"resolvedPrefill?.contentVariable\"\n [disabled]=\"safeDisabled$ | async\"\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"] }] });
|
|
3032
|
+
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 (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"documentVariable\"\n [title]=\"'documentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-select\n name=\"storageTarget\"\n [title]=\"'storageTarget' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'storageTargetTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"storageTargetOptions\"\n [defaultSelectionId]=\"resolvedPrefill?.storageTarget || defaultStorageTarget\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n *ngIf=\"(selectedTarget$ | async) === 'TEMPORARY_RESOURCE'\"\n name=\"resourceIdVariable\"\n [title]=\"'resourceIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resourceIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.resourceIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n *ngIf=\"(selectedTarget$ | async) === 'PROCESS_VARIABLE'\"\n name=\"contentVariable\"\n [title]=\"'contentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'contentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.contentVariable\"\n [disabled]=\"safeDisabled$ | async\"\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"] }, { 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"] }] });
|
|
2281
3033
|
}
|
|
2282
3034
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, decorators: [{
|
|
2283
3035
|
type: Component,
|
|
2284
|
-
args: [{ selector: 'epistola-download-document-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"documentVariable\"\n [title]=\"'documentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentVariable\"\n [disabled]=\"safeDisabled$ | async\"\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]=\"resolvedPrefill?.contentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n" }]
|
|
3036
|
+
args: [{ selector: 'epistola-download-document-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule, SelectModule], template: "<v-form (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"documentVariable\"\n [title]=\"'documentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-select\n name=\"storageTarget\"\n [title]=\"'storageTarget' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'storageTargetTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"storageTargetOptions\"\n [defaultSelectionId]=\"resolvedPrefill?.storageTarget || defaultStorageTarget\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n *ngIf=\"(selectedTarget$ | async) === 'TEMPORARY_RESOURCE'\"\n name=\"resourceIdVariable\"\n [title]=\"'resourceIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resourceIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.resourceIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n *ngIf=\"(selectedTarget$ | async) === 'PROCESS_VARIABLE'\"\n name=\"contentVariable\"\n [title]=\"'contentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'contentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.contentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n" }]
|
|
2285
3037
|
}], propDecorators: { save$: [{
|
|
2286
3038
|
type: Input
|
|
2287
3039
|
}], disabled$: [{
|
|
@@ -2296,6 +3048,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2296
3048
|
type: Output
|
|
2297
3049
|
}] } });
|
|
2298
3050
|
|
|
3051
|
+
/*
|
|
3052
|
+
* Copyright 2025 Epistola.
|
|
3053
|
+
*
|
|
3054
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
3055
|
+
* you may not use this file except in compliance with the License.
|
|
3056
|
+
* You may obtain a copy of the License at
|
|
3057
|
+
*
|
|
3058
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
3059
|
+
*
|
|
3060
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
3061
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
3062
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
3063
|
+
* See the License for the specific language governing permissions and
|
|
3064
|
+
* limitations under the License.
|
|
3065
|
+
*
|
|
3066
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
3067
|
+
*/
|
|
2299
3068
|
/**
|
|
2300
3069
|
* Unified Formio component for the after-generation Epistola PDF UX. Reads
|
|
2301
3070
|
* the PDF id and tenant id from named process variables on the caller's
|
|
@@ -2316,7 +3085,6 @@ class EpistolaDocumentComponent {
|
|
|
2316
3085
|
epistolaPluginService;
|
|
2317
3086
|
sanitizer;
|
|
2318
3087
|
formIoStateService;
|
|
2319
|
-
taskContext;
|
|
2320
3088
|
cdr;
|
|
2321
3089
|
value;
|
|
2322
3090
|
valueChange = new EventEmitter();
|
|
@@ -2337,6 +3105,11 @@ class EpistolaDocumentComponent {
|
|
|
2337
3105
|
tenantIdVariable = 'epistolaTenantId';
|
|
2338
3106
|
/** Filename used for the download disposition. */
|
|
2339
3107
|
filename = 'document.pdf';
|
|
3108
|
+
/**
|
|
3109
|
+
* Task id forwarded by the Formio wrapper from the server-prefilled form
|
|
3110
|
+
* ({@code epistola:taskId} value resolver), populated in every Valtimo task-open flow.
|
|
3111
|
+
*/
|
|
3112
|
+
taskInstanceId;
|
|
2340
3113
|
loading = false;
|
|
2341
3114
|
downloading = false;
|
|
2342
3115
|
error = null;
|
|
@@ -2346,18 +3119,21 @@ class EpistolaDocumentComponent {
|
|
|
2346
3119
|
get designMode() {
|
|
2347
3120
|
return !this.formIoStateService.documentId;
|
|
2348
3121
|
}
|
|
2349
|
-
constructor(epistolaPluginService, sanitizer, formIoStateService,
|
|
3122
|
+
constructor(epistolaPluginService, sanitizer, formIoStateService, cdr) {
|
|
2350
3123
|
this.epistolaPluginService = epistolaPluginService;
|
|
2351
3124
|
this.sanitizer = sanitizer;
|
|
2352
3125
|
this.formIoStateService = formIoStateService;
|
|
2353
|
-
this.taskContext = taskContext;
|
|
2354
3126
|
this.cdr = cdr;
|
|
2355
3127
|
}
|
|
2356
|
-
|
|
2357
|
-
if (this.designMode) {
|
|
3128
|
+
ngOnChanges(changes) {
|
|
3129
|
+
if (this.designMode || this.display === 'button') {
|
|
2358
3130
|
return;
|
|
2359
3131
|
}
|
|
2360
|
-
|
|
3132
|
+
// The Formio wrapper sets taskInstanceId around attach, so it can arrive after the
|
|
3133
|
+
// first change — (re)load the inline document once it's available instead of leaving
|
|
3134
|
+
// the "Document is alleen beschikbaar binnen een taak" message until a manual refresh.
|
|
3135
|
+
// (For display="button" the download() click reads the task id on demand.)
|
|
3136
|
+
if (changes['taskInstanceId'] && this.taskInstanceId) {
|
|
2361
3137
|
this.loadInline();
|
|
2362
3138
|
}
|
|
2363
3139
|
}
|
|
@@ -2424,7 +3200,7 @@ class EpistolaDocumentComponent {
|
|
|
2424
3200
|
});
|
|
2425
3201
|
}
|
|
2426
3202
|
buildRequest(disposition) {
|
|
2427
|
-
const taskId = this.
|
|
3203
|
+
const taskId = this.taskInstanceId ?? null;
|
|
2428
3204
|
const caseDocumentId = this.formIoStateService.documentId;
|
|
2429
3205
|
if (!taskId || !caseDocumentId) {
|
|
2430
3206
|
this.error = 'Document is alleen beschikbaar binnen een taak.';
|
|
@@ -2447,8 +3223,8 @@ class EpistolaDocumentComponent {
|
|
|
2447
3223
|
this.previewUrl = null;
|
|
2448
3224
|
}
|
|
2449
3225
|
}
|
|
2450
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentComponent, deps: [{ token: EpistolaPluginService }, { token: i2$
|
|
2451
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaDocumentComponent, isStandalone: true, selector: "epistola-document-component", inputs: { value: "value", disabled: "disabled", label: "label", display: "display", documentVariable: "documentVariable", tenantIdVariable: "tenantIdVariable", filename: "filename" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: `
|
|
3226
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentComponent, deps: [{ token: EpistolaPluginService }, { token: i2$3.DomSanitizer }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3227
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaDocumentComponent, isStandalone: true, selector: "epistola-document-component", inputs: { value: "value", disabled: "disabled", label: "label", display: "display", documentVariable: "documentVariable", tenantIdVariable: "tenantIdVariable", filename: "filename", taskInstanceId: "taskInstanceId" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2452
3228
|
<!-- Design-time placeholder -->
|
|
2453
3229
|
<div *ngIf="designMode" class="epistola-doc-panel">
|
|
2454
3230
|
<div class="doc-header">
|
|
@@ -2603,7 +3379,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2603
3379
|
</div>
|
|
2604
3380
|
</div>
|
|
2605
3381
|
`, styles: [".epistola-doc-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.doc-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057}.doc-controls{display:flex;gap:.25rem}.doc-icon-btn{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .5rem;font-size:.9rem;cursor:pointer}.doc-icon-btn:hover:not(:disabled){background:#e9ecef}.doc-icon-btn:disabled{opacity:.5;cursor:not-allowed}.doc-body{display:flex;flex-direction:column;min-height:500px}.doc-loading,.doc-unavailable{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.doc-unavailable i{margin-right:.25rem}.doc-pdf{width:100%;flex:1;min-height:500px}.design-info{padding:1rem;min-height:auto}.design-section{margin-bottom:.5rem}.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-tag{font-size:.7rem;font-weight:400;color:#6c757d;font-style:italic}\n"] }]
|
|
2606
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$
|
|
3382
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$3.DomSanitizer }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
2607
3383
|
type: Input
|
|
2608
3384
|
}], valueChange: [{
|
|
2609
3385
|
type: Output
|
|
@@ -2619,19 +3395,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2619
3395
|
type: Input
|
|
2620
3396
|
}], filename: [{
|
|
2621
3397
|
type: Input
|
|
3398
|
+
}], taskInstanceId: [{
|
|
3399
|
+
type: Input
|
|
2622
3400
|
}] } });
|
|
2623
3401
|
|
|
3402
|
+
/*
|
|
3403
|
+
* Copyright 2025 Epistola.
|
|
3404
|
+
*
|
|
3405
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
3406
|
+
* you may not use this file except in compliance with the License.
|
|
3407
|
+
* You may obtain a copy of the License at
|
|
3408
|
+
*
|
|
3409
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
3410
|
+
*
|
|
3411
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
3412
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
3413
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
3414
|
+
* See the License for the specific language governing permissions and
|
|
3415
|
+
* limitations under the License.
|
|
3416
|
+
*
|
|
3417
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
3418
|
+
*/
|
|
2624
3419
|
class EpistolaRetryFormComponent {
|
|
2625
3420
|
epistolaPluginService;
|
|
2626
|
-
formIoStateService;
|
|
2627
3421
|
cdr;
|
|
2628
3422
|
sanitizer;
|
|
2629
|
-
taskContext;
|
|
2630
3423
|
value;
|
|
2631
3424
|
valueChange = new EventEmitter();
|
|
2632
3425
|
disabled = false;
|
|
2633
3426
|
label = 'Document Data';
|
|
2634
3427
|
sourceActivityId;
|
|
3428
|
+
/**
|
|
3429
|
+
* Task id forwarded by the Formio wrapper from the server-prefilled form
|
|
3430
|
+
* ({@code epistola:taskId} value resolver), populated in every Valtimo task-open flow.
|
|
3431
|
+
*/
|
|
3432
|
+
taskInstanceId;
|
|
2635
3433
|
formDefinition;
|
|
2636
3434
|
submission;
|
|
2637
3435
|
loading = true;
|
|
@@ -2646,17 +3444,14 @@ class EpistolaRetryFormComponent {
|
|
|
2646
3444
|
previewSubject = new Subject();
|
|
2647
3445
|
currentBlobUrl = null;
|
|
2648
3446
|
resolvedSourceActivityId;
|
|
2649
|
-
processDefinitionKey;
|
|
2650
3447
|
formOptions = {
|
|
2651
3448
|
noAlerts: true,
|
|
2652
3449
|
buttonSettings: { showCancel: false, showSubmit: false, showPrevious: false, showNext: false },
|
|
2653
3450
|
};
|
|
2654
|
-
constructor(epistolaPluginService,
|
|
3451
|
+
constructor(epistolaPluginService, cdr, sanitizer) {
|
|
2655
3452
|
this.epistolaPluginService = epistolaPluginService;
|
|
2656
|
-
this.formIoStateService = formIoStateService;
|
|
2657
3453
|
this.cdr = cdr;
|
|
2658
3454
|
this.sanitizer = sanitizer;
|
|
2659
|
-
this.taskContext = taskContext;
|
|
2660
3455
|
// Debounce preview calls
|
|
2661
3456
|
this.previewSubscription = this.previewSubject.pipe(debounceTime$1(1500)).subscribe((data) => {
|
|
2662
3457
|
this.loadPreview(data);
|
|
@@ -2666,6 +3461,13 @@ class EpistolaRetryFormComponent {
|
|
|
2666
3461
|
if (!this.loaded) {
|
|
2667
3462
|
this.loaded = true;
|
|
2668
3463
|
this.loadForm();
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
// The Formio wrapper sets taskInstanceId after attach, so it can land after the
|
|
3467
|
+
// first render — if the form failed to load for lack of a task id, retry once it
|
|
3468
|
+
// arrives instead of leaving the "only available from within a user task" message.
|
|
3469
|
+
if (changes['taskInstanceId'] && this.taskInstanceId && !this.formDefinition) {
|
|
3470
|
+
this.loadForm();
|
|
2669
3471
|
}
|
|
2670
3472
|
}
|
|
2671
3473
|
ngOnDestroy() {
|
|
@@ -2689,11 +3491,8 @@ class EpistolaRetryFormComponent {
|
|
|
2689
3491
|
}
|
|
2690
3492
|
}
|
|
2691
3493
|
loadPreview(formData) {
|
|
2692
|
-
|
|
2693
|
-
const
|
|
2694
|
-
if (!documentId || !processInstanceId)
|
|
2695
|
-
return;
|
|
2696
|
-
const taskId = this.taskContext.taskInstanceId;
|
|
3494
|
+
// The backend derives the process instance and case document from the task.
|
|
3495
|
+
const taskId = this.taskInstanceId ?? null;
|
|
2697
3496
|
if (!taskId) {
|
|
2698
3497
|
this.previewError = 'Preview is only available from within a user task.';
|
|
2699
3498
|
this.cdr.markForCheck();
|
|
@@ -2710,8 +3509,6 @@ class EpistolaRetryFormComponent {
|
|
|
2710
3509
|
this.epistolaPluginService
|
|
2711
3510
|
.previewToBlob({
|
|
2712
3511
|
taskId,
|
|
2713
|
-
documentId,
|
|
2714
|
-
processInstanceId,
|
|
2715
3512
|
sourceActivityId: this.sourceActivityId || null,
|
|
2716
3513
|
overrides: formData,
|
|
2717
3514
|
})
|
|
@@ -2748,15 +3545,8 @@ class EpistolaRetryFormComponent {
|
|
|
2748
3545
|
});
|
|
2749
3546
|
}
|
|
2750
3547
|
loadForm() {
|
|
2751
|
-
|
|
2752
|
-
const
|
|
2753
|
-
if (!processInstanceId) {
|
|
2754
|
-
this.error = 'Could not determine process instance ID.';
|
|
2755
|
-
this.loading = false;
|
|
2756
|
-
this.cdr.markForCheck();
|
|
2757
|
-
return;
|
|
2758
|
-
}
|
|
2759
|
-
const taskId = this.taskContext.taskInstanceId;
|
|
3548
|
+
// The backend derives the process instance and case document from the task.
|
|
3549
|
+
const taskId = this.taskInstanceId ?? null;
|
|
2760
3550
|
if (!taskId) {
|
|
2761
3551
|
this.error = 'Retry form is only available from within a user task.';
|
|
2762
3552
|
this.loading = false;
|
|
@@ -2764,7 +3554,7 @@ class EpistolaRetryFormComponent {
|
|
|
2764
3554
|
return;
|
|
2765
3555
|
}
|
|
2766
3556
|
this.loadSubscription = this.epistolaPluginService
|
|
2767
|
-
.getRetryForm(taskId,
|
|
3557
|
+
.getRetryForm(taskId, this.sourceActivityId)
|
|
2768
3558
|
.subscribe({
|
|
2769
3559
|
next: (form) => {
|
|
2770
3560
|
this.formDefinition = form;
|
|
@@ -2787,8 +3577,8 @@ class EpistolaRetryFormComponent {
|
|
|
2787
3577
|
},
|
|
2788
3578
|
});
|
|
2789
3579
|
}
|
|
2790
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, deps: [{ token: EpistolaPluginService }, { token:
|
|
2791
|
-
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: `
|
|
3580
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, deps: [{ token: EpistolaPluginService }, { token: i0.ChangeDetectorRef }, { token: i2$3.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
|
|
3581
|
+
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", taskInstanceId: "taskInstanceId" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2792
3582
|
<div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
|
|
2793
3583
|
<div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
|
|
2794
3584
|
<div
|
|
@@ -2826,7 +3616,7 @@ class EpistolaRetryFormComponent {
|
|
|
2826
3616
|
</div>
|
|
2827
3617
|
</div>
|
|
2828
3618
|
</div>
|
|
2829
|
-
`, isInline: true, 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"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormioModule }, { kind: "component", type:
|
|
3619
|
+
`, isInline: true, 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"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormioModule }, { kind: "component", type: i4.FormioComponent, selector: "formio" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2830
3620
|
}
|
|
2831
3621
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, decorators: [{
|
|
2832
3622
|
type: Component,
|
|
@@ -2869,7 +3659,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2869
3659
|
</div>
|
|
2870
3660
|
</div>
|
|
2871
3661
|
`, 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"] }]
|
|
2872
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type:
|
|
3662
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i0.ChangeDetectorRef }, { type: i2$3.DomSanitizer }], propDecorators: { value: [{
|
|
2873
3663
|
type: Input
|
|
2874
3664
|
}], valueChange: [{
|
|
2875
3665
|
type: Output
|
|
@@ -2879,21 +3669,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2879
3669
|
type: Input
|
|
2880
3670
|
}], sourceActivityId: [{
|
|
2881
3671
|
type: Input
|
|
3672
|
+
}], taskInstanceId: [{
|
|
3673
|
+
type: Input
|
|
2882
3674
|
}] } });
|
|
2883
3675
|
|
|
3676
|
+
/*
|
|
3677
|
+
* Copyright 2025 Epistola.
|
|
3678
|
+
*
|
|
3679
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
3680
|
+
* you may not use this file except in compliance with the License.
|
|
3681
|
+
* You may obtain a copy of the License at
|
|
3682
|
+
*
|
|
3683
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
3684
|
+
*
|
|
3685
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
3686
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
3687
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
3688
|
+
* See the License for the specific language governing permissions and
|
|
3689
|
+
* limitations under the License.
|
|
3690
|
+
*
|
|
3691
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
3692
|
+
*/
|
|
2884
3693
|
class EpistolaDocumentPreviewComponent {
|
|
2885
3694
|
epistolaPluginService;
|
|
2886
3695
|
sanitizer;
|
|
2887
3696
|
formIoStateService;
|
|
2888
3697
|
cdr;
|
|
2889
|
-
taskContext;
|
|
2890
3698
|
value;
|
|
2891
3699
|
valueChange = new EventEmitter();
|
|
2892
3700
|
disabled = false;
|
|
2893
3701
|
label = 'Document Preview';
|
|
2894
3702
|
processDefinitionKey;
|
|
2895
3703
|
sourceActivityId;
|
|
3704
|
+
/**
|
|
3705
|
+
* The override mapping: a JSONata expression string over `$form`, or — for
|
|
3706
|
+
* not-yet-re-saved forms — the legacy `form:`-ref object.
|
|
3707
|
+
*/
|
|
2896
3708
|
overrideMapping;
|
|
3709
|
+
/**
|
|
3710
|
+
* Task id forwarded by the Formio wrapper from the server-prefilled form
|
|
3711
|
+
* ({@code epistola:taskId} value resolver), populated in every Valtimo task-open flow.
|
|
3712
|
+
*/
|
|
3713
|
+
taskInstanceId;
|
|
3714
|
+
/**
|
|
3715
|
+
* The computed input overrides (`{ doc, pv }`) the preview renders with, pushed
|
|
3716
|
+
* by the Formio wrapper. Kept separate from the Formio `value`: Valtimo's custom
|
|
3717
|
+
* component bridge only mirrors `value` to the DOM (never to Formio's data
|
|
3718
|
+
* model), so Formio resets it to `emptyValue` on every redraw — which would
|
|
3719
|
+
* cancel the preview. This dedicated input is never touched by Formio.
|
|
3720
|
+
*/
|
|
3721
|
+
inputOverrides;
|
|
3722
|
+
/**
|
|
3723
|
+
* Forces the Formio wrapper to recompute the input overrides from the live form
|
|
3724
|
+
* data. Set by the wrapper for override-driven previews; lets the Refresh button
|
|
3725
|
+
* work before the first change (e.g. on initial load with pre-filled fields).
|
|
3726
|
+
*/
|
|
3727
|
+
requestOverrides;
|
|
3728
|
+
/**
|
|
3729
|
+
* Current auto-refresh state, forwarded by the wrapper (seeded from the builder's
|
|
3730
|
+
* `autoRefresh` option, default on). Seeds the header toggle's initial state.
|
|
3731
|
+
*/
|
|
3732
|
+
autoRefresh;
|
|
3733
|
+
/**
|
|
3734
|
+
* Tells the wrapper to enable/disable auto-refresh (recompute on change/blur).
|
|
3735
|
+
* Set by the wrapper for override-driven previews; called by the header toggle.
|
|
3736
|
+
*/
|
|
3737
|
+
setAutoRefresh;
|
|
3738
|
+
/** Runtime state of the header auto-refresh toggle. */
|
|
3739
|
+
autoRefreshEnabled = true;
|
|
2897
3740
|
loading = false;
|
|
2898
3741
|
error = null;
|
|
2899
3742
|
previewUrl = null;
|
|
@@ -2901,31 +3744,35 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2901
3744
|
initialized = false;
|
|
2902
3745
|
currentBlobUrl = null;
|
|
2903
3746
|
previewSubscription;
|
|
2904
|
-
constructor(epistolaPluginService, sanitizer, formIoStateService, cdr
|
|
3747
|
+
constructor(epistolaPluginService, sanitizer, formIoStateService, cdr) {
|
|
2905
3748
|
this.epistolaPluginService = epistolaPluginService;
|
|
2906
3749
|
this.sanitizer = sanitizer;
|
|
2907
3750
|
this.formIoStateService = formIoStateService;
|
|
2908
3751
|
this.cdr = cdr;
|
|
2909
|
-
this.taskContext = taskContext;
|
|
2910
3752
|
}
|
|
2911
3753
|
/**
|
|
2912
|
-
*
|
|
2913
|
-
*
|
|
2914
|
-
*
|
|
3754
|
+
* The active task id, forwarded by the Formio wrapper from the server-prefilled
|
|
3755
|
+
* form ({@code epistola:taskId} value resolver). Null outside a task context
|
|
3756
|
+
* (e.g. Formio builder), in which case the component fails closed.
|
|
2915
3757
|
*/
|
|
2916
3758
|
get currentTaskId() {
|
|
2917
|
-
return this.
|
|
3759
|
+
return this.taskInstanceId ?? null;
|
|
2918
3760
|
}
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
3761
|
+
/**
|
|
3762
|
+
* The override mapping as a JSONata expression for the design-mode summary.
|
|
3763
|
+
* Legacy `form:`-ref objects are converted on the fly for display.
|
|
3764
|
+
*/
|
|
3765
|
+
get overrideExpression() {
|
|
3766
|
+
const mapping = this.overrideMapping;
|
|
3767
|
+
if (!mapping)
|
|
3768
|
+
return '';
|
|
3769
|
+
return isLegacyOverrideMapping(mapping) ? legacyOverrideToJsonata(mapping) : String(mapping);
|
|
2927
3770
|
}
|
|
2928
3771
|
ngOnChanges(changes) {
|
|
3772
|
+
// Seed the runtime auto-refresh toggle from the wrapper-forwarded state.
|
|
3773
|
+
if (changes['autoRefresh']) {
|
|
3774
|
+
this.autoRefreshEnabled = this.autoRefresh !== false;
|
|
3775
|
+
}
|
|
2929
3776
|
if (!this.initialized) {
|
|
2930
3777
|
this.initialized = true;
|
|
2931
3778
|
// Detect design mode: no runtime context (Formio builder)
|
|
@@ -2940,12 +3787,17 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2940
3787
|
this.cdr.markForCheck();
|
|
2941
3788
|
return;
|
|
2942
3789
|
}
|
|
2943
|
-
this.
|
|
3790
|
+
this.triggerPreview();
|
|
2944
3791
|
return;
|
|
2945
3792
|
}
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
3793
|
+
if (this.designMode)
|
|
3794
|
+
return;
|
|
3795
|
+
// React to input-override changes, and to the task id arriving late: the Formio
|
|
3796
|
+
// wrapper sets taskInstanceId after attach, so it can land after the first render —
|
|
3797
|
+
// re-run the preview once it does, instead of leaving the "only available from
|
|
3798
|
+
// within a user task" message until a manual refresh.
|
|
3799
|
+
if (changes['inputOverrides'] || changes['taskInstanceId']) {
|
|
3800
|
+
this.triggerPreview();
|
|
2949
3801
|
}
|
|
2950
3802
|
}
|
|
2951
3803
|
ngOnDestroy() {
|
|
@@ -2953,31 +3805,58 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2953
3805
|
this.revokeBlobUrl();
|
|
2954
3806
|
}
|
|
2955
3807
|
refresh() {
|
|
3808
|
+
// For an override-driven preview whose value isn't ready yet (e.g. initial load
|
|
3809
|
+
// before the overrides have been computed), recompute from the live form data;
|
|
3810
|
+
// the resulting value change drives the preview. Otherwise re-fetch directly.
|
|
3811
|
+
if (this.requestOverrides && !shouldLoadPreview(this.overrideMapping, this.inputOverrides)) {
|
|
3812
|
+
this.requestOverrides();
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3815
|
+
this.triggerPreview();
|
|
3816
|
+
}
|
|
3817
|
+
/** Toggle auto-refresh for this session; flipping it on triggers an immediate refresh. */
|
|
3818
|
+
onToggleAutoRefresh(event) {
|
|
3819
|
+
const enabled = event.target.checked;
|
|
3820
|
+
this.autoRefreshEnabled = enabled;
|
|
3821
|
+
// The wrapper owns the change/blur listeners, so it must learn about the flip;
|
|
3822
|
+
// turning it on recomputes immediately (handled wrapper-side).
|
|
3823
|
+
this.setAutoRefresh?.(enabled);
|
|
3824
|
+
this.cdr.markForCheck();
|
|
3825
|
+
}
|
|
3826
|
+
/**
|
|
3827
|
+
* Load the preview only when there is enough data for it. Override-driven
|
|
3828
|
+
* previews (those with an override mapping) wait until the mapped form data
|
|
3829
|
+
* has been computed; until then they show a placeholder rather than firing a
|
|
3830
|
+
* request that Epistola would reject with a 400 for missing required fields.
|
|
3831
|
+
*/
|
|
3832
|
+
triggerPreview() {
|
|
3833
|
+
if (!shouldLoadPreview(this.overrideMapping, this.inputOverrides)) {
|
|
3834
|
+
this.showWaitingForInput();
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
2956
3837
|
this.loadPreview();
|
|
2957
3838
|
}
|
|
3839
|
+
showWaitingForInput() {
|
|
3840
|
+
this.revokeBlobUrl();
|
|
3841
|
+
this.previewSubscription?.unsubscribe();
|
|
3842
|
+
this.previewUrl = null;
|
|
3843
|
+
this.loading = false;
|
|
3844
|
+
this.error = 'Complete the form to generate a preview.';
|
|
3845
|
+
this.cdr.markForCheck();
|
|
3846
|
+
}
|
|
2958
3847
|
/**
|
|
2959
3848
|
* Preview using the explicitly configured process link + input overrides.
|
|
2960
3849
|
* Requires a runtime task context — the backend authorizes the request against
|
|
2961
3850
|
* the task's process instance and case document, so all three ids must match.
|
|
2962
3851
|
*/
|
|
2963
3852
|
loadPreview() {
|
|
2964
|
-
const documentId = this.formIoStateService.documentId;
|
|
2965
|
-
if (!documentId) {
|
|
2966
|
-
this.error = 'Could not determine document ID from context.';
|
|
2967
|
-
this.cdr.markForCheck();
|
|
2968
|
-
return;
|
|
2969
|
-
}
|
|
2970
3853
|
if (!this.sourceActivityId) {
|
|
2971
3854
|
this.error = 'Preview is not configured: set the source activity on the form component.';
|
|
2972
3855
|
this.cdr.markForCheck();
|
|
2973
3856
|
return;
|
|
2974
3857
|
}
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
this.error = 'Preview is only available from within a running process.';
|
|
2978
|
-
this.cdr.markForCheck();
|
|
2979
|
-
return;
|
|
2980
|
-
}
|
|
3858
|
+
// The backend derives the process instance and case document from the task, so the
|
|
3859
|
+
// task id is the only runtime context the request carries.
|
|
2981
3860
|
const taskId = this.currentTaskId;
|
|
2982
3861
|
if (!taskId) {
|
|
2983
3862
|
this.error = 'Preview is only available from within a user task.';
|
|
@@ -2992,11 +3871,8 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2992
3871
|
this.previewSubscription = this.epistolaPluginService
|
|
2993
3872
|
.previewToBlob({
|
|
2994
3873
|
taskId,
|
|
2995
|
-
documentId,
|
|
2996
|
-
processDefinitionKey: this.processDefinitionKey || null,
|
|
2997
|
-
processInstanceId,
|
|
2998
3874
|
sourceActivityId: this.sourceActivityId,
|
|
2999
|
-
inputOverrides: this.
|
|
3875
|
+
inputOverrides: this.inputOverrides || null,
|
|
3000
3876
|
overrides: null,
|
|
3001
3877
|
})
|
|
3002
3878
|
.subscribe({
|
|
@@ -3039,8 +3915,8 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3039
3915
|
this.previewUrl = null;
|
|
3040
3916
|
}
|
|
3041
3917
|
}
|
|
3042
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, deps: [{ token: EpistolaPluginService }, { token: i2$
|
|
3043
|
-
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: `
|
|
3918
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, deps: [{ token: EpistolaPluginService }, { token: i2$3.DomSanitizer }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3919
|
+
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", taskInstanceId: "taskInstanceId", inputOverrides: "inputOverrides", requestOverrides: "requestOverrides", autoRefresh: "autoRefresh", setAutoRefresh: "setAutoRefresh" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
3044
3920
|
<!-- Design-time view: show configuration summary when no runtime context -->
|
|
3045
3921
|
<div *ngIf="designMode" class="epistola-preview-panel">
|
|
3046
3922
|
<div class="preview-header">
|
|
@@ -3053,16 +3929,9 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3053
3929
|
<div class="design-label">Activity</div>
|
|
3054
3930
|
<div class="design-value">{{ sourceActivityId }}</div>
|
|
3055
3931
|
</div>
|
|
3056
|
-
<div class="design-section" *ngIf="
|
|
3057
|
-
<div class="design-label">Input Overrides</div>
|
|
3058
|
-
<
|
|
3059
|
-
<div *ngFor="let entry of overrideMappingEntries(scope)" class="design-entry">
|
|
3060
|
-
<span class="design-scope">{{ scope }}</span
|
|
3061
|
-
>.{{ entry.path }}
|
|
3062
|
-
<i class="mdi mdi-arrow-left"></i>
|
|
3063
|
-
<span class="design-field">{{ entry.field }}</span>
|
|
3064
|
-
</div>
|
|
3065
|
-
</div>
|
|
3932
|
+
<div class="design-section" *ngIf="overrideExpression">
|
|
3933
|
+
<div class="design-label">Input Overrides ($form)</div>
|
|
3934
|
+
<pre class="design-expression">{{ overrideExpression }}</pre>
|
|
3066
3935
|
</div>
|
|
3067
3936
|
<div *ngIf="!sourceActivityId" class="design-unconfigured">
|
|
3068
3937
|
Auto-discover mode (no process link configured)
|
|
@@ -3075,6 +3944,18 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3075
3944
|
<div class="preview-header">
|
|
3076
3945
|
<span>{{ label || 'Document Preview' }}</span>
|
|
3077
3946
|
<div class="preview-controls">
|
|
3947
|
+
<label
|
|
3948
|
+
*ngIf="overrideMapping"
|
|
3949
|
+
class="preview-autorefresh"
|
|
3950
|
+
title="Automatically refresh the preview as you fill in the form"
|
|
3951
|
+
>
|
|
3952
|
+
<input
|
|
3953
|
+
type="checkbox"
|
|
3954
|
+
[checked]="autoRefreshEnabled"
|
|
3955
|
+
(change)="onToggleAutoRefresh($event)"
|
|
3956
|
+
/>
|
|
3957
|
+
Auto-refresh
|
|
3958
|
+
</label>
|
|
3078
3959
|
<button type="button" class="preview-refresh" [disabled]="loading" (click)="refresh()">
|
|
3079
3960
|
<i class="mdi mdi-refresh mr-1"></i>
|
|
3080
3961
|
{{ loading ? 'Generating...' : 'Refresh' }}
|
|
@@ -3097,7 +3978,7 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3097
3978
|
</object>
|
|
3098
3979
|
</div>
|
|
3099
3980
|
</div>
|
|
3100
|
-
`, 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:.
|
|
3981
|
+
`, 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:.75rem}.preview-autorefresh{display:flex;align-items:center;gap:.3rem;font-size:.8rem;font-weight:400;color:#495057;cursor:pointer;margin:0;white-space:nowrap}.preview-autorefresh input{cursor:pointer;margin:0}.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-expression{font-family:monospace;font-size:.8rem;color:#212529;background:#eef0f2;border-radius:4px;padding:.5rem;margin:.25rem 0 0;white-space:pre-wrap;word-break:break-word}.design-unconfigured{color:#6c757d;font-style:italic;font-size:.85rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3101
3982
|
}
|
|
3102
3983
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, decorators: [{
|
|
3103
3984
|
type: Component,
|
|
@@ -3114,16 +3995,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3114
3995
|
<div class="design-label">Activity</div>
|
|
3115
3996
|
<div class="design-value">{{ sourceActivityId }}</div>
|
|
3116
3997
|
</div>
|
|
3117
|
-
<div class="design-section" *ngIf="
|
|
3118
|
-
<div class="design-label">Input Overrides</div>
|
|
3119
|
-
<
|
|
3120
|
-
<div *ngFor="let entry of overrideMappingEntries(scope)" class="design-entry">
|
|
3121
|
-
<span class="design-scope">{{ scope }}</span
|
|
3122
|
-
>.{{ entry.path }}
|
|
3123
|
-
<i class="mdi mdi-arrow-left"></i>
|
|
3124
|
-
<span class="design-field">{{ entry.field }}</span>
|
|
3125
|
-
</div>
|
|
3126
|
-
</div>
|
|
3998
|
+
<div class="design-section" *ngIf="overrideExpression">
|
|
3999
|
+
<div class="design-label">Input Overrides ($form)</div>
|
|
4000
|
+
<pre class="design-expression">{{ overrideExpression }}</pre>
|
|
3127
4001
|
</div>
|
|
3128
4002
|
<div *ngIf="!sourceActivityId" class="design-unconfigured">
|
|
3129
4003
|
Auto-discover mode (no process link configured)
|
|
@@ -3136,6 +4010,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3136
4010
|
<div class="preview-header">
|
|
3137
4011
|
<span>{{ label || 'Document Preview' }}</span>
|
|
3138
4012
|
<div class="preview-controls">
|
|
4013
|
+
<label
|
|
4014
|
+
*ngIf="overrideMapping"
|
|
4015
|
+
class="preview-autorefresh"
|
|
4016
|
+
title="Automatically refresh the preview as you fill in the form"
|
|
4017
|
+
>
|
|
4018
|
+
<input
|
|
4019
|
+
type="checkbox"
|
|
4020
|
+
[checked]="autoRefreshEnabled"
|
|
4021
|
+
(change)="onToggleAutoRefresh($event)"
|
|
4022
|
+
/>
|
|
4023
|
+
Auto-refresh
|
|
4024
|
+
</label>
|
|
3139
4025
|
<button type="button" class="preview-refresh" [disabled]="loading" (click)="refresh()">
|
|
3140
4026
|
<i class="mdi mdi-refresh mr-1"></i>
|
|
3141
4027
|
{{ loading ? 'Generating...' : 'Refresh' }}
|
|
@@ -3158,8 +4044,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3158
4044
|
</object>
|
|
3159
4045
|
</div>
|
|
3160
4046
|
</div>
|
|
3161
|
-
`, 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:.
|
|
3162
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$
|
|
4047
|
+
`, 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:.75rem}.preview-autorefresh{display:flex;align-items:center;gap:.3rem;font-size:.8rem;font-weight:400;color:#495057;cursor:pointer;margin:0;white-space:nowrap}.preview-autorefresh input{cursor:pointer;margin:0}.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-expression{font-family:monospace;font-size:.8rem;color:#212529;background:#eef0f2;border-radius:4px;padding:.5rem;margin:.25rem 0 0;white-space:pre-wrap;word-break:break-word}.design-unconfigured{color:#6c757d;font-style:italic;font-size:.85rem}\n"] }]
|
|
4048
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$3.DomSanitizer }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
3163
4049
|
type: Input
|
|
3164
4050
|
}], valueChange: [{
|
|
3165
4051
|
type: Output
|
|
@@ -3173,8 +4059,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3173
4059
|
type: Input
|
|
3174
4060
|
}], overrideMapping: [{
|
|
3175
4061
|
type: Input
|
|
4062
|
+
}], taskInstanceId: [{
|
|
4063
|
+
type: Input
|
|
4064
|
+
}], inputOverrides: [{
|
|
4065
|
+
type: Input
|
|
4066
|
+
}], requestOverrides: [{
|
|
4067
|
+
type: Input
|
|
4068
|
+
}], autoRefresh: [{
|
|
4069
|
+
type: Input
|
|
4070
|
+
}], setAutoRefresh: [{
|
|
4071
|
+
type: Input
|
|
3176
4072
|
}] } });
|
|
3177
4073
|
|
|
4074
|
+
/*
|
|
4075
|
+
* Copyright 2025 Epistola.
|
|
4076
|
+
*
|
|
4077
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
4078
|
+
* you may not use this file except in compliance with the License.
|
|
4079
|
+
* You may obtain a copy of the License at
|
|
4080
|
+
*
|
|
4081
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
4082
|
+
*
|
|
4083
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
4084
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
4085
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
4086
|
+
* See the License for the specific language governing permissions and
|
|
4087
|
+
* limitations under the License.
|
|
4088
|
+
*
|
|
4089
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4090
|
+
*/
|
|
3178
4091
|
class EpistolaAdminPageComponent {
|
|
3179
4092
|
adminService;
|
|
3180
4093
|
route;
|
|
@@ -3182,18 +4095,28 @@ class EpistolaAdminPageComponent {
|
|
|
3182
4095
|
cards = [];
|
|
3183
4096
|
selectedCard = null;
|
|
3184
4097
|
activeTab = 'actions';
|
|
4098
|
+
// NOTE: the 'forms' tab is TEMPORARY (remove in 1.0.0) — see the carrier-repair block below.
|
|
3185
4099
|
overviewTab = 'configurations';
|
|
3186
4100
|
loading = false;
|
|
3187
4101
|
pluginVersion = null;
|
|
3188
4102
|
changelog = null;
|
|
3189
4103
|
changelogLoading = false;
|
|
3190
|
-
|
|
4104
|
+
validationReport = null;
|
|
3191
4105
|
reconcilingExecutionIds = new Set();
|
|
3192
4106
|
reconcileFeedback = null;
|
|
3193
4107
|
catalogs = [];
|
|
3194
4108
|
catalogsLoading = false;
|
|
3195
4109
|
redeployingSlugs = new Set();
|
|
3196
4110
|
catalogFeedback = null;
|
|
4111
|
+
// TEMPORARY (removed in 1.0.0): task-id carrier detection + repair.
|
|
4112
|
+
formIssues = null;
|
|
4113
|
+
formIssuesLoading = false;
|
|
4114
|
+
repairingFormIds = new Set();
|
|
4115
|
+
repairingAll = false;
|
|
4116
|
+
formFeedback = null;
|
|
4117
|
+
// TEMPORARY: forms still using the legacy override-mapping object format.
|
|
4118
|
+
legacyOverrideForms = null;
|
|
4119
|
+
legacyOverrideLoading = false;
|
|
3197
4120
|
connectionStatuses = [];
|
|
3198
4121
|
usageEntries = [];
|
|
3199
4122
|
pendingJobs = [];
|
|
@@ -3206,6 +4129,22 @@ class EpistolaAdminPageComponent {
|
|
|
3206
4129
|
this.route = route;
|
|
3207
4130
|
this.router = router;
|
|
3208
4131
|
}
|
|
4132
|
+
/** Violations from the latest report (empty when healthy or not yet loaded). */
|
|
4133
|
+
get validationViolations() {
|
|
4134
|
+
return this.validationReport?.violations ?? [];
|
|
4135
|
+
}
|
|
4136
|
+
/** Scan cadence in whole minutes, for the "refreshes every N min" note. */
|
|
4137
|
+
get refreshIntervalMinutes() {
|
|
4138
|
+
return Math.round((this.validationReport?.refreshIntervalMs ?? 600000) / 60000);
|
|
4139
|
+
}
|
|
4140
|
+
/** Combined "forms needing attention" count for the tab badge (carrier + legacy override). */
|
|
4141
|
+
get formsAttentionCount() {
|
|
4142
|
+
return (this.formIssues?.length ?? 0) + (this.legacyOverrideForms?.length ?? 0);
|
|
4143
|
+
}
|
|
4144
|
+
/** Whether the forms tab has loaded at least one of its two scans. */
|
|
4145
|
+
get formsScanLoaded() {
|
|
4146
|
+
return this.formIssues !== null || this.legacyOverrideForms !== null;
|
|
4147
|
+
}
|
|
3209
4148
|
ngOnInit() {
|
|
3210
4149
|
this.deepLinkConfigId = this.route.snapshot.queryParamMap.get('configurationId');
|
|
3211
4150
|
const tab = this.route.snapshot.queryParamMap.get('tab');
|
|
@@ -3232,11 +4171,96 @@ class EpistolaAdminPageComponent {
|
|
|
3232
4171
|
this.activeTab = tab;
|
|
3233
4172
|
this.updateUrl(this.selectedCard?.configurationId ?? null, tab);
|
|
3234
4173
|
}
|
|
4174
|
+
// 'forms' is TEMPORARY (remove in 1.0.0).
|
|
3235
4175
|
setOverviewTab(tab) {
|
|
3236
4176
|
this.overviewTab = tab;
|
|
3237
4177
|
if (tab === 'changelog' && this.changelog === null && !this.changelogLoading) {
|
|
3238
4178
|
this.loadChangelog();
|
|
3239
4179
|
}
|
|
4180
|
+
if (tab === 'forms' && this.formIssues === null && !this.formIssuesLoading) {
|
|
4181
|
+
this.loadFormIssues();
|
|
4182
|
+
}
|
|
4183
|
+
if (tab === 'forms' && this.legacyOverrideForms === null && !this.legacyOverrideLoading) {
|
|
4184
|
+
this.loadLegacyOverrideForms();
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
// ---- TEMPORARY: legacy override-mapping format detection ----
|
|
4188
|
+
loadLegacyOverrideForms() {
|
|
4189
|
+
this.legacyOverrideLoading = true;
|
|
4190
|
+
this.adminService.getLegacyOverrideForms().subscribe({
|
|
4191
|
+
next: (forms) => {
|
|
4192
|
+
this.legacyOverrideForms = forms;
|
|
4193
|
+
this.legacyOverrideLoading = false;
|
|
4194
|
+
},
|
|
4195
|
+
error: () => {
|
|
4196
|
+
this.legacyOverrideForms = [];
|
|
4197
|
+
this.legacyOverrideLoading = false;
|
|
4198
|
+
},
|
|
4199
|
+
});
|
|
4200
|
+
}
|
|
4201
|
+
// ---- TEMPORARY (removed in 1.0.0): task-id carrier detection + repair ----
|
|
4202
|
+
loadFormIssues() {
|
|
4203
|
+
this.formIssuesLoading = true;
|
|
4204
|
+
this.formFeedback = null;
|
|
4205
|
+
this.adminService.getFormCarrierIssues().subscribe({
|
|
4206
|
+
next: (issues) => {
|
|
4207
|
+
this.formIssues = issues;
|
|
4208
|
+
this.formIssuesLoading = false;
|
|
4209
|
+
},
|
|
4210
|
+
error: () => {
|
|
4211
|
+
this.formIssues = [];
|
|
4212
|
+
this.formIssuesLoading = false;
|
|
4213
|
+
},
|
|
4214
|
+
});
|
|
4215
|
+
}
|
|
4216
|
+
isRepairingForm(issue) {
|
|
4217
|
+
return this.repairingFormIds.has(issue.formId);
|
|
4218
|
+
}
|
|
4219
|
+
repairForm(issue) {
|
|
4220
|
+
if (this.repairingFormIds.has(issue.formId)) {
|
|
4221
|
+
return;
|
|
4222
|
+
}
|
|
4223
|
+
this.repairingFormIds.add(issue.formId);
|
|
4224
|
+
this.formFeedback = null;
|
|
4225
|
+
this.adminService.repairFormCarrier(issue.formId).subscribe({
|
|
4226
|
+
next: (result) => {
|
|
4227
|
+
this.repairingFormIds.delete(issue.formId);
|
|
4228
|
+
this.formFeedback = {
|
|
4229
|
+
formId: issue.formId,
|
|
4230
|
+
type: 'success',
|
|
4231
|
+
message: `OK — ${result.componentsPatched} component(s) patched`,
|
|
4232
|
+
};
|
|
4233
|
+
this.loadFormIssues();
|
|
4234
|
+
},
|
|
4235
|
+
error: (err) => {
|
|
4236
|
+
this.repairingFormIds.delete(issue.formId);
|
|
4237
|
+
const message = err?.error?.errorMessage ?? err?.error?.message ?? err?.message ?? 'unknown error';
|
|
4238
|
+
this.formFeedback = { formId: issue.formId, type: 'error', message };
|
|
4239
|
+
},
|
|
4240
|
+
});
|
|
4241
|
+
}
|
|
4242
|
+
repairAllForms() {
|
|
4243
|
+
if (this.repairingAll) {
|
|
4244
|
+
return;
|
|
4245
|
+
}
|
|
4246
|
+
this.repairingAll = true;
|
|
4247
|
+
this.formFeedback = null;
|
|
4248
|
+
this.adminService.repairAllFormCarriers().subscribe({
|
|
4249
|
+
next: (summary) => {
|
|
4250
|
+
this.repairingAll = false;
|
|
4251
|
+
this.formFeedback = {
|
|
4252
|
+
formId: 'all',
|
|
4253
|
+
type: summary.failed > 0 ? 'error' : 'success',
|
|
4254
|
+
message: `Repaired ${summary.formsRepaired} form(s), ${summary.componentsPatched} component(s)${summary.failed > 0 ? `, ${summary.failed} failed` : ''}`,
|
|
4255
|
+
};
|
|
4256
|
+
this.loadFormIssues();
|
|
4257
|
+
},
|
|
4258
|
+
error: (err) => {
|
|
4259
|
+
this.repairingAll = false;
|
|
4260
|
+
const message = err?.error?.message ?? err?.message ?? 'unknown error';
|
|
4261
|
+
this.formFeedback = { formId: 'all', type: 'error', message };
|
|
4262
|
+
},
|
|
4263
|
+
});
|
|
3240
4264
|
}
|
|
3241
4265
|
loadChangelog() {
|
|
3242
4266
|
this.changelogLoading = true;
|
|
@@ -3425,14 +4449,14 @@ class EpistolaAdminPageComponent {
|
|
|
3425
4449
|
this.tryBuildCards();
|
|
3426
4450
|
},
|
|
3427
4451
|
});
|
|
3428
|
-
// Validation
|
|
3429
|
-
// gate the loading flag on
|
|
3430
|
-
this.adminService.
|
|
3431
|
-
next: (
|
|
3432
|
-
this.
|
|
4452
|
+
// Validation report is independent of cards — load alongside but don't
|
|
4453
|
+
// gate the loading flag on it.
|
|
4454
|
+
this.adminService.getValidationReport().subscribe({
|
|
4455
|
+
next: (report) => {
|
|
4456
|
+
this.validationReport = report;
|
|
3433
4457
|
},
|
|
3434
4458
|
error: () => {
|
|
3435
|
-
this.
|
|
4459
|
+
this.validationReport = null;
|
|
3436
4460
|
},
|
|
3437
4461
|
});
|
|
3438
4462
|
}
|
|
@@ -3476,14 +4500,31 @@ class EpistolaAdminPageComponent {
|
|
|
3476
4500
|
},
|
|
3477
4501
|
});
|
|
3478
4502
|
}
|
|
3479
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, deps: [{ token: EpistolaAdminService }, { token: i2$
|
|
3480
|
-
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: tabs (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 <ng-template #configurationsHeading>\n {{ 'epistolaAdminConfigurations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ cards.length }}</cds-tag>\n </ng-template>\n\n <ng-template #validationsHeading>\n {{ 'epistolaAdminValidations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" [type]=\"validationViolations.length > 0 ? 'red' : 'gray'\" class=\"ms-1\">\n {{ validationViolations.length }}\n </cds-tag>\n </ng-template>\n\n <ng-template #changelogHeading>\n {{ 'epistolaAdminChangelog' | pluginTranslate: 'epistola' | async }}\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"configurationsHeading\"\n [active]=\"overviewTab === 'configurations'\"\n (selected)=\"setOverviewTab('configurations')\"\n >\n <div *ngIf=\"loading\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted mt-3\">\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 </cds-tab>\n\n <cds-tab\n [heading]=\"validationsHeading\"\n [active]=\"overviewTab === 'validations'\"\n (selected)=\"setOverviewTab('validations')\"\n >\n <div *ngIf=\"validationViolations.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoValidations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"validationViolations.length > 0\" class=\"mt-3\">\n <p class=\"text-muted mb-3\">\n {{ 'epistolaAdminValidationWarningBody' | pluginTranslate: 'epistola' | async }}\n </p>\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminValidationCode' | pluginTranslate: 'epistola' | async }}</th>\n <th>\n {{ 'epistolaAdminValidationMessage' | pluginTranslate: 'epistola' | async }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let v of validationViolations\">\n <td>\n {{ v.processDefinitionName || v.processDefinitionKey }}\n <div *ngIf=\"v.processDefinitionName\" class=\"text-muted small\">\n <code>{{ v.processDefinitionKey }}</code>\n </div>\n </td>\n <td>\n <code>{{ v.activityId }}</code>\n </td>\n <td>\n <cds-tag size=\"sm\" type=\"red\">{{ v.code }}</cds-tag>\n </td>\n <td>{{ v.message }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </cds-tab>\n\n <cds-tab\n [heading]=\"changelogHeading\"\n [active]=\"overviewTab === 'changelog'\"\n (selected)=\"setOverviewTab('changelog')\"\n >\n <div class=\"d-flex align-items-center mt-3 mb-3\">\n <span class=\"text-muted me-2\">{{\n 'epistolaAdminRunningVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">v{{ pluginVersion || '\u2014' }}</cds-tag>\n </div>\n\n <div *ngIf=\"changelogLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoChangelog' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length > 0\"\n class=\"epistola-changelog\"\n >\n <section *ngFor=\"let release of changelog\" class=\"changelog-release\">\n <div class=\"changelog-release__header\">\n <cds-tag size=\"sm\" type=\"purple\">{{ release.version }}</cds-tag>\n <span *ngIf=\"release.date\" class=\"text-muted ms-2\">{{ release.date }}</span>\n </div>\n <div *ngFor=\"let section of release.sections\" class=\"changelog-section\">\n <h6 class=\"changelog-section__title\">{{ section.title }}</h6>\n <ul class=\"changelog-section__items\">\n <li *ngFor=\"let item of section.items\">{{ item }}</li>\n </ul>\n </div>\n </section>\n </div>\n </cds-tab>\n </cds-tabs>\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 <ng-template #catalogsHeading>\n {{ 'epistolaAdminCatalogs' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ catalogs.length }}</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 <th></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 <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"reconcilePending(job)\"\n [disabled]=\"isReconciling(job)\"\n [title]=\"'epistolaAdminReconcileTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isReconciling(job)\">\n {{ 'epistolaAdminReconcile' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isReconciling(job)\">\n {{ 'epistolaAdminReconciling' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"reconcileFeedback && reconcileFeedback.executionId === job.executionId\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"reconcileFeedback.type === 'success'\"\n [class.text-warning]=\"reconcileFeedback.type === 'pending'\"\n [class.text-danger]=\"reconcileFeedback.type === 'error'\"\n >\n {{ reconcileFeedback.message }}\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"catalogsHeading\"\n [active]=\"activeTab === 'catalogs'\"\n (selected)=\"setActiveTab('catalogs')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminCatalogsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"catalogsLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!catalogsLoading && catalogs.length === 0\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminNoCatalogs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"!catalogsLoading && catalogs.length > 0\" class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCatalogSlug' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogVersion' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogStatus' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let catalog of catalogs\">\n <td>\n <code>{{ catalog.slug }}</code>\n </td>\n <td>{{ catalog.version }}</td>\n <td>\n <cds-tag *ngIf=\"catalog.status === 'IN_EPISTOLA'\" size=\"sm\" type=\"green\">\n {{ 'epistolaAdminCatalogInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'NOT_IN_EPISTOLA'\" size=\"sm\" type=\"red\">\n {{ 'epistolaAdminCatalogNotInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'UNKNOWN'\" size=\"sm\" type=\"gray\">\n {{ 'epistolaAdminCatalogStatusUnknown' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n </td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"redeployCatalog(catalog)\"\n [disabled]=\"isRedeploying(catalog)\"\n [title]=\"'epistolaAdminRedeployTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploy' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploying' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"catalogFeedback && catalogFeedback.slug === catalog.slug\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"catalogFeedback.type === 'success'\"\n [class.text-danger]=\"catalogFeedback.type === 'error'\"\n >\n {{ catalogFeedback.message }}\n </div>\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}.epistola-admin .epistola-changelog{max-height:60vh;overflow:auto;padding:.5rem 1rem;border:1px solid #dee2e6;border-radius:4px}.epistola-admin .epistola-changelog .changelog-release{padding:.75rem 0}.epistola-admin .epistola-changelog .changelog-release+.changelog-release{border-top:1px solid #eee}.epistola-admin .epistola-changelog .changelog-release__header{display:flex;align-items:center;margin-bottom:.5rem}.epistola-admin .epistola-changelog .changelog-section{margin:.25rem 0 .75rem}.epistola-admin .epistola-changelog .changelog-section__title{font-size:.8125rem;font-weight:600;text-transform:uppercase;letter-spacing:.02em;color:#6f6f6f;margin-bottom:.25rem}.epistola-admin .epistola-changelog .changelog-section__items{margin:0;padding-left:1.25rem;font-size:.8125rem;line-height:1.5}.epistola-admin .epistola-changelog .changelog-section__items li{margin-bottom:.25rem;word-break:break-word}\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$5.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$1.Tabs, selector: "cds-tabs, ibm-tabs", inputs: ["position", "cacheActive", "followFocus", "isNavigation", "ariaLabel", "ariaLabelledby", "type", "theme", "skeleton"] }, { kind: "component", type: i5$1.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"] }] });
|
|
4503
|
+
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 });
|
|
4504
|
+
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: tabs (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 <ng-template #configurationsHeading>\n {{ 'epistolaAdminConfigurations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ cards.length }}</cds-tag>\n </ng-template>\n\n <ng-template #validationsHeading>\n {{ 'epistolaAdminValidations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" [type]=\"validationViolations.length > 0 ? 'red' : 'gray'\" class=\"ms-1\">\n {{ validationViolations.length }}\n </cds-tag>\n </ng-template>\n\n <ng-template #changelogHeading>\n {{ 'epistolaAdminChangelog' | pluginTranslate: 'epistola' | async }}\n </ng-template>\n\n <!-- TEMPORARY (removed in 1.0.0): forms missing the task-id carrier -->\n <ng-template #formsHeading>\n {{ 'epistolaAdminForms' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n *ngIf=\"formsScanLoaded\"\n size=\"sm\"\n [type]=\"formsAttentionCount > 0 ? 'red' : 'gray'\"\n class=\"ms-1\"\n >{{ formsAttentionCount }}</cds-tag\n >\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"configurationsHeading\"\n [active]=\"overviewTab === 'configurations'\"\n (selected)=\"setOverviewTab('configurations')\"\n >\n <div *ngIf=\"loading\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted mt-3\">\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 </cds-tab>\n\n <cds-tab\n [heading]=\"validationsHeading\"\n [active]=\"overviewTab === 'validations'\"\n (selected)=\"setOverviewTab('validations')\"\n >\n <div class=\"text-muted small mt-3\">\n <div>\n {{ 'epistolaAdminValidationLastChecked' | pluginTranslate: 'epistola' | async }}:\n <ng-container *ngIf=\"validationReport?.lastCheckedAt; else notYetRun\">\n {{ validationReport?.lastCheckedAt | date: 'medium' }}\n </ng-container>\n <ng-template #notYetRun>\n {{ 'epistolaAdminValidationNotYetRun' | pluginTranslate: 'epistola' | async }}\n </ng-template>\n \u00B7 {{ 'epistolaAdminValidationAutoRefresh' | pluginTranslate: 'epistola' | async }}\n {{ refreshIntervalMinutes }} min.\n </div>\n <div>\n {{ 'epistolaAdminValidationLatestVersionNote' | pluginTranslate: 'epistola' | async }}\n </div>\n </div>\n\n <div *ngIf=\"validationViolations.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoValidations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"validationViolations.length > 0\" class=\"mt-3\">\n <p class=\"text-muted mb-3\">\n {{ 'epistolaAdminValidationWarningBody' | pluginTranslate: 'epistola' | async }}\n </p>\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminValidationCode' | pluginTranslate: 'epistola' | async }}</th>\n <th>\n {{ 'epistolaAdminValidationMessage' | pluginTranslate: 'epistola' | async }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let v of validationViolations\">\n <td>\n {{ v.processDefinitionName || v.processDefinitionKey }}\n <div *ngIf=\"v.processDefinitionName\" class=\"text-muted small\">\n <code>{{ v.processDefinitionKey }}</code>\n </div>\n </td>\n <td>\n <code>{{ v.activityId }}</code>\n </td>\n <td>\n <cds-tag size=\"sm\" type=\"red\">{{ v.code }}</cds-tag>\n </td>\n <td>{{ v.message }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </cds-tab>\n\n <cds-tab\n [heading]=\"changelogHeading\"\n [active]=\"overviewTab === 'changelog'\"\n (selected)=\"setOverviewTab('changelog')\"\n >\n <div class=\"d-flex align-items-center mt-3 mb-3\">\n <span class=\"text-muted me-2\">{{\n 'epistolaAdminRunningVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">v{{ pluginVersion || '\u2014' }}</cds-tag>\n </div>\n\n <div *ngIf=\"changelogLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoChangelog' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length > 0\"\n class=\"epistola-changelog\"\n >\n <section *ngFor=\"let release of changelog\" class=\"changelog-release\">\n <div class=\"changelog-release__header\">\n <cds-tag size=\"sm\" type=\"purple\">{{ release.version }}</cds-tag>\n <span *ngIf=\"release.date\" class=\"text-muted ms-2\">{{ release.date }}</span>\n </div>\n <div *ngFor=\"let section of release.sections\" class=\"changelog-section\">\n <h6 class=\"changelog-section__title\">{{ section.title }}</h6>\n <ul class=\"changelog-section__items\">\n <li *ngFor=\"let item of section.items\">{{ item }}</li>\n </ul>\n </div>\n </section>\n </div>\n </cds-tab>\n\n <!-- TEMPORARY (removed in 1.0.0): forms missing the task-id carrier -->\n <cds-tab\n [heading]=\"formsHeading\"\n [active]=\"overviewTab === 'forms'\"\n (selected)=\"setOverviewTab('forms')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminFormsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"formIssuesLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!formIssuesLoading && formIssues && formIssues.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoFormIssues' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!formIssuesLoading && formIssues && formIssues.length > 0\">\n <div class=\"mb-2\">\n <button\n class=\"btn btn-sm btn-primary\"\n (click)=\"repairAllForms()\"\n [disabled]=\"repairingAll\"\n >\n {{\n (repairingAll ? 'epistolaAdminRepairing' : 'epistolaAdminRepairAll')\n | pluginTranslate: 'epistola'\n | async\n }}\n </button>\n <span\n *ngIf=\"formFeedback && formFeedback.formId === 'all'\"\n class=\"small ms-2\"\n [class.text-success]=\"formFeedback.type === 'success'\"\n [class.text-danger]=\"formFeedback.type === 'error'\"\n >{{ formFeedback.message }}</span\n >\n </div>\n\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminFormName' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminFormMissing' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let issue of formIssues\">\n <td>\n <code>{{ issue.name }}</code>\n <cds-tag\n *ngIf=\"issue.readOnly\"\n size=\"sm\"\n type=\"warm-gray\"\n class=\"ms-1\"\n [title]=\"'epistolaAdminFormReadOnlyHint' | pluginTranslate: 'epistola' | async\"\n >{{\n 'epistolaAdminFormReadOnly' | pluginTranslate: 'epistola' | async\n }}</cds-tag\n >\n </td>\n <td>{{ issue.missingComponents }}</td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"repairForm(issue)\"\n [disabled]=\"isRepairingForm(issue)\"\n [title]=\"'epistolaAdminRepairTooltip' | pluginTranslate: 'epistola' | async\"\n >\n {{\n (isRepairingForm(issue) ? 'epistolaAdminRepairing' : 'epistolaAdminRepair')\n | pluginTranslate: 'epistola'\n | async\n }}\n </button>\n <div\n *ngIf=\"formFeedback && formFeedback.formId === issue.formId\"\n class=\"small mt-1\"\n [class.text-success]=\"formFeedback.type === 'success'\"\n [class.text-danger]=\"formFeedback.type === 'error'\"\n >\n {{ formFeedback.message }}\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- TEMPORARY: forms still using the legacy override-mapping object format -->\n <hr class=\"my-4\" />\n <h5 class=\"mb-1\">\n {{ 'epistolaAdminLegacyOverrideTitle' | pluginTranslate: 'epistola' | async }}\n </h5>\n <p class=\"text-muted mb-3\">\n {{ 'epistolaAdminLegacyOverrideIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"legacyOverrideLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!legacyOverrideLoading && legacyOverrideForms && legacyOverrideForms.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoLegacyOverride' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table\n *ngIf=\"!legacyOverrideLoading && legacyOverrideForms && legacyOverrideForms.length > 0\"\n class=\"table table-striped\"\n >\n <thead>\n <tr>\n <th>{{ 'epistolaAdminFormName' | pluginTranslate: 'epistola' | async }}</th>\n <th>\n {{ 'epistolaAdminLegacyOverrideComponents' | pluginTranslate: 'epistola' | async }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let form of legacyOverrideForms\">\n <td>\n <code>{{ form.name }}</code>\n <cds-tag\n *ngIf=\"form.readOnly\"\n size=\"sm\"\n type=\"warm-gray\"\n class=\"ms-1\"\n [title]=\"'epistolaAdminFormReadOnlyHint' | pluginTranslate: 'epistola' | async\"\n >{{ 'epistolaAdminFormReadOnly' | pluginTranslate: 'epistola' | async }}</cds-tag\n >\n </td>\n <td>{{ form.legacyComponents }}</td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n </cds-tabs>\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 <ng-template #catalogsHeading>\n {{ 'epistolaAdminCatalogs' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ catalogs.length }}</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>{{ 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminRequestId' | pluginTranslate: 'epistola' | async }}</th>\n <th></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 <cds-tag\n *ngIf=\"job.status === 'UNWIRED'; else waitingTag\"\n size=\"sm\"\n type=\"red\"\n [title]=\"'epistolaAdminUnwiredTooltip' | pluginTranslate: 'epistola' | async\"\n >\n {{ 'epistolaAdminStatusUnwired' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <ng-template #waitingTag>\n <cds-tag size=\"sm\" type=\"blue\">\n {{ 'epistolaAdminStatusWaiting' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n </ng-template>\n </td>\n <td>\n <code *ngIf=\"job.status !== 'UNWIRED'\">{{ job.requestId }}</code>\n <span *ngIf=\"job.status === 'UNWIRED'\" class=\"text-muted\">\u2014</span>\n </td>\n <td class=\"text-end\">\n <button\n *ngIf=\"job.status !== 'UNWIRED'\"\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"reconcilePending(job)\"\n [disabled]=\"isReconciling(job)\"\n [title]=\"'epistolaAdminReconcileTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isReconciling(job)\">\n {{ 'epistolaAdminReconcile' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isReconciling(job)\">\n {{ 'epistolaAdminReconciling' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <span\n *ngIf=\"job.status === 'UNWIRED'\"\n class=\"text-muted small\"\n [title]=\"'epistolaAdminUnwiredTooltip' | pluginTranslate: 'epistola' | async\"\n >\n {{ 'epistolaAdminUnwiredHint' | pluginTranslate: 'epistola' | async }}\n </span>\n <div\n *ngIf=\"reconcileFeedback && reconcileFeedback.executionId === job.executionId\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"reconcileFeedback.type === 'success'\"\n [class.text-warning]=\"reconcileFeedback.type === 'pending'\"\n [class.text-danger]=\"reconcileFeedback.type === 'error'\"\n >\n {{ reconcileFeedback.message }}\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"catalogsHeading\"\n [active]=\"activeTab === 'catalogs'\"\n (selected)=\"setActiveTab('catalogs')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminCatalogsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"catalogsLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!catalogsLoading && catalogs.length === 0\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminNoCatalogs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"!catalogsLoading && catalogs.length > 0\" class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCatalogSlug' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogVersion' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogStatus' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let catalog of catalogs\">\n <td>\n <code>{{ catalog.slug }}</code>\n </td>\n <td>{{ catalog.version }}</td>\n <td>\n <cds-tag *ngIf=\"catalog.status === 'IN_EPISTOLA'\" size=\"sm\" type=\"green\">\n {{ 'epistolaAdminCatalogInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'NOT_IN_EPISTOLA'\" size=\"sm\" type=\"red\">\n {{ 'epistolaAdminCatalogNotInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'UNKNOWN'\" size=\"sm\" type=\"gray\">\n {{ 'epistolaAdminCatalogStatusUnknown' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n </td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"redeployCatalog(catalog)\"\n [disabled]=\"isRedeploying(catalog)\"\n [title]=\"'epistolaAdminRedeployTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploy' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploying' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"catalogFeedback && catalogFeedback.slug === catalog.slug\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"catalogFeedback.type === 'success'\"\n [class.text-danger]=\"catalogFeedback.type === 'error'\"\n >\n {{ catalogFeedback.message }}\n </div>\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}.epistola-admin .epistola-changelog{max-height:60vh;overflow:auto;padding:.5rem 1rem;border:1px solid #dee2e6;border-radius:4px}.epistola-admin .epistola-changelog .changelog-release{padding:.75rem 0}.epistola-admin .epistola-changelog .changelog-release+.changelog-release{border-top:1px solid #eee}.epistola-admin .epistola-changelog .changelog-release__header{display:flex;align-items:center;margin-bottom:.5rem}.epistola-admin .epistola-changelog .changelog-section{margin:.25rem 0 .75rem}.epistola-admin .epistola-changelog .changelog-section__title{font-size:.8125rem;font-weight:600;text-transform:uppercase;letter-spacing:.02em;color:#6f6f6f;margin-bottom:.25rem}.epistola-admin .epistola-changelog .changelog-section__items{margin:0;padding-left:1.25rem;font-size:.8125rem;line-height:1.5}.epistola-admin .epistola-changelog .changelog-section__items li{margin-bottom:.25rem;word-break:break-word}\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: "pipe", type: i1$1.DatePipe, name: "date" }, { 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"] }] });
|
|
3481
4505
|
}
|
|
3482
4506
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, decorators: [{
|
|
3483
4507
|
type: Component,
|
|
3484
|
-
args: [{ selector: 'epistola-admin-page', standalone: true, imports: [CommonModule, RouterModule, PluginTranslatePipeModule, TabsModule, TagModule], template: "<div class=\"epistola-admin\">\n <!-- Overview: tabs (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 <ng-template #configurationsHeading>\n {{ 'epistolaAdminConfigurations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ cards.length }}</cds-tag>\n </ng-template>\n\n <ng-template #validationsHeading>\n {{ 'epistolaAdminValidations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" [type]=\"validationViolations.length > 0 ? 'red' : 'gray'\" class=\"ms-1\">\n {{ validationViolations.length }}\n </cds-tag>\n </ng-template>\n\n <ng-template #changelogHeading>\n {{ 'epistolaAdminChangelog' | pluginTranslate: 'epistola' | async }}\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"configurationsHeading\"\n [active]=\"overviewTab === 'configurations'\"\n (selected)=\"setOverviewTab('configurations')\"\n >\n <div *ngIf=\"loading\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted mt-3\">\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 </cds-tab>\n\n <cds-tab\n [heading]=\"validationsHeading\"\n [active]=\"overviewTab === 'validations'\"\n (selected)=\"setOverviewTab('validations')\"\n >\n <div *ngIf=\"validationViolations.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoValidations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"validationViolations.length > 0\" class=\"mt-3\">\n <p class=\"text-muted mb-3\">\n {{ 'epistolaAdminValidationWarningBody' | pluginTranslate: 'epistola' | async }}\n </p>\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminValidationCode' | pluginTranslate: 'epistola' | async }}</th>\n <th>\n {{ 'epistolaAdminValidationMessage' | pluginTranslate: 'epistola' | async }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let v of validationViolations\">\n <td>\n {{ v.processDefinitionName || v.processDefinitionKey }}\n <div *ngIf=\"v.processDefinitionName\" class=\"text-muted small\">\n <code>{{ v.processDefinitionKey }}</code>\n </div>\n </td>\n <td>\n <code>{{ v.activityId }}</code>\n </td>\n <td>\n <cds-tag size=\"sm\" type=\"red\">{{ v.code }}</cds-tag>\n </td>\n <td>{{ v.message }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </cds-tab>\n\n <cds-tab\n [heading]=\"changelogHeading\"\n [active]=\"overviewTab === 'changelog'\"\n (selected)=\"setOverviewTab('changelog')\"\n >\n <div class=\"d-flex align-items-center mt-3 mb-3\">\n <span class=\"text-muted me-2\">{{\n 'epistolaAdminRunningVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">v{{ pluginVersion || '\u2014' }}</cds-tag>\n </div>\n\n <div *ngIf=\"changelogLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoChangelog' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length > 0\"\n class=\"epistola-changelog\"\n >\n <section *ngFor=\"let release of changelog\" class=\"changelog-release\">\n <div class=\"changelog-release__header\">\n <cds-tag size=\"sm\" type=\"purple\">{{ release.version }}</cds-tag>\n <span *ngIf=\"release.date\" class=\"text-muted ms-2\">{{ release.date }}</span>\n </div>\n <div *ngFor=\"let section of release.sections\" class=\"changelog-section\">\n <h6 class=\"changelog-section__title\">{{ section.title }}</h6>\n <ul class=\"changelog-section__items\">\n <li *ngFor=\"let item of section.items\">{{ item }}</li>\n </ul>\n </div>\n </section>\n </div>\n </cds-tab>\n </cds-tabs>\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 <ng-template #catalogsHeading>\n {{ 'epistolaAdminCatalogs' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ catalogs.length }}</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 <th></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 <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"reconcilePending(job)\"\n [disabled]=\"isReconciling(job)\"\n [title]=\"'epistolaAdminReconcileTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isReconciling(job)\">\n {{ 'epistolaAdminReconcile' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isReconciling(job)\">\n {{ 'epistolaAdminReconciling' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"reconcileFeedback && reconcileFeedback.executionId === job.executionId\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"reconcileFeedback.type === 'success'\"\n [class.text-warning]=\"reconcileFeedback.type === 'pending'\"\n [class.text-danger]=\"reconcileFeedback.type === 'error'\"\n >\n {{ reconcileFeedback.message }}\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"catalogsHeading\"\n [active]=\"activeTab === 'catalogs'\"\n (selected)=\"setActiveTab('catalogs')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminCatalogsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"catalogsLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!catalogsLoading && catalogs.length === 0\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminNoCatalogs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"!catalogsLoading && catalogs.length > 0\" class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCatalogSlug' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogVersion' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogStatus' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let catalog of catalogs\">\n <td>\n <code>{{ catalog.slug }}</code>\n </td>\n <td>{{ catalog.version }}</td>\n <td>\n <cds-tag *ngIf=\"catalog.status === 'IN_EPISTOLA'\" size=\"sm\" type=\"green\">\n {{ 'epistolaAdminCatalogInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'NOT_IN_EPISTOLA'\" size=\"sm\" type=\"red\">\n {{ 'epistolaAdminCatalogNotInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'UNKNOWN'\" size=\"sm\" type=\"gray\">\n {{ 'epistolaAdminCatalogStatusUnknown' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n </td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"redeployCatalog(catalog)\"\n [disabled]=\"isRedeploying(catalog)\"\n [title]=\"'epistolaAdminRedeployTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploy' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploying' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"catalogFeedback && catalogFeedback.slug === catalog.slug\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"catalogFeedback.type === 'success'\"\n [class.text-danger]=\"catalogFeedback.type === 'error'\"\n >\n {{ catalogFeedback.message }}\n </div>\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}.epistola-admin .epistola-changelog{max-height:60vh;overflow:auto;padding:.5rem 1rem;border:1px solid #dee2e6;border-radius:4px}.epistola-admin .epistola-changelog .changelog-release{padding:.75rem 0}.epistola-admin .epistola-changelog .changelog-release+.changelog-release{border-top:1px solid #eee}.epistola-admin .epistola-changelog .changelog-release__header{display:flex;align-items:center;margin-bottom:.5rem}.epistola-admin .epistola-changelog .changelog-section{margin:.25rem 0 .75rem}.epistola-admin .epistola-changelog .changelog-section__title{font-size:.8125rem;font-weight:600;text-transform:uppercase;letter-spacing:.02em;color:#6f6f6f;margin-bottom:.25rem}.epistola-admin .epistola-changelog .changelog-section__items{margin:0;padding-left:1.25rem;font-size:.8125rem;line-height:1.5}.epistola-admin .epistola-changelog .changelog-section__items li{margin-bottom:.25rem;word-break:break-word}\n"] }]
|
|
3485
|
-
}], ctorParameters: () => [{ type: EpistolaAdminService }, { type: i2$
|
|
4508
|
+
args: [{ selector: 'epistola-admin-page', standalone: true, imports: [CommonModule, RouterModule, PluginTranslatePipeModule, TabsModule, TagModule], template: "<div class=\"epistola-admin\">\n <!-- Overview: tabs (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 <ng-template #configurationsHeading>\n {{ 'epistolaAdminConfigurations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ cards.length }}</cds-tag>\n </ng-template>\n\n <ng-template #validationsHeading>\n {{ 'epistolaAdminValidations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" [type]=\"validationViolations.length > 0 ? 'red' : 'gray'\" class=\"ms-1\">\n {{ validationViolations.length }}\n </cds-tag>\n </ng-template>\n\n <ng-template #changelogHeading>\n {{ 'epistolaAdminChangelog' | pluginTranslate: 'epistola' | async }}\n </ng-template>\n\n <!-- TEMPORARY (removed in 1.0.0): forms missing the task-id carrier -->\n <ng-template #formsHeading>\n {{ 'epistolaAdminForms' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n *ngIf=\"formsScanLoaded\"\n size=\"sm\"\n [type]=\"formsAttentionCount > 0 ? 'red' : 'gray'\"\n class=\"ms-1\"\n >{{ formsAttentionCount }}</cds-tag\n >\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"configurationsHeading\"\n [active]=\"overviewTab === 'configurations'\"\n (selected)=\"setOverviewTab('configurations')\"\n >\n <div *ngIf=\"loading\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted mt-3\">\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 </cds-tab>\n\n <cds-tab\n [heading]=\"validationsHeading\"\n [active]=\"overviewTab === 'validations'\"\n (selected)=\"setOverviewTab('validations')\"\n >\n <div class=\"text-muted small mt-3\">\n <div>\n {{ 'epistolaAdminValidationLastChecked' | pluginTranslate: 'epistola' | async }}:\n <ng-container *ngIf=\"validationReport?.lastCheckedAt; else notYetRun\">\n {{ validationReport?.lastCheckedAt | date: 'medium' }}\n </ng-container>\n <ng-template #notYetRun>\n {{ 'epistolaAdminValidationNotYetRun' | pluginTranslate: 'epistola' | async }}\n </ng-template>\n \u00B7 {{ 'epistolaAdminValidationAutoRefresh' | pluginTranslate: 'epistola' | async }}\n {{ refreshIntervalMinutes }} min.\n </div>\n <div>\n {{ 'epistolaAdminValidationLatestVersionNote' | pluginTranslate: 'epistola' | async }}\n </div>\n </div>\n\n <div *ngIf=\"validationViolations.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoValidations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"validationViolations.length > 0\" class=\"mt-3\">\n <p class=\"text-muted mb-3\">\n {{ 'epistolaAdminValidationWarningBody' | pluginTranslate: 'epistola' | async }}\n </p>\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminValidationCode' | pluginTranslate: 'epistola' | async }}</th>\n <th>\n {{ 'epistolaAdminValidationMessage' | pluginTranslate: 'epistola' | async }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let v of validationViolations\">\n <td>\n {{ v.processDefinitionName || v.processDefinitionKey }}\n <div *ngIf=\"v.processDefinitionName\" class=\"text-muted small\">\n <code>{{ v.processDefinitionKey }}</code>\n </div>\n </td>\n <td>\n <code>{{ v.activityId }}</code>\n </td>\n <td>\n <cds-tag size=\"sm\" type=\"red\">{{ v.code }}</cds-tag>\n </td>\n <td>{{ v.message }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </cds-tab>\n\n <cds-tab\n [heading]=\"changelogHeading\"\n [active]=\"overviewTab === 'changelog'\"\n (selected)=\"setOverviewTab('changelog')\"\n >\n <div class=\"d-flex align-items-center mt-3 mb-3\">\n <span class=\"text-muted me-2\">{{\n 'epistolaAdminRunningVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">v{{ pluginVersion || '\u2014' }}</cds-tag>\n </div>\n\n <div *ngIf=\"changelogLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoChangelog' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length > 0\"\n class=\"epistola-changelog\"\n >\n <section *ngFor=\"let release of changelog\" class=\"changelog-release\">\n <div class=\"changelog-release__header\">\n <cds-tag size=\"sm\" type=\"purple\">{{ release.version }}</cds-tag>\n <span *ngIf=\"release.date\" class=\"text-muted ms-2\">{{ release.date }}</span>\n </div>\n <div *ngFor=\"let section of release.sections\" class=\"changelog-section\">\n <h6 class=\"changelog-section__title\">{{ section.title }}</h6>\n <ul class=\"changelog-section__items\">\n <li *ngFor=\"let item of section.items\">{{ item }}</li>\n </ul>\n </div>\n </section>\n </div>\n </cds-tab>\n\n <!-- TEMPORARY (removed in 1.0.0): forms missing the task-id carrier -->\n <cds-tab\n [heading]=\"formsHeading\"\n [active]=\"overviewTab === 'forms'\"\n (selected)=\"setOverviewTab('forms')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminFormsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"formIssuesLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!formIssuesLoading && formIssues && formIssues.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoFormIssues' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!formIssuesLoading && formIssues && formIssues.length > 0\">\n <div class=\"mb-2\">\n <button\n class=\"btn btn-sm btn-primary\"\n (click)=\"repairAllForms()\"\n [disabled]=\"repairingAll\"\n >\n {{\n (repairingAll ? 'epistolaAdminRepairing' : 'epistolaAdminRepairAll')\n | pluginTranslate: 'epistola'\n | async\n }}\n </button>\n <span\n *ngIf=\"formFeedback && formFeedback.formId === 'all'\"\n class=\"small ms-2\"\n [class.text-success]=\"formFeedback.type === 'success'\"\n [class.text-danger]=\"formFeedback.type === 'error'\"\n >{{ formFeedback.message }}</span\n >\n </div>\n\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminFormName' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminFormMissing' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let issue of formIssues\">\n <td>\n <code>{{ issue.name }}</code>\n <cds-tag\n *ngIf=\"issue.readOnly\"\n size=\"sm\"\n type=\"warm-gray\"\n class=\"ms-1\"\n [title]=\"'epistolaAdminFormReadOnlyHint' | pluginTranslate: 'epistola' | async\"\n >{{\n 'epistolaAdminFormReadOnly' | pluginTranslate: 'epistola' | async\n }}</cds-tag\n >\n </td>\n <td>{{ issue.missingComponents }}</td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"repairForm(issue)\"\n [disabled]=\"isRepairingForm(issue)\"\n [title]=\"'epistolaAdminRepairTooltip' | pluginTranslate: 'epistola' | async\"\n >\n {{\n (isRepairingForm(issue) ? 'epistolaAdminRepairing' : 'epistolaAdminRepair')\n | pluginTranslate: 'epistola'\n | async\n }}\n </button>\n <div\n *ngIf=\"formFeedback && formFeedback.formId === issue.formId\"\n class=\"small mt-1\"\n [class.text-success]=\"formFeedback.type === 'success'\"\n [class.text-danger]=\"formFeedback.type === 'error'\"\n >\n {{ formFeedback.message }}\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- TEMPORARY: forms still using the legacy override-mapping object format -->\n <hr class=\"my-4\" />\n <h5 class=\"mb-1\">\n {{ 'epistolaAdminLegacyOverrideTitle' | pluginTranslate: 'epistola' | async }}\n </h5>\n <p class=\"text-muted mb-3\">\n {{ 'epistolaAdminLegacyOverrideIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"legacyOverrideLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!legacyOverrideLoading && legacyOverrideForms && legacyOverrideForms.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoLegacyOverride' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table\n *ngIf=\"!legacyOverrideLoading && legacyOverrideForms && legacyOverrideForms.length > 0\"\n class=\"table table-striped\"\n >\n <thead>\n <tr>\n <th>{{ 'epistolaAdminFormName' | pluginTranslate: 'epistola' | async }}</th>\n <th>\n {{ 'epistolaAdminLegacyOverrideComponents' | pluginTranslate: 'epistola' | async }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let form of legacyOverrideForms\">\n <td>\n <code>{{ form.name }}</code>\n <cds-tag\n *ngIf=\"form.readOnly\"\n size=\"sm\"\n type=\"warm-gray\"\n class=\"ms-1\"\n [title]=\"'epistolaAdminFormReadOnlyHint' | pluginTranslate: 'epistola' | async\"\n >{{ 'epistolaAdminFormReadOnly' | pluginTranslate: 'epistola' | async }}</cds-tag\n >\n </td>\n <td>{{ form.legacyComponents }}</td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n </cds-tabs>\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 <ng-template #catalogsHeading>\n {{ 'epistolaAdminCatalogs' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ catalogs.length }}</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>{{ 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminRequestId' | pluginTranslate: 'epistola' | async }}</th>\n <th></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 <cds-tag\n *ngIf=\"job.status === 'UNWIRED'; else waitingTag\"\n size=\"sm\"\n type=\"red\"\n [title]=\"'epistolaAdminUnwiredTooltip' | pluginTranslate: 'epistola' | async\"\n >\n {{ 'epistolaAdminStatusUnwired' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <ng-template #waitingTag>\n <cds-tag size=\"sm\" type=\"blue\">\n {{ 'epistolaAdminStatusWaiting' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n </ng-template>\n </td>\n <td>\n <code *ngIf=\"job.status !== 'UNWIRED'\">{{ job.requestId }}</code>\n <span *ngIf=\"job.status === 'UNWIRED'\" class=\"text-muted\">\u2014</span>\n </td>\n <td class=\"text-end\">\n <button\n *ngIf=\"job.status !== 'UNWIRED'\"\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"reconcilePending(job)\"\n [disabled]=\"isReconciling(job)\"\n [title]=\"'epistolaAdminReconcileTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isReconciling(job)\">\n {{ 'epistolaAdminReconcile' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isReconciling(job)\">\n {{ 'epistolaAdminReconciling' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <span\n *ngIf=\"job.status === 'UNWIRED'\"\n class=\"text-muted small\"\n [title]=\"'epistolaAdminUnwiredTooltip' | pluginTranslate: 'epistola' | async\"\n >\n {{ 'epistolaAdminUnwiredHint' | pluginTranslate: 'epistola' | async }}\n </span>\n <div\n *ngIf=\"reconcileFeedback && reconcileFeedback.executionId === job.executionId\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"reconcileFeedback.type === 'success'\"\n [class.text-warning]=\"reconcileFeedback.type === 'pending'\"\n [class.text-danger]=\"reconcileFeedback.type === 'error'\"\n >\n {{ reconcileFeedback.message }}\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"catalogsHeading\"\n [active]=\"activeTab === 'catalogs'\"\n (selected)=\"setActiveTab('catalogs')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminCatalogsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"catalogsLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!catalogsLoading && catalogs.length === 0\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminNoCatalogs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"!catalogsLoading && catalogs.length > 0\" class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCatalogSlug' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogVersion' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogStatus' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let catalog of catalogs\">\n <td>\n <code>{{ catalog.slug }}</code>\n </td>\n <td>{{ catalog.version }}</td>\n <td>\n <cds-tag *ngIf=\"catalog.status === 'IN_EPISTOLA'\" size=\"sm\" type=\"green\">\n {{ 'epistolaAdminCatalogInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'NOT_IN_EPISTOLA'\" size=\"sm\" type=\"red\">\n {{ 'epistolaAdminCatalogNotInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'UNKNOWN'\" size=\"sm\" type=\"gray\">\n {{ 'epistolaAdminCatalogStatusUnknown' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n </td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"redeployCatalog(catalog)\"\n [disabled]=\"isRedeploying(catalog)\"\n [title]=\"'epistolaAdminRedeployTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploy' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploying' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"catalogFeedback && catalogFeedback.slug === catalog.slug\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"catalogFeedback.type === 'success'\"\n [class.text-danger]=\"catalogFeedback.type === 'error'\"\n >\n {{ catalogFeedback.message }}\n </div>\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}.epistola-admin .epistola-changelog{max-height:60vh;overflow:auto;padding:.5rem 1rem;border:1px solid #dee2e6;border-radius:4px}.epistola-admin .epistola-changelog .changelog-release{padding:.75rem 0}.epistola-admin .epistola-changelog .changelog-release+.changelog-release{border-top:1px solid #eee}.epistola-admin .epistola-changelog .changelog-release__header{display:flex;align-items:center;margin-bottom:.5rem}.epistola-admin .epistola-changelog .changelog-section{margin:.25rem 0 .75rem}.epistola-admin .epistola-changelog .changelog-section__title{font-size:.8125rem;font-weight:600;text-transform:uppercase;letter-spacing:.02em;color:#6f6f6f;margin-bottom:.25rem}.epistola-admin .epistola-changelog .changelog-section__items{margin:0;padding-left:1.25rem;font-size:.8125rem;line-height:1.5}.epistola-admin .epistola-changelog .changelog-section__items li{margin-bottom:.25rem;word-break:break-word}\n"] }]
|
|
4509
|
+
}], ctorParameters: () => [{ type: EpistolaAdminService }, { type: i2$4.ActivatedRoute }, { type: i2$4.Router }] });
|
|
3486
4510
|
|
|
4511
|
+
/*
|
|
4512
|
+
* Copyright 2025 Epistola.
|
|
4513
|
+
*
|
|
4514
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
4515
|
+
* you may not use this file except in compliance with the License.
|
|
4516
|
+
* You may obtain a copy of the License at
|
|
4517
|
+
*
|
|
4518
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
4519
|
+
*
|
|
4520
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
4521
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
4522
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
4523
|
+
* See the License for the specific language governing permissions and
|
|
4524
|
+
* limitations under the License.
|
|
4525
|
+
*
|
|
4526
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4527
|
+
*/
|
|
3487
4528
|
function isRuntimeWindow(value) {
|
|
3488
4529
|
return typeof value === 'object' && value !== null;
|
|
3489
4530
|
}
|
|
@@ -3513,12 +4554,46 @@ function isEpistolaEnabled() {
|
|
|
3513
4554
|
return flag !== false && flag !== 'false';
|
|
3514
4555
|
}
|
|
3515
4556
|
|
|
4557
|
+
/*
|
|
4558
|
+
* Copyright 2025 Epistola.
|
|
4559
|
+
*
|
|
4560
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
4561
|
+
* you may not use this file except in compliance with the License.
|
|
4562
|
+
* You may obtain a copy of the License at
|
|
4563
|
+
*
|
|
4564
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
4565
|
+
*
|
|
4566
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
4567
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
4568
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
4569
|
+
* See the License for the specific language governing permissions and
|
|
4570
|
+
* limitations under the License.
|
|
4571
|
+
*
|
|
4572
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4573
|
+
*/
|
|
3516
4574
|
const epistolaEnabledGuard = () => {
|
|
3517
4575
|
if (isEpistolaEnabled())
|
|
3518
4576
|
return true;
|
|
3519
4577
|
return inject(Router).parseUrl('/');
|
|
3520
4578
|
};
|
|
3521
4579
|
|
|
4580
|
+
/*
|
|
4581
|
+
* Copyright 2025 Epistola.
|
|
4582
|
+
*
|
|
4583
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
4584
|
+
* you may not use this file except in compliance with the License.
|
|
4585
|
+
* You may obtain a copy of the License at
|
|
4586
|
+
*
|
|
4587
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
4588
|
+
*
|
|
4589
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
4590
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
4591
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
4592
|
+
* See the License for the specific language governing permissions and
|
|
4593
|
+
* limitations under the License.
|
|
4594
|
+
*
|
|
4595
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4596
|
+
*/
|
|
3522
4597
|
const routes = [
|
|
3523
4598
|
{
|
|
3524
4599
|
path: 'epistola',
|
|
@@ -3529,7 +4604,7 @@ const routes = [
|
|
|
3529
4604
|
];
|
|
3530
4605
|
class EpistolaAdminRoutingModule {
|
|
3531
4606
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
3532
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [i2$
|
|
4607
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [i2$4.RouterModule], exports: [RouterModule] });
|
|
3533
4608
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [RouterModule.forChild(routes), RouterModule] });
|
|
3534
4609
|
}
|
|
3535
4610
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, decorators: [{
|
|
@@ -3540,6 +4615,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3540
4615
|
}]
|
|
3541
4616
|
}] });
|
|
3542
4617
|
|
|
4618
|
+
/*
|
|
4619
|
+
* Copyright 2025 Epistola.
|
|
4620
|
+
*
|
|
4621
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
4622
|
+
* you may not use this file except in compliance with the License.
|
|
4623
|
+
* You may obtain a copy of the License at
|
|
4624
|
+
*
|
|
4625
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
4626
|
+
*
|
|
4627
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
4628
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
4629
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
4630
|
+
* See the License for the specific language governing permissions and
|
|
4631
|
+
* limitations under the License.
|
|
4632
|
+
*
|
|
4633
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4634
|
+
*/
|
|
3543
4635
|
const EPISTOLA_DOCUMENT_OPTIONS = {
|
|
3544
4636
|
type: 'epistola-document',
|
|
3545
4637
|
selector: 'epistola-document-element',
|
|
@@ -3547,15 +4639,146 @@ const EPISTOLA_DOCUMENT_OPTIONS = {
|
|
|
3547
4639
|
group: 'basic',
|
|
3548
4640
|
icon: 'file-pdf-o',
|
|
3549
4641
|
emptyValue: null,
|
|
3550
|
-
|
|
4642
|
+
// tenantIdVariable is intentionally absent: it is not author-configurable in the builder
|
|
4643
|
+
// (the tenant is process-wide, always the default), and Valtimo copies every fieldOptions
|
|
4644
|
+
// key onto the element unconditionally — listing it would overwrite the component's
|
|
4645
|
+
// `epistolaTenantId` @Input() default with `undefined` and break the download.
|
|
4646
|
+
fieldOptions: ['label', 'display', 'documentVariable', 'filename'],
|
|
4647
|
+
// Embed the hidden task-id carrier so dropping this component is enough — no separate
|
|
4648
|
+
// field for the author to add. Valtimo prefills it server-side via the epistola: resolver.
|
|
4649
|
+
schema: { components: [PREFILLED_TASK_ID_CARRIER] },
|
|
4650
|
+
// Minimal edit form: only the five properties this component actually reads (its
|
|
4651
|
+
// @Input()s / fieldOptions). A flat `components` array (no `tabs` wrapper) replaces the
|
|
4652
|
+
// inherited stock text-field dialog (Display/Data/Validation/API/Conditional/Logic/Layout)
|
|
4653
|
+
// entirely. Keys must match the @Input() names verbatim so the fieldOptions copy works.
|
|
4654
|
+
editForm: () => ({
|
|
4655
|
+
components: [
|
|
4656
|
+
{
|
|
4657
|
+
type: 'textfield',
|
|
4658
|
+
key: 'label',
|
|
4659
|
+
label: 'Label',
|
|
4660
|
+
weight: 10,
|
|
4661
|
+
defaultValue: 'Document',
|
|
4662
|
+
},
|
|
4663
|
+
{
|
|
4664
|
+
type: 'select',
|
|
4665
|
+
key: 'display',
|
|
4666
|
+
label: 'Display',
|
|
4667
|
+
weight: 20,
|
|
4668
|
+
defaultValue: 'both',
|
|
4669
|
+
dataSrc: 'values',
|
|
4670
|
+
data: {
|
|
4671
|
+
values: [
|
|
4672
|
+
{ label: 'Inline preview', value: 'inline' },
|
|
4673
|
+
{ label: 'Download button', value: 'button' },
|
|
4674
|
+
{ label: 'Both', value: 'both' },
|
|
4675
|
+
],
|
|
4676
|
+
},
|
|
4677
|
+
},
|
|
4678
|
+
{
|
|
4679
|
+
type: 'textfield',
|
|
4680
|
+
key: 'documentVariable',
|
|
4681
|
+
label: 'Document variable',
|
|
4682
|
+
weight: 30,
|
|
4683
|
+
defaultValue: 'epistolaResult',
|
|
4684
|
+
tooltip: 'Name of the process variable holding the Epistola result (PDF id).',
|
|
4685
|
+
},
|
|
4686
|
+
{
|
|
4687
|
+
type: 'textfield',
|
|
4688
|
+
key: 'filename',
|
|
4689
|
+
label: 'Filename',
|
|
4690
|
+
weight: 40,
|
|
4691
|
+
defaultValue: 'document.pdf',
|
|
4692
|
+
tooltip: 'Filename used for the download (Content-Disposition).',
|
|
4693
|
+
},
|
|
4694
|
+
],
|
|
4695
|
+
}),
|
|
3551
4696
|
};
|
|
3552
4697
|
function registerEpistolaDocumentComponent(injector) {
|
|
3553
4698
|
if (customElements.get(EPISTOLA_DOCUMENT_OPTIONS.selector)) {
|
|
3554
4699
|
return;
|
|
3555
4700
|
}
|
|
3556
4701
|
registerCustomFormioComponent(EPISTOLA_DOCUMENT_OPTIONS, EpistolaDocumentComponent, injector);
|
|
4702
|
+
// Extend the base class to forward the server-prefilled task id (epistola: value
|
|
4703
|
+
// resolver) to the Angular element, so the download authorizes against the exact task in
|
|
4704
|
+
// every Valtimo task-open flow.
|
|
4705
|
+
const Formio = window.Formio;
|
|
4706
|
+
const BaseComponent = Formio?.Components?.components?.[EPISTOLA_DOCUMENT_OPTIONS.type];
|
|
4707
|
+
if (!BaseComponent) {
|
|
4708
|
+
return;
|
|
4709
|
+
}
|
|
4710
|
+
class EpistolaDocumentWithTaskContext extends BaseComponent {
|
|
4711
|
+
attach(element) {
|
|
4712
|
+
const result = super.attach(element);
|
|
4713
|
+
if (this._customAngularElement) {
|
|
4714
|
+
const prefilledTaskId = readPrefilledTaskId(this.root);
|
|
4715
|
+
if (prefilledTaskId) {
|
|
4716
|
+
this._customAngularElement['taskInstanceId'] = prefilledTaskId;
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
return result;
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
Formio.Components.setComponent(EPISTOLA_DOCUMENT_OPTIONS.type, EpistolaDocumentWithTaskContext);
|
|
3557
4723
|
}
|
|
3558
4724
|
|
|
4725
|
+
/*
|
|
4726
|
+
* Copyright 2025 Epistola.
|
|
4727
|
+
*
|
|
4728
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
4729
|
+
* you may not use this file except in compliance with the License.
|
|
4730
|
+
* You may obtain a copy of the License at
|
|
4731
|
+
*
|
|
4732
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
4733
|
+
*
|
|
4734
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
4735
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
4736
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
4737
|
+
* See the License for the specific language governing permissions and
|
|
4738
|
+
* limitations under the License.
|
|
4739
|
+
*
|
|
4740
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4741
|
+
*/
|
|
4742
|
+
/**
|
|
4743
|
+
* Hides a registered custom Formio component from the builder's component palette,
|
|
4744
|
+
* while keeping it fully usable inside other components' `editForm`s and at runtime.
|
|
4745
|
+
*
|
|
4746
|
+
* Formio's `WebformBuilder` only adds a component to the palette when
|
|
4747
|
+
* `component.builderInfo && component.builderInfo.schema` is truthy. Overriding the
|
|
4748
|
+
* registered class's static `builderInfo` getter to `false` therefore removes it from
|
|
4749
|
+
* the palette. Runtime instantiation and editForm usage don't consult `builderInfo`,
|
|
4750
|
+
* so they are unaffected.
|
|
4751
|
+
*
|
|
4752
|
+
* Call this AFTER the component is registered (and after any `setComponent` re-registration),
|
|
4753
|
+
* so it targets the final class in `Formio.Components.components[type]`.
|
|
4754
|
+
*/
|
|
4755
|
+
function hideFormioComponentFromBuilder(type) {
|
|
4756
|
+
const registered = window.Formio?.Components?.components?.[type];
|
|
4757
|
+
if (registered) {
|
|
4758
|
+
Object.defineProperty(registered, 'builderInfo', {
|
|
4759
|
+
get: () => false,
|
|
4760
|
+
configurable: true,
|
|
4761
|
+
});
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
|
|
4765
|
+
/*
|
|
4766
|
+
* Copyright 2025 Epistola.
|
|
4767
|
+
*
|
|
4768
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
4769
|
+
* you may not use this file except in compliance with the License.
|
|
4770
|
+
* You may obtain a copy of the License at
|
|
4771
|
+
*
|
|
4772
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
4773
|
+
*
|
|
4774
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
4775
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
4776
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
4777
|
+
* See the License for the specific language governing permissions and
|
|
4778
|
+
* limitations under the License.
|
|
4779
|
+
*
|
|
4780
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4781
|
+
*/
|
|
3559
4782
|
const EPISTOLA_RETRY_FORM_OPTIONS = {
|
|
3560
4783
|
type: 'epistola-retry-form',
|
|
3561
4784
|
selector: 'epistola-retry-form-element',
|
|
@@ -3564,13 +4787,60 @@ const EPISTOLA_RETRY_FORM_OPTIONS = {
|
|
|
3564
4787
|
icon: 'refresh',
|
|
3565
4788
|
emptyValue: null,
|
|
3566
4789
|
fieldOptions: ['sourceActivityId', 'label'], // sourceActivityId is optional (set via BPMN input parameter)
|
|
4790
|
+
// Embed the hidden task-id carrier so dropping this component is enough — no separate
|
|
4791
|
+
// field for the author to add. Valtimo prefills it server-side via the epistola: resolver.
|
|
4792
|
+
schema: { components: [PREFILLED_TASK_ID_CARRIER] },
|
|
3567
4793
|
};
|
|
3568
4794
|
function registerEpistolaRetryFormComponent(injector) {
|
|
3569
|
-
if (
|
|
3570
|
-
|
|
4795
|
+
if (customElements.get(EPISTOLA_RETRY_FORM_OPTIONS.selector)) {
|
|
4796
|
+
return;
|
|
4797
|
+
}
|
|
4798
|
+
registerCustomFormioComponent(EPISTOLA_RETRY_FORM_OPTIONS, EpistolaRetryFormComponent, injector);
|
|
4799
|
+
// Extend the base class to forward the server-prefilled task id (epistola: value
|
|
4800
|
+
// resolver) to the Angular element, so the retry form authorizes against the exact task in
|
|
4801
|
+
// every Valtimo task-open flow.
|
|
4802
|
+
const Formio = window.Formio;
|
|
4803
|
+
const BaseComponent = Formio?.Components?.components?.[EPISTOLA_RETRY_FORM_OPTIONS.type];
|
|
4804
|
+
if (!BaseComponent) {
|
|
4805
|
+
return;
|
|
3571
4806
|
}
|
|
4807
|
+
class EpistolaRetryFormWithTaskContext extends BaseComponent {
|
|
4808
|
+
attach(element) {
|
|
4809
|
+
const result = super.attach(element);
|
|
4810
|
+
if (this._customAngularElement) {
|
|
4811
|
+
const prefilledTaskId = readPrefilledTaskId(this.root);
|
|
4812
|
+
if (prefilledTaskId) {
|
|
4813
|
+
this._customAngularElement['taskInstanceId'] = prefilledTaskId;
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
return result;
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
Formio.Components.setComponent(EPISTOLA_RETRY_FORM_OPTIONS.type, EpistolaRetryFormWithTaskContext);
|
|
4820
|
+
// Part of the plugin's auto-deployed retry form, not a drop-anywhere component — hide it
|
|
4821
|
+
// from the builder palette. It still renders wherever it's already present in a form.
|
|
4822
|
+
hideFormioComponentFromBuilder(EPISTOLA_RETRY_FORM_OPTIONS.type);
|
|
3572
4823
|
}
|
|
3573
4824
|
|
|
4825
|
+
/*
|
|
4826
|
+
* Copyright 2025 Epistola.
|
|
4827
|
+
*
|
|
4828
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
4829
|
+
* you may not use this file except in compliance with the License.
|
|
4830
|
+
* You may obtain a copy of the License at
|
|
4831
|
+
*
|
|
4832
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
4833
|
+
*
|
|
4834
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
4835
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
4836
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
4837
|
+
* See the License for the specific language governing permissions and
|
|
4838
|
+
* limitations under the License.
|
|
4839
|
+
*
|
|
4840
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4841
|
+
*/
|
|
4842
|
+
/** Default debounce for the auto-refresh, in milliseconds. */
|
|
4843
|
+
const DEFAULT_REFRESH_DEBOUNCE_MS = 1500;
|
|
3574
4844
|
const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
|
|
3575
4845
|
type: 'epistola-document-preview',
|
|
3576
4846
|
selector: 'epistola-document-preview-element',
|
|
@@ -3579,6 +4849,9 @@ const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
|
|
|
3579
4849
|
icon: 'file-pdf-o',
|
|
3580
4850
|
emptyValue: null,
|
|
3581
4851
|
fieldOptions: ['label', 'processDefinitionKey', 'sourceActivityId', 'overrideMapping'],
|
|
4852
|
+
// Embed the hidden task-id carrier so dropping this component is enough — no separate
|
|
4853
|
+
// field for the author to add. Valtimo prefills it server-side via the epistola: resolver.
|
|
4854
|
+
schema: { components: [PREFILLED_TASK_ID_CARRIER] },
|
|
3582
4855
|
editForm: () => ({
|
|
3583
4856
|
components: [
|
|
3584
4857
|
{
|
|
@@ -3594,6 +4867,23 @@ const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
|
|
|
3594
4867
|
label: 'Input Overrides',
|
|
3595
4868
|
weight: 20,
|
|
3596
4869
|
},
|
|
4870
|
+
{
|
|
4871
|
+
type: 'checkbox',
|
|
4872
|
+
key: 'autoRefresh',
|
|
4873
|
+
label: 'Auto-refresh preview as the form is filled in',
|
|
4874
|
+
tooltip: 'When on, the preview refreshes automatically while the form is edited — debounced, and only when a field loses focus, not on every keystroke. Turn off to refresh only with the Refresh button.',
|
|
4875
|
+
defaultValue: true,
|
|
4876
|
+
weight: 30,
|
|
4877
|
+
},
|
|
4878
|
+
{
|
|
4879
|
+
type: 'number',
|
|
4880
|
+
key: 'refreshDebounceMs',
|
|
4881
|
+
label: 'Auto-refresh debounce (ms)',
|
|
4882
|
+
tooltip: 'How long to wait after the last change before refreshing. Higher values feel calmer; lower values feel more responsive.',
|
|
4883
|
+
defaultValue: DEFAULT_REFRESH_DEBOUNCE_MS,
|
|
4884
|
+
weight: 40,
|
|
4885
|
+
conditional: { show: true, when: 'autoRefresh', eq: 'true' },
|
|
4886
|
+
},
|
|
3597
4887
|
],
|
|
3598
4888
|
}),
|
|
3599
4889
|
};
|
|
@@ -3614,7 +4904,39 @@ function registerEpistolaDocumentPreviewComponent(injector) {
|
|
|
3614
4904
|
class PreviewWithOverrides extends BasePreviewComponent {
|
|
3615
4905
|
_debounceTimer = null;
|
|
3616
4906
|
_changeListenerAttached = false;
|
|
4907
|
+
_changeHandler = null;
|
|
4908
|
+
_blurHandler = null;
|
|
4909
|
+
_blurTarget = null;
|
|
4910
|
+
_destroyed = false;
|
|
4911
|
+
_debounceMs = DEFAULT_REFRESH_DEBOUNCE_MS;
|
|
4912
|
+
// Serialized form of the last value pushed, so we skip re-rendering the preview
|
|
4913
|
+
// when a change recomputes to the same overrides (e.g. typing in a field that
|
|
4914
|
+
// isn't part of the mapping). undefined = nothing pushed yet.
|
|
4915
|
+
_lastPushedJson = undefined;
|
|
4916
|
+
// Whether the last compute produced usable overrides. Drives the initial-paint
|
|
4917
|
+
// retry below: it stops once the form data is present.
|
|
4918
|
+
_hasUsableValue = false;
|
|
4919
|
+
// Timers for the initial-paint retry — Valtimo can prefill form data
|
|
4920
|
+
// asynchronously after the component mounts, sometimes without a change event.
|
|
4921
|
+
_initialPaintTimers = [];
|
|
4922
|
+
// Whether auto-refresh (recompute on change/blur) is currently active. Seeded
|
|
4923
|
+
// once from the builder's autoRefresh option, then toggled at runtime by the
|
|
4924
|
+
// end-user via the preview header. Persisted across redraws (only seeded once).
|
|
4925
|
+
_autoRefreshEnabled = true;
|
|
4926
|
+
_autoRefreshInitialized = false;
|
|
3617
4927
|
attach(element) {
|
|
4928
|
+
// Formio detaches and re-attaches components on every redraw — not only at
|
|
4929
|
+
// teardown — so a re-attach means the component is alive again. Clear the
|
|
4930
|
+
// destroyed flag here; it only stays set when detach() is the final call
|
|
4931
|
+
// (genuine teardown, e.g. task completion), which is what suppresses the
|
|
4932
|
+
// post-submit preview.
|
|
4933
|
+
this._destroyed = false;
|
|
4934
|
+
// Seed the runtime auto-refresh state from the builder option, once. Re-attach
|
|
4935
|
+
// (redraw) must not clobber a choice the end-user made via the header toggle.
|
|
4936
|
+
if (!this._autoRefreshInitialized) {
|
|
4937
|
+
this._autoRefreshEnabled = this.component?.autoRefresh !== false;
|
|
4938
|
+
this._autoRefreshInitialized = true;
|
|
4939
|
+
}
|
|
3618
4940
|
// Bidirectional sync between processLinkSelection object and separate properties.
|
|
3619
4941
|
// The editForm uses processLinkSelection (single field), while the component
|
|
3620
4942
|
// config and Angular inputs use processDefinitionKey + sourceActivityId.
|
|
@@ -3634,78 +4956,436 @@ function registerEpistolaDocumentPreviewComponent(injector) {
|
|
|
3634
4956
|
this._customAngularElement['processDefinitionKey'] =
|
|
3635
4957
|
this.component.processDefinitionKey || '';
|
|
3636
4958
|
this._customAngularElement['sourceActivityId'] = this.component.sourceActivityId || '';
|
|
4959
|
+
// Forward the server-prefilled task id (epistola: value resolver) so the
|
|
4960
|
+
// component authorizes against the exact task in every Valtimo task-open flow.
|
|
4961
|
+
const prefilledTaskId = readPrefilledTaskId(this.root);
|
|
4962
|
+
if (prefilledTaskId) {
|
|
4963
|
+
this._customAngularElement['taskInstanceId'] = prefilledTaskId;
|
|
4964
|
+
}
|
|
4965
|
+
if (this.component?.overrideMapping) {
|
|
4966
|
+
// Let the component's Refresh button force a recompute from the live form
|
|
4967
|
+
// data, so it works before the first change (e.g. on initial load with
|
|
4968
|
+
// pre-filled fields) rather than reading a not-yet-populated value.
|
|
4969
|
+
this._customAngularElement['requestOverrides'] = () => this._computeAndSetOverrides(true);
|
|
4970
|
+
// Reflect the current auto-refresh state to the header toggle (current
|
|
4971
|
+
// state, not the builder default — so a redraw keeps the user's choice),
|
|
4972
|
+
// and let the toggle flip it. Turning it on does an immediate refresh.
|
|
4973
|
+
this._customAngularElement['autoRefresh'] = this._autoRefreshEnabled;
|
|
4974
|
+
this._customAngularElement['setAutoRefresh'] = (enabled) => {
|
|
4975
|
+
this._autoRefreshEnabled = enabled;
|
|
4976
|
+
if (enabled) {
|
|
4977
|
+
this._computeAndSetOverrides(true);
|
|
4978
|
+
}
|
|
4979
|
+
};
|
|
4980
|
+
}
|
|
3637
4981
|
}
|
|
3638
|
-
//
|
|
4982
|
+
// Compute input overrides from the mapping and wire up the live listeners.
|
|
3639
4983
|
if (this.root && this.component?.overrideMapping && !this._changeListenerAttached) {
|
|
3640
4984
|
this._changeListenerAttached = true;
|
|
3641
|
-
this.
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
//
|
|
3645
|
-
this._computeAndSetOverrides();
|
|
4985
|
+
this._debounceMs = this._resolveDebounceMs();
|
|
4986
|
+
// Compute the initial overrides immediately (no debounce) so a pre-filled
|
|
4987
|
+
// form paints its preview without the debounce delay. This runs regardless
|
|
4988
|
+
// of the auto-refresh state, so the preview still shows once on open.
|
|
4989
|
+
this._computeAndSetOverrides(true);
|
|
4990
|
+
// Valtimo can prefill the form data asynchronously after the component
|
|
4991
|
+
// mounts, sometimes without a change event we can hook — so the single
|
|
4992
|
+
// compute above may see empty data. Re-attempt a few times over ~2s until
|
|
4993
|
+
// usable overrides appear, so a pre-filled form previews itself without a
|
|
4994
|
+
// manual edit or Refresh click. Each attempt is skipped once the data is in.
|
|
4995
|
+
this._initialPaintTimers = [400, 1000, 2000].map((ms) => setTimeout(() => {
|
|
4996
|
+
if (!this._hasUsableValue && !this._destroyed) {
|
|
4997
|
+
void this._runCompute();
|
|
4998
|
+
}
|
|
4999
|
+
}, ms));
|
|
5000
|
+
// Always wire the change + blur listeners; the runtime auto-refresh toggle
|
|
5001
|
+
// (_autoRefreshEnabled) gates whether they actually recompute, so the
|
|
5002
|
+
// end-user can switch it on/off live without re-attaching anything.
|
|
5003
|
+
//
|
|
5004
|
+
// Debounced recompute on any form change — collapses bursts of edits and,
|
|
5005
|
+
// together with the dedup, only re-renders when the mapped data changes.
|
|
5006
|
+
this._changeHandler = () => {
|
|
5007
|
+
if (this._autoRefreshEnabled)
|
|
5008
|
+
this._computeAndSetOverrides();
|
|
5009
|
+
};
|
|
5010
|
+
this.root.on('change', this._changeHandler);
|
|
5011
|
+
// Flush immediately when a field loses focus. `focusout` bubbles (unlike
|
|
5012
|
+
// `blur`), so one listener on the form root catches every input — and it
|
|
5013
|
+
// fires on blur rather than on each keystroke, which keeps the refresh from
|
|
5014
|
+
// feeling hectic.
|
|
5015
|
+
const formEl = this.root?.element;
|
|
5016
|
+
if (formEl?.addEventListener) {
|
|
5017
|
+
this._blurHandler = () => {
|
|
5018
|
+
if (this._autoRefreshEnabled)
|
|
5019
|
+
this._computeAndSetOverrides(true);
|
|
5020
|
+
};
|
|
5021
|
+
formEl.addEventListener('focusout', this._blurHandler);
|
|
5022
|
+
this._blurTarget = formEl;
|
|
5023
|
+
}
|
|
3646
5024
|
}
|
|
3647
5025
|
return result;
|
|
3648
5026
|
}
|
|
3649
|
-
|
|
5027
|
+
// Tear down the change listener and any pending debounce so a preview is never
|
|
5028
|
+
// fired after the form is unmounted (e.g. on task completion). Without this the
|
|
5029
|
+
// 1.5s debounce can outlive submit and POST /preview with reset/incomplete data,
|
|
5030
|
+
// which Epistola rejects with a 400.
|
|
5031
|
+
detach() {
|
|
5032
|
+
this._destroyed = true;
|
|
3650
5033
|
if (this._debounceTimer) {
|
|
3651
5034
|
clearTimeout(this._debounceTimer);
|
|
5035
|
+
this._debounceTimer = null;
|
|
3652
5036
|
}
|
|
3653
|
-
this.
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
5037
|
+
this._initialPaintTimers.forEach((t) => clearTimeout(t));
|
|
5038
|
+
this._initialPaintTimers = [];
|
|
5039
|
+
this._hasUsableValue = false;
|
|
5040
|
+
if (this._changeHandler && this.root?.off) {
|
|
5041
|
+
this.root.off('change', this._changeHandler);
|
|
5042
|
+
this._changeHandler = null;
|
|
5043
|
+
}
|
|
5044
|
+
if (this._blurHandler && this._blurTarget?.removeEventListener) {
|
|
5045
|
+
this._blurTarget.removeEventListener('focusout', this._blurHandler);
|
|
5046
|
+
this._blurHandler = null;
|
|
5047
|
+
this._blurTarget = null;
|
|
5048
|
+
}
|
|
5049
|
+
this._changeListenerAttached = false;
|
|
5050
|
+
this._lastPushedJson = undefined;
|
|
5051
|
+
return super.detach();
|
|
5052
|
+
}
|
|
5053
|
+
_computeAndSetOverrides(immediate = false) {
|
|
5054
|
+
if (this._debounceTimer) {
|
|
5055
|
+
clearTimeout(this._debounceTimer);
|
|
5056
|
+
}
|
|
5057
|
+
this._debounceTimer = setTimeout(() => void this._runCompute(), immediate ? 0 : this._debounceMs);
|
|
5058
|
+
}
|
|
5059
|
+
// Compute the input overrides from the live form data and push them to the
|
|
5060
|
+
// component (deduped). Separated from the debounce scheduling so the
|
|
5061
|
+
// initial-paint retry can invoke it directly without another timer hop.
|
|
5062
|
+
async _runCompute() {
|
|
5063
|
+
// Skip if the form is being/has been submitted or the component is gone —
|
|
5064
|
+
// those previews would run with incomplete/reset data and 400 from Epistola.
|
|
5065
|
+
if (this._destroyed || this.root?.submitting || this.root?.submitted) {
|
|
5066
|
+
return;
|
|
5067
|
+
}
|
|
5068
|
+
const mapping = this.component?.overrideMapping;
|
|
5069
|
+
const formData = this.root?.data;
|
|
5070
|
+
if (!mapping || !formData) {
|
|
5071
|
+
return;
|
|
5072
|
+
}
|
|
5073
|
+
// computeInputOverrides evaluates a JSONata expression (async). Re-check
|
|
5074
|
+
// the submit/teardown guards after the await — they can flip while the
|
|
5075
|
+
// promise is in flight.
|
|
5076
|
+
const overrides = await computeInputOverrides(mapping, formData);
|
|
5077
|
+
if (this._destroyed || this.root?.submitting || this.root?.submitted) {
|
|
5078
|
+
return;
|
|
5079
|
+
}
|
|
5080
|
+
// Push null when there's nothing usable yet so the component reverts to
|
|
5081
|
+
// its "complete the form" placeholder instead of keeping a stale preview.
|
|
5082
|
+
const next = Object.keys(overrides).length > 0 ? overrides : null;
|
|
5083
|
+
// Track whether the data is in yet (stops the initial-paint retry).
|
|
5084
|
+
this._hasUsableValue = next !== null;
|
|
5085
|
+
// Dedup: only push (and re-render) when the computed overrides actually
|
|
5086
|
+
// changed since the last push. A change/blur that doesn't affect the
|
|
5087
|
+
// mapped data recomputes to the same value and is dropped here.
|
|
5088
|
+
const nextJson = JSON.stringify(next);
|
|
5089
|
+
if (nextJson === this._lastPushedJson) {
|
|
5090
|
+
return;
|
|
5091
|
+
}
|
|
5092
|
+
this._lastPushedJson = nextJson;
|
|
5093
|
+
this._pushOverrides(next);
|
|
5094
|
+
}
|
|
5095
|
+
// Push the computed overrides to the Angular component via a dedicated input.
|
|
5096
|
+
// NOT Formio's setValue: Valtimo's bridge only mirrors `value` to the DOM and
|
|
5097
|
+
// never to Formio's data model, so Formio resets it to emptyValue on the next
|
|
5098
|
+
// redraw — which would cancel the preview. A plain element property is left
|
|
5099
|
+
// untouched by Formio and so sticks.
|
|
5100
|
+
_pushOverrides(value) {
|
|
5101
|
+
if (this._customAngularElement) {
|
|
5102
|
+
this._customAngularElement['inputOverrides'] = value;
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
/**
|
|
5106
|
+
* Resolve the configured auto-refresh debounce (ms), falling back to the
|
|
5107
|
+
* default for missing or non-numeric/negative values.
|
|
5108
|
+
*/
|
|
5109
|
+
_resolveDebounceMs() {
|
|
5110
|
+
const configured = Number(this.component?.refreshDebounceMs);
|
|
5111
|
+
return Number.isFinite(configured) && configured >= 0
|
|
5112
|
+
? configured
|
|
5113
|
+
: DEFAULT_REFRESH_DEBOUNCE_MS;
|
|
3663
5114
|
}
|
|
3664
5115
|
}
|
|
3665
5116
|
// Re-register with the extended class
|
|
3666
5117
|
Formio.Components.setComponent(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS.type, PreviewWithOverrides);
|
|
3667
5118
|
}
|
|
3668
5119
|
|
|
3669
|
-
|
|
5120
|
+
/*
|
|
5121
|
+
* Copyright 2025 Epistola.
|
|
5122
|
+
*
|
|
5123
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
5124
|
+
* you may not use this file except in compliance with the License.
|
|
5125
|
+
* You may obtain a copy of the License at
|
|
5126
|
+
*
|
|
5127
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
5128
|
+
*
|
|
5129
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
5130
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
5131
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5132
|
+
* See the License for the specific language governing permissions and
|
|
5133
|
+
* limitations under the License.
|
|
5134
|
+
*
|
|
5135
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
5136
|
+
*/
|
|
5137
|
+
const jsonata = _jsonata.default || _jsonata;
|
|
5138
|
+
const SCOPES = ['doc', 'pv', 'case'];
|
|
5139
|
+
function isScope(value) {
|
|
5140
|
+
return typeof value === 'string' && SCOPES.includes(value);
|
|
5141
|
+
}
|
|
5142
|
+
/**
|
|
5143
|
+
* Statically extract every `$doc`/`$pv`/`$case` path referenced anywhere in a JSONata
|
|
5144
|
+
* expression. Used to surface — informationally — which inputs a template's data mapping
|
|
5145
|
+
* consumes, so the override-builder author sees what is worth overriding during preview.
|
|
5146
|
+
*
|
|
5147
|
+
* This is a best-effort static read: paths built dynamically (`$lookup`, custom functions,
|
|
5148
|
+
* computed keys) can't be resolved and simply won't appear. Treat the result as suggestions,
|
|
5149
|
+
* never as validation — an empty result (e.g. on a parse error) means "nothing to suggest".
|
|
5150
|
+
*
|
|
5151
|
+
* Generalizes the variable-path primitive in `utils/jsonata-converter.ts` (`classifyValue`)
|
|
5152
|
+
* to recurse over the whole AST rather than only top-level object values.
|
|
5153
|
+
*/
|
|
5154
|
+
function extractReferencedPaths(expression) {
|
|
5155
|
+
if (!expression || !expression.trim()) {
|
|
5156
|
+
return [];
|
|
5157
|
+
}
|
|
5158
|
+
let ast;
|
|
5159
|
+
try {
|
|
5160
|
+
ast = jsonata(expression).ast();
|
|
5161
|
+
}
|
|
5162
|
+
catch {
|
|
5163
|
+
return [];
|
|
5164
|
+
}
|
|
5165
|
+
const seen = new Map();
|
|
5166
|
+
walk(ast, seen);
|
|
5167
|
+
return [...seen.values()].sort((a, b) => a.scope.localeCompare(b.scope) || a.path.localeCompare(b.path));
|
|
5168
|
+
}
|
|
5169
|
+
function record(node, seen) {
|
|
5170
|
+
// Two shapes carry a scope reference:
|
|
5171
|
+
// - a bare `variable` node (`$doc`), where the scope is `node.value` and the path is empty;
|
|
5172
|
+
// - a `path` node whose first step is the `$<scope>` variable, followed by the property
|
|
5173
|
+
// names: `$doc.aanvrager.naam` → steps [doc, aanvrager, naam].
|
|
5174
|
+
const scope = node?.type === 'variable' ? node.value : node?.steps?.[0]?.value;
|
|
5175
|
+
if (!isScope(scope)) {
|
|
5176
|
+
return;
|
|
5177
|
+
}
|
|
5178
|
+
const path = (node.steps ?? [])
|
|
5179
|
+
.slice(1)
|
|
5180
|
+
.map((step) => step?.value)
|
|
5181
|
+
.filter((segment) => typeof segment === 'string')
|
|
5182
|
+
.join('.');
|
|
5183
|
+
const key = `${scope}.${path}`;
|
|
5184
|
+
if (!seen.has(key)) {
|
|
5185
|
+
seen.set(key, { scope, path });
|
|
5186
|
+
}
|
|
5187
|
+
}
|
|
5188
|
+
/** Recursively walk every child node, recording any `$doc`/`$pv`/`$case` path reference. */
|
|
5189
|
+
function walk(node, seen) {
|
|
5190
|
+
if (!node || typeof node !== 'object') {
|
|
5191
|
+
return;
|
|
5192
|
+
}
|
|
5193
|
+
// A bare scope variable with no property access, e.g. `$spread($doc)`.
|
|
5194
|
+
if (node.type === 'variable') {
|
|
5195
|
+
if (isScope(node.value)) {
|
|
5196
|
+
record(node, seen);
|
|
5197
|
+
}
|
|
5198
|
+
return;
|
|
5199
|
+
}
|
|
5200
|
+
// A path rooted at a scope variable, e.g. `$doc.aanvrager.naam`. Record the whole path,
|
|
5201
|
+
// then walk only the filters/predicates attached to its steps — not the variable/name
|
|
5202
|
+
// steps themselves, which would otherwise re-record the leading scope with an empty path.
|
|
5203
|
+
if (node.type === 'path' &&
|
|
5204
|
+
node.steps?.[0]?.type === 'variable' &&
|
|
5205
|
+
isScope(node.steps[0].value)) {
|
|
5206
|
+
record(node, seen);
|
|
5207
|
+
for (const step of node.steps) {
|
|
5208
|
+
walkValue(step?.predicate, seen);
|
|
5209
|
+
walkValue(step?.stages, seen);
|
|
5210
|
+
walkValue(step?.group, seen);
|
|
5211
|
+
}
|
|
5212
|
+
return;
|
|
5213
|
+
}
|
|
5214
|
+
// Generic recursion into every structural child the dashjoin AST uses. Object literals
|
|
5215
|
+
// carry their entries as `lhs` = array of [keyNode, valueNode] pairs.
|
|
5216
|
+
for (const child of [
|
|
5217
|
+
node.lhs,
|
|
5218
|
+
node.rhs,
|
|
5219
|
+
node.condition,
|
|
5220
|
+
node.then,
|
|
5221
|
+
node.else,
|
|
5222
|
+
node.procedure,
|
|
5223
|
+
node.group,
|
|
5224
|
+
node.pattern,
|
|
5225
|
+
node.update,
|
|
5226
|
+
node.delete,
|
|
5227
|
+
]) {
|
|
5228
|
+
walkValue(child, seen);
|
|
5229
|
+
}
|
|
5230
|
+
for (const list of [
|
|
5231
|
+
node.steps,
|
|
5232
|
+
node.arguments,
|
|
5233
|
+
node.stages,
|
|
5234
|
+
node.expressions,
|
|
5235
|
+
node.terms,
|
|
5236
|
+
node.predicate,
|
|
5237
|
+
]) {
|
|
5238
|
+
walkValue(list, seen);
|
|
5239
|
+
}
|
|
5240
|
+
}
|
|
5241
|
+
/** Walk a value that may be a node, an array of nodes, or an array of [key, value] pairs. */
|
|
5242
|
+
function walkValue(value, seen) {
|
|
5243
|
+
if (Array.isArray(value)) {
|
|
5244
|
+
for (const item of value) {
|
|
5245
|
+
walkValue(item, seen);
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
else {
|
|
5249
|
+
walk(value, seen);
|
|
5250
|
+
}
|
|
5251
|
+
}
|
|
5252
|
+
|
|
5253
|
+
/*
|
|
5254
|
+
* Copyright 2025 Epistola.
|
|
5255
|
+
*
|
|
5256
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
5257
|
+
* you may not use this file except in compliance with the License.
|
|
5258
|
+
* You may obtain a copy of the License at
|
|
5259
|
+
*
|
|
5260
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
5261
|
+
*
|
|
5262
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
5263
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
5264
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5265
|
+
* See the License for the specific language governing permissions and
|
|
5266
|
+
* limitations under the License.
|
|
5267
|
+
*
|
|
5268
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
5269
|
+
*/
|
|
3670
5270
|
class EpistolaOverrideBuilderComponent {
|
|
3671
5271
|
cdr;
|
|
5272
|
+
pluginService;
|
|
3672
5273
|
value;
|
|
3673
5274
|
valueChange = new EventEmitter();
|
|
3674
5275
|
disabled = false;
|
|
3675
5276
|
label = 'Input Overrides';
|
|
3676
5277
|
availableFields = [];
|
|
5278
|
+
/**
|
|
5279
|
+
* Identify the selected generate-document process link, forwarded from the
|
|
5280
|
+
* preview component's editForm. Used to fetch the link's data mapping and
|
|
5281
|
+
* surface which `$doc`/`$pv` paths it consumes — purely informational guidance.
|
|
5282
|
+
*/
|
|
5283
|
+
processDefinitionKey = '';
|
|
5284
|
+
sourceActivityId = '';
|
|
3677
5285
|
rows = [];
|
|
3678
5286
|
advancedMode = false;
|
|
3679
|
-
|
|
3680
|
-
|
|
5287
|
+
/** True when the current expression can't be represented by the simple table. */
|
|
5288
|
+
simpleUnavailable = false;
|
|
5289
|
+
expression = '';
|
|
5290
|
+
/** `$doc`/`$pv`/`$case` paths the selected template's data mapping references. */
|
|
5291
|
+
referencedPaths = [];
|
|
5292
|
+
exampleExpression = '{ "doc": { "naam": $form.voornaam & \' \' & $form.achternaam } }';
|
|
3681
5293
|
initialized = false;
|
|
3682
|
-
|
|
5294
|
+
destroy$ = new Subject();
|
|
5295
|
+
/** Link last fetched, so we refetch only when the selected process link changes. */
|
|
5296
|
+
lastFetchedLinkKey = null;
|
|
5297
|
+
constructor(cdr, pluginService) {
|
|
3683
5298
|
this.cdr = cdr;
|
|
5299
|
+
this.pluginService = pluginService;
|
|
5300
|
+
}
|
|
5301
|
+
get formFieldKeys() {
|
|
5302
|
+
return this.availableFields.map((f) => f.key);
|
|
3684
5303
|
}
|
|
3685
|
-
|
|
3686
|
-
|
|
5304
|
+
get hasReferencedPaths() {
|
|
5305
|
+
return this.referencedPaths.length > 0;
|
|
5306
|
+
}
|
|
5307
|
+
/** Referenced paths for a scope, excluding whole-scope refs (empty path) that aren't completions. */
|
|
5308
|
+
referencedPathsForScope(scope) {
|
|
5309
|
+
return this.referencedPaths.filter((p) => p.scope === scope && p.path).map((p) => p.path);
|
|
5310
|
+
}
|
|
5311
|
+
/** Autocomplete context for the advanced editor: form fields plus the mapping's referenced paths. */
|
|
5312
|
+
get editorContextVariables() {
|
|
5313
|
+
return {
|
|
5314
|
+
form: this.formFieldKeys,
|
|
5315
|
+
doc: this.referencedPathsForScope('doc'),
|
|
5316
|
+
pv: this.referencedPathsForScope('pv'),
|
|
5317
|
+
case: this.referencedPathsForScope('case'),
|
|
5318
|
+
};
|
|
5319
|
+
}
|
|
5320
|
+
/** Render a referenced path as a `$scope.path` reference (or `$scope` for a whole-scope ref). */
|
|
5321
|
+
formatReferencedPath(ref) {
|
|
5322
|
+
return ref.path ? `$${ref.scope}.${ref.path}` : `$${ref.scope}`;
|
|
5323
|
+
}
|
|
5324
|
+
ngOnChanges(_changes) {
|
|
5325
|
+
if (!this.initialized && this.value != null) {
|
|
3687
5326
|
this.initialized = true;
|
|
3688
|
-
|
|
3689
|
-
|
|
5327
|
+
// Migrate a legacy object value to JSONata once, and persist it upward so
|
|
5328
|
+
// the form is saved in the new format. Everything below works on a string.
|
|
5329
|
+
if (isLegacyOverrideMapping(this.value)) {
|
|
5330
|
+
this.expression = legacyOverrideToJsonata(this.value);
|
|
5331
|
+
this.value = this.expression || null;
|
|
5332
|
+
this.valueChange.emit(this.value);
|
|
5333
|
+
}
|
|
5334
|
+
else {
|
|
5335
|
+
this.expression = String(this.value);
|
|
5336
|
+
}
|
|
5337
|
+
this.loadFromExpression(this.expression);
|
|
3690
5338
|
}
|
|
5339
|
+
this.refreshReferencedPaths();
|
|
3691
5340
|
this.cdr.markForCheck();
|
|
3692
5341
|
}
|
|
5342
|
+
ngOnDestroy() {
|
|
5343
|
+
this.destroy$.next();
|
|
5344
|
+
this.destroy$.complete();
|
|
5345
|
+
}
|
|
5346
|
+
/**
|
|
5347
|
+
* Fetch the selected process link's data mapping and extract the `$doc`/`$pv`/`$case`
|
|
5348
|
+
* paths it references, so the author sees what this template consumes. Refetches only
|
|
5349
|
+
* when the selected link changes; clears when no link is selected. Best-effort and
|
|
5350
|
+
* non-blocking — a failed fetch simply shows no suggestions.
|
|
5351
|
+
*/
|
|
5352
|
+
refreshReferencedPaths() {
|
|
5353
|
+
const linkKey = this.processDefinitionKey && this.sourceActivityId
|
|
5354
|
+
? `${this.processDefinitionKey}::${this.sourceActivityId}`
|
|
5355
|
+
: null;
|
|
5356
|
+
if (!linkKey) {
|
|
5357
|
+
if (this.lastFetchedLinkKey !== null) {
|
|
5358
|
+
this.lastFetchedLinkKey = null;
|
|
5359
|
+
this.referencedPaths = [];
|
|
5360
|
+
}
|
|
5361
|
+
return;
|
|
5362
|
+
}
|
|
5363
|
+
if (linkKey === this.lastFetchedLinkKey) {
|
|
5364
|
+
return;
|
|
5365
|
+
}
|
|
5366
|
+
this.lastFetchedLinkKey = linkKey;
|
|
5367
|
+
this.pluginService
|
|
5368
|
+
.getProcessLinkMapping(this.processDefinitionKey, this.sourceActivityId)
|
|
5369
|
+
.pipe(takeUntil$1(this.destroy$), catchError(() => of({ dataMapping: '' })))
|
|
5370
|
+
.subscribe((mapping) => {
|
|
5371
|
+
this.referencedPaths = extractReferencedPaths(mapping.dataMapping);
|
|
5372
|
+
this.cdr.markForCheck();
|
|
5373
|
+
});
|
|
5374
|
+
}
|
|
3693
5375
|
toggleMode() {
|
|
3694
|
-
this.advancedMode = !this.advancedMode;
|
|
3695
5376
|
if (this.advancedMode) {
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
5377
|
+
// Advanced -> simple: only possible when the expression round-trips.
|
|
5378
|
+
const parsed = parseOverrideJsonata(this.expression);
|
|
5379
|
+
if (parsed === null) {
|
|
5380
|
+
this.simpleUnavailable = true;
|
|
5381
|
+
return;
|
|
5382
|
+
}
|
|
5383
|
+
this.rows = parsed;
|
|
5384
|
+
this.simpleUnavailable = false;
|
|
5385
|
+
this.advancedMode = false;
|
|
3699
5386
|
}
|
|
3700
5387
|
else {
|
|
3701
|
-
|
|
3702
|
-
const parsed = this.jsonText.trim() ? JSON.parse(this.jsonText) : {};
|
|
3703
|
-
this.rows = this.mappingToRows(parsed);
|
|
3704
|
-
this.jsonError = null;
|
|
3705
|
-
}
|
|
3706
|
-
catch {
|
|
3707
|
-
// Keep current rows if JSON is invalid
|
|
3708
|
-
}
|
|
5388
|
+
this.advancedMode = true;
|
|
3709
5389
|
}
|
|
3710
5390
|
}
|
|
3711
5391
|
addRow() {
|
|
@@ -3715,65 +5395,105 @@ class EpistolaOverrideBuilderComponent {
|
|
|
3715
5395
|
this.rows.splice(index, 1);
|
|
3716
5396
|
this.emitChange();
|
|
3717
5397
|
}
|
|
5398
|
+
/** Simple-table change: serialize rows back to a JSONata expression. */
|
|
3718
5399
|
emitChange() {
|
|
3719
|
-
|
|
3720
|
-
this.
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
this.
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
this.
|
|
3734
|
-
this.
|
|
3735
|
-
this.valueChange.emit(parsed);
|
|
5400
|
+
this.expression = serializeOverrideRows(this.rows);
|
|
5401
|
+
this.emit(this.expression);
|
|
5402
|
+
}
|
|
5403
|
+
/** Advanced-editor change. */
|
|
5404
|
+
onExpressionChange(expr) {
|
|
5405
|
+
this.expression = expr;
|
|
5406
|
+
this.simpleUnavailable = parseOverrideJsonata(expr) === null;
|
|
5407
|
+
this.emit(expr);
|
|
5408
|
+
}
|
|
5409
|
+
loadFromExpression(expression) {
|
|
5410
|
+
const parsed = parseOverrideJsonata(expression);
|
|
5411
|
+
if (parsed === null) {
|
|
5412
|
+
// Richer than the simple table can show — start in advanced mode.
|
|
5413
|
+
this.simpleUnavailable = true;
|
|
5414
|
+
this.advancedMode = true;
|
|
5415
|
+
this.rows = [];
|
|
3736
5416
|
}
|
|
3737
|
-
|
|
3738
|
-
this.
|
|
5417
|
+
else {
|
|
5418
|
+
this.simpleUnavailable = false;
|
|
5419
|
+
this.rows = parsed;
|
|
3739
5420
|
}
|
|
3740
5421
|
}
|
|
3741
|
-
|
|
3742
|
-
const
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
if (!mapping[row.scope]) {
|
|
3746
|
-
mapping[row.scope] = {};
|
|
3747
|
-
}
|
|
3748
|
-
mapping[row.scope][row.inputPath] = FORM_REF_PREFIX + row.formFieldKey;
|
|
3749
|
-
}
|
|
3750
|
-
}
|
|
3751
|
-
return mapping;
|
|
3752
|
-
}
|
|
3753
|
-
mappingToRows(mapping) {
|
|
3754
|
-
const rows = [];
|
|
3755
|
-
for (const [scope, fields] of Object.entries(mapping)) {
|
|
3756
|
-
if (scope === 'doc' || scope === 'pv') {
|
|
3757
|
-
for (const [path, ref] of Object.entries(fields)) {
|
|
3758
|
-
const formFieldKey = String(ref).startsWith(FORM_REF_PREFIX)
|
|
3759
|
-
? String(ref).substring(FORM_REF_PREFIX.length)
|
|
3760
|
-
: String(ref);
|
|
3761
|
-
rows.push({ scope, inputPath: path, formFieldKey });
|
|
3762
|
-
}
|
|
3763
|
-
}
|
|
3764
|
-
}
|
|
3765
|
-
return rows;
|
|
5422
|
+
emit(expression) {
|
|
5423
|
+
const next = expression && expression.trim() ? expression : null;
|
|
5424
|
+
this.value = next;
|
|
5425
|
+
this.valueChange.emit(next);
|
|
3766
5426
|
}
|
|
3767
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaOverrideBuilderComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3768
|
-
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: `
|
|
5427
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaOverrideBuilderComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: EpistolaPluginService }], target: i0.ɵɵFactoryTarget.Component });
|
|
5428
|
+
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", processDefinitionKey: "processDefinitionKey", sourceActivityId: "sourceActivityId" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
3769
5429
|
<div class="override-builder">
|
|
3770
5430
|
<div class="builder-header">
|
|
3771
5431
|
<span class="builder-label">{{ label || 'Input Overrides' }}</span>
|
|
3772
|
-
<button
|
|
5432
|
+
<button
|
|
5433
|
+
type="button"
|
|
5434
|
+
class="mode-toggle"
|
|
5435
|
+
[disabled]="simpleUnavailable && !advancedMode"
|
|
5436
|
+
(click)="toggleMode()"
|
|
5437
|
+
>
|
|
3773
5438
|
{{ advancedMode ? 'Simple' : 'Advanced' }}
|
|
3774
5439
|
</button>
|
|
3775
5440
|
</div>
|
|
3776
5441
|
|
|
5442
|
+
<!-- Inline guidance for the author -->
|
|
5443
|
+
<p class="builder-intro">
|
|
5444
|
+
Make the preview reflect what the user is typing — <em>before</em> they submit — by feeding
|
|
5445
|
+
live form values into the document inputs.
|
|
5446
|
+
</p>
|
|
5447
|
+
<details class="builder-help">
|
|
5448
|
+
<summary>When should I map a field?</summary>
|
|
5449
|
+
<ul>
|
|
5450
|
+
<li>
|
|
5451
|
+
<strong>Map</strong> a field when its value ends up in the generated document — i.e. the
|
|
5452
|
+
template's data mapping reads that <code>doc</code>/<code>pv</code> path. The preview
|
|
5453
|
+
then updates live as the field is filled in.
|
|
5454
|
+
</li>
|
|
5455
|
+
<li>
|
|
5456
|
+
<strong>Don't map</strong> fields that don't affect the document, or values that are
|
|
5457
|
+
already saved on the case/process before this task — those are read from the real data
|
|
5458
|
+
automatically.
|
|
5459
|
+
</li>
|
|
5460
|
+
<li>
|
|
5461
|
+
Overriding a path the template never reads has <strong>no effect</strong> on the
|
|
5462
|
+
preview.
|
|
5463
|
+
</li>
|
|
5464
|
+
</ul>
|
|
5465
|
+
<p class="builder-help__how">
|
|
5466
|
+
<strong>How it works:</strong> <code>$form</code> holds the current form values; the
|
|
5467
|
+
mapping returns a <code>{{ '{' }} doc, pv {{ '}' }}</code> overlay used
|
|
5468
|
+
<strong>only for the preview</strong>. The actual document is always generated from the
|
|
5469
|
+
real saved data after the form is submitted.
|
|
5470
|
+
</p>
|
|
5471
|
+
</details>
|
|
5472
|
+
|
|
5473
|
+
<!-- Variables the selected template's mapping consumes (read-only guidance) -->
|
|
5474
|
+
<details *ngIf="hasReferencedPaths" class="used-by-template">
|
|
5475
|
+
<summary class="used-by-template__label">
|
|
5476
|
+
Used by this template ({{ referencedPaths.length }})
|
|
5477
|
+
</summary>
|
|
5478
|
+
<p class="used-by-template__hint">
|
|
5479
|
+
This template's data mapping reads these inputs — the paths worth overriding for the
|
|
5480
|
+
preview.
|
|
5481
|
+
</p>
|
|
5482
|
+
<ul class="used-by-template__list">
|
|
5483
|
+
<li *ngFor="let ref of referencedPaths">
|
|
5484
|
+
<code>{{ formatReferencedPath(ref) }}</code>
|
|
5485
|
+
</li>
|
|
5486
|
+
</ul>
|
|
5487
|
+
</details>
|
|
5488
|
+
|
|
5489
|
+
<!-- Per-scope autocomplete options for the Input Path column -->
|
|
5490
|
+
<datalist id="epistola-override-paths-doc">
|
|
5491
|
+
<option *ngFor="let p of referencedPathsForScope('doc')" [value]="p"></option>
|
|
5492
|
+
</datalist>
|
|
5493
|
+
<datalist id="epistola-override-paths-pv">
|
|
5494
|
+
<option *ngFor="let p of referencedPathsForScope('pv')" [value]="p"></option>
|
|
5495
|
+
</datalist>
|
|
5496
|
+
|
|
3777
5497
|
<!-- Simple mode: table -->
|
|
3778
5498
|
<div *ngIf="!advancedMode" class="builder-table">
|
|
3779
5499
|
<div *ngIf="rows.length > 0" class="table-header">
|
|
@@ -3792,6 +5512,7 @@ class EpistolaOverrideBuilderComponent {
|
|
|
3792
5512
|
type="text"
|
|
3793
5513
|
[(ngModel)]="row.inputPath"
|
|
3794
5514
|
(ngModelChange)="emitChange()"
|
|
5515
|
+
[attr.list]="'epistola-override-paths-' + row.scope"
|
|
3795
5516
|
placeholder="e.g. beslissing.tekst"
|
|
3796
5517
|
/>
|
|
3797
5518
|
<!-- Dropdown when form fields are available, text input as fallback -->
|
|
@@ -3823,31 +5544,96 @@ class EpistolaOverrideBuilderComponent {
|
|
|
3823
5544
|
</button>
|
|
3824
5545
|
</div>
|
|
3825
5546
|
|
|
3826
|
-
<!-- Advanced mode:
|
|
5547
|
+
<!-- Advanced mode: JSONata editor over $form -->
|
|
3827
5548
|
<div *ngIf="advancedMode" class="builder-advanced">
|
|
3828
|
-
<
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
5549
|
+
<div *ngIf="simpleUnavailable" class="advanced-note">
|
|
5550
|
+
This expression is too rich for the simple table — edit it here.
|
|
5551
|
+
</div>
|
|
5552
|
+
<epistola-jsonata-editor
|
|
5553
|
+
[expression]="expression"
|
|
5554
|
+
[contextVariables]="editorContextVariables"
|
|
5555
|
+
variablesHint="$form"
|
|
5556
|
+
(expressionChange)="onExpressionChange($event)"
|
|
5557
|
+
></epistola-jsonata-editor>
|
|
5558
|
+
<div class="advanced-hint">
|
|
5559
|
+
Map form fields onto a <code>{{ '{' }} doc, pv {{ '}' }}</code> overlay, e.g.
|
|
5560
|
+
<code>{{ exampleExpression }}</code>
|
|
5561
|
+
</div>
|
|
3836
5562
|
</div>
|
|
3837
5563
|
</div>
|
|
3838
|
-
`, 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}.
|
|
5564
|
+
`, 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}.builder-intro{font-size:.78rem;color:#495057;margin:0 0 .4rem;line-height:1.4}.builder-help{margin:0 0 .6rem;font-size:.76rem;color:#6c757d}.builder-help>summary{cursor:pointer;color:#0d6efd;font-size:.76rem;-webkit-user-select:none;user-select:none}.builder-help ul{margin:.35rem 0;padding-left:1.1rem;line-height:1.45}.builder-help li{margin-bottom:.2rem}.builder-help__how{margin:.35rem 0 0;line-height:1.45}.builder-help code{background:#eef0f2;border-radius:3px;padding:0 .2rem}.used-by-template{border:1px solid #d6e4ff;background:#f0f6ff;border-radius:4px;padding:.5rem .6rem;margin:0 0 .6rem}.used-by-template__label{font-weight:600;font-size:.78rem;color:#495057;cursor:pointer;-webkit-user-select:none;user-select:none}.used-by-template[open] .used-by-template__label{margin-bottom:.1rem}.used-by-template__hint{margin:.2rem 0 .4rem;font-size:.74rem;color:#6c757d;line-height:1.4}.used-by-template__list{margin:0;padding-left:1.1rem;display:flex;flex-wrap:wrap;gap:.15rem 1rem;list-style:none}.used-by-template__list code{background:#e2ecff;border-radius:3px;padding:0 .25rem;font-size:.76rem;color:#0d4a9c}.mode-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.15rem .5rem;font-size:.75rem;cursor:pointer}.mode-toggle:hover:not(:disabled){background:#e9ecef}.mode-toggle:disabled{opacity:.5;cursor:not-allowed}.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}.advanced-note{color:#b54708;font-size:.75rem;margin-bottom:.4rem}.advanced-hint{color:#6c757d;font-size:.72rem;margin-top:.35rem;line-height:1.4}.advanced-hint code{background:#eef0f2;border-radius:3px;padding:0 .2rem;font-size:.95em}\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: i3$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$1.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: i3$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: JsonataEditorComponent, selector: "epistola-jsonata-editor", inputs: ["expression", "disabled", "contextVariables", "functions", "variablesHint"], outputs: ["expressionChange", "validChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3839
5565
|
}
|
|
3840
5566
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaOverrideBuilderComponent, decorators: [{
|
|
3841
5567
|
type: Component,
|
|
3842
|
-
args: [{ standalone: true, imports: [CommonModule, FormsModule], selector: 'epistola-override-builder-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
5568
|
+
args: [{ standalone: true, imports: [CommonModule, FormsModule, JsonataEditorComponent], selector: 'epistola-override-builder-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3843
5569
|
<div class="override-builder">
|
|
3844
5570
|
<div class="builder-header">
|
|
3845
5571
|
<span class="builder-label">{{ label || 'Input Overrides' }}</span>
|
|
3846
|
-
<button
|
|
5572
|
+
<button
|
|
5573
|
+
type="button"
|
|
5574
|
+
class="mode-toggle"
|
|
5575
|
+
[disabled]="simpleUnavailable && !advancedMode"
|
|
5576
|
+
(click)="toggleMode()"
|
|
5577
|
+
>
|
|
3847
5578
|
{{ advancedMode ? 'Simple' : 'Advanced' }}
|
|
3848
5579
|
</button>
|
|
3849
5580
|
</div>
|
|
3850
5581
|
|
|
5582
|
+
<!-- Inline guidance for the author -->
|
|
5583
|
+
<p class="builder-intro">
|
|
5584
|
+
Make the preview reflect what the user is typing — <em>before</em> they submit — by feeding
|
|
5585
|
+
live form values into the document inputs.
|
|
5586
|
+
</p>
|
|
5587
|
+
<details class="builder-help">
|
|
5588
|
+
<summary>When should I map a field?</summary>
|
|
5589
|
+
<ul>
|
|
5590
|
+
<li>
|
|
5591
|
+
<strong>Map</strong> a field when its value ends up in the generated document — i.e. the
|
|
5592
|
+
template's data mapping reads that <code>doc</code>/<code>pv</code> path. The preview
|
|
5593
|
+
then updates live as the field is filled in.
|
|
5594
|
+
</li>
|
|
5595
|
+
<li>
|
|
5596
|
+
<strong>Don't map</strong> fields that don't affect the document, or values that are
|
|
5597
|
+
already saved on the case/process before this task — those are read from the real data
|
|
5598
|
+
automatically.
|
|
5599
|
+
</li>
|
|
5600
|
+
<li>
|
|
5601
|
+
Overriding a path the template never reads has <strong>no effect</strong> on the
|
|
5602
|
+
preview.
|
|
5603
|
+
</li>
|
|
5604
|
+
</ul>
|
|
5605
|
+
<p class="builder-help__how">
|
|
5606
|
+
<strong>How it works:</strong> <code>$form</code> holds the current form values; the
|
|
5607
|
+
mapping returns a <code>{{ '{' }} doc, pv {{ '}' }}</code> overlay used
|
|
5608
|
+
<strong>only for the preview</strong>. The actual document is always generated from the
|
|
5609
|
+
real saved data after the form is submitted.
|
|
5610
|
+
</p>
|
|
5611
|
+
</details>
|
|
5612
|
+
|
|
5613
|
+
<!-- Variables the selected template's mapping consumes (read-only guidance) -->
|
|
5614
|
+
<details *ngIf="hasReferencedPaths" class="used-by-template">
|
|
5615
|
+
<summary class="used-by-template__label">
|
|
5616
|
+
Used by this template ({{ referencedPaths.length }})
|
|
5617
|
+
</summary>
|
|
5618
|
+
<p class="used-by-template__hint">
|
|
5619
|
+
This template's data mapping reads these inputs — the paths worth overriding for the
|
|
5620
|
+
preview.
|
|
5621
|
+
</p>
|
|
5622
|
+
<ul class="used-by-template__list">
|
|
5623
|
+
<li *ngFor="let ref of referencedPaths">
|
|
5624
|
+
<code>{{ formatReferencedPath(ref) }}</code>
|
|
5625
|
+
</li>
|
|
5626
|
+
</ul>
|
|
5627
|
+
</details>
|
|
5628
|
+
|
|
5629
|
+
<!-- Per-scope autocomplete options for the Input Path column -->
|
|
5630
|
+
<datalist id="epistola-override-paths-doc">
|
|
5631
|
+
<option *ngFor="let p of referencedPathsForScope('doc')" [value]="p"></option>
|
|
5632
|
+
</datalist>
|
|
5633
|
+
<datalist id="epistola-override-paths-pv">
|
|
5634
|
+
<option *ngFor="let p of referencedPathsForScope('pv')" [value]="p"></option>
|
|
5635
|
+
</datalist>
|
|
5636
|
+
|
|
3851
5637
|
<!-- Simple mode: table -->
|
|
3852
5638
|
<div *ngIf="!advancedMode" class="builder-table">
|
|
3853
5639
|
<div *ngIf="rows.length > 0" class="table-header">
|
|
@@ -3866,6 +5652,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3866
5652
|
type="text"
|
|
3867
5653
|
[(ngModel)]="row.inputPath"
|
|
3868
5654
|
(ngModelChange)="emitChange()"
|
|
5655
|
+
[attr.list]="'epistola-override-paths-' + row.scope"
|
|
3869
5656
|
placeholder="e.g. beslissing.tekst"
|
|
3870
5657
|
/>
|
|
3871
5658
|
<!-- Dropdown when form fields are available, text input as fallback -->
|
|
@@ -3897,20 +5684,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3897
5684
|
</button>
|
|
3898
5685
|
</div>
|
|
3899
5686
|
|
|
3900
|
-
<!-- Advanced mode:
|
|
5687
|
+
<!-- Advanced mode: JSONata editor over $form -->
|
|
3901
5688
|
<div *ngIf="advancedMode" class="builder-advanced">
|
|
3902
|
-
<
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
5689
|
+
<div *ngIf="simpleUnavailable" class="advanced-note">
|
|
5690
|
+
This expression is too rich for the simple table — edit it here.
|
|
5691
|
+
</div>
|
|
5692
|
+
<epistola-jsonata-editor
|
|
5693
|
+
[expression]="expression"
|
|
5694
|
+
[contextVariables]="editorContextVariables"
|
|
5695
|
+
variablesHint="$form"
|
|
5696
|
+
(expressionChange)="onExpressionChange($event)"
|
|
5697
|
+
></epistola-jsonata-editor>
|
|
5698
|
+
<div class="advanced-hint">
|
|
5699
|
+
Map form fields onto a <code>{{ '{' }} doc, pv {{ '}' }}</code> overlay, e.g.
|
|
5700
|
+
<code>{{ exampleExpression }}</code>
|
|
5701
|
+
</div>
|
|
3910
5702
|
</div>
|
|
3911
5703
|
</div>
|
|
3912
|
-
`, 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}.
|
|
3913
|
-
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
5704
|
+
`, 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}.builder-intro{font-size:.78rem;color:#495057;margin:0 0 .4rem;line-height:1.4}.builder-help{margin:0 0 .6rem;font-size:.76rem;color:#6c757d}.builder-help>summary{cursor:pointer;color:#0d6efd;font-size:.76rem;-webkit-user-select:none;user-select:none}.builder-help ul{margin:.35rem 0;padding-left:1.1rem;line-height:1.45}.builder-help li{margin-bottom:.2rem}.builder-help__how{margin:.35rem 0 0;line-height:1.45}.builder-help code{background:#eef0f2;border-radius:3px;padding:0 .2rem}.used-by-template{border:1px solid #d6e4ff;background:#f0f6ff;border-radius:4px;padding:.5rem .6rem;margin:0 0 .6rem}.used-by-template__label{font-weight:600;font-size:.78rem;color:#495057;cursor:pointer;-webkit-user-select:none;user-select:none}.used-by-template[open] .used-by-template__label{margin-bottom:.1rem}.used-by-template__hint{margin:.2rem 0 .4rem;font-size:.74rem;color:#6c757d;line-height:1.4}.used-by-template__list{margin:0;padding-left:1.1rem;display:flex;flex-wrap:wrap;gap:.15rem 1rem;list-style:none}.used-by-template__list code{background:#e2ecff;border-radius:3px;padding:0 .25rem;font-size:.76rem;color:#0d4a9c}.mode-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.15rem .5rem;font-size:.75rem;cursor:pointer}.mode-toggle:hover:not(:disabled){background:#e9ecef}.mode-toggle:disabled{opacity:.5;cursor:not-allowed}.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}.advanced-note{color:#b54708;font-size:.75rem;margin-bottom:.4rem}.advanced-hint{color:#6c757d;font-size:.72rem;margin-top:.35rem;line-height:1.4}.advanced-hint code{background:#eef0f2;border-radius:3px;padding:0 .2rem;font-size:.95em}\n"] }]
|
|
5705
|
+
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: EpistolaPluginService }], propDecorators: { value: [{
|
|
3914
5706
|
type: Input
|
|
3915
5707
|
}], valueChange: [{
|
|
3916
5708
|
type: Output
|
|
@@ -3920,8 +5712,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3920
5712
|
type: Input
|
|
3921
5713
|
}], availableFields: [{
|
|
3922
5714
|
type: Input
|
|
5715
|
+
}], processDefinitionKey: [{
|
|
5716
|
+
type: Input
|
|
5717
|
+
}], sourceActivityId: [{
|
|
5718
|
+
type: Input
|
|
3923
5719
|
}] } });
|
|
3924
5720
|
|
|
5721
|
+
/*
|
|
5722
|
+
* Copyright 2025 Epistola.
|
|
5723
|
+
*
|
|
5724
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
5725
|
+
* you may not use this file except in compliance with the License.
|
|
5726
|
+
* You may obtain a copy of the License at
|
|
5727
|
+
*
|
|
5728
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
5729
|
+
*
|
|
5730
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
5731
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
5732
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5733
|
+
* See the License for the specific language governing permissions and
|
|
5734
|
+
* limitations under the License.
|
|
5735
|
+
*
|
|
5736
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
5737
|
+
*/
|
|
3925
5738
|
const EPISTOLA_OVERRIDE_BUILDER_OPTIONS = {
|
|
3926
5739
|
type: 'epistola-override-builder',
|
|
3927
5740
|
selector: 'epistola-override-builder-element',
|
|
@@ -3929,7 +5742,7 @@ const EPISTOLA_OVERRIDE_BUILDER_OPTIONS = {
|
|
|
3929
5742
|
group: 'basic',
|
|
3930
5743
|
icon: 'list',
|
|
3931
5744
|
emptyValue: null,
|
|
3932
|
-
fieldOptions: ['label', 'availableFields'],
|
|
5745
|
+
fieldOptions: ['label', 'availableFields', 'processDefinitionKey', 'sourceActivityId'],
|
|
3933
5746
|
};
|
|
3934
5747
|
/**
|
|
3935
5748
|
* Recursively collect input field keys and labels from a Formio component tree.
|
|
@@ -3967,12 +5780,45 @@ function registerEpistolaOverrideBuilderComponent(injector) {
|
|
|
3967
5780
|
const BaseComponent = Formio.Components.components[EPISTOLA_OVERRIDE_BUILDER_OPTIONS.type];
|
|
3968
5781
|
if (!BaseComponent)
|
|
3969
5782
|
return;
|
|
3970
|
-
// Extend the base class to pass available form fields
|
|
5783
|
+
// Extend the base class to pass available form fields and the selected process link
|
|
5784
|
+
// to the Angular component.
|
|
3971
5785
|
class OverrideBuilderWithFields extends BaseComponent {
|
|
5786
|
+
_selectionChangeHandler = null;
|
|
3972
5787
|
attach(element) {
|
|
3973
|
-
// Set
|
|
5788
|
+
// Set inputs on the component BEFORE super.attach() reads fieldOptions.
|
|
3974
5789
|
this.component.availableFields = this._extractFormFields();
|
|
3975
|
-
|
|
5790
|
+
this._applyProcessLinkSelection();
|
|
5791
|
+
const result = super.attach(element);
|
|
5792
|
+
// The override builder lives in the preview component's editForm alongside the
|
|
5793
|
+
// process-link selector (key `processLinkSelection`). When the author changes the
|
|
5794
|
+
// selected link, push the new identity straight to the Angular element so it can
|
|
5795
|
+
// refetch the link's data mapping — Formio doesn't always redraw this widget on a
|
|
5796
|
+
// sibling change. Mirrors the listener lifecycle in epistola-document-preview.formio.ts.
|
|
5797
|
+
if (this.root?.on && !this._selectionChangeHandler) {
|
|
5798
|
+
this._selectionChangeHandler = () => {
|
|
5799
|
+
const selection = this.root?.data?.processLinkSelection;
|
|
5800
|
+
if (this._customAngularElement) {
|
|
5801
|
+
this._customAngularElement['processDefinitionKey'] =
|
|
5802
|
+
selection?.processDefinitionKey || '';
|
|
5803
|
+
this._customAngularElement['sourceActivityId'] = selection?.sourceActivityId || '';
|
|
5804
|
+
}
|
|
5805
|
+
};
|
|
5806
|
+
this.root.on('change', this._selectionChangeHandler);
|
|
5807
|
+
}
|
|
5808
|
+
return result;
|
|
5809
|
+
}
|
|
5810
|
+
detach() {
|
|
5811
|
+
if (this._selectionChangeHandler && this.root?.off) {
|
|
5812
|
+
this.root.off('change', this._selectionChangeHandler);
|
|
5813
|
+
this._selectionChangeHandler = null;
|
|
5814
|
+
}
|
|
5815
|
+
return super.detach();
|
|
5816
|
+
}
|
|
5817
|
+
_applyProcessLinkSelection() {
|
|
5818
|
+
// The editForm dialog stores the selected link under `processLinkSelection`.
|
|
5819
|
+
const selection = this.root?.data?.processLinkSelection;
|
|
5820
|
+
this.component.processDefinitionKey = selection?.processDefinitionKey || '';
|
|
5821
|
+
this.component.sourceActivityId = selection?.sourceActivityId || '';
|
|
3976
5822
|
}
|
|
3977
5823
|
_extractFormFields() {
|
|
3978
5824
|
// The Formio builder passes the main form schema as options.editForm
|
|
@@ -3986,8 +5832,27 @@ function registerEpistolaOverrideBuilderComponent(injector) {
|
|
|
3986
5832
|
}
|
|
3987
5833
|
// Re-register with the extended class
|
|
3988
5834
|
Formio.Components.setComponent(EPISTOLA_OVERRIDE_BUILDER_OPTIONS.type, OverrideBuilderWithFields);
|
|
5835
|
+
// Internal editForm widget — not a standalone form field. Hide it from the builder palette.
|
|
5836
|
+
hideFormioComponentFromBuilder(EPISTOLA_OVERRIDE_BUILDER_OPTIONS.type);
|
|
3989
5837
|
}
|
|
3990
5838
|
|
|
5839
|
+
/*
|
|
5840
|
+
* Copyright 2025 Epistola.
|
|
5841
|
+
*
|
|
5842
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
5843
|
+
* you may not use this file except in compliance with the License.
|
|
5844
|
+
* You may obtain a copy of the License at
|
|
5845
|
+
*
|
|
5846
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
5847
|
+
*
|
|
5848
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
5849
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
5850
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5851
|
+
* See the License for the specific language governing permissions and
|
|
5852
|
+
* limitations under the License.
|
|
5853
|
+
*
|
|
5854
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
5855
|
+
*/
|
|
3991
5856
|
/**
|
|
3992
5857
|
* The plugin action definition key the backend serializes for generate-document
|
|
3993
5858
|
* process links. It carries the `epistola-` prefix — see `EPISTOLA_ACTION_KEYS`
|
|
@@ -4000,6 +5865,23 @@ function filterGenerateDocumentEntries(entries) {
|
|
|
4000
5865
|
return entries.filter((e) => e.actionKey === GENERATE_DOCUMENT_ACTION_KEY);
|
|
4001
5866
|
}
|
|
4002
5867
|
|
|
5868
|
+
/*
|
|
5869
|
+
* Copyright 2025 Epistola.
|
|
5870
|
+
*
|
|
5871
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
5872
|
+
* you may not use this file except in compliance with the License.
|
|
5873
|
+
* You may obtain a copy of the License at
|
|
5874
|
+
*
|
|
5875
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
5876
|
+
*
|
|
5877
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
5878
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
5879
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5880
|
+
* See the License for the specific language governing permissions and
|
|
5881
|
+
* limitations under the License.
|
|
5882
|
+
*
|
|
5883
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
5884
|
+
*/
|
|
4003
5885
|
class EpistolaProcessLinkSelectorComponent {
|
|
4004
5886
|
adminService;
|
|
4005
5887
|
cdr;
|
|
@@ -4082,7 +5964,7 @@ class EpistolaProcessLinkSelectorComponent {
|
|
|
4082
5964
|
</select>
|
|
4083
5965
|
<div *ngIf="error" class="selector-error">{{ error }}</div>
|
|
4084
5966
|
</div>
|
|
4085
|
-
`, 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:
|
|
5967
|
+
`, 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: i3$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4086
5968
|
}
|
|
4087
5969
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaProcessLinkSelectorComponent, decorators: [{
|
|
4088
5970
|
type: Component,
|
|
@@ -4113,6 +5995,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
4113
5995
|
type: Input
|
|
4114
5996
|
}] } });
|
|
4115
5997
|
|
|
5998
|
+
/*
|
|
5999
|
+
* Copyright 2025 Epistola.
|
|
6000
|
+
*
|
|
6001
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
6002
|
+
* you may not use this file except in compliance with the License.
|
|
6003
|
+
* You may obtain a copy of the License at
|
|
6004
|
+
*
|
|
6005
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
6006
|
+
*
|
|
6007
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
6008
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
6009
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
6010
|
+
* See the License for the specific language governing permissions and
|
|
6011
|
+
* limitations under the License.
|
|
6012
|
+
*
|
|
6013
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
6014
|
+
*/
|
|
4116
6015
|
const EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS = {
|
|
4117
6016
|
type: 'epistola-process-link-selector',
|
|
4118
6017
|
selector: 'epistola-process-link-selector-element',
|
|
@@ -4125,9 +6024,28 @@ const EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS = {
|
|
|
4125
6024
|
function registerEpistolaProcessLinkSelectorComponent(injector) {
|
|
4126
6025
|
if (!customElements.get(EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS.selector)) {
|
|
4127
6026
|
registerCustomFormioComponent(EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS, EpistolaProcessLinkSelectorComponent, injector);
|
|
6027
|
+
// Internal editForm widget — not a standalone form field. Hide it from the builder palette.
|
|
6028
|
+
hideFormioComponentFromBuilder(EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS.type);
|
|
4128
6029
|
}
|
|
4129
6030
|
}
|
|
4130
6031
|
|
|
6032
|
+
/*
|
|
6033
|
+
* Copyright 2025 Epistola.
|
|
6034
|
+
*
|
|
6035
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
6036
|
+
* you may not use this file except in compliance with the License.
|
|
6037
|
+
* You may obtain a copy of the License at
|
|
6038
|
+
*
|
|
6039
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
6040
|
+
*
|
|
6041
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
6042
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
6043
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
6044
|
+
* See the License for the specific language governing permissions and
|
|
6045
|
+
* limitations under the License.
|
|
6046
|
+
*
|
|
6047
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
6048
|
+
*/
|
|
4131
6049
|
class EpistolaPluginModule {
|
|
4132
6050
|
// Kept for back-compat with hosts that follow the README's `forRoot()`
|
|
4133
6051
|
// setup. The providers above are now module-level so `imports: [EpistolaPluginModule]`
|
|
@@ -4162,11 +6080,6 @@ class EpistolaPluginModule {
|
|
|
4162
6080
|
EpistolaPluginService,
|
|
4163
6081
|
EpistolaAdminService,
|
|
4164
6082
|
EpistolaMenuService,
|
|
4165
|
-
{
|
|
4166
|
-
provide: HTTP_INTERCEPTORS,
|
|
4167
|
-
useClass: EpistolaTaskContextInterceptor,
|
|
4168
|
-
multi: true,
|
|
4169
|
-
},
|
|
4170
6083
|
{
|
|
4171
6084
|
provide: ENVIRONMENT_INITIALIZER,
|
|
4172
6085
|
multi: true,
|
|
@@ -4233,11 +6146,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
4233
6146
|
EpistolaPluginService,
|
|
4234
6147
|
EpistolaAdminService,
|
|
4235
6148
|
EpistolaMenuService,
|
|
4236
|
-
{
|
|
4237
|
-
provide: HTTP_INTERCEPTORS,
|
|
4238
|
-
useClass: EpistolaTaskContextInterceptor,
|
|
4239
|
-
multi: true,
|
|
4240
|
-
},
|
|
4241
6149
|
{
|
|
4242
6150
|
provide: ENVIRONMENT_INITIALIZER,
|
|
4243
6151
|
multi: true,
|
|
@@ -4258,8 +6166,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
4258
6166
|
}]
|
|
4259
6167
|
}] });
|
|
4260
6168
|
|
|
6169
|
+
/*
|
|
6170
|
+
* Copyright 2025 Epistola.
|
|
6171
|
+
*
|
|
6172
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
6173
|
+
* you may not use this file except in compliance with the License.
|
|
6174
|
+
* You may obtain a copy of the License at
|
|
6175
|
+
*
|
|
6176
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
6177
|
+
*
|
|
6178
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
6179
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
6180
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
6181
|
+
* See the License for the specific language governing permissions and
|
|
6182
|
+
* limitations under the License.
|
|
6183
|
+
*
|
|
6184
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
6185
|
+
*/
|
|
4261
6186
|
const EPISTOLA_PLUGIN_LOGO_BASE64 = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAgMTIwIiB3aWR0aD0iMTIwIiBoZWlnaHQ9IjEyMCI+CiAgPCEtLSBTdGFjayBiYXNlIC0tPgogIDxyZWN0IHg9IjM2IiB5PSIxNiIgd2lkdGg9IjU0IiBoZWlnaHQ9IjcwIiByeD0iMyIgZmlsbD0iI2U2YzJiMCIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIiIHRyYW5zZm9ybT0icm90YXRlKDUgNjMgNTEpIi8+CiAgPHJlY3QgeD0iMzIiIHk9IjIyIiB3aWR0aD0iNTQiIGhlaWdodD0iNzAiIHJ4PSIzIiBmaWxsPSIjZjBkOGM4IiBzdHJva2U9IiM0ZjJmMmIiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxyZWN0IHg9IjI4IiB5PSIyOCIgd2lkdGg9IjU0IiBoZWlnaHQ9IjcwIiByeD0iMyIgZmlsbD0iI2Y1ZWJlMyIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIuNSIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjQ0IiB4Mj0iNzIiIHkyPSI0NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjU0IiB4Mj0iNzIiIHkyPSI1NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjY0IiB4Mj0iNTgiIHkyPSI2NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDwhLS0gV2F4IHNlYWwgd2l0aCBjaGVja21hcmsgLS0+CiAgPGNpcmNsZSBjeD0iNTUiIGN5PSI4NCIgcj0iMTUiIGZpbGw9IiNiODVjM2MiIHN0cm9rZT0iIzRmMmYyYiIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPGNpcmNsZSBjeD0iNTUiIGN5PSI4NCIgcj0iMTAuNSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDQ4MzZhIiBzdHJva2Utd2lkdGg9IjEiLz4KICA8cG9seWxpbmUgcG9pbnRzPSI0OSw4NCA1Myw4OSA2Miw3OCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo=';
|
|
4262
6187
|
|
|
6188
|
+
/*
|
|
6189
|
+
* Copyright 2025 Epistola.
|
|
6190
|
+
*
|
|
6191
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
6192
|
+
* you may not use this file except in compliance with the License.
|
|
6193
|
+
* You may obtain a copy of the License at
|
|
6194
|
+
*
|
|
6195
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
6196
|
+
*
|
|
6197
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
6198
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
6199
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
6200
|
+
* See the License for the specific language governing permissions and
|
|
6201
|
+
* limitations under the License.
|
|
6202
|
+
*
|
|
6203
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
6204
|
+
*/
|
|
6205
|
+
|
|
6206
|
+
/*
|
|
6207
|
+
* Copyright 2025 Epistola.
|
|
6208
|
+
*
|
|
6209
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
6210
|
+
* you may not use this file except in compliance with the License.
|
|
6211
|
+
* You may obtain a copy of the License at
|
|
6212
|
+
*
|
|
6213
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
6214
|
+
*
|
|
6215
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
6216
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
6217
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
6218
|
+
* See the License for the specific language governing permissions and
|
|
6219
|
+
* limitations under the License.
|
|
6220
|
+
*
|
|
6221
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
6222
|
+
*/
|
|
4263
6223
|
const EPISTOLA_PLUGIN_ID = 'epistola';
|
|
4264
6224
|
const DISABLED_EPISTOLA_PLUGIN_ID = '__epistola_disabled__';
|
|
4265
6225
|
const epistolaPluginSpecification = {
|
|
@@ -4382,8 +6342,12 @@ const epistolaPluginSpecification = {
|
|
|
4382
6342
|
'epistola-download-document': 'Download Document',
|
|
4383
6343
|
documentVariable: 'Document Variabele',
|
|
4384
6344
|
documentVariableTooltip: 'Naam van de procesvariabele met het Epistola resultaat. Mag een String document ID zijn (legacy) of een rich-result object met een documentId-veld; de actie haalt het document ID eruit.',
|
|
6345
|
+
storageTarget: 'Opslagdoel',
|
|
6346
|
+
storageTargetTooltip: 'Waar het gedownloade PDF wordt opgeslagen. "Tijdelijke resource-opslag" (aanbevolen) zet alleen een resource-id in de variabele — geschikt om door te geven aan documenten-api:store-temp-document. "Procesvariabele" zet de ruwe bytes inline in de variabele (alleen voor kleine, niet-gevoelige documenten; deze worden in de taakrespons meegestuurd).',
|
|
6347
|
+
resourceIdVariable: 'Resource-ID Variabele',
|
|
6348
|
+
resourceIdVariableTooltip: 'Naam van de procesvariabele waarin het tijdelijke resource-id wordt opgeslagen (geef deze door aan documenten-api:store-temp-document).',
|
|
4385
6349
|
contentVariable: 'Inhoud Variabele',
|
|
4386
|
-
contentVariableTooltip: 'Naam van de procesvariabele waarin de
|
|
6350
|
+
contentVariableTooltip: 'Naam van de procesvariabele waarin de ruwe PDF-bytes inline worden opgeslagen (alleen voor kleine, niet-gevoelige documenten).',
|
|
4387
6351
|
// Admin page
|
|
4388
6352
|
epistolaAdminOverview: 'Overzicht',
|
|
4389
6353
|
epistolaAdminRefresh: 'Vernieuwen',
|
|
@@ -4411,6 +6375,10 @@ const epistolaPluginSpecification = {
|
|
|
4411
6375
|
epistolaAdminReconcile: 'Hersynchroniseer',
|
|
4412
6376
|
epistolaAdminReconciling: 'Bezig...',
|
|
4413
6377
|
epistolaAdminReconcileTooltip: 'Vraag de huidige status op bij Epistola en hervat het wachtende proces als het klaar is.',
|
|
6378
|
+
epistolaAdminStatusWaiting: 'Wachtend',
|
|
6379
|
+
epistolaAdminStatusUnwired: 'Vastgelopen',
|
|
6380
|
+
epistolaAdminUnwiredTooltip: 'Deze taak heeft geen correlatie-token (epistolaWaitFor), dus de collector kan hem nooit hervatten — het proces is vastgelopen. Meestal een dubbelzinnig samengevoegd catch event (zie BPMN-validatie). Op te lossen in het procesmodel; hersynchroniseren werkt hier niet.',
|
|
6381
|
+
epistolaAdminUnwiredHint: 'Niet te hersynchroniseren',
|
|
4414
6382
|
epistolaAdminConfigurations: 'Configuraties',
|
|
4415
6383
|
epistolaAdminValidations: 'BPMN-validatie',
|
|
4416
6384
|
epistolaAdminNoValidations: 'Geen race-onveilige procesdefinities gevonden. Alles ziet er goed uit.',
|
|
@@ -4418,6 +6386,10 @@ const epistolaPluginSpecification = {
|
|
|
4418
6386
|
epistolaAdminValidationWarningBody: 'In deze procesdefinities is de grens tussen de generate-document service task en de EpistolaDocumentGenerated catch event niet synchroon. Resultaten kunnen verloren gaan; gebruik in dat geval de Hersynchroniseer-knop in de Wachtende taken-tab.',
|
|
4419
6387
|
epistolaAdminValidationCode: 'Code',
|
|
4420
6388
|
epistolaAdminValidationMessage: 'Bericht',
|
|
6389
|
+
epistolaAdminValidationLastChecked: 'Laatst gecontroleerd',
|
|
6390
|
+
epistolaAdminValidationNotYetRun: 'nog niet uitgevoerd',
|
|
6391
|
+
epistolaAdminValidationAutoRefresh: 'automatisch opnieuw gecontroleerd, ongeveer elke',
|
|
6392
|
+
epistolaAdminValidationLatestVersionNote: 'Alleen de meest recente versie van elke procesdefinitie wordt gecontroleerd; oudere versies met nog lopende instanties kunnen problemen hebben die hier niet worden getoond.',
|
|
4421
6393
|
epistolaAdminCatalogs: 'Catalogi',
|
|
4422
6394
|
epistolaAdminCatalogsIntro: 'Catalogi op het classpath van de applicatie. Deze worden bij het opstarten automatisch uitgerold; gebruik Opnieuw uitrollen om een catalogus handmatig (geforceerd) naar deze Epistola-omgeving te sturen, bijvoorbeeld als de automatische uitrol is mislukt.',
|
|
4423
6395
|
epistolaAdminNoCatalogs: 'Geen catalogi gevonden op het classpath.',
|
|
@@ -4433,6 +6405,23 @@ const epistolaPluginSpecification = {
|
|
|
4433
6405
|
epistolaAdminChangelog: 'Changelog',
|
|
4434
6406
|
epistolaAdminRunningVersion: 'Actieve plugin-versie:',
|
|
4435
6407
|
epistolaAdminNoChangelog: 'Geen changelog beschikbaar in deze build.',
|
|
6408
|
+
// TEMPORARY (remove in 1.0.0): task-id carrier repair (admin "Forms" tab)
|
|
6409
|
+
epistolaAdminForms: 'Formulieren',
|
|
6410
|
+
epistolaAdminFormsIntro: 'Formulieren met een Epistola-component dat het verborgen task-id veld mist. Zonder dat veld werkt het voorbeeld/downloaden/opnieuw genereren niet in elke taak-openflow. Herstel voegt het veld toe.',
|
|
6411
|
+
epistolaAdminFormName: 'Formulier',
|
|
6412
|
+
epistolaAdminFormMissing: 'Ontbrekende componenten',
|
|
6413
|
+
epistolaAdminFormReadOnly: 'Alleen-lezen',
|
|
6414
|
+
epistolaAdminFormReadOnlyHint: 'Dit formulier komt van het classpath en wordt bij de volgende herstart teruggezet naar de bron. Voeg het veld toe aan de bron (component opnieuw plaatsen) voor een blijvende oplossing.',
|
|
6415
|
+
epistolaAdminRepair: 'Herstellen',
|
|
6416
|
+
epistolaAdminRepairAll: 'Alles herstellen',
|
|
6417
|
+
epistolaAdminRepairing: 'Bezig...',
|
|
6418
|
+
epistolaAdminRepairTooltip: 'Voeg het verborgen task-id veld toe aan alle Epistola-componenten in dit formulier.',
|
|
6419
|
+
epistolaAdminNoFormIssues: 'Geen formulieren met een ontbrekend task-id veld gevonden.',
|
|
6420
|
+
// TEMPORARY: legacy override-mapping format detection (admin "Forms" tab)
|
|
6421
|
+
epistolaAdminLegacyOverrideTitle: 'Verouderd invoer-override formaat',
|
|
6422
|
+
epistolaAdminLegacyOverrideIntro: 'Formulieren waarvan een document-voorbeeldcomponent de invoer-overrides nog als object opslaat ("form:"-verwijzingen) in plaats van als JSONata-expressie over $form. Ze blijven werken, maar worden pas naar het nieuwe formaat omgezet als je het formulier opnieuw opslaat in de formulierbouwer.',
|
|
6423
|
+
epistolaAdminLegacyOverrideComponents: 'Verouderde componenten',
|
|
6424
|
+
epistolaAdminNoLegacyOverride: 'Geen formulieren met het verouderde override-formaat gevonden.',
|
|
4436
6425
|
},
|
|
4437
6426
|
en: {
|
|
4438
6427
|
title: 'Epistola Document Suite',
|
|
@@ -4538,8 +6527,12 @@ const epistolaPluginSpecification = {
|
|
|
4538
6527
|
'epistola-download-document': 'Download Document',
|
|
4539
6528
|
documentVariable: 'Document Variable',
|
|
4540
6529
|
documentVariableTooltip: 'Name of the process variable holding the Epistola result. May be a String document id (legacy) or a rich result object with a `documentId` key — the action extracts the document id from it.',
|
|
6530
|
+
storageTarget: 'Storage Target',
|
|
6531
|
+
storageTargetTooltip: 'Where the downloaded PDF is stored. "Temporary resource storage" (recommended) writes only a resource id to the variable — ready to hand to documenten-api:store-temp-document. "Process variable" writes the raw bytes inline (small, non-sensitive documents only; they are included in the task response).',
|
|
6532
|
+
resourceIdVariable: 'Resource ID Variable',
|
|
6533
|
+
resourceIdVariableTooltip: 'Name of the process variable to store the temporary resource id in (hand this to documenten-api:store-temp-document).',
|
|
4541
6534
|
contentVariable: 'Content Variable',
|
|
4542
|
-
contentVariableTooltip: 'Name of the process variable to store the
|
|
6535
|
+
contentVariableTooltip: 'Name of the process variable to store the raw PDF bytes inline in (small, non-sensitive documents only).',
|
|
4543
6536
|
// Admin page
|
|
4544
6537
|
epistolaAdminOverview: 'Overview',
|
|
4545
6538
|
epistolaAdminRefresh: 'Refresh',
|
|
@@ -4567,6 +6560,10 @@ const epistolaPluginSpecification = {
|
|
|
4567
6560
|
epistolaAdminReconcile: 'Reconcile',
|
|
4568
6561
|
epistolaAdminReconciling: 'Reconciling...',
|
|
4569
6562
|
epistolaAdminReconcileTooltip: "Ask Epistola for this job's current status and resume the waiting process if it has finished.",
|
|
6563
|
+
epistolaAdminStatusWaiting: 'Waiting',
|
|
6564
|
+
epistolaAdminStatusUnwired: 'Stuck',
|
|
6565
|
+
epistolaAdminUnwiredTooltip: 'This wait has no correlation token (epistolaWaitFor), so the collector can never resume it — the process is stuck. Usually an ambiguous merged catch event (see BPMN validation). Fix it in the process model; reconcile cannot recover it.',
|
|
6566
|
+
epistolaAdminUnwiredHint: 'Cannot reconcile',
|
|
4570
6567
|
epistolaAdminConfigurations: 'Configurations',
|
|
4571
6568
|
epistolaAdminValidations: 'BPMN validation',
|
|
4572
6569
|
epistolaAdminNoValidations: 'No race-unsafe process definitions detected. Everything looks good.',
|
|
@@ -4574,6 +6571,10 @@ const epistolaPluginSpecification = {
|
|
|
4574
6571
|
epistolaAdminValidationWarningBody: 'These process definitions have a non-synchronous boundary between the generate-document service task and the EpistolaDocumentGenerated catch event. Results can be missed; use the Reconcile button on the Pending Jobs tab to recover.',
|
|
4575
6572
|
epistolaAdminValidationCode: 'Code',
|
|
4576
6573
|
epistolaAdminValidationMessage: 'Message',
|
|
6574
|
+
epistolaAdminValidationLastChecked: 'Last checked',
|
|
6575
|
+
epistolaAdminValidationNotYetRun: 'not yet run',
|
|
6576
|
+
epistolaAdminValidationAutoRefresh: 'automatically re-checked roughly every',
|
|
6577
|
+
epistolaAdminValidationLatestVersionNote: "Only the latest deployed version of each process definition is checked; older versions with running instances may have problems that aren't shown here.",
|
|
4577
6578
|
epistolaAdminCatalogs: 'Catalogs',
|
|
4578
6579
|
epistolaAdminCatalogsIntro: 'Catalogs on the application classpath. These are deployed automatically on startup; use Redeploy to manually (force) push one to this Epistola installation — for example when the automatic startup deploy failed.',
|
|
4579
6580
|
epistolaAdminNoCatalogs: 'No catalogs found on the classpath.',
|
|
@@ -4589,10 +6590,44 @@ const epistolaPluginSpecification = {
|
|
|
4589
6590
|
epistolaAdminChangelog: 'Changelog',
|
|
4590
6591
|
epistolaAdminRunningVersion: 'Running plugin version:',
|
|
4591
6592
|
epistolaAdminNoChangelog: 'No changelog available in this build.',
|
|
6593
|
+
// TEMPORARY (remove in 1.0.0): task-id carrier repair (admin "Forms" tab)
|
|
6594
|
+
epistolaAdminForms: 'Forms',
|
|
6595
|
+
epistolaAdminFormsIntro: 'Forms with an Epistola component that is missing the hidden task-id field. Without it, preview/download/retry do not work in every task-open flow. Repair adds the field.',
|
|
6596
|
+
epistolaAdminFormName: 'Form',
|
|
6597
|
+
epistolaAdminFormMissing: 'Missing components',
|
|
6598
|
+
epistolaAdminFormReadOnly: 'Read-only',
|
|
6599
|
+
epistolaAdminFormReadOnlyHint: 'This form is deployed from the classpath and is reconciled to its source on the next restart. Add the field to the source (re-drop the component) for a permanent fix.',
|
|
6600
|
+
epistolaAdminRepair: 'Repair',
|
|
6601
|
+
epistolaAdminRepairAll: 'Repair all',
|
|
6602
|
+
epistolaAdminRepairing: 'Working...',
|
|
6603
|
+
epistolaAdminRepairTooltip: 'Add the hidden task-id field to all Epistola components in this form.',
|
|
6604
|
+
epistolaAdminNoFormIssues: 'No forms with a missing task-id field found.',
|
|
6605
|
+
// TEMPORARY: legacy override-mapping format detection (admin "Forms" tab)
|
|
6606
|
+
epistolaAdminLegacyOverrideTitle: 'Legacy input-override format',
|
|
6607
|
+
epistolaAdminLegacyOverrideIntro: 'Forms whose document-preview component still stores input overrides as an object ("form:" references) instead of a JSONata expression over $form. They keep working, but only migrate to the new format once you re-save the form in the form builder.',
|
|
6608
|
+
epistolaAdminLegacyOverrideComponents: 'Legacy components',
|
|
6609
|
+
epistolaAdminNoLegacyOverride: 'No forms using the legacy override format found.',
|
|
4592
6610
|
},
|
|
4593
6611
|
},
|
|
4594
6612
|
};
|
|
4595
6613
|
|
|
6614
|
+
/*
|
|
6615
|
+
* Copyright 2025 Epistola.
|
|
6616
|
+
*
|
|
6617
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
6618
|
+
* you may not use this file except in compliance with the License.
|
|
6619
|
+
* You may obtain a copy of the License at
|
|
6620
|
+
*
|
|
6621
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
6622
|
+
*
|
|
6623
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
6624
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
6625
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
6626
|
+
* See the License for the specific language governing permissions and
|
|
6627
|
+
* limitations under the License.
|
|
6628
|
+
*
|
|
6629
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
6630
|
+
*/
|
|
4596
6631
|
/*
|
|
4597
6632
|
* Public API Surface of epistola plugin
|
|
4598
6633
|
*/
|
|
@@ -4601,5 +6636,5 @@ const epistolaPluginSpecification = {
|
|
|
4601
6636
|
* Generated bundle index. Do not edit.
|
|
4602
6637
|
*/
|
|
4603
6638
|
|
|
4604
|
-
export { CheckJobStatusConfigurationComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOCUMENT_OPTIONS, EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EPISTOLA_OVERRIDE_BUILDER_OPTIONS, EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS, EPISTOLA_RETRY_FORM_OPTIONS, EpistolaAdminPageComponent, EpistolaAdminRoutingModule, EpistolaAdminService, EpistolaConfigurationComponent, EpistolaDocumentComponent, EpistolaDocumentPreviewComponent, EpistolaMenuService, EpistolaOverrideBuilderComponent, EpistolaPluginModule, EpistolaPluginService, EpistolaProcessLinkSelectorComponent, EpistolaRetryFormComponent,
|
|
6639
|
+
export { CheckJobStatusConfigurationComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOCUMENT_OPTIONS, EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EPISTOLA_OVERRIDE_BUILDER_OPTIONS, EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS, EPISTOLA_RETRY_FORM_OPTIONS, EpistolaAdminPageComponent, EpistolaAdminRoutingModule, EpistolaAdminService, EpistolaConfigurationComponent, EpistolaDocumentComponent, EpistolaDocumentPreviewComponent, EpistolaMenuService, EpistolaOverrideBuilderComponent, EpistolaPluginModule, EpistolaPluginService, EpistolaProcessLinkSelectorComponent, EpistolaRetryFormComponent, GenerateDocumentConfigurationComponent, JsonataEditorComponent, MappingBuilderComponent, PREFILLED_TASK_ID_CARRIER, PREFILLED_TASK_ID_DATA_KEY, PREFILLED_TASK_ID_SOURCE_KEY, epistolaPluginSpecification, errorResource, initialResource, isEpistolaEnabled, loadingResource, readPrefilledTaskId, registerEpistolaDocumentComponent, registerEpistolaDocumentPreviewComponent, registerEpistolaOverrideBuilderComponent, registerEpistolaProcessLinkSelectorComponent, registerEpistolaRetryFormComponent, successResource };
|
|
4605
6640
|
//# sourceMappingURL=epistola.app-valtimo-plugin.mjs.map
|