@epistola.app/valtimo-plugin 0.10.0 → 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 +1931 -228
- 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-configuration.component.d.ts +2 -2
- package/lib/components/epistola-admin-page/epistola-admin-page.component.d.ts +8 -1
- package/lib/components/epistola-configuration/epistola-configuration.component.d.ts +2 -2
- package/lib/components/epistola-document-preview/epistola-document-preview.component.d.ts +39 -7
- package/lib/components/epistola-document-preview/preview-utils.d.ts +19 -8
- 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 +19 -0
- package/lib/models/config.d.ts +9 -0
- package/lib/services/epistola-admin.service.d.ts +3 -1
- package/lib/services/epistola-plugin.service.d.ts +7 -1
- 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
|
@@ -12,14 +12,14 @@ 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$
|
|
18
|
+
import * as i2$2 from '@valtimo/process-link';
|
|
19
|
+
import * as i2$3 from '@angular/platform-browser';
|
|
20
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
24
|
import * as i5 from 'carbon-components-angular/tabs';
|
|
25
25
|
import { TabsModule } from 'carbon-components-angular/tabs';
|
|
@@ -27,6 +27,41 @@ 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.
|
|
@@ -140,6 +264,11 @@ class EpistolaAdminService {
|
|
|
140
264
|
repairAllFormCarriers() {
|
|
141
265
|
return this.http.post(`${this.apiEndpoint}/forms/repair-carrier`, null);
|
|
142
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
|
+
}
|
|
143
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 });
|
|
144
273
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminService });
|
|
145
274
|
}
|
|
@@ -147,6 +276,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
147
276
|
type: Injectable
|
|
148
277
|
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.ConfigService }] });
|
|
149
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
|
+
*/
|
|
150
296
|
/**
|
|
151
297
|
* Registers the Epistola admin page menu item under the Admin > Other section.
|
|
152
298
|
* Instantiated eagerly via ENVIRONMENT_INITIALIZER so the menu item
|
|
@@ -183,6 +329,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
183
329
|
type: Injectable
|
|
184
330
|
}], ctorParameters: () => [{ type: i3.MenuService }] });
|
|
185
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
|
+
*/
|
|
186
349
|
/**
|
|
187
350
|
* Service for interacting with Epistola plugin API endpoints.
|
|
188
351
|
* Provides methods to fetch templates, environments, variants,
|
|
@@ -250,6 +413,16 @@ class EpistolaPluginService {
|
|
|
250
413
|
params: { processDefinitionKey },
|
|
251
414
|
});
|
|
252
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
|
+
}
|
|
253
426
|
/**
|
|
254
427
|
* Get variable suggestions for JSONata autocompletion.
|
|
255
428
|
*/
|
|
@@ -341,6 +514,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
341
514
|
type: Injectable
|
|
342
515
|
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.ConfigService }] });
|
|
343
516
|
|
|
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
|
|
533
|
+
*/
|
|
344
534
|
/**
|
|
345
535
|
* Helpers for reading the active user task's id out of a Valtimo task form that was
|
|
346
536
|
* prefilled server-side by the {@code epistola:} value resolver (see the backend
|
|
@@ -433,12 +623,49 @@ function findSourceKeyDefaultValue(node, sourceKey) {
|
|
|
433
623
|
return null;
|
|
434
624
|
}
|
|
435
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
|
+
*/
|
|
436
661
|
class EpistolaConfigurationComponent {
|
|
437
662
|
save$;
|
|
438
663
|
disabled$;
|
|
439
664
|
pluginId;
|
|
440
665
|
prefillConfiguration$;
|
|
441
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.
|
|
442
669
|
configuration = new EventEmitter();
|
|
443
670
|
/** Epistola slug pattern: lowercase alphanumeric with hyphens, no leading/trailing hyphens. */
|
|
444
671
|
static SLUG_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
@@ -487,7 +714,7 @@ class EpistolaConfigurationComponent {
|
|
|
487
714
|
combineLatest([this.formValue$, this.valid$])
|
|
488
715
|
.pipe(take(1))
|
|
489
716
|
.subscribe(([formValue, valid]) => {
|
|
490
|
-
if (valid) {
|
|
717
|
+
if (valid && formValue) {
|
|
491
718
|
this.configuration.emit(formValue);
|
|
492
719
|
}
|
|
493
720
|
});
|
|
@@ -513,12 +740,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
513
740
|
type: Output
|
|
514
741
|
}] } });
|
|
515
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
|
+
*/
|
|
516
760
|
/**
|
|
517
761
|
* Shared state for the JSONata completion provider.
|
|
518
762
|
* Updated by the editor component when suggestions/functions change.
|
|
519
763
|
*/
|
|
520
764
|
const jsonataCompletionData = {
|
|
521
|
-
|
|
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: {},
|
|
522
771
|
functions: [],
|
|
523
772
|
};
|
|
524
773
|
/**
|
|
@@ -601,7 +850,8 @@ function registerJsonataLanguage(monaco) {
|
|
|
601
850
|
const CompletionItemKind = monaco.languages.CompletionItemKind;
|
|
602
851
|
// After "$" — suggest variables and functions
|
|
603
852
|
if (textUntilPosition.endsWith('$')) {
|
|
604
|
-
|
|
853
|
+
// Variables are whatever the host put in scope (doc/pv/case/form/…).
|
|
854
|
+
suggestions.push(...Object.keys(jsonataCompletionData.variables).map((v) => ({
|
|
605
855
|
label: `$${v}`,
|
|
606
856
|
kind: CompletionItemKind.Variable,
|
|
607
857
|
insertText: v,
|
|
@@ -656,27 +906,20 @@ function registerJsonataLanguage(monaco) {
|
|
|
656
906
|
});
|
|
657
907
|
}
|
|
658
908
|
}
|
|
659
|
-
// After "
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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) {
|
|
663
915
|
suggestions.push({
|
|
664
|
-
label:
|
|
916
|
+
label: field,
|
|
665
917
|
kind: CompletionItemKind.Field,
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
// After "$pv." — suggest process variables
|
|
672
|
-
if (/\$pv\.\s*$/.test(textUntilPosition) || /\$pv\.[a-zA-Z_]*$/.test(textUntilPosition)) {
|
|
673
|
-
const pvNames = jsonataCompletionData.suggestions?.pv || [];
|
|
674
|
-
for (const name of pvNames) {
|
|
675
|
-
suggestions.push({
|
|
676
|
-
label: name,
|
|
677
|
-
kind: CompletionItemKind.Variable,
|
|
678
|
-
insertText: name,
|
|
679
|
-
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`,
|
|
680
923
|
});
|
|
681
924
|
}
|
|
682
925
|
}
|
|
@@ -685,12 +928,37 @@ function registerJsonataLanguage(monaco) {
|
|
|
685
928
|
});
|
|
686
929
|
}
|
|
687
930
|
|
|
688
|
-
|
|
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;
|
|
689
949
|
class JsonataEditorComponent {
|
|
690
950
|
expression = '';
|
|
691
951
|
disabled = false;
|
|
692
|
-
|
|
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 = {};
|
|
693
959
|
functions = [];
|
|
960
|
+
/** Footer hint listing the context variables in scope. */
|
|
961
|
+
variablesHint = '$doc · $pv · $case';
|
|
694
962
|
expressionChange = new EventEmitter();
|
|
695
963
|
validChange = new EventEmitter();
|
|
696
964
|
editorModel = { value: '', language: 'jsonata' };
|
|
@@ -720,8 +988,8 @@ class JsonataEditorComponent {
|
|
|
720
988
|
this.editorModel = { value: this.expression || '', language: 'jsonata' };
|
|
721
989
|
this.validate$.next(this.expression);
|
|
722
990
|
}
|
|
723
|
-
if (changes['
|
|
724
|
-
jsonataCompletionData.
|
|
991
|
+
if (changes['contextVariables']) {
|
|
992
|
+
jsonataCompletionData.variables = this.contextVariables || {};
|
|
725
993
|
}
|
|
726
994
|
if (changes['functions']) {
|
|
727
995
|
jsonataCompletionData.functions = this.functions;
|
|
@@ -749,7 +1017,7 @@ class JsonataEditorComponent {
|
|
|
749
1017
|
if (m) {
|
|
750
1018
|
registerJsonataLanguage(m);
|
|
751
1019
|
this.languageRegistered = true;
|
|
752
|
-
jsonataCompletionData.
|
|
1020
|
+
jsonataCompletionData.variables = this.contextVariables || {};
|
|
753
1021
|
jsonataCompletionData.functions = this.functions;
|
|
754
1022
|
}
|
|
755
1023
|
}
|
|
@@ -760,7 +1028,7 @@ class JsonataEditorComponent {
|
|
|
760
1028
|
return;
|
|
761
1029
|
}
|
|
762
1030
|
try {
|
|
763
|
-
jsonata$
|
|
1031
|
+
jsonata$4(value);
|
|
764
1032
|
this.error = null;
|
|
765
1033
|
this.validChange.emit(true);
|
|
766
1034
|
}
|
|
@@ -770,7 +1038,7 @@ class JsonataEditorComponent {
|
|
|
770
1038
|
}
|
|
771
1039
|
}
|
|
772
1040
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: JsonataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
773
|
-
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: `
|
|
774
1042
|
<div class="jsonata-editor">
|
|
775
1043
|
<valtimo-editor
|
|
776
1044
|
[model]="editorModel"
|
|
@@ -783,7 +1051,7 @@ class JsonataEditorComponent {
|
|
|
783
1051
|
<div class="jsonata-editor__footer">
|
|
784
1052
|
<span *ngIf="error" class="jsonata-editor__error">{{ error }}</span>
|
|
785
1053
|
<span *ngIf="!error && expression" class="jsonata-editor__valid">✓</span>
|
|
786
|
-
<span class="jsonata-editor__variables"
|
|
1054
|
+
<span class="jsonata-editor__variables">{{ variablesHint }}</span>
|
|
787
1055
|
</div>
|
|
788
1056
|
</div>
|
|
789
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"] }] });
|
|
@@ -803,7 +1071,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
803
1071
|
<div class="jsonata-editor__footer">
|
|
804
1072
|
<span *ngIf="error" class="jsonata-editor__error">{{ error }}</span>
|
|
805
1073
|
<span *ngIf="!error && expression" class="jsonata-editor__valid">✓</span>
|
|
806
|
-
<span class="jsonata-editor__variables"
|
|
1074
|
+
<span class="jsonata-editor__variables">{{ variablesHint }}</span>
|
|
807
1075
|
</div>
|
|
808
1076
|
</div>
|
|
809
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"] }]
|
|
@@ -811,16 +1079,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
811
1079
|
type: Input
|
|
812
1080
|
}], disabled: [{
|
|
813
1081
|
type: Input
|
|
814
|
-
}],
|
|
1082
|
+
}], contextVariables: [{
|
|
815
1083
|
type: Input
|
|
816
1084
|
}], functions: [{
|
|
817
1085
|
type: Input
|
|
1086
|
+
}], variablesHint: [{
|
|
1087
|
+
type: Input
|
|
818
1088
|
}], expressionChange: [{
|
|
819
1089
|
type: Output
|
|
820
1090
|
}], validChange: [{
|
|
821
1091
|
type: Output
|
|
822
1092
|
}] } });
|
|
823
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
|
+
*/
|
|
824
1111
|
class ExpectedStructureComponent {
|
|
825
1112
|
templateFields = [];
|
|
826
1113
|
structureText = '{}';
|
|
@@ -885,6 +1172,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
885
1172
|
type: Input
|
|
886
1173
|
}] } });
|
|
887
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
|
+
*/
|
|
888
1192
|
class BuilderFieldComponent {
|
|
889
1193
|
field;
|
|
890
1194
|
path = [];
|
|
@@ -964,7 +1268,7 @@ class BuilderFieldComponent {
|
|
|
964
1268
|
></epistola-builder-field>
|
|
965
1269
|
</div>
|
|
966
1270
|
</div>
|
|
967
|
-
`, 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"] }] });
|
|
968
1272
|
}
|
|
969
1273
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BuilderFieldComponent, decorators: [{
|
|
970
1274
|
type: Component,
|
|
@@ -1055,7 +1359,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1055
1359
|
type: Output
|
|
1056
1360
|
}] } });
|
|
1057
1361
|
|
|
1058
|
-
|
|
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;
|
|
1059
1380
|
/**
|
|
1060
1381
|
* Parse a JSONata expression into BuilderField array.
|
|
1061
1382
|
* Only supports top-level object literals with simple path references or nested objects.
|
|
@@ -1066,7 +1387,7 @@ function parseJsonataToBuilder(expression) {
|
|
|
1066
1387
|
return [];
|
|
1067
1388
|
}
|
|
1068
1389
|
try {
|
|
1069
|
-
const ast = jsonata(expression).ast();
|
|
1390
|
+
const ast = jsonata$3(expression).ast();
|
|
1070
1391
|
if (ast.type === 'unary' && ast.value === '{') {
|
|
1071
1392
|
return parseObjectEntries(ast.lhs, expression);
|
|
1072
1393
|
}
|
|
@@ -1212,6 +1533,23 @@ function formatFieldEntry(field, indent = ' ') {
|
|
|
1212
1533
|
return `${indent}"${field.name}": ${value}`;
|
|
1213
1534
|
}
|
|
1214
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
|
+
*/
|
|
1215
1553
|
class MappingBuilderComponent {
|
|
1216
1554
|
expression = '';
|
|
1217
1555
|
templateFields = [];
|
|
@@ -1413,6 +1751,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1413
1751
|
type: Output
|
|
1414
1752
|
}] } });
|
|
1415
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
|
+
*/
|
|
1416
1771
|
class MappingPreviewComponent {
|
|
1417
1772
|
epistolaPluginService;
|
|
1418
1773
|
expression = '';
|
|
@@ -1547,7 +1902,7 @@ class MappingPreviewComponent {
|
|
|
1547
1902
|
<strong>{{ missingRequired.join(', ') }}</strong>
|
|
1548
1903
|
</div>
|
|
1549
1904
|
</div>
|
|
1550
|
-
`, 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" }] });
|
|
1551
1906
|
}
|
|
1552
1907
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MappingPreviewComponent, decorators: [{
|
|
1553
1908
|
type: Component,
|
|
@@ -1618,7 +1973,227 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1618
1973
|
type: Input
|
|
1619
1974
|
}] } });
|
|
1620
1975
|
|
|
1621
|
-
|
|
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;
|
|
1622
2197
|
/**
|
|
1623
2198
|
* Detect if a string value is a JSONata expression (vs a plain literal).
|
|
1624
2199
|
* Checks for characters that indicate JSONata operators: $, &, (, {, ?, [
|
|
@@ -1652,7 +2227,11 @@ function expandDotNotation(flat) {
|
|
|
1652
2227
|
* doc/case data.
|
|
1653
2228
|
*/
|
|
1654
2229
|
function isOverrideDriven(mapping) {
|
|
1655
|
-
|
|
2230
|
+
if (!mapping)
|
|
2231
|
+
return false;
|
|
2232
|
+
if (typeof mapping === 'string')
|
|
2233
|
+
return mapping.trim().length > 0;
|
|
2234
|
+
return Object.keys(mapping).length > 0;
|
|
1656
2235
|
}
|
|
1657
2236
|
/**
|
|
1658
2237
|
* Whether the computed input overrides carry any usable data yet.
|
|
@@ -1677,32 +2256,63 @@ function shouldLoadPreview(mapping, overrides) {
|
|
|
1677
2256
|
return true;
|
|
1678
2257
|
}
|
|
1679
2258
|
/**
|
|
1680
|
-
* Given an override mapping
|
|
1681
|
-
*
|
|
1682
|
-
*
|
|
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.
|
|
1683
2268
|
*/
|
|
1684
|
-
function computeInputOverrides(mapping, formData) {
|
|
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
|
+
}
|
|
1685
2289
|
const result = {};
|
|
1686
|
-
for (const [
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
for (const [inputPath, ref] of Object.entries(fields)) {
|
|
1691
|
-
const formFieldKey = String(ref).startsWith(FORM_REF_PREFIX$1)
|
|
1692
|
-
? String(ref).substring(FORM_REF_PREFIX$1.length)
|
|
1693
|
-
: String(ref);
|
|
1694
|
-
const value = formData[formFieldKey];
|
|
1695
|
-
if (value !== undefined) {
|
|
1696
|
-
flatOverrides[inputPath] = value;
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
if (Object.keys(flatOverrides).length > 0) {
|
|
1700
|
-
result[scope] = expandDotNotation(flatOverrides);
|
|
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;
|
|
1701
2294
|
}
|
|
1702
2295
|
}
|
|
1703
2296
|
return result;
|
|
1704
2297
|
}
|
|
1705
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
|
+
*/
|
|
1706
2316
|
class GenerateDocumentConfigurationComponent {
|
|
1707
2317
|
epistolaPluginService;
|
|
1708
2318
|
processLinkStateService;
|
|
@@ -1714,6 +2324,8 @@ class GenerateDocumentConfigurationComponent {
|
|
|
1714
2324
|
selectedPluginConfigurationData$;
|
|
1715
2325
|
context$;
|
|
1716
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.
|
|
1717
2329
|
configuration = new EventEmitter();
|
|
1718
2330
|
catalogs$ = new BehaviorSubject(initialResource([]));
|
|
1719
2331
|
templates$ = new BehaviorSubject(initialResource([]));
|
|
@@ -1760,6 +2372,8 @@ class GenerateDocumentConfigurationComponent {
|
|
|
1760
2372
|
processVariables = [];
|
|
1761
2373
|
expressionFunctions = [];
|
|
1762
2374
|
variableSuggestions = null;
|
|
2375
|
+
/** Context variables for the JSONata editor's autocomplete ($doc/$pv/$case). */
|
|
2376
|
+
editorContextVariables = { doc: [], pv: [], case: [] };
|
|
1763
2377
|
prefillDataMapping = {};
|
|
1764
2378
|
validationErrors$ = new BehaviorSubject([]);
|
|
1765
2379
|
destroy$ = new Subject();
|
|
@@ -2073,6 +2687,12 @@ class GenerateDocumentConfigurationComponent {
|
|
|
2073
2687
|
.pipe(takeUntil$1(this.destroy$), catchError(() => of({ doc: [], pv: [] })))
|
|
2074
2688
|
.subscribe((suggestions) => {
|
|
2075
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
|
+
};
|
|
2076
2696
|
this.cdr.markForCheck();
|
|
2077
2697
|
});
|
|
2078
2698
|
}
|
|
@@ -2165,8 +2785,8 @@ class GenerateDocumentConfigurationComponent {
|
|
|
2165
2785
|
}
|
|
2166
2786
|
});
|
|
2167
2787
|
}
|
|
2168
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, deps: [{ token: EpistolaPluginService }, { token: i2$
|
|
2169
|
-
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 });
|
|
2170
2790
|
}
|
|
2171
2791
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, decorators: [{
|
|
2172
2792
|
type: Component,
|
|
@@ -2181,8 +2801,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2181
2801
|
JsonataEditorComponent,
|
|
2182
2802
|
MappingBuilderComponent,
|
|
2183
2803
|
MappingPreviewComponent,
|
|
2184
|
-
], 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"] }]
|
|
2185
|
-
}], 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$: [{
|
|
2186
2806
|
type: Input
|
|
2187
2807
|
}], disabled$: [{
|
|
2188
2808
|
type: Input
|
|
@@ -2200,12 +2820,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2200
2820
|
type: Output
|
|
2201
2821
|
}] } });
|
|
2202
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
|
+
*/
|
|
2203
2840
|
class CheckJobStatusConfigurationComponent {
|
|
2204
2841
|
save$;
|
|
2205
2842
|
disabled$;
|
|
2206
2843
|
pluginId;
|
|
2207
2844
|
prefillConfiguration$;
|
|
2208
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.
|
|
2209
2849
|
configuration = new EventEmitter();
|
|
2210
2850
|
saveSubscription;
|
|
2211
2851
|
formValue$ = new BehaviorSubject(null);
|
|
@@ -2267,8 +2907,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2267
2907
|
type: Output
|
|
2268
2908
|
}] } });
|
|
2269
2909
|
|
|
2270
|
-
|
|
2271
|
-
|
|
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. */
|
|
2272
2929
|
function resolveStorageTarget(target) {
|
|
2273
2930
|
return target === 'PROCESS_VARIABLE' ? 'PROCESS_VARIABLE' : DEFAULT_STORAGE_TARGET;
|
|
2274
2931
|
}
|
|
@@ -2285,12 +2942,31 @@ function isDownloadDocumentConfigValid(config) {
|
|
|
2285
2942
|
: !!config.resourceIdVariable;
|
|
2286
2943
|
}
|
|
2287
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
|
+
*/
|
|
2288
2962
|
class DownloadDocumentConfigurationComponent {
|
|
2289
2963
|
save$;
|
|
2290
2964
|
disabled$;
|
|
2291
2965
|
pluginId;
|
|
2292
2966
|
prefillConfiguration$;
|
|
2293
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.
|
|
2294
2970
|
configuration = new EventEmitter();
|
|
2295
2971
|
saveSubscription;
|
|
2296
2972
|
formValue$ = new BehaviorSubject(null);
|
|
@@ -2372,6 +3048,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2372
3048
|
type: Output
|
|
2373
3049
|
}] } });
|
|
2374
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
|
+
*/
|
|
2375
3068
|
/**
|
|
2376
3069
|
* Unified Formio component for the after-generation Epistola PDF UX. Reads
|
|
2377
3070
|
* the PDF id and tenant id from named process variables on the caller's
|
|
@@ -2530,7 +3223,7 @@ class EpistolaDocumentComponent {
|
|
|
2530
3223
|
this.previewUrl = null;
|
|
2531
3224
|
}
|
|
2532
3225
|
}
|
|
2533
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentComponent, deps: [{ token: EpistolaPluginService }, { token: i2$
|
|
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 });
|
|
2534
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: `
|
|
2535
3228
|
<!-- Design-time placeholder -->
|
|
2536
3229
|
<div *ngIf="designMode" class="epistola-doc-panel">
|
|
@@ -2686,7 +3379,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2686
3379
|
</div>
|
|
2687
3380
|
</div>
|
|
2688
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"] }]
|
|
2689
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$
|
|
3382
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$3.DomSanitizer }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
2690
3383
|
type: Input
|
|
2691
3384
|
}], valueChange: [{
|
|
2692
3385
|
type: Output
|
|
@@ -2706,6 +3399,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2706
3399
|
type: Input
|
|
2707
3400
|
}] } });
|
|
2708
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
|
+
*/
|
|
2709
3419
|
class EpistolaRetryFormComponent {
|
|
2710
3420
|
epistolaPluginService;
|
|
2711
3421
|
cdr;
|
|
@@ -2867,7 +3577,7 @@ class EpistolaRetryFormComponent {
|
|
|
2867
3577
|
},
|
|
2868
3578
|
});
|
|
2869
3579
|
}
|
|
2870
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, deps: [{ token: EpistolaPluginService }, { token: i0.ChangeDetectorRef }, { token: i2$
|
|
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 });
|
|
2871
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: `
|
|
2872
3582
|
<div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
|
|
2873
3583
|
<div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
|
|
@@ -2949,7 +3659,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2949
3659
|
</div>
|
|
2950
3660
|
</div>
|
|
2951
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"] }]
|
|
2952
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i0.ChangeDetectorRef }, { type: i2$
|
|
3662
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i0.ChangeDetectorRef }, { type: i2$3.DomSanitizer }], propDecorators: { value: [{
|
|
2953
3663
|
type: Input
|
|
2954
3664
|
}], valueChange: [{
|
|
2955
3665
|
type: Output
|
|
@@ -2963,6 +3673,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2963
3673
|
type: Input
|
|
2964
3674
|
}] } });
|
|
2965
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
|
+
*/
|
|
2966
3693
|
class EpistolaDocumentPreviewComponent {
|
|
2967
3694
|
epistolaPluginService;
|
|
2968
3695
|
sanitizer;
|
|
@@ -2974,12 +3701,42 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2974
3701
|
label = 'Document Preview';
|
|
2975
3702
|
processDefinitionKey;
|
|
2976
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
|
+
*/
|
|
2977
3708
|
overrideMapping;
|
|
2978
3709
|
/**
|
|
2979
3710
|
* Task id forwarded by the Formio wrapper from the server-prefilled form
|
|
2980
3711
|
* ({@code epistola:taskId} value resolver), populated in every Valtimo task-open flow.
|
|
2981
3712
|
*/
|
|
2982
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;
|
|
2983
3740
|
loading = false;
|
|
2984
3741
|
error = null;
|
|
2985
3742
|
previewUrl = null;
|
|
@@ -3001,16 +3758,21 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3001
3758
|
get currentTaskId() {
|
|
3002
3759
|
return this.taskInstanceId ?? null;
|
|
3003
3760
|
}
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
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);
|
|
3012
3770
|
}
|
|
3013
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
|
+
}
|
|
3014
3776
|
if (!this.initialized) {
|
|
3015
3777
|
this.initialized = true;
|
|
3016
3778
|
// Detect design mode: no runtime context (Formio builder)
|
|
@@ -3034,7 +3796,7 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3034
3796
|
// wrapper sets taskInstanceId after attach, so it can land after the first render —
|
|
3035
3797
|
// re-run the preview once it does, instead of leaving the "only available from
|
|
3036
3798
|
// within a user task" message until a manual refresh.
|
|
3037
|
-
if (changes['
|
|
3799
|
+
if (changes['inputOverrides'] || changes['taskInstanceId']) {
|
|
3038
3800
|
this.triggerPreview();
|
|
3039
3801
|
}
|
|
3040
3802
|
}
|
|
@@ -3043,8 +3805,24 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3043
3805
|
this.revokeBlobUrl();
|
|
3044
3806
|
}
|
|
3045
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
|
+
}
|
|
3046
3815
|
this.triggerPreview();
|
|
3047
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
|
+
}
|
|
3048
3826
|
/**
|
|
3049
3827
|
* Load the preview only when there is enough data for it. Override-driven
|
|
3050
3828
|
* previews (those with an override mapping) wait until the mapped form data
|
|
@@ -3052,7 +3830,7 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3052
3830
|
* request that Epistola would reject with a 400 for missing required fields.
|
|
3053
3831
|
*/
|
|
3054
3832
|
triggerPreview() {
|
|
3055
|
-
if (!shouldLoadPreview(this.overrideMapping, this.
|
|
3833
|
+
if (!shouldLoadPreview(this.overrideMapping, this.inputOverrides)) {
|
|
3056
3834
|
this.showWaitingForInput();
|
|
3057
3835
|
return;
|
|
3058
3836
|
}
|
|
@@ -3094,7 +3872,7 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3094
3872
|
.previewToBlob({
|
|
3095
3873
|
taskId,
|
|
3096
3874
|
sourceActivityId: this.sourceActivityId,
|
|
3097
|
-
inputOverrides: this.
|
|
3875
|
+
inputOverrides: this.inputOverrides || null,
|
|
3098
3876
|
overrides: null,
|
|
3099
3877
|
})
|
|
3100
3878
|
.subscribe({
|
|
@@ -3137,8 +3915,8 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3137
3915
|
this.previewUrl = null;
|
|
3138
3916
|
}
|
|
3139
3917
|
}
|
|
3140
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, deps: [{ token: EpistolaPluginService }, { token: i2$
|
|
3141
|
-
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" }, 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: `
|
|
3142
3920
|
<!-- Design-time view: show configuration summary when no runtime context -->
|
|
3143
3921
|
<div *ngIf="designMode" class="epistola-preview-panel">
|
|
3144
3922
|
<div class="preview-header">
|
|
@@ -3151,16 +3929,9 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3151
3929
|
<div class="design-label">Activity</div>
|
|
3152
3930
|
<div class="design-value">{{ sourceActivityId }}</div>
|
|
3153
3931
|
</div>
|
|
3154
|
-
<div class="design-section" *ngIf="
|
|
3155
|
-
<div class="design-label">Input Overrides</div>
|
|
3156
|
-
<
|
|
3157
|
-
<div *ngFor="let entry of overrideMappingEntries(scope)" class="design-entry">
|
|
3158
|
-
<span class="design-scope">{{ scope }}</span
|
|
3159
|
-
>.{{ entry.path }}
|
|
3160
|
-
<i class="mdi mdi-arrow-left"></i>
|
|
3161
|
-
<span class="design-field">{{ entry.field }}</span>
|
|
3162
|
-
</div>
|
|
3163
|
-
</div>
|
|
3932
|
+
<div class="design-section" *ngIf="overrideExpression">
|
|
3933
|
+
<div class="design-label">Input Overrides ($form)</div>
|
|
3934
|
+
<pre class="design-expression">{{ overrideExpression }}</pre>
|
|
3164
3935
|
</div>
|
|
3165
3936
|
<div *ngIf="!sourceActivityId" class="design-unconfigured">
|
|
3166
3937
|
Auto-discover mode (no process link configured)
|
|
@@ -3173,6 +3944,18 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3173
3944
|
<div class="preview-header">
|
|
3174
3945
|
<span>{{ label || 'Document Preview' }}</span>
|
|
3175
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>
|
|
3176
3959
|
<button type="button" class="preview-refresh" [disabled]="loading" (click)="refresh()">
|
|
3177
3960
|
<i class="mdi mdi-refresh mr-1"></i>
|
|
3178
3961
|
{{ loading ? 'Generating...' : 'Refresh' }}
|
|
@@ -3195,7 +3978,7 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3195
3978
|
</object>
|
|
3196
3979
|
</div>
|
|
3197
3980
|
</div>
|
|
3198
|
-
`, 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 });
|
|
3199
3982
|
}
|
|
3200
3983
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, decorators: [{
|
|
3201
3984
|
type: Component,
|
|
@@ -3212,16 +3995,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3212
3995
|
<div class="design-label">Activity</div>
|
|
3213
3996
|
<div class="design-value">{{ sourceActivityId }}</div>
|
|
3214
3997
|
</div>
|
|
3215
|
-
<div class="design-section" *ngIf="
|
|
3216
|
-
<div class="design-label">Input Overrides</div>
|
|
3217
|
-
<
|
|
3218
|
-
<div *ngFor="let entry of overrideMappingEntries(scope)" class="design-entry">
|
|
3219
|
-
<span class="design-scope">{{ scope }}</span
|
|
3220
|
-
>.{{ entry.path }}
|
|
3221
|
-
<i class="mdi mdi-arrow-left"></i>
|
|
3222
|
-
<span class="design-field">{{ entry.field }}</span>
|
|
3223
|
-
</div>
|
|
3224
|
-
</div>
|
|
3998
|
+
<div class="design-section" *ngIf="overrideExpression">
|
|
3999
|
+
<div class="design-label">Input Overrides ($form)</div>
|
|
4000
|
+
<pre class="design-expression">{{ overrideExpression }}</pre>
|
|
3225
4001
|
</div>
|
|
3226
4002
|
<div *ngIf="!sourceActivityId" class="design-unconfigured">
|
|
3227
4003
|
Auto-discover mode (no process link configured)
|
|
@@ -3234,6 +4010,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3234
4010
|
<div class="preview-header">
|
|
3235
4011
|
<span>{{ label || 'Document Preview' }}</span>
|
|
3236
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>
|
|
3237
4025
|
<button type="button" class="preview-refresh" [disabled]="loading" (click)="refresh()">
|
|
3238
4026
|
<i class="mdi mdi-refresh mr-1"></i>
|
|
3239
4027
|
{{ loading ? 'Generating...' : 'Refresh' }}
|
|
@@ -3256,8 +4044,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3256
4044
|
</object>
|
|
3257
4045
|
</div>
|
|
3258
4046
|
</div>
|
|
3259
|
-
`, 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:.
|
|
3260
|
-
}], 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: [{
|
|
3261
4049
|
type: Input
|
|
3262
4050
|
}], valueChange: [{
|
|
3263
4051
|
type: Output
|
|
@@ -3273,8 +4061,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3273
4061
|
type: Input
|
|
3274
4062
|
}], taskInstanceId: [{
|
|
3275
4063
|
type: Input
|
|
4064
|
+
}], inputOverrides: [{
|
|
4065
|
+
type: Input
|
|
4066
|
+
}], requestOverrides: [{
|
|
4067
|
+
type: Input
|
|
4068
|
+
}], autoRefresh: [{
|
|
4069
|
+
type: Input
|
|
4070
|
+
}], setAutoRefresh: [{
|
|
4071
|
+
type: Input
|
|
3276
4072
|
}] } });
|
|
3277
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
|
+
*/
|
|
3278
4091
|
class EpistolaAdminPageComponent {
|
|
3279
4092
|
adminService;
|
|
3280
4093
|
route;
|
|
@@ -3301,6 +4114,9 @@ class EpistolaAdminPageComponent {
|
|
|
3301
4114
|
repairingFormIds = new Set();
|
|
3302
4115
|
repairingAll = false;
|
|
3303
4116
|
formFeedback = null;
|
|
4117
|
+
// TEMPORARY: forms still using the legacy override-mapping object format.
|
|
4118
|
+
legacyOverrideForms = null;
|
|
4119
|
+
legacyOverrideLoading = false;
|
|
3304
4120
|
connectionStatuses = [];
|
|
3305
4121
|
usageEntries = [];
|
|
3306
4122
|
pendingJobs = [];
|
|
@@ -3321,6 +4137,14 @@ class EpistolaAdminPageComponent {
|
|
|
3321
4137
|
get refreshIntervalMinutes() {
|
|
3322
4138
|
return Math.round((this.validationReport?.refreshIntervalMs ?? 600000) / 60000);
|
|
3323
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
|
+
}
|
|
3324
4148
|
ngOnInit() {
|
|
3325
4149
|
this.deepLinkConfigId = this.route.snapshot.queryParamMap.get('configurationId');
|
|
3326
4150
|
const tab = this.route.snapshot.queryParamMap.get('tab');
|
|
@@ -3356,6 +4180,23 @@ class EpistolaAdminPageComponent {
|
|
|
3356
4180
|
if (tab === 'forms' && this.formIssues === null && !this.formIssuesLoading) {
|
|
3357
4181
|
this.loadFormIssues();
|
|
3358
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
|
+
});
|
|
3359
4200
|
}
|
|
3360
4201
|
// ---- TEMPORARY (removed in 1.0.0): task-id carrier detection + repair ----
|
|
3361
4202
|
loadFormIssues() {
|
|
@@ -3659,14 +4500,31 @@ class EpistolaAdminPageComponent {
|
|
|
3659
4500
|
},
|
|
3660
4501
|
});
|
|
3661
4502
|
}
|
|
3662
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, deps: [{ token: EpistolaAdminService }, { token: i2$
|
|
3663
|
-
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=\"formIssues\"\n size=\"sm\"\n [type]=\"formIssues.length > 0 ? 'red' : 'gray'\"\n class=\"ms-1\"\n >{{ formIssues.length }}</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 </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: "pipe", type: i1$1.DatePipe, name: "date" }, { 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.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"] }] });
|
|
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"] }] });
|
|
3664
4505
|
}
|
|
3665
4506
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, decorators: [{
|
|
3666
4507
|
type: Component,
|
|
3667
|
-
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=\"formIssues\"\n size=\"sm\"\n [type]=\"formIssues.length > 0 ? 'red' : 'gray'\"\n class=\"ms-1\"\n >{{ formIssues.length }}</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 </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"] }]
|
|
3668
|
-
}], 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 }] });
|
|
3669
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
|
+
*/
|
|
3670
4528
|
function isRuntimeWindow(value) {
|
|
3671
4529
|
return typeof value === 'object' && value !== null;
|
|
3672
4530
|
}
|
|
@@ -3696,12 +4554,46 @@ function isEpistolaEnabled() {
|
|
|
3696
4554
|
return flag !== false && flag !== 'false';
|
|
3697
4555
|
}
|
|
3698
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
|
+
*/
|
|
3699
4574
|
const epistolaEnabledGuard = () => {
|
|
3700
4575
|
if (isEpistolaEnabled())
|
|
3701
4576
|
return true;
|
|
3702
4577
|
return inject(Router).parseUrl('/');
|
|
3703
4578
|
};
|
|
3704
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
|
+
*/
|
|
3705
4597
|
const routes = [
|
|
3706
4598
|
{
|
|
3707
4599
|
path: 'epistola',
|
|
@@ -3712,7 +4604,7 @@ const routes = [
|
|
|
3712
4604
|
];
|
|
3713
4605
|
class EpistolaAdminRoutingModule {
|
|
3714
4606
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
3715
|
-
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] });
|
|
3716
4608
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [RouterModule.forChild(routes), RouterModule] });
|
|
3717
4609
|
}
|
|
3718
4610
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, decorators: [{
|
|
@@ -3723,6 +4615,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3723
4615
|
}]
|
|
3724
4616
|
}] });
|
|
3725
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
|
+
*/
|
|
3726
4635
|
const EPISTOLA_DOCUMENT_OPTIONS = {
|
|
3727
4636
|
type: 'epistola-document',
|
|
3728
4637
|
selector: 'epistola-document-element',
|
|
@@ -3730,10 +4639,60 @@ const EPISTOLA_DOCUMENT_OPTIONS = {
|
|
|
3730
4639
|
group: 'basic',
|
|
3731
4640
|
icon: 'file-pdf-o',
|
|
3732
4641
|
emptyValue: null,
|
|
3733
|
-
|
|
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'],
|
|
3734
4647
|
// Embed the hidden task-id carrier so dropping this component is enough — no separate
|
|
3735
4648
|
// field for the author to add. Valtimo prefills it server-side via the epistola: resolver.
|
|
3736
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
|
+
}),
|
|
3737
4696
|
};
|
|
3738
4697
|
function registerEpistolaDocumentComponent(injector) {
|
|
3739
4698
|
if (customElements.get(EPISTOLA_DOCUMENT_OPTIONS.selector)) {
|
|
@@ -3763,6 +4722,23 @@ function registerEpistolaDocumentComponent(injector) {
|
|
|
3763
4722
|
Formio.Components.setComponent(EPISTOLA_DOCUMENT_OPTIONS.type, EpistolaDocumentWithTaskContext);
|
|
3764
4723
|
}
|
|
3765
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
|
+
*/
|
|
3766
4742
|
/**
|
|
3767
4743
|
* Hides a registered custom Formio component from the builder's component palette,
|
|
3768
4744
|
* while keeping it fully usable inside other components' `editForm`s and at runtime.
|
|
@@ -3786,6 +4762,23 @@ function hideFormioComponentFromBuilder(type) {
|
|
|
3786
4762
|
}
|
|
3787
4763
|
}
|
|
3788
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
|
+
*/
|
|
3789
4782
|
const EPISTOLA_RETRY_FORM_OPTIONS = {
|
|
3790
4783
|
type: 'epistola-retry-form',
|
|
3791
4784
|
selector: 'epistola-retry-form-element',
|
|
@@ -3829,6 +4822,25 @@ function registerEpistolaRetryFormComponent(injector) {
|
|
|
3829
4822
|
hideFormioComponentFromBuilder(EPISTOLA_RETRY_FORM_OPTIONS.type);
|
|
3830
4823
|
}
|
|
3831
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;
|
|
3832
4844
|
const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
|
|
3833
4845
|
type: 'epistola-document-preview',
|
|
3834
4846
|
selector: 'epistola-document-preview-element',
|
|
@@ -3855,6 +4867,23 @@ const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
|
|
|
3855
4867
|
label: 'Input Overrides',
|
|
3856
4868
|
weight: 20,
|
|
3857
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
|
+
},
|
|
3858
4887
|
],
|
|
3859
4888
|
}),
|
|
3860
4889
|
};
|
|
@@ -3876,7 +4905,25 @@ function registerEpistolaDocumentPreviewComponent(injector) {
|
|
|
3876
4905
|
_debounceTimer = null;
|
|
3877
4906
|
_changeListenerAttached = false;
|
|
3878
4907
|
_changeHandler = null;
|
|
4908
|
+
_blurHandler = null;
|
|
4909
|
+
_blurTarget = null;
|
|
3879
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;
|
|
3880
4927
|
attach(element) {
|
|
3881
4928
|
// Formio detaches and re-attaches components on every redraw — not only at
|
|
3882
4929
|
// teardown — so a re-attach means the component is alive again. Clear the
|
|
@@ -3884,6 +4931,12 @@ function registerEpistolaDocumentPreviewComponent(injector) {
|
|
|
3884
4931
|
// (genuine teardown, e.g. task completion), which is what suppresses the
|
|
3885
4932
|
// post-submit preview.
|
|
3886
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
|
+
}
|
|
3887
4940
|
// Bidirectional sync between processLinkSelection object and separate properties.
|
|
3888
4941
|
// The editForm uses processLinkSelection (single field), while the component
|
|
3889
4942
|
// config and Angular inputs use processDefinitionKey + sourceActivityId.
|
|
@@ -3909,15 +4962,65 @@ function registerEpistolaDocumentPreviewComponent(injector) {
|
|
|
3909
4962
|
if (prefilledTaskId) {
|
|
3910
4963
|
this._customAngularElement['taskInstanceId'] = prefilledTaskId;
|
|
3911
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
|
+
}
|
|
3912
4981
|
}
|
|
3913
|
-
//
|
|
4982
|
+
// Compute input overrides from the mapping and wire up the live listeners.
|
|
3914
4983
|
if (this.root && this.component?.overrideMapping && !this._changeListenerAttached) {
|
|
3915
4984
|
this._changeListenerAttached = true;
|
|
3916
|
-
this.
|
|
3917
|
-
this.root.on('change', this._changeHandler);
|
|
4985
|
+
this._debounceMs = this._resolveDebounceMs();
|
|
3918
4986
|
// Compute the initial overrides immediately (no debounce) so a pre-filled
|
|
3919
|
-
// form paints its preview without the
|
|
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.
|
|
3920
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
|
+
}
|
|
3921
5024
|
}
|
|
3922
5025
|
return result;
|
|
3923
5026
|
}
|
|
@@ -3931,79 +5034,358 @@ function registerEpistolaDocumentPreviewComponent(injector) {
|
|
|
3931
5034
|
clearTimeout(this._debounceTimer);
|
|
3932
5035
|
this._debounceTimer = null;
|
|
3933
5036
|
}
|
|
5037
|
+
this._initialPaintTimers.forEach((t) => clearTimeout(t));
|
|
5038
|
+
this._initialPaintTimers = [];
|
|
5039
|
+
this._hasUsableValue = false;
|
|
3934
5040
|
if (this._changeHandler && this.root?.off) {
|
|
3935
5041
|
this.root.off('change', this._changeHandler);
|
|
3936
5042
|
this._changeHandler = null;
|
|
3937
5043
|
}
|
|
5044
|
+
if (this._blurHandler && this._blurTarget?.removeEventListener) {
|
|
5045
|
+
this._blurTarget.removeEventListener('focusout', this._blurHandler);
|
|
5046
|
+
this._blurHandler = null;
|
|
5047
|
+
this._blurTarget = null;
|
|
5048
|
+
}
|
|
3938
5049
|
this._changeListenerAttached = false;
|
|
5050
|
+
this._lastPushedJson = undefined;
|
|
3939
5051
|
return super.detach();
|
|
3940
5052
|
}
|
|
3941
5053
|
_computeAndSetOverrides(immediate = false) {
|
|
3942
5054
|
if (this._debounceTimer) {
|
|
3943
5055
|
clearTimeout(this._debounceTimer);
|
|
3944
5056
|
}
|
|
3945
|
-
this._debounceTimer = setTimeout(() =>
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
}
|
|
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;
|
|
3961
5114
|
}
|
|
3962
5115
|
}
|
|
3963
5116
|
// Re-register with the extended class
|
|
3964
5117
|
Formio.Components.setComponent(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS.type, PreviewWithOverrides);
|
|
3965
5118
|
}
|
|
3966
5119
|
|
|
3967
|
-
|
|
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
|
+
*/
|
|
3968
5270
|
class EpistolaOverrideBuilderComponent {
|
|
3969
5271
|
cdr;
|
|
5272
|
+
pluginService;
|
|
3970
5273
|
value;
|
|
3971
5274
|
valueChange = new EventEmitter();
|
|
3972
5275
|
disabled = false;
|
|
3973
5276
|
label = 'Input Overrides';
|
|
3974
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 = '';
|
|
3975
5285
|
rows = [];
|
|
3976
5286
|
advancedMode = false;
|
|
3977
|
-
|
|
3978
|
-
|
|
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 } }';
|
|
3979
5293
|
initialized = false;
|
|
3980
|
-
|
|
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) {
|
|
3981
5298
|
this.cdr = cdr;
|
|
5299
|
+
this.pluginService = pluginService;
|
|
5300
|
+
}
|
|
5301
|
+
get formFieldKeys() {
|
|
5302
|
+
return this.availableFields.map((f) => f.key);
|
|
5303
|
+
}
|
|
5304
|
+
get hasReferencedPaths() {
|
|
5305
|
+
return this.referencedPaths.length > 0;
|
|
3982
5306
|
}
|
|
3983
|
-
|
|
3984
|
-
|
|
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) {
|
|
3985
5326
|
this.initialized = true;
|
|
3986
|
-
|
|
3987
|
-
|
|
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);
|
|
3988
5338
|
}
|
|
5339
|
+
this.refreshReferencedPaths();
|
|
3989
5340
|
this.cdr.markForCheck();
|
|
3990
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
|
+
}
|
|
3991
5375
|
toggleMode() {
|
|
3992
|
-
this.advancedMode = !this.advancedMode;
|
|
3993
5376
|
if (this.advancedMode) {
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
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;
|
|
3997
5386
|
}
|
|
3998
5387
|
else {
|
|
3999
|
-
|
|
4000
|
-
const parsed = this.jsonText.trim() ? JSON.parse(this.jsonText) : {};
|
|
4001
|
-
this.rows = this.mappingToRows(parsed);
|
|
4002
|
-
this.jsonError = null;
|
|
4003
|
-
}
|
|
4004
|
-
catch {
|
|
4005
|
-
// Keep current rows if JSON is invalid
|
|
4006
|
-
}
|
|
5388
|
+
this.advancedMode = true;
|
|
4007
5389
|
}
|
|
4008
5390
|
}
|
|
4009
5391
|
addRow() {
|
|
@@ -4013,65 +5395,105 @@ class EpistolaOverrideBuilderComponent {
|
|
|
4013
5395
|
this.rows.splice(index, 1);
|
|
4014
5396
|
this.emitChange();
|
|
4015
5397
|
}
|
|
5398
|
+
/** Simple-table change: serialize rows back to a JSONata expression. */
|
|
4016
5399
|
emitChange() {
|
|
4017
|
-
|
|
4018
|
-
this.
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
this.
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
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 = [];
|
|
4028
5416
|
}
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
this.
|
|
4032
|
-
this.value = parsed;
|
|
4033
|
-
this.valueChange.emit(parsed);
|
|
4034
|
-
}
|
|
4035
|
-
catch (e) {
|
|
4036
|
-
this.jsonError = 'Invalid JSON';
|
|
5417
|
+
else {
|
|
5418
|
+
this.simpleUnavailable = false;
|
|
5419
|
+
this.rows = parsed;
|
|
4037
5420
|
}
|
|
4038
5421
|
}
|
|
4039
|
-
|
|
4040
|
-
const
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
if (!mapping[row.scope]) {
|
|
4044
|
-
mapping[row.scope] = {};
|
|
4045
|
-
}
|
|
4046
|
-
mapping[row.scope][row.inputPath] = FORM_REF_PREFIX + row.formFieldKey;
|
|
4047
|
-
}
|
|
4048
|
-
}
|
|
4049
|
-
return mapping;
|
|
4050
|
-
}
|
|
4051
|
-
mappingToRows(mapping) {
|
|
4052
|
-
const rows = [];
|
|
4053
|
-
for (const [scope, fields] of Object.entries(mapping)) {
|
|
4054
|
-
if (scope === 'doc' || scope === 'pv') {
|
|
4055
|
-
for (const [path, ref] of Object.entries(fields)) {
|
|
4056
|
-
const formFieldKey = String(ref).startsWith(FORM_REF_PREFIX)
|
|
4057
|
-
? String(ref).substring(FORM_REF_PREFIX.length)
|
|
4058
|
-
: String(ref);
|
|
4059
|
-
rows.push({ scope, inputPath: path, formFieldKey });
|
|
4060
|
-
}
|
|
4061
|
-
}
|
|
4062
|
-
}
|
|
4063
|
-
return rows;
|
|
5422
|
+
emit(expression) {
|
|
5423
|
+
const next = expression && expression.trim() ? expression : null;
|
|
5424
|
+
this.value = next;
|
|
5425
|
+
this.valueChange.emit(next);
|
|
4064
5426
|
}
|
|
4065
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaOverrideBuilderComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
4066
|
-
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: `
|
|
4067
5429
|
<div class="override-builder">
|
|
4068
5430
|
<div class="builder-header">
|
|
4069
5431
|
<span class="builder-label">{{ label || 'Input Overrides' }}</span>
|
|
4070
|
-
<button
|
|
5432
|
+
<button
|
|
5433
|
+
type="button"
|
|
5434
|
+
class="mode-toggle"
|
|
5435
|
+
[disabled]="simpleUnavailable && !advancedMode"
|
|
5436
|
+
(click)="toggleMode()"
|
|
5437
|
+
>
|
|
4071
5438
|
{{ advancedMode ? 'Simple' : 'Advanced' }}
|
|
4072
5439
|
</button>
|
|
4073
5440
|
</div>
|
|
4074
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
|
+
|
|
4075
5497
|
<!-- Simple mode: table -->
|
|
4076
5498
|
<div *ngIf="!advancedMode" class="builder-table">
|
|
4077
5499
|
<div *ngIf="rows.length > 0" class="table-header">
|
|
@@ -4090,6 +5512,7 @@ class EpistolaOverrideBuilderComponent {
|
|
|
4090
5512
|
type="text"
|
|
4091
5513
|
[(ngModel)]="row.inputPath"
|
|
4092
5514
|
(ngModelChange)="emitChange()"
|
|
5515
|
+
[attr.list]="'epistola-override-paths-' + row.scope"
|
|
4093
5516
|
placeholder="e.g. beslissing.tekst"
|
|
4094
5517
|
/>
|
|
4095
5518
|
<!-- Dropdown when form fields are available, text input as fallback -->
|
|
@@ -4121,31 +5544,96 @@ class EpistolaOverrideBuilderComponent {
|
|
|
4121
5544
|
</button>
|
|
4122
5545
|
</div>
|
|
4123
5546
|
|
|
4124
|
-
<!-- Advanced mode:
|
|
5547
|
+
<!-- Advanced mode: JSONata editor over $form -->
|
|
4125
5548
|
<div *ngIf="advancedMode" class="builder-advanced">
|
|
4126
|
-
<
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
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>
|
|
4134
5562
|
</div>
|
|
4135
5563
|
</div>
|
|
4136
|
-
`, 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 });
|
|
4137
5565
|
}
|
|
4138
5566
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaOverrideBuilderComponent, decorators: [{
|
|
4139
5567
|
type: Component,
|
|
4140
|
-
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: `
|
|
4141
5569
|
<div class="override-builder">
|
|
4142
5570
|
<div class="builder-header">
|
|
4143
5571
|
<span class="builder-label">{{ label || 'Input Overrides' }}</span>
|
|
4144
|
-
<button
|
|
5572
|
+
<button
|
|
5573
|
+
type="button"
|
|
5574
|
+
class="mode-toggle"
|
|
5575
|
+
[disabled]="simpleUnavailable && !advancedMode"
|
|
5576
|
+
(click)="toggleMode()"
|
|
5577
|
+
>
|
|
4145
5578
|
{{ advancedMode ? 'Simple' : 'Advanced' }}
|
|
4146
5579
|
</button>
|
|
4147
5580
|
</div>
|
|
4148
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
|
+
|
|
4149
5637
|
<!-- Simple mode: table -->
|
|
4150
5638
|
<div *ngIf="!advancedMode" class="builder-table">
|
|
4151
5639
|
<div *ngIf="rows.length > 0" class="table-header">
|
|
@@ -4164,6 +5652,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
4164
5652
|
type="text"
|
|
4165
5653
|
[(ngModel)]="row.inputPath"
|
|
4166
5654
|
(ngModelChange)="emitChange()"
|
|
5655
|
+
[attr.list]="'epistola-override-paths-' + row.scope"
|
|
4167
5656
|
placeholder="e.g. beslissing.tekst"
|
|
4168
5657
|
/>
|
|
4169
5658
|
<!-- Dropdown when form fields are available, text input as fallback -->
|
|
@@ -4195,20 +5684,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
4195
5684
|
</button>
|
|
4196
5685
|
</div>
|
|
4197
5686
|
|
|
4198
|
-
<!-- Advanced mode:
|
|
5687
|
+
<!-- Advanced mode: JSONata editor over $form -->
|
|
4199
5688
|
<div *ngIf="advancedMode" class="builder-advanced">
|
|
4200
|
-
<
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
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>
|
|
4208
5702
|
</div>
|
|
4209
5703
|
</div>
|
|
4210
|
-
`, 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}.
|
|
4211
|
-
}], 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: [{
|
|
4212
5706
|
type: Input
|
|
4213
5707
|
}], valueChange: [{
|
|
4214
5708
|
type: Output
|
|
@@ -4218,8 +5712,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
4218
5712
|
type: Input
|
|
4219
5713
|
}], availableFields: [{
|
|
4220
5714
|
type: Input
|
|
5715
|
+
}], processDefinitionKey: [{
|
|
5716
|
+
type: Input
|
|
5717
|
+
}], sourceActivityId: [{
|
|
5718
|
+
type: Input
|
|
4221
5719
|
}] } });
|
|
4222
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
|
+
*/
|
|
4223
5738
|
const EPISTOLA_OVERRIDE_BUILDER_OPTIONS = {
|
|
4224
5739
|
type: 'epistola-override-builder',
|
|
4225
5740
|
selector: 'epistola-override-builder-element',
|
|
@@ -4227,7 +5742,7 @@ const EPISTOLA_OVERRIDE_BUILDER_OPTIONS = {
|
|
|
4227
5742
|
group: 'basic',
|
|
4228
5743
|
icon: 'list',
|
|
4229
5744
|
emptyValue: null,
|
|
4230
|
-
fieldOptions: ['label', 'availableFields'],
|
|
5745
|
+
fieldOptions: ['label', 'availableFields', 'processDefinitionKey', 'sourceActivityId'],
|
|
4231
5746
|
};
|
|
4232
5747
|
/**
|
|
4233
5748
|
* Recursively collect input field keys and labels from a Formio component tree.
|
|
@@ -4265,12 +5780,45 @@ function registerEpistolaOverrideBuilderComponent(injector) {
|
|
|
4265
5780
|
const BaseComponent = Formio.Components.components[EPISTOLA_OVERRIDE_BUILDER_OPTIONS.type];
|
|
4266
5781
|
if (!BaseComponent)
|
|
4267
5782
|
return;
|
|
4268
|
-
// 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.
|
|
4269
5785
|
class OverrideBuilderWithFields extends BaseComponent {
|
|
5786
|
+
_selectionChangeHandler = null;
|
|
4270
5787
|
attach(element) {
|
|
4271
|
-
// Set
|
|
5788
|
+
// Set inputs on the component BEFORE super.attach() reads fieldOptions.
|
|
4272
5789
|
this.component.availableFields = this._extractFormFields();
|
|
4273
|
-
|
|
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 || '';
|
|
4274
5822
|
}
|
|
4275
5823
|
_extractFormFields() {
|
|
4276
5824
|
// The Formio builder passes the main form schema as options.editForm
|
|
@@ -4288,6 +5836,23 @@ function registerEpistolaOverrideBuilderComponent(injector) {
|
|
|
4288
5836
|
hideFormioComponentFromBuilder(EPISTOLA_OVERRIDE_BUILDER_OPTIONS.type);
|
|
4289
5837
|
}
|
|
4290
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
|
+
*/
|
|
4291
5856
|
/**
|
|
4292
5857
|
* The plugin action definition key the backend serializes for generate-document
|
|
4293
5858
|
* process links. It carries the `epistola-` prefix — see `EPISTOLA_ACTION_KEYS`
|
|
@@ -4300,6 +5865,23 @@ function filterGenerateDocumentEntries(entries) {
|
|
|
4300
5865
|
return entries.filter((e) => e.actionKey === GENERATE_DOCUMENT_ACTION_KEY);
|
|
4301
5866
|
}
|
|
4302
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
|
+
*/
|
|
4303
5885
|
class EpistolaProcessLinkSelectorComponent {
|
|
4304
5886
|
adminService;
|
|
4305
5887
|
cdr;
|
|
@@ -4382,7 +5964,7 @@ class EpistolaProcessLinkSelectorComponent {
|
|
|
4382
5964
|
</select>
|
|
4383
5965
|
<div *ngIf="error" class="selector-error">{{ error }}</div>
|
|
4384
5966
|
</div>
|
|
4385
|
-
`, 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 });
|
|
4386
5968
|
}
|
|
4387
5969
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaProcessLinkSelectorComponent, decorators: [{
|
|
4388
5970
|
type: Component,
|
|
@@ -4413,6 +5995,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
4413
5995
|
type: Input
|
|
4414
5996
|
}] } });
|
|
4415
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
|
+
*/
|
|
4416
6015
|
const EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS = {
|
|
4417
6016
|
type: 'epistola-process-link-selector',
|
|
4418
6017
|
selector: 'epistola-process-link-selector-element',
|
|
@@ -4430,6 +6029,23 @@ function registerEpistolaProcessLinkSelectorComponent(injector) {
|
|
|
4430
6029
|
}
|
|
4431
6030
|
}
|
|
4432
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
|
+
*/
|
|
4433
6049
|
class EpistolaPluginModule {
|
|
4434
6050
|
// Kept for back-compat with hosts that follow the README's `forRoot()`
|
|
4435
6051
|
// setup. The providers above are now module-level so `imports: [EpistolaPluginModule]`
|
|
@@ -4550,8 +6166,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
4550
6166
|
}]
|
|
4551
6167
|
}] });
|
|
4552
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
|
+
*/
|
|
4553
6186
|
const EPISTOLA_PLUGIN_LOGO_BASE64 = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAgMTIwIiB3aWR0aD0iMTIwIiBoZWlnaHQ9IjEyMCI+CiAgPCEtLSBTdGFjayBiYXNlIC0tPgogIDxyZWN0IHg9IjM2IiB5PSIxNiIgd2lkdGg9IjU0IiBoZWlnaHQ9IjcwIiByeD0iMyIgZmlsbD0iI2U2YzJiMCIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIiIHRyYW5zZm9ybT0icm90YXRlKDUgNjMgNTEpIi8+CiAgPHJlY3QgeD0iMzIiIHk9IjIyIiB3aWR0aD0iNTQiIGhlaWdodD0iNzAiIHJ4PSIzIiBmaWxsPSIjZjBkOGM4IiBzdHJva2U9IiM0ZjJmMmIiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxyZWN0IHg9IjI4IiB5PSIyOCIgd2lkdGg9IjU0IiBoZWlnaHQ9IjcwIiByeD0iMyIgZmlsbD0iI2Y1ZWJlMyIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIuNSIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjQ0IiB4Mj0iNzIiIHkyPSI0NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjU0IiB4Mj0iNzIiIHkyPSI1NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjY0IiB4Mj0iNTgiIHkyPSI2NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDwhLS0gV2F4IHNlYWwgd2l0aCBjaGVja21hcmsgLS0+CiAgPGNpcmNsZSBjeD0iNTUiIGN5PSI4NCIgcj0iMTUiIGZpbGw9IiNiODVjM2MiIHN0cm9rZT0iIzRmMmYyYiIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPGNpcmNsZSBjeD0iNTUiIGN5PSI4NCIgcj0iMTAuNSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDQ4MzZhIiBzdHJva2Utd2lkdGg9IjEiLz4KICA8cG9seWxpbmUgcG9pbnRzPSI0OSw4NCA1Myw4OSA2Miw3OCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo=';
|
|
4554
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
|
+
*/
|
|
4555
6223
|
const EPISTOLA_PLUGIN_ID = 'epistola';
|
|
4556
6224
|
const DISABLED_EPISTOLA_PLUGIN_ID = '__epistola_disabled__';
|
|
4557
6225
|
const epistolaPluginSpecification = {
|
|
@@ -4707,6 +6375,10 @@ const epistolaPluginSpecification = {
|
|
|
4707
6375
|
epistolaAdminReconcile: 'Hersynchroniseer',
|
|
4708
6376
|
epistolaAdminReconciling: 'Bezig...',
|
|
4709
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',
|
|
4710
6382
|
epistolaAdminConfigurations: 'Configuraties',
|
|
4711
6383
|
epistolaAdminValidations: 'BPMN-validatie',
|
|
4712
6384
|
epistolaAdminNoValidations: 'Geen race-onveilige procesdefinities gevonden. Alles ziet er goed uit.',
|
|
@@ -4745,6 +6417,11 @@ const epistolaPluginSpecification = {
|
|
|
4745
6417
|
epistolaAdminRepairing: 'Bezig...',
|
|
4746
6418
|
epistolaAdminRepairTooltip: 'Voeg het verborgen task-id veld toe aan alle Epistola-componenten in dit formulier.',
|
|
4747
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.',
|
|
4748
6425
|
},
|
|
4749
6426
|
en: {
|
|
4750
6427
|
title: 'Epistola Document Suite',
|
|
@@ -4883,6 +6560,10 @@ const epistolaPluginSpecification = {
|
|
|
4883
6560
|
epistolaAdminReconcile: 'Reconcile',
|
|
4884
6561
|
epistolaAdminReconciling: 'Reconciling...',
|
|
4885
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',
|
|
4886
6567
|
epistolaAdminConfigurations: 'Configurations',
|
|
4887
6568
|
epistolaAdminValidations: 'BPMN validation',
|
|
4888
6569
|
epistolaAdminNoValidations: 'No race-unsafe process definitions detected. Everything looks good.',
|
|
@@ -4921,10 +6602,32 @@ const epistolaPluginSpecification = {
|
|
|
4921
6602
|
epistolaAdminRepairing: 'Working...',
|
|
4922
6603
|
epistolaAdminRepairTooltip: 'Add the hidden task-id field to all Epistola components in this form.',
|
|
4923
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.',
|
|
4924
6610
|
},
|
|
4925
6611
|
},
|
|
4926
6612
|
};
|
|
4927
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
|
+
*/
|
|
4928
6631
|
/*
|
|
4929
6632
|
* Public API Surface of epistola plugin
|
|
4930
6633
|
*/
|