@epistola.app/valtimo-plugin 0.6.0 → 0.8.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 +1514 -456
- package/fesm2022/epistola.app-valtimo-plugin.mjs.map +1 -1
- package/lib/assets/epistola-logo.d.ts +1 -1
- package/lib/components/check-job-status-configuration/check-job-status-configuration.component.d.ts +4 -1
- package/lib/components/download-document-configuration/download-document-configuration.component.d.ts +8 -1
- package/lib/components/epistola-admin-page/epistola-admin-page.component.d.ts +17 -1
- package/lib/components/epistola-document/epistola-document.component.d.ts +65 -0
- package/lib/components/{epistola-download/epistola-download.formio.d.ts → epistola-document/epistola-document.formio.d.ts} +2 -2
- package/lib/components/epistola-document-preview/epistola-document-preview.component.d.ts +29 -18
- package/lib/components/epistola-document-preview/preview-utils.d.ts +18 -0
- package/lib/components/epistola-retry-form/epistola-retry-form.component.d.ts +3 -7
- package/lib/components/generate-document-configuration/generate-document-configuration.component.d.ts +20 -1
- package/lib/components/override-builder/override-builder.component.d.ts +42 -0
- package/lib/components/override-builder/override-builder.formio.d.ts +4 -0
- package/lib/components/process-link-selector/process-link-selector.component.d.ts +31 -0
- package/lib/components/process-link-selector/process-link-selector.formio.d.ts +4 -0
- package/lib/epistola-enabled.guard.d.ts +2 -0
- package/lib/epistola-runtime-config.d.ts +28 -0
- package/lib/epistola.module.d.ts +4 -5
- package/lib/models/admin.d.ts +28 -0
- package/lib/models/config.d.ts +27 -12
- package/lib/services/epistola-admin.service.d.ts +14 -1
- package/lib/services/epistola-plugin.service.d.ts +51 -7
- package/lib/services/epistola-task-context.interceptor.d.ts +29 -0
- package/lib/services/epistola-task-context.matcher.d.ts +19 -0
- package/lib/services/epistola-task-context.service.d.ts +26 -0
- package/lib/services/index.d.ts +2 -0
- package/package.json +4 -2
- package/public_api.d.ts +7 -4
- package/sbom.json +1 -0
- package/lib/components/epistola-download/epistola-download.component.d.ts +0 -24
- package/lib/components/epistola-preview-button/epistola-preview-button.component.d.ts +0 -35
- package/lib/components/epistola-preview-button/epistola-preview-button.formio.d.ts +0 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, EventEmitter, Output, Input, Component, ChangeDetectionStrategy, NgModule, ENVIRONMENT_INITIALIZER,
|
|
2
|
+
import { Injectable, EventEmitter, Output, Input, Component, ChangeDetectionStrategy, inject, NgModule, ENVIRONMENT_INITIALIZER, Injector } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common/http';
|
|
4
|
-
import { HttpHeaders, HttpClientModule } from '@angular/common/http';
|
|
4
|
+
import { HttpHeaders, HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
|
5
5
|
import * as i2 from '@valtimo/shared';
|
|
6
6
|
import { ROLE_ADMIN } from '@valtimo/shared';
|
|
7
7
|
import { of, BehaviorSubject, combineLatest, take, Subject, debounceTime, takeUntil, merge } from 'rxjs';
|
|
@@ -12,16 +12,16 @@ 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 i2$2 from '@angular/forms';
|
|
16
16
|
import { FormsModule } from '@angular/forms';
|
|
17
17
|
import * as _jsonata from 'jsonata';
|
|
18
|
-
import * as i2$
|
|
19
|
-
import * as
|
|
18
|
+
import * as i2$3 from '@valtimo/process-link';
|
|
19
|
+
import * as i2$4 from '@angular/platform-browser';
|
|
20
|
+
import * as i5 from '@formio/angular';
|
|
20
21
|
import { FormioModule } from '@formio/angular';
|
|
21
|
-
import * as
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import * as i5 from 'carbon-components-angular/tabs';
|
|
22
|
+
import * as i2$5 from '@angular/router';
|
|
23
|
+
import { RouterModule, Router } from '@angular/router';
|
|
24
|
+
import * as i5$1 from 'carbon-components-angular/tabs';
|
|
25
25
|
import { TabsModule } from 'carbon-components-angular/tabs';
|
|
26
26
|
import * as i6 from 'carbon-components-angular/tag';
|
|
27
27
|
import { TagModule } from 'carbon-components-angular/tag';
|
|
@@ -77,6 +77,23 @@ class EpistolaAdminService {
|
|
|
77
77
|
getPendingJobs() {
|
|
78
78
|
return this.http.get(`${this.apiEndpoint}/pending`);
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Manually reconcile a stuck Epistola catch event by querying Epistola for the
|
|
82
|
+
* job's current status and re-running message correlation. Returns 200 with
|
|
83
|
+
* `correlated=true` on success, 409 with `correlated=false` when the job is
|
|
84
|
+
* still in flight, or surfaces a validation error when the execution can't be
|
|
85
|
+
* recovered (no subscription, missing variable, unknown tenant).
|
|
86
|
+
*/
|
|
87
|
+
reconcilePending(executionId) {
|
|
88
|
+
return this.http.post(`${this.apiEndpoint}/pending/${encodeURIComponent(executionId)}/reconcile`, null);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the latest BPMN race-safety validation violations across deployed
|
|
92
|
+
* process definitions. Empty list = healthy.
|
|
93
|
+
*/
|
|
94
|
+
getValidationViolations() {
|
|
95
|
+
return this.http.get(`${this.apiEndpoint}/validations`);
|
|
96
|
+
}
|
|
80
97
|
/**
|
|
81
98
|
* Export a single process link as a .process-link.json file.
|
|
82
99
|
*/
|
|
@@ -220,9 +237,11 @@ class EpistolaPluginService {
|
|
|
220
237
|
}
|
|
221
238
|
/**
|
|
222
239
|
* Get a dynamically generated Formio form for retrying a failed document generation.
|
|
240
|
+
*
|
|
241
|
+
* @param taskId Operaton user task id (required — backend authorizes via OperatonTask:VIEW)
|
|
223
242
|
*/
|
|
224
|
-
getRetryForm(processInstanceId, documentId, sourceActivityId) {
|
|
225
|
-
const params = { processInstanceId };
|
|
243
|
+
getRetryForm(taskId, processInstanceId, documentId, sourceActivityId) {
|
|
244
|
+
const params = { taskId, processInstanceId };
|
|
226
245
|
if (documentId) {
|
|
227
246
|
params['documentId'] = documentId;
|
|
228
247
|
}
|
|
@@ -238,24 +257,43 @@ class EpistolaPluginService {
|
|
|
238
257
|
return this.http.get(`${this.apiEndpoint}/expression-functions`);
|
|
239
258
|
}
|
|
240
259
|
/**
|
|
241
|
-
*
|
|
260
|
+
* Validate the JSONata syntax of action-config expressions before save.
|
|
261
|
+
* Parse-only; runtime errors (missing variables, type mismatches) are not detected.
|
|
262
|
+
*/
|
|
263
|
+
validateJsonata(request) {
|
|
264
|
+
return this.http.post(`${this.apiEndpoint}/validate-jsonata`, request);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Preview a document by dry-running the {@code generate-document} process
|
|
268
|
+
* link. Returns the rendered PDF as a {@link Blob}.
|
|
269
|
+
*
|
|
270
|
+
* <p>The {@code X-Skip-Interceptor: 422} header tells the global Valtimo
|
|
271
|
+
* error interceptor to skip its toast for validation failures so the
|
|
272
|
+
* caller can render an inline error message.
|
|
242
273
|
*/
|
|
243
|
-
|
|
244
|
-
return this.http.
|
|
245
|
-
|
|
274
|
+
previewToBlob(request) {
|
|
275
|
+
return this.http.post(`${this.apiEndpoint}/preview`, request, {
|
|
276
|
+
responseType: 'blob',
|
|
277
|
+
headers: new HttpHeaders().set('X-Skip-Interceptor', '422'),
|
|
246
278
|
});
|
|
247
279
|
}
|
|
248
280
|
/**
|
|
249
|
-
*
|
|
250
|
-
*
|
|
281
|
+
* Stream an already-generated Epistola PDF for the caller's task. The
|
|
282
|
+
* backend resolves the Epistola document id and tenant id from the named
|
|
283
|
+
* process variables on the task's process instance, so the wire never
|
|
284
|
+
* carries a raw PDF id.
|
|
251
285
|
*/
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
286
|
+
downloadDocumentBlob(request) {
|
|
287
|
+
const params = new URLSearchParams({
|
|
288
|
+
taskId: request.taskId,
|
|
289
|
+
caseDocumentId: request.caseDocumentId,
|
|
290
|
+
documentVariable: request.documentVariable,
|
|
291
|
+
tenantIdVariable: request.tenantIdVariable,
|
|
292
|
+
filename: request.filename,
|
|
293
|
+
disposition: request.disposition,
|
|
294
|
+
});
|
|
295
|
+
return this.http.get(`${this.apiEndpoint}/documents/download?${params.toString()}`, {
|
|
296
|
+
responseType: 'blob',
|
|
259
297
|
});
|
|
260
298
|
}
|
|
261
299
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginService, deps: [{ token: i1.HttpClient }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -265,6 +303,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
265
303
|
type: Injectable
|
|
266
304
|
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.ConfigService }] });
|
|
267
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Holds the currently-open Operaton user task instance id so that custom
|
|
308
|
+
* Formio components rendered inside a Valtimo task form (preview, download,
|
|
309
|
+
* retry-form) can include it in backend requests for PBAC.
|
|
310
|
+
*
|
|
311
|
+
* Populated by {@link EpistolaTaskContextInterceptor}, which sniffs Valtimo's
|
|
312
|
+
* canonical "load process link for task" GET (`/api/v2/process-link/task/{taskId}`)
|
|
313
|
+
* — the request always fires when a task opens, before the form renders.
|
|
314
|
+
*
|
|
315
|
+
* <p><b>Why this exists:</b> Valtimo 13.21 does not expose the active task
|
|
316
|
+
* instance id through any service that custom Formio components can inject
|
|
317
|
+
* (`FormIoStateService` carries documentId and processInstanceId only;
|
|
318
|
+
* `TaskDetailContentComponent.taskInstanceId$` is private to that component
|
|
319
|
+
* and Formio elements live in their own injector tree). This service is a
|
|
320
|
+
* workaround until upstream exposes `taskInstanceId` via `FormIoStateService`.
|
|
321
|
+
*/
|
|
322
|
+
class EpistolaTaskContextService {
|
|
323
|
+
_taskInstanceId$ = new BehaviorSubject(null);
|
|
324
|
+
taskInstanceId$ = this._taskInstanceId$.asObservable();
|
|
325
|
+
get taskInstanceId() {
|
|
326
|
+
return this._taskInstanceId$.value;
|
|
327
|
+
}
|
|
328
|
+
setTaskInstanceId(id) {
|
|
329
|
+
this._taskInstanceId$.next(id);
|
|
330
|
+
}
|
|
331
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
332
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, providedIn: 'root' });
|
|
333
|
+
}
|
|
334
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, decorators: [{
|
|
335
|
+
type: Injectable,
|
|
336
|
+
args: [{ providedIn: 'root' }]
|
|
337
|
+
}] });
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Pure helpers for {@link EpistolaTaskContextInterceptor}, extracted so they
|
|
341
|
+
* can be unit-tested without pulling in {@code @angular/core} (which ts-jest
|
|
342
|
+
* cannot transform without {@code jest-preset-angular}).
|
|
343
|
+
*/
|
|
344
|
+
/**
|
|
345
|
+
* Pattern Valtimo uses for the canonical task-open call:
|
|
346
|
+
* {@code GET /api/v2/process-link/task/{taskInstanceId}}.
|
|
347
|
+
*
|
|
348
|
+
* Captures the {@code taskInstanceId} (UUID v4-style 36-character hyphenated
|
|
349
|
+
* hex string). Anchored at the end of the URL or at a query-string delimiter
|
|
350
|
+
* so we don't accidentally match a longer trailing segment.
|
|
351
|
+
*/
|
|
352
|
+
const TASK_PROCESS_LINK_PATTERN = /\/api\/v2\/process-link\/task\/([0-9a-fA-F-]{36})(?:\?|$)/;
|
|
353
|
+
/**
|
|
354
|
+
* Returns the captured {@code taskInstanceId} from a Valtimo task-open
|
|
355
|
+
* request URL, or {@code null} if the request does not match.
|
|
356
|
+
*/
|
|
357
|
+
function extractTaskInstanceIdFromUrl(method, url) {
|
|
358
|
+
if (method !== 'GET')
|
|
359
|
+
return null;
|
|
360
|
+
const match = TASK_PROCESS_LINK_PATTERN.exec(url);
|
|
361
|
+
return match ? match[1] : null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Sniffs Valtimo's task-open signal and pushes the active taskInstanceId into
|
|
366
|
+
* {@link EpistolaTaskContextService}. The signal is the canonical
|
|
367
|
+
* {@code GET /api/v2/process-link/task/{taskId}} call that
|
|
368
|
+
* {@code TaskDetailContentComponent.loadTaskDetails(...)} fires unconditionally
|
|
369
|
+
* before any task form is rendered (see @valtimo/task internals).
|
|
370
|
+
*
|
|
371
|
+
* <p>This interceptor does <b>not</b> modify the outgoing request. It only
|
|
372
|
+
* captures the taskId from the URL.
|
|
373
|
+
*
|
|
374
|
+
* <p>Workaround for Valtimo 13.21 not exposing taskInstanceId through any
|
|
375
|
+
* injectable service. Remove once upstream adds e.g.
|
|
376
|
+
* {@code FormIoStateService.setTaskInstanceId(...)}.
|
|
377
|
+
*
|
|
378
|
+
* <p>The actual URL-matching logic lives in
|
|
379
|
+
* {@link extractTaskInstanceIdFromUrl} so it can be unit-tested without an
|
|
380
|
+
* Angular harness.
|
|
381
|
+
*/
|
|
382
|
+
class EpistolaTaskContextInterceptor {
|
|
383
|
+
taskContext;
|
|
384
|
+
constructor(taskContext) {
|
|
385
|
+
this.taskContext = taskContext;
|
|
386
|
+
}
|
|
387
|
+
intercept(request, next) {
|
|
388
|
+
const taskId = extractTaskInstanceIdFromUrl(request.method, request.url);
|
|
389
|
+
if (taskId !== null && taskId !== this.taskContext.taskInstanceId) {
|
|
390
|
+
this.taskContext.setTaskInstanceId(taskId);
|
|
391
|
+
}
|
|
392
|
+
return next.handle(request);
|
|
393
|
+
}
|
|
394
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextInterceptor, deps: [{ token: EpistolaTaskContextService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
395
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextInterceptor });
|
|
396
|
+
}
|
|
397
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextInterceptor, decorators: [{
|
|
398
|
+
type: Injectable
|
|
399
|
+
}], ctorParameters: () => [{ type: EpistolaTaskContextService }] });
|
|
400
|
+
|
|
268
401
|
class EpistolaConfigurationComponent {
|
|
269
402
|
save$;
|
|
270
403
|
disabled$;
|
|
@@ -796,7 +929,7 @@ class BuilderFieldComponent {
|
|
|
796
929
|
></epistola-builder-field>
|
|
797
930
|
</div>
|
|
798
931
|
</div>
|
|
799
|
-
`, isInline: true, styles: [".builder-field{margin-bottom:4px}.builder-field__name{margin-bottom:2px}.builder-field__name--clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.builder-field__name--clickable:hover{color:#0f62fe}.builder-field__chevron{font-size:.7em;margin-right:4px}.builder-field__label{font-weight:500;font-size:.9em}.builder-field__required{color:#da1e28;margin-left:2px}.builder-field__type{color:#8d8d8d;font-size:.8em;margin-left:4px}.builder-field__value{display:flex;align-items:center;gap:4px}.builder-field__input{flex:1;padding:6px 8px;border:1px solid #e0e0e0;border-radius:4px;font-size:.85em;font-family:IBM Plex Mono,monospace}.builder-field__input:focus{outline:2px solid #0f62fe;border-color:#0f62fe}.builder-field__input--raw{background:#f4f4f4}.builder-field__mode-toggle{width:28px;height:28px;border:1px solid #e0e0e0;border-radius:4px;background:#fff;cursor:pointer;font-family:monospace;font-size:.8em;display:flex;align-items:center;justify-content:center}.builder-field__mode-toggle:hover{background:#f4f4f4}.builder-field__children{border-left:2px solid #e0e0e0;padding-left:12px;margin-top:4px}\n"], dependencies: [{ kind: "component", type: BuilderFieldComponent, selector: "epistola-builder-field", inputs: ["field", "path", "suggestions", "disabled", "collapsed", "required", "collapsedPaths"], outputs: ["valueChange", "modeToggle", "collapseToggle"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type:
|
|
932
|
+
`, isInline: true, styles: [".builder-field{margin-bottom:4px}.builder-field__name{margin-bottom:2px}.builder-field__name--clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.builder-field__name--clickable:hover{color:#0f62fe}.builder-field__chevron{font-size:.7em;margin-right:4px}.builder-field__label{font-weight:500;font-size:.9em}.builder-field__required{color:#da1e28;margin-left:2px}.builder-field__type{color:#8d8d8d;font-size:.8em;margin-left:4px}.builder-field__value{display:flex;align-items:center;gap:4px}.builder-field__input{flex:1;padding:6px 8px;border:1px solid #e0e0e0;border-radius:4px;font-size:.85em;font-family:IBM Plex Mono,monospace}.builder-field__input:focus{outline:2px solid #0f62fe;border-color:#0f62fe}.builder-field__input--raw{background:#f4f4f4}.builder-field__mode-toggle{width:28px;height:28px;border:1px solid #e0e0e0;border-radius:4px;background:#fff;cursor:pointer;font-family:monospace;font-size:.8em;display:flex;align-items:center;justify-content:center}.builder-field__mode-toggle:hover{background:#f4f4f4}.builder-field__children{border-left:2px solid #e0e0e0;padding-left:12px;margin-top:4px}\n"], dependencies: [{ kind: "component", type: BuilderFieldComponent, selector: "epistola-builder-field", inputs: ["field", "path", "suggestions", "disabled", "collapsed", "required", "collapsedPaths"], outputs: ["valueChange", "modeToggle", "collapseToggle"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
800
933
|
}
|
|
801
934
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BuilderFieldComponent, decorators: [{
|
|
802
935
|
type: Component,
|
|
@@ -1379,7 +1512,7 @@ class MappingPreviewComponent {
|
|
|
1379
1512
|
<strong>{{ missingRequired.join(', ') }}</strong>
|
|
1380
1513
|
</div>
|
|
1381
1514
|
</div>
|
|
1382
|
-
`, isInline: true, styles: [".preview{border:1px solid #e0e0e0;border-radius:4px;margin-top:16px;overflow:hidden}.preview__header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#f4f4f4;border-bottom:1px solid #e0e0e0}.preview__title{font-weight:600;font-size:.85em}.preview__controls{display:flex;gap:4px}.preview__doc-input{padding:4px 8px;border:1px solid #e0e0e0;border-radius:4px;font-size:.8em;width:220px;font-family:monospace}.preview__run-btn{padding:4px 10px;border:1px solid #0f62fe;border-radius:4px;background:#0f62fe;color:#fff;cursor:pointer;font-size:.8em}.preview__run-btn:disabled{opacity:.4;cursor:not-allowed}.preview__panels{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:#e0e0e0}.preview__panel{background:#fff;padding:8px 12px;min-height:80px}.preview__panel-label{font-size:.75em;color:#6f6f6f;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}.preview__code{font-family:IBM Plex Mono,monospace;font-size:.8em;line-height:1.4;margin:0;white-space:pre-wrap;word-break:break-word}.preview__loading{color:#8d8d8d}.preview__error{color:#da1e28;font-size:.85em}.preview__placeholder{color:#8d8d8d;font-size:.85em;font-style:italic}.preview__warnings{padding:8px 12px;background:#fff8e1;border-top:1px solid #e0e0e0;font-size:.85em;color:#663c00}.preview__warning-icon{margin-right:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type:
|
|
1515
|
+
`, isInline: true, styles: [".preview{border:1px solid #e0e0e0;border-radius:4px;margin-top:16px;overflow:hidden}.preview__header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#f4f4f4;border-bottom:1px solid #e0e0e0}.preview__title{font-weight:600;font-size:.85em}.preview__controls{display:flex;gap:4px}.preview__doc-input{padding:4px 8px;border:1px solid #e0e0e0;border-radius:4px;font-size:.8em;width:220px;font-family:monospace}.preview__run-btn{padding:4px 10px;border:1px solid #0f62fe;border-radius:4px;background:#0f62fe;color:#fff;cursor:pointer;font-size:.8em}.preview__run-btn:disabled{opacity:.4;cursor:not-allowed}.preview__panels{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:#e0e0e0}.preview__panel{background:#fff;padding:8px 12px;min-height:80px}.preview__panel-label{font-size:.75em;color:#6f6f6f;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}.preview__code{font-family:IBM Plex Mono,monospace;font-size:.8em;line-height:1.4;margin:0;white-space:pre-wrap;word-break:break-word}.preview__loading{color:#8d8d8d}.preview__error{color:#da1e28;font-size:.85em}.preview__placeholder{color:#8d8d8d;font-size:.85em;font-style:italic}.preview__warnings{padding:8px 12px;background:#fff8e1;border-top:1px solid #e0e0e0;font-size:.85em;color:#663c00}.preview__warning-icon{margin-right:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }] });
|
|
1383
1516
|
}
|
|
1384
1517
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MappingPreviewComponent, decorators: [{
|
|
1385
1518
|
type: Component,
|
|
@@ -1450,6 +1583,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1450
1583
|
type: Input
|
|
1451
1584
|
}] } });
|
|
1452
1585
|
|
|
1586
|
+
const FORM_REF_PREFIX$1 = 'form:';
|
|
1587
|
+
/**
|
|
1588
|
+
* Detect if a string value is a JSONata expression (vs a plain literal).
|
|
1589
|
+
* Checks for characters that indicate JSONata operators: $, &, (, {, ?, [
|
|
1590
|
+
*/
|
|
1591
|
+
function isExpression(value) {
|
|
1592
|
+
return /[$&({?\[]/.test(value);
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Expand dot-notation keys into nested objects.
|
|
1596
|
+
* e.g. { "beslissing.tekst": "value" } -> { beslissing: { tekst: "value" } }
|
|
1597
|
+
*/
|
|
1598
|
+
function expandDotNotation(flat) {
|
|
1599
|
+
const result = {};
|
|
1600
|
+
for (const [key, value] of Object.entries(flat)) {
|
|
1601
|
+
const parts = key.split('.');
|
|
1602
|
+
let current = result;
|
|
1603
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1604
|
+
if (!current[parts[i]] || typeof current[parts[i]] !== 'object') {
|
|
1605
|
+
current[parts[i]] = {};
|
|
1606
|
+
}
|
|
1607
|
+
current = current[parts[i]];
|
|
1608
|
+
}
|
|
1609
|
+
current[parts[parts.length - 1]] = value;
|
|
1610
|
+
}
|
|
1611
|
+
return result;
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Given an override mapping (scope -> { inputPath -> "form:<componentKey>" })
|
|
1615
|
+
* and form data, produce the inputOverrides object for the backend.
|
|
1616
|
+
* The "form:" prefix identifies form field references; the remainder is the Formio component key.
|
|
1617
|
+
*/
|
|
1618
|
+
function computeInputOverrides(mapping, formData) {
|
|
1619
|
+
const result = {};
|
|
1620
|
+
for (const [scope, fields] of Object.entries(mapping)) {
|
|
1621
|
+
if (scope !== 'doc' && scope !== 'pv')
|
|
1622
|
+
continue;
|
|
1623
|
+
const flatOverrides = {};
|
|
1624
|
+
for (const [inputPath, ref] of Object.entries(fields)) {
|
|
1625
|
+
const formFieldKey = String(ref).startsWith(FORM_REF_PREFIX$1)
|
|
1626
|
+
? String(ref).substring(FORM_REF_PREFIX$1.length)
|
|
1627
|
+
: String(ref);
|
|
1628
|
+
const value = formData[formFieldKey];
|
|
1629
|
+
if (value !== undefined) {
|
|
1630
|
+
flatOverrides[inputPath] = value;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
if (Object.keys(flatOverrides).length > 0) {
|
|
1634
|
+
result[scope] = expandDotNotation(flatOverrides);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
return result;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1453
1640
|
class GenerateDocumentConfigurationComponent {
|
|
1454
1641
|
epistolaPluginService;
|
|
1455
1642
|
processLinkStateService;
|
|
@@ -1480,6 +1667,10 @@ class GenerateDocumentConfigurationComponent {
|
|
|
1480
1667
|
selectedTemplateId$ = new BehaviorSubject('');
|
|
1481
1668
|
selectedVariantId$ = new BehaviorSubject('');
|
|
1482
1669
|
variantSelectionMode = 'explicit';
|
|
1670
|
+
variantIdExpressionMode = false;
|
|
1671
|
+
variantIdExpression = '';
|
|
1672
|
+
filenameExpressionMode = false;
|
|
1673
|
+
filenameExpression = '';
|
|
1483
1674
|
variantAttributeEntries = [];
|
|
1484
1675
|
availableAttributeKeys = [];
|
|
1485
1676
|
caseDefinitionKey = null;
|
|
@@ -1488,6 +1679,7 @@ class GenerateDocumentConfigurationComponent {
|
|
|
1488
1679
|
variableSuggestions = null;
|
|
1489
1680
|
requiredFieldsStatus = { mapped: 0, total: 0 };
|
|
1490
1681
|
prefillDataMapping = {};
|
|
1682
|
+
validationErrors$ = new BehaviorSubject([]);
|
|
1491
1683
|
destroy$ = new Subject();
|
|
1492
1684
|
saveSubscription;
|
|
1493
1685
|
formValue$ = new BehaviorSubject(null);
|
|
@@ -1567,6 +1759,12 @@ class GenerateDocumentConfigurationComponent {
|
|
|
1567
1759
|
onAttributeEntryChange() {
|
|
1568
1760
|
this.revalidate();
|
|
1569
1761
|
}
|
|
1762
|
+
onVariantIdExpressionChange() {
|
|
1763
|
+
this.revalidate();
|
|
1764
|
+
}
|
|
1765
|
+
onFilenameExpressionChange() {
|
|
1766
|
+
this.revalidate();
|
|
1767
|
+
}
|
|
1570
1768
|
onKeySelected(entry, value) {
|
|
1571
1769
|
if (value === '__custom__') {
|
|
1572
1770
|
entry._customKey = true;
|
|
@@ -1717,6 +1915,7 @@ class GenerateDocumentConfigurationComponent {
|
|
|
1717
1915
|
key: e.key,
|
|
1718
1916
|
value: e.value,
|
|
1719
1917
|
required: e.required !== false,
|
|
1918
|
+
_expressionMode: isExpression(e.value),
|
|
1720
1919
|
}));
|
|
1721
1920
|
}
|
|
1722
1921
|
else {
|
|
@@ -1725,7 +1924,18 @@ class GenerateDocumentConfigurationComponent {
|
|
|
1725
1924
|
}
|
|
1726
1925
|
else if (config.variantId) {
|
|
1727
1926
|
this.variantSelectionMode = 'explicit';
|
|
1728
|
-
|
|
1927
|
+
if (isExpression(config.variantId)) {
|
|
1928
|
+
this.variantIdExpressionMode = true;
|
|
1929
|
+
this.variantIdExpression = config.variantId;
|
|
1930
|
+
}
|
|
1931
|
+
else {
|
|
1932
|
+
this.selectedVariantId$.next(config.variantId);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
// Detect expression mode for filename
|
|
1936
|
+
if (config.filename && isExpression(config.filename)) {
|
|
1937
|
+
this.filenameExpressionMode = true;
|
|
1938
|
+
this.filenameExpression = config.filename;
|
|
1729
1939
|
}
|
|
1730
1940
|
// Apply dataMapping prefill (JSONata expression string)
|
|
1731
1941
|
if (config.dataMapping) {
|
|
@@ -1796,25 +2006,66 @@ class GenerateDocumentConfigurationComponent {
|
|
|
1796
2006
|
environmentId: formValue.environmentId || undefined,
|
|
1797
2007
|
dataMapping: dataMapping,
|
|
1798
2008
|
outputFormat: formValue.outputFormat,
|
|
1799
|
-
filename: formValue.filename,
|
|
2009
|
+
filename: this.filenameExpressionMode ? this.filenameExpression : formValue.filename,
|
|
1800
2010
|
correlationId: formValue.correlationId || undefined,
|
|
1801
2011
|
resultProcessVariable: formValue.resultProcessVariable,
|
|
1802
2012
|
};
|
|
1803
2013
|
if (this.variantSelectionMode === 'explicit') {
|
|
1804
|
-
config.variantId =
|
|
2014
|
+
config.variantId = this.variantIdExpressionMode
|
|
2015
|
+
? this.variantIdExpression
|
|
2016
|
+
: formValue.variantId;
|
|
1805
2017
|
}
|
|
1806
2018
|
else {
|
|
1807
2019
|
config.variantAttributes = this.variantAttributeEntries
|
|
1808
2020
|
.filter((e) => e.key && e.value)
|
|
1809
2021
|
.map((e) => ({ key: e.key, value: e.value, required: e.required }));
|
|
1810
2022
|
}
|
|
1811
|
-
this.
|
|
2023
|
+
this.validateAndEmit(config);
|
|
1812
2024
|
}
|
|
1813
2025
|
});
|
|
1814
2026
|
});
|
|
1815
2027
|
}
|
|
1816
|
-
|
|
1817
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: GenerateDocumentConfigurationComponent, isStandalone: true, selector: "epistola-generate-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$", selectedPluginConfigurationData$: "selectedPluginConfigurationData$", context$: "context$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n catalogs: catalogs$ | async,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedCatalogId: selectedCatalogId$ | async,\n selectedTemplateId: selectedTemplateId$ | async,\n } as obs\"\n>\n <v-select\n name=\"catalogId\"\n [title]=\"'catalogId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'catalogIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.catalogs.data\"\n [defaultSelectionId]=\"obs.prefill?.catalogId\"\n [disabled]=\"obs.disabled || obs.catalogs.loading\"\n [required]=\"true\"\n [loading]=\"obs.catalogs.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.catalogs.error\" class=\"loading-error\">{{ obs.catalogs.error }}</div>\n\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading || !obs.selectedCatalogId\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{\n 'variantSelectionMode' | pluginTranslate: pluginId | async\n }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >\n {{ 'selectByVariant' | pluginTranslate: pluginId | async }}\n </button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >\n {{ 'selectByAttributes' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\n\n <!-- Attribute-based variant selection -->\n <div\n *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\"\n class=\"variant-attributes-section\"\n >\n <label class=\"variant-attributes-label\">{{\n 'variantAttributes' | pluginTranslate: pluginId | async\n }}</label>\n <div class=\"variant-attributes-list\">\n <div\n *ngFor=\"let entry of variantAttributeEntries; let i = index\"\n class=\"variant-attribute-row\"\n >\n <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>\n {{ 'attributeKey' | pluginTranslate: pluginId | async }}\n </option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">\n {{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}\n </option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"custom-key-cancel\"\n (click)=\"cancelCustomKey(entry)\"\n [disabled]=\"obs.disabled\"\n >\n ×\n </button>\n </div>\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{\n (entry.required ? 'attributeRequired' : 'attributePreferred')\n | pluginTranslate: pluginId\n | async\n }}</span>\n </label>\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >\n ×\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >\n + {{ 'addAttribute' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">\n {{ templateFieldsError }}\n</div>\n\n<div *ngIf=\"selectedTemplateId$ | async\" class=\"mapping-section\">\n <h5 class=\"mapping-section__title\">\n {{ 'dataMappingTitle' | pluginTranslate: pluginId | async }}\n </h5>\n <p class=\"mapping-section__description\">\n {{ 'dataMappingDescription' | pluginTranslate: pluginId | async }}\n </p>\n <div class=\"mapping-mode-toggle\">\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'simple'\"\n (click)=\"mappingMode = 'simple'\"\n >\n {{ 'mappingModeSimple' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'advanced'\"\n (click)=\"mappingMode = 'advanced'\"\n >\n {{ 'mappingModeAdvanced' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <!-- Editor area (full width) -->\n <epistola-mapping-builder\n *ngIf=\"mappingMode === 'simple'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [suggestions]=\"variableSuggestions\"\n [disabled]=\"!!(disabled$ | async)\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-mapping-builder>\n\n <epistola-jsonata-editor\n *ngIf=\"mappingMode === 'advanced'\"\n [expression]=\"dataMapping$ | async\"\n [disabled]=\"!!(disabled$ | async)\"\n [suggestions]=\"variableSuggestions\"\n [functions]=\"expressionFunctions\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-jsonata-editor>\n\n <!-- Bottom tabs: Schema + Preview (collapsible) -->\n <div class=\"mapping-tools\" [class.mapping-tools--collapsed]=\"toolsCollapsed\">\n <div class=\"mapping-tools__header\" (click)=\"toolsCollapsed = !toolsCollapsed\">\n <span class=\"mapping-tools__chevron\">{{ toolsCollapsed ? '▶' : '▼' }}</span>\n <span>{{ 'mappingTools' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div *ngIf=\"!toolsCollapsed\" class=\"mapping-tools__content\">\n <div class=\"mapping-tools__tabs\">\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'schema'\"\n (click)=\"activeToolTab = 'schema'\"\n >\n {{ 'expectedStructure' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'preview'\"\n (click)=\"activeToolTab = 'preview'\"\n >\n {{ 'previewTitle' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <epistola-expected-structure\n *ngIf=\"activeToolTab === 'schema'\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n ></epistola-expected-structure>\n\n <epistola-mapping-preview\n *ngIf=\"activeToolTab === 'preview'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n ></epistola-mapping-preview>\n </div>\n </div>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}.mapping-section{margin-top:1rem}.mapping-section__title{font-size:1rem;font-weight:600;margin:0 0 4px}.mapping-section__description{font-size:.85em;color:#6f6f6f;margin:0 0 12px}.mapping-mode-toggle{display:flex;gap:0;margin-bottom:12px}.mapping-mode-toggle__btn{padding:6px 16px;border:1px solid #e0e0e0;background:#fff;font-size:.85em;cursor:pointer}.mapping-mode-toggle__btn:first-child{border-radius:4px 0 0 4px}.mapping-mode-toggle__btn:last-child{border-radius:0 4px 4px 0;border-left:none}.mapping-mode-toggle__btn--active{background:#0f62fe;color:#fff;border-color:#0f62fe}.mapping-tools{margin-top:12px;border:1px solid #e0e0e0;border-radius:4px;overflow:hidden}.mapping-tools__header{display:flex;align-items:center;gap:6px;padding:8px 12px;background:#f4f4f4;cursor:pointer;font-size:.85em;font-weight:500;-webkit-user-select:none;user-select:none}.mapping-tools__header:hover{background:#e8e8e8}.mapping-tools__chevron{font-size:.7em}.mapping-tools__content{border-top:1px solid #e0e0e0}.mapping-tools__tabs{display:flex;border-bottom:1px solid #e0e0e0}.mapping-tools__tab{padding:6px 16px;border:none;background:transparent;font-size:.8em;cursor:pointer;border-bottom:2px solid transparent}.mapping-tools__tab--active{border-bottom-color:#0f62fe;font-weight:500}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.SelectComponent, selector: "v-select", inputs: ["items", "defaultSelection", "defaultSelectionId", "defaultSelectionIds", "disabled", "dropUp", "invalid", "multiple", "margin", "widthInPx", "notFoundText", "clearAllText", "clearText", "clearable", "name", "title", "titleTranslationKey", "clearSelectionSubject$", "tooltip", "required", "loading", "loadingText", "placeholder", "smallMargin", "carbonTheme", "appendInline", "warn", "warnText", "dataTestId"], outputs: ["selectedChange"] }, { kind: "component", type: ExpectedStructureComponent, selector: "epistola-expected-structure", inputs: ["templateFields"] }, { kind: "component", type: JsonataEditorComponent, selector: "epistola-jsonata-editor", inputs: ["expression", "disabled", "suggestions", "functions"], outputs: ["expressionChange", "validChange"] }, { kind: "component", type: MappingBuilderComponent, selector: "epistola-mapping-builder", inputs: ["expression", "templateFields", "suggestions", "disabled"], outputs: ["expressionChange"] }, { kind: "component", type: MappingPreviewComponent, selector: "epistola-mapping-preview", inputs: ["expression", "templateFields", "caseDefinitionKey"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2028
|
+
/**
|
|
2029
|
+
* Build a JSONata validation request from the config and call the backend.
|
|
2030
|
+
* Only fields that are JSONata expressions get validated:
|
|
2031
|
+
* - dataMapping is always JSONata
|
|
2032
|
+
* - filename / variantId only when their `fx` toggle is on
|
|
2033
|
+
* - variant attribute values only when isExpression() reports true
|
|
2034
|
+
* On invalid response, surface errors and abort the emit.
|
|
2035
|
+
* If the validator endpoint itself fails (network/server), proceed with the
|
|
2036
|
+
* emit — the validation is a quality-of-life check, not a hard gate.
|
|
2037
|
+
*/
|
|
2038
|
+
validateAndEmit(config) {
|
|
2039
|
+
const variantAttributeValues = {};
|
|
2040
|
+
if (config.variantAttributes) {
|
|
2041
|
+
for (const attr of config.variantAttributes) {
|
|
2042
|
+
if (isExpression(attr.value)) {
|
|
2043
|
+
variantAttributeValues[attr.key] = attr.value;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
const request = {
|
|
2048
|
+
dataMapping: config.dataMapping || null,
|
|
2049
|
+
filename: this.filenameExpressionMode ? config.filename : null,
|
|
2050
|
+
variantId: this.variantIdExpressionMode ? config.variantId || null : null,
|
|
2051
|
+
variantAttributeValues: Object.keys(variantAttributeValues).length > 0 ? variantAttributeValues : null,
|
|
2052
|
+
};
|
|
2053
|
+
this.epistolaPluginService
|
|
2054
|
+
.validateJsonata(request)
|
|
2055
|
+
.pipe(take$1(1), catchError(() => of({ valid: true, errors: [] })))
|
|
2056
|
+
.subscribe((result) => {
|
|
2057
|
+
if (result.valid) {
|
|
2058
|
+
this.validationErrors$.next([]);
|
|
2059
|
+
this.configuration.emit(config);
|
|
2060
|
+
}
|
|
2061
|
+
else {
|
|
2062
|
+
this.validationErrors$.next(result.errors);
|
|
2063
|
+
this.cdr.markForCheck();
|
|
2064
|
+
}
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, deps: [{ token: EpistolaPluginService }, { token: i2$3.ProcessLinkStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
2068
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: GenerateDocumentConfigurationComponent, isStandalone: true, selector: "epistola-generate-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$", selectedPluginConfigurationData$: "selectedPluginConfigurationData$", context$: "context$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n catalogs: catalogs$ | async,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedCatalogId: selectedCatalogId$ | async,\n selectedTemplateId: selectedTemplateId$ | async,\n validationErrors: validationErrors$ | async,\n } as obs\"\n>\n <div\n *ngIf=\"obs.validationErrors && obs.validationErrors.length > 0\"\n class=\"jsonata-validation-errors\"\n >\n <strong>{{ 'jsonataValidationErrorsHeading' | pluginTranslate: pluginId | async }}</strong>\n <ul>\n <li *ngFor=\"let err of obs.validationErrors\">\n <code>{{ err.field }}</code\n >: {{ err.message }}\n </li>\n </ul>\n </div>\n <v-select\n name=\"catalogId\"\n [title]=\"'catalogId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'catalogIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.catalogs.data\"\n [defaultSelectionId]=\"obs.prefill?.catalogId\"\n [disabled]=\"obs.disabled || obs.catalogs.loading\"\n [required]=\"true\"\n [loading]=\"obs.catalogs.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.catalogs.error\" class=\"loading-error\">{{ obs.catalogs.error }}</div>\n\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading || !obs.selectedCatalogId\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{\n 'variantSelectionMode' | pluginTranslate: pluginId | async\n }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >\n {{ 'selectByVariant' | pluginTranslate: pluginId | async }}\n </button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >\n {{ 'selectByAttributes' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown or expression) -->\n <div *ngIf=\"variantSelectionMode === 'explicit'\" class=\"field-with-fx\">\n <v-select\n *ngIf=\"!variantIdExpressionMode\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"variantIdExpressionMode\" class=\"fx-input-group\">\n <label class=\"fx-input-label\">{{ 'variantId' | pluginTranslate: pluginId | async }}</label>\n <input\n type=\"text\"\n class=\"fx-input\"\n [ngModel]=\"variantIdExpression\"\n (ngModelChange)=\"variantIdExpression = $event; onVariantIdExpressionChange()\"\n [disabled]=\"obs.disabled\"\n placeholder=\"$pv.letterType\"\n />\n </div>\n <button\n type=\"button\"\n class=\"fx-toggle\"\n (click)=\"variantIdExpressionMode = !variantIdExpressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"variantIdExpressionMode ? 'Switch to dropdown' : 'Switch to expression'\"\n >\n {{ variantIdExpressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\n\n <!-- Attribute-based variant selection -->\n <div\n *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\"\n class=\"variant-attributes-section\"\n >\n <label class=\"variant-attributes-label\">{{\n 'variantAttributes' | pluginTranslate: pluginId | async\n }}</label>\n <div class=\"variant-attributes-list\">\n <div\n *ngFor=\"let entry of variantAttributeEntries; let i = index\"\n class=\"variant-attribute-row\"\n >\n <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>\n {{ 'attributeKey' | pluginTranslate: pluginId | async }}\n </option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">\n {{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}\n </option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"custom-key-cancel\"\n (click)=\"cancelCustomKey(entry)\"\n [disabled]=\"obs.disabled\"\n >\n ×\n </button>\n </div>\n <div class=\"attribute-value-with-fx\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [class.fx-input]=\"entry._expressionMode\"\n [placeholder]=\"\n entry._expressionMode\n ? '$pv.language'\n : ('attributeValue' | pluginTranslate: pluginId | async)\n \"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"fx-toggle fx-toggle--inline\"\n (click)=\"entry._expressionMode = !entry._expressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"entry._expressionMode ? 'Switch to plain value' : 'Switch to expression'\"\n >\n {{ entry._expressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\n <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{\n (entry.required ? 'attributeRequired' : 'attributePreferred')\n | pluginTranslate: pluginId\n | async\n }}</span>\n </label>\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >\n ×\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >\n + {{ 'addAttribute' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <div class=\"field-with-fx\">\n <v-input\n *ngIf=\"!filenameExpressionMode\"\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n <div *ngIf=\"filenameExpressionMode\" class=\"fx-input-group\">\n <label class=\"fx-input-label\">{{ 'filename' | pluginTranslate: pluginId | async }}</label>\n <input\n type=\"text\"\n class=\"fx-input\"\n [ngModel]=\"filenameExpression\"\n (ngModelChange)=\"filenameExpression = $event; onFilenameExpressionChange()\"\n [disabled]=\"obs.disabled\"\n placeholder='\"besluit-\" & $doc.name & \".pdf\"'\n />\n </div>\n <button\n type=\"button\"\n class=\"fx-toggle\"\n (click)=\"filenameExpressionMode = !filenameExpressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"filenameExpressionMode ? 'Switch to plain input' : 'Switch to expression'\"\n >\n {{ filenameExpressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">\n {{ templateFieldsError }}\n</div>\n\n<div *ngIf=\"selectedTemplateId$ | async\" class=\"mapping-section\">\n <h5 class=\"mapping-section__title\">\n {{ 'dataMappingTitle' | pluginTranslate: pluginId | async }}\n </h5>\n <p class=\"mapping-section__description\">\n {{ 'dataMappingDescription' | pluginTranslate: pluginId | async }}\n </p>\n <div class=\"mapping-mode-toggle\">\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'simple'\"\n (click)=\"mappingMode = 'simple'\"\n >\n {{ 'mappingModeSimple' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'advanced'\"\n (click)=\"mappingMode = 'advanced'\"\n >\n {{ 'mappingModeAdvanced' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <!-- Editor area (full width) -->\n <epistola-mapping-builder\n *ngIf=\"mappingMode === 'simple'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [suggestions]=\"variableSuggestions\"\n [disabled]=\"!!(disabled$ | async)\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-mapping-builder>\n\n <epistola-jsonata-editor\n *ngIf=\"mappingMode === 'advanced'\"\n [expression]=\"dataMapping$ | async\"\n [disabled]=\"!!(disabled$ | async)\"\n [suggestions]=\"variableSuggestions\"\n [functions]=\"expressionFunctions\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-jsonata-editor>\n\n <!-- Bottom tabs: Schema + Preview (collapsible) -->\n <div class=\"mapping-tools\" [class.mapping-tools--collapsed]=\"toolsCollapsed\">\n <div class=\"mapping-tools__header\" (click)=\"toolsCollapsed = !toolsCollapsed\">\n <span class=\"mapping-tools__chevron\">{{ toolsCollapsed ? '▶' : '▼' }}</span>\n <span>{{ 'mappingTools' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div *ngIf=\"!toolsCollapsed\" class=\"mapping-tools__content\">\n <div class=\"mapping-tools__tabs\">\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'schema'\"\n (click)=\"activeToolTab = 'schema'\"\n >\n {{ 'expectedStructure' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'preview'\"\n (click)=\"activeToolTab = 'preview'\"\n >\n {{ 'previewTitle' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <epistola-expected-structure\n *ngIf=\"activeToolTab === 'schema'\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n ></epistola-expected-structure>\n\n <epistola-mapping-preview\n *ngIf=\"activeToolTab === 'preview'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n ></epistola-mapping-preview>\n </div>\n </div>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.jsonata-validation-errors{margin-bottom:1rem;padding:.75rem 1rem;border:1px solid #dc3545;border-radius:4px;background:#fdf3f4;color:#dc3545;font-size:.875rem}.jsonata-validation-errors ul{margin:.5rem 0 0;padding-left:1.25rem}.jsonata-validation-errors code{background:#dc35451a;padding:0 .25rem;border-radius:2px;font-family:monospace}.validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}.field-with-fx{display:flex;align-items:flex-start;gap:4px}.field-with-fx>*:first-child{flex:1;min-width:0}.fx-toggle{width:28px;height:28px;margin-top:22px;border:1px solid #e0e0e0;border-radius:4px;background:#fff;cursor:pointer;font-family:monospace;font-size:.8em;display:flex;align-items:center;justify-content:center;flex-shrink:0}.fx-toggle:hover{background:#f4f4f4}.fx-toggle--inline{margin-top:0}.fx-input-group{flex:1;min-width:0;margin-bottom:.75rem}.fx-input-label{display:block;font-size:.875rem;margin-bottom:.25rem;color:#525252}.fx-input{width:100%;border:1px solid #8d8d8d;border-radius:0;padding:.4rem .75rem;font-family:monospace;font-size:.875rem;background:#f4f4f4}.attribute-value-with-fx{display:flex;align-items:center;gap:4px;flex:1;min-width:0}.attribute-value-with-fx>input{flex:1;min-width:0}.mapping-section{margin-top:1rem}.mapping-section__title{font-size:1rem;font-weight:600;margin:0 0 4px}.mapping-section__description{font-size:.85em;color:#6f6f6f;margin:0 0 12px}.mapping-mode-toggle{display:flex;gap:0;margin-bottom:12px}.mapping-mode-toggle__btn{padding:6px 16px;border:1px solid #e0e0e0;background:#fff;font-size:.85em;cursor:pointer}.mapping-mode-toggle__btn:first-child{border-radius:4px 0 0 4px}.mapping-mode-toggle__btn:last-child{border-radius:0 4px 4px 0;border-left:none}.mapping-mode-toggle__btn--active{background:#0f62fe;color:#fff;border-color:#0f62fe}.mapping-tools{margin-top:12px;border:1px solid #e0e0e0;border-radius:4px;overflow:hidden}.mapping-tools__header{display:flex;align-items:center;gap:6px;padding:8px 12px;background:#f4f4f4;cursor:pointer;font-size:.85em;font-weight:500;-webkit-user-select:none;user-select:none}.mapping-tools__header:hover{background:#e8e8e8}.mapping-tools__chevron{font-size:.7em}.mapping-tools__content{border-top:1px solid #e0e0e0}.mapping-tools__tabs{display:flex;border-bottom:1px solid #e0e0e0}.mapping-tools__tab{padding:6px 16px;border:none;background:transparent;font-size:.8em;cursor:pointer;border-bottom:2px solid transparent}.mapping-tools__tab--active{border-bottom-color:#0f62fe;font-weight:500}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.SelectComponent, selector: "v-select", inputs: ["items", "defaultSelection", "defaultSelectionId", "defaultSelectionIds", "disabled", "dropUp", "invalid", "multiple", "margin", "widthInPx", "notFoundText", "clearAllText", "clearText", "clearable", "name", "title", "titleTranslationKey", "clearSelectionSubject$", "tooltip", "required", "loading", "loadingText", "placeholder", "smallMargin", "carbonTheme", "appendInline", "warn", "warnText", "dataTestId"], outputs: ["selectedChange"] }, { kind: "component", type: ExpectedStructureComponent, selector: "epistola-expected-structure", inputs: ["templateFields"] }, { kind: "component", type: JsonataEditorComponent, selector: "epistola-jsonata-editor", inputs: ["expression", "disabled", "suggestions", "functions"], outputs: ["expressionChange", "validChange"] }, { kind: "component", type: MappingBuilderComponent, selector: "epistola-mapping-builder", inputs: ["expression", "templateFields", "suggestions", "disabled"], outputs: ["expressionChange"] }, { kind: "component", type: MappingPreviewComponent, selector: "epistola-mapping-preview", inputs: ["expression", "templateFields", "caseDefinitionKey"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1818
2069
|
}
|
|
1819
2070
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, decorators: [{
|
|
1820
2071
|
type: Component,
|
|
@@ -1829,8 +2080,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1829
2080
|
JsonataEditorComponent,
|
|
1830
2081
|
MappingBuilderComponent,
|
|
1831
2082
|
MappingPreviewComponent,
|
|
1832
|
-
], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n catalogs: catalogs$ | async,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedCatalogId: selectedCatalogId$ | async,\n selectedTemplateId: selectedTemplateId$ | async,\n } as obs\"\n>\n <v-select\n name=\"catalogId\"\n [title]=\"'catalogId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'catalogIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.catalogs.data\"\n [defaultSelectionId]=\"obs.prefill?.catalogId\"\n [disabled]=\"obs.disabled || obs.catalogs.loading\"\n [required]=\"true\"\n [loading]=\"obs.catalogs.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.catalogs.error\" class=\"loading-error\">{{ obs.catalogs.error }}</div>\n\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading || !obs.selectedCatalogId\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{\n 'variantSelectionMode' | pluginTranslate: pluginId | async\n }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >\n {{ 'selectByVariant' | pluginTranslate: pluginId | async }}\n </button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >\n {{ 'selectByAttributes' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\n\n <!-- Attribute-based variant selection -->\n <div\n *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\"\n class=\"variant-attributes-section\"\n >\n <label class=\"variant-attributes-label\">{{\n 'variantAttributes' | pluginTranslate: pluginId | async\n }}</label>\n <div class=\"variant-attributes-list\">\n <div\n *ngFor=\"let entry of variantAttributeEntries; let i = index\"\n class=\"variant-attribute-row\"\n >\n <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>\n {{ 'attributeKey' | pluginTranslate: pluginId | async }}\n </option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">\n {{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}\n </option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"custom-key-cancel\"\n (click)=\"cancelCustomKey(entry)\"\n [disabled]=\"obs.disabled\"\n >\n ×\n </button>\n </div>\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{\n (entry.required ? 'attributeRequired' : 'attributePreferred')\n | pluginTranslate: pluginId\n | async\n }}</span>\n </label>\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >\n ×\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >\n + {{ 'addAttribute' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">\n {{ templateFieldsError }}\n</div>\n\n<div *ngIf=\"selectedTemplateId$ | async\" class=\"mapping-section\">\n <h5 class=\"mapping-section__title\">\n {{ 'dataMappingTitle' | pluginTranslate: pluginId | async }}\n </h5>\n <p class=\"mapping-section__description\">\n {{ 'dataMappingDescription' | pluginTranslate: pluginId | async }}\n </p>\n <div class=\"mapping-mode-toggle\">\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'simple'\"\n (click)=\"mappingMode = 'simple'\"\n >\n {{ 'mappingModeSimple' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'advanced'\"\n (click)=\"mappingMode = 'advanced'\"\n >\n {{ 'mappingModeAdvanced' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <!-- Editor area (full width) -->\n <epistola-mapping-builder\n *ngIf=\"mappingMode === 'simple'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [suggestions]=\"variableSuggestions\"\n [disabled]=\"!!(disabled$ | async)\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-mapping-builder>\n\n <epistola-jsonata-editor\n *ngIf=\"mappingMode === 'advanced'\"\n [expression]=\"dataMapping$ | async\"\n [disabled]=\"!!(disabled$ | async)\"\n [suggestions]=\"variableSuggestions\"\n [functions]=\"expressionFunctions\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-jsonata-editor>\n\n <!-- Bottom tabs: Schema + Preview (collapsible) -->\n <div class=\"mapping-tools\" [class.mapping-tools--collapsed]=\"toolsCollapsed\">\n <div class=\"mapping-tools__header\" (click)=\"toolsCollapsed = !toolsCollapsed\">\n <span class=\"mapping-tools__chevron\">{{ toolsCollapsed ? '▶' : '▼' }}</span>\n <span>{{ 'mappingTools' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div *ngIf=\"!toolsCollapsed\" class=\"mapping-tools__content\">\n <div class=\"mapping-tools__tabs\">\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'schema'\"\n (click)=\"activeToolTab = 'schema'\"\n >\n {{ 'expectedStructure' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'preview'\"\n (click)=\"activeToolTab = 'preview'\"\n >\n {{ 'previewTitle' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <epistola-expected-structure\n *ngIf=\"activeToolTab === 'schema'\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n ></epistola-expected-structure>\n\n <epistola-mapping-preview\n *ngIf=\"activeToolTab === 'preview'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n ></epistola-mapping-preview>\n </div>\n </div>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}.mapping-section{margin-top:1rem}.mapping-section__title{font-size:1rem;font-weight:600;margin:0 0 4px}.mapping-section__description{font-size:.85em;color:#6f6f6f;margin:0 0 12px}.mapping-mode-toggle{display:flex;gap:0;margin-bottom:12px}.mapping-mode-toggle__btn{padding:6px 16px;border:1px solid #e0e0e0;background:#fff;font-size:.85em;cursor:pointer}.mapping-mode-toggle__btn:first-child{border-radius:4px 0 0 4px}.mapping-mode-toggle__btn:last-child{border-radius:0 4px 4px 0;border-left:none}.mapping-mode-toggle__btn--active{background:#0f62fe;color:#fff;border-color:#0f62fe}.mapping-tools{margin-top:12px;border:1px solid #e0e0e0;border-radius:4px;overflow:hidden}.mapping-tools__header{display:flex;align-items:center;gap:6px;padding:8px 12px;background:#f4f4f4;cursor:pointer;font-size:.85em;font-weight:500;-webkit-user-select:none;user-select:none}.mapping-tools__header:hover{background:#e8e8e8}.mapping-tools__chevron{font-size:.7em}.mapping-tools__content{border-top:1px solid #e0e0e0}.mapping-tools__tabs{display:flex;border-bottom:1px solid #e0e0e0}.mapping-tools__tab{padding:6px 16px;border:none;background:transparent;font-size:.8em;cursor:pointer;border-bottom:2px solid transparent}.mapping-tools__tab--active{border-bottom-color:#0f62fe;font-weight:500}\n"] }]
|
|
1833
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$
|
|
2083
|
+
], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n catalogs: catalogs$ | async,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedCatalogId: selectedCatalogId$ | async,\n selectedTemplateId: selectedTemplateId$ | async,\n validationErrors: validationErrors$ | async,\n } as obs\"\n>\n <div\n *ngIf=\"obs.validationErrors && obs.validationErrors.length > 0\"\n class=\"jsonata-validation-errors\"\n >\n <strong>{{ 'jsonataValidationErrorsHeading' | pluginTranslate: pluginId | async }}</strong>\n <ul>\n <li *ngFor=\"let err of obs.validationErrors\">\n <code>{{ err.field }}</code\n >: {{ err.message }}\n </li>\n </ul>\n </div>\n <v-select\n name=\"catalogId\"\n [title]=\"'catalogId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'catalogIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.catalogs.data\"\n [defaultSelectionId]=\"obs.prefill?.catalogId\"\n [disabled]=\"obs.disabled || obs.catalogs.loading\"\n [required]=\"true\"\n [loading]=\"obs.catalogs.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.catalogs.error\" class=\"loading-error\">{{ obs.catalogs.error }}</div>\n\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading || !obs.selectedCatalogId\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{\n 'variantSelectionMode' | pluginTranslate: pluginId | async\n }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >\n {{ 'selectByVariant' | pluginTranslate: pluginId | async }}\n </button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >\n {{ 'selectByAttributes' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown or expression) -->\n <div *ngIf=\"variantSelectionMode === 'explicit'\" class=\"field-with-fx\">\n <v-select\n *ngIf=\"!variantIdExpressionMode\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"variantIdExpressionMode\" class=\"fx-input-group\">\n <label class=\"fx-input-label\">{{ 'variantId' | pluginTranslate: pluginId | async }}</label>\n <input\n type=\"text\"\n class=\"fx-input\"\n [ngModel]=\"variantIdExpression\"\n (ngModelChange)=\"variantIdExpression = $event; onVariantIdExpressionChange()\"\n [disabled]=\"obs.disabled\"\n placeholder=\"$pv.letterType\"\n />\n </div>\n <button\n type=\"button\"\n class=\"fx-toggle\"\n (click)=\"variantIdExpressionMode = !variantIdExpressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"variantIdExpressionMode ? 'Switch to dropdown' : 'Switch to expression'\"\n >\n {{ variantIdExpressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\n\n <!-- Attribute-based variant selection -->\n <div\n *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\"\n class=\"variant-attributes-section\"\n >\n <label class=\"variant-attributes-label\">{{\n 'variantAttributes' | pluginTranslate: pluginId | async\n }}</label>\n <div class=\"variant-attributes-list\">\n <div\n *ngFor=\"let entry of variantAttributeEntries; let i = index\"\n class=\"variant-attribute-row\"\n >\n <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>\n {{ 'attributeKey' | pluginTranslate: pluginId | async }}\n </option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">\n {{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}\n </option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"custom-key-cancel\"\n (click)=\"cancelCustomKey(entry)\"\n [disabled]=\"obs.disabled\"\n >\n ×\n </button>\n </div>\n <div class=\"attribute-value-with-fx\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [class.fx-input]=\"entry._expressionMode\"\n [placeholder]=\"\n entry._expressionMode\n ? '$pv.language'\n : ('attributeValue' | pluginTranslate: pluginId | async)\n \"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"fx-toggle fx-toggle--inline\"\n (click)=\"entry._expressionMode = !entry._expressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"entry._expressionMode ? 'Switch to plain value' : 'Switch to expression'\"\n >\n {{ entry._expressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\n <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{\n (entry.required ? 'attributeRequired' : 'attributePreferred')\n | pluginTranslate: pluginId\n | async\n }}</span>\n </label>\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >\n ×\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >\n + {{ 'addAttribute' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <div class=\"field-with-fx\">\n <v-input\n *ngIf=\"!filenameExpressionMode\"\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n <div *ngIf=\"filenameExpressionMode\" class=\"fx-input-group\">\n <label class=\"fx-input-label\">{{ 'filename' | pluginTranslate: pluginId | async }}</label>\n <input\n type=\"text\"\n class=\"fx-input\"\n [ngModel]=\"filenameExpression\"\n (ngModelChange)=\"filenameExpression = $event; onFilenameExpressionChange()\"\n [disabled]=\"obs.disabled\"\n placeholder='\"besluit-\" & $doc.name & \".pdf\"'\n />\n </div>\n <button\n type=\"button\"\n class=\"fx-toggle\"\n (click)=\"filenameExpressionMode = !filenameExpressionMode\"\n [disabled]=\"obs.disabled\"\n [title]=\"filenameExpressionMode ? 'Switch to plain input' : 'Switch to expression'\"\n >\n {{ filenameExpressionMode ? '\u00B7' : 'fx' }}\n </button>\n </div>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">\n {{ templateFieldsError }}\n</div>\n\n<div *ngIf=\"selectedTemplateId$ | async\" class=\"mapping-section\">\n <h5 class=\"mapping-section__title\">\n {{ 'dataMappingTitle' | pluginTranslate: pluginId | async }}\n </h5>\n <p class=\"mapping-section__description\">\n {{ 'dataMappingDescription' | pluginTranslate: pluginId | async }}\n </p>\n <div class=\"mapping-mode-toggle\">\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'simple'\"\n (click)=\"mappingMode = 'simple'\"\n >\n {{ 'mappingModeSimple' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-mode-toggle__btn\"\n [class.mapping-mode-toggle__btn--active]=\"mappingMode === 'advanced'\"\n (click)=\"mappingMode = 'advanced'\"\n >\n {{ 'mappingModeAdvanced' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <!-- Editor area (full width) -->\n <epistola-mapping-builder\n *ngIf=\"mappingMode === 'simple'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [suggestions]=\"variableSuggestions\"\n [disabled]=\"!!(disabled$ | async)\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-mapping-builder>\n\n <epistola-jsonata-editor\n *ngIf=\"mappingMode === 'advanced'\"\n [expression]=\"dataMapping$ | async\"\n [disabled]=\"!!(disabled$ | async)\"\n [suggestions]=\"variableSuggestions\"\n [functions]=\"expressionFunctions\"\n (expressionChange)=\"onDataMappingChange($event)\"\n ></epistola-jsonata-editor>\n\n <!-- Bottom tabs: Schema + Preview (collapsible) -->\n <div class=\"mapping-tools\" [class.mapping-tools--collapsed]=\"toolsCollapsed\">\n <div class=\"mapping-tools__header\" (click)=\"toolsCollapsed = !toolsCollapsed\">\n <span class=\"mapping-tools__chevron\">{{ toolsCollapsed ? '▶' : '▼' }}</span>\n <span>{{ 'mappingTools' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div *ngIf=\"!toolsCollapsed\" class=\"mapping-tools__content\">\n <div class=\"mapping-tools__tabs\">\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'schema'\"\n (click)=\"activeToolTab = 'schema'\"\n >\n {{ 'expectedStructure' | pluginTranslate: pluginId | async }}\n </button>\n <button\n class=\"mapping-tools__tab\"\n [class.mapping-tools__tab--active]=\"activeToolTab === 'preview'\"\n (click)=\"activeToolTab = 'preview'\"\n >\n {{ 'previewTitle' | pluginTranslate: pluginId | async }}\n </button>\n </div>\n\n <epistola-expected-structure\n *ngIf=\"activeToolTab === 'schema'\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n ></epistola-expected-structure>\n\n <epistola-mapping-preview\n *ngIf=\"activeToolTab === 'preview'\"\n [expression]=\"dataMapping$ | async\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n ></epistola-mapping-preview>\n </div>\n </div>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.jsonata-validation-errors{margin-bottom:1rem;padding:.75rem 1rem;border:1px solid #dc3545;border-radius:4px;background:#fdf3f4;color:#dc3545;font-size:.875rem}.jsonata-validation-errors ul{margin:.5rem 0 0;padding-left:1.25rem}.jsonata-validation-errors code{background:#dc35451a;padding:0 .25rem;border-radius:2px;font-family:monospace}.validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}.field-with-fx{display:flex;align-items:flex-start;gap:4px}.field-with-fx>*:first-child{flex:1;min-width:0}.fx-toggle{width:28px;height:28px;margin-top:22px;border:1px solid #e0e0e0;border-radius:4px;background:#fff;cursor:pointer;font-family:monospace;font-size:.8em;display:flex;align-items:center;justify-content:center;flex-shrink:0}.fx-toggle:hover{background:#f4f4f4}.fx-toggle--inline{margin-top:0}.fx-input-group{flex:1;min-width:0;margin-bottom:.75rem}.fx-input-label{display:block;font-size:.875rem;margin-bottom:.25rem;color:#525252}.fx-input{width:100%;border:1px solid #8d8d8d;border-radius:0;padding:.4rem .75rem;font-family:monospace;font-size:.875rem;background:#f4f4f4}.attribute-value-with-fx{display:flex;align-items:center;gap:4px;flex:1;min-width:0}.attribute-value-with-fx>input{flex:1;min-width:0}.mapping-section{margin-top:1rem}.mapping-section__title{font-size:1rem;font-weight:600;margin:0 0 4px}.mapping-section__description{font-size:.85em;color:#6f6f6f;margin:0 0 12px}.mapping-mode-toggle{display:flex;gap:0;margin-bottom:12px}.mapping-mode-toggle__btn{padding:6px 16px;border:1px solid #e0e0e0;background:#fff;font-size:.85em;cursor:pointer}.mapping-mode-toggle__btn:first-child{border-radius:4px 0 0 4px}.mapping-mode-toggle__btn:last-child{border-radius:0 4px 4px 0;border-left:none}.mapping-mode-toggle__btn--active{background:#0f62fe;color:#fff;border-color:#0f62fe}.mapping-tools{margin-top:12px;border:1px solid #e0e0e0;border-radius:4px;overflow:hidden}.mapping-tools__header{display:flex;align-items:center;gap:6px;padding:8px 12px;background:#f4f4f4;cursor:pointer;font-size:.85em;font-weight:500;-webkit-user-select:none;user-select:none}.mapping-tools__header:hover{background:#e8e8e8}.mapping-tools__chevron{font-size:.7em}.mapping-tools__content{border-top:1px solid #e0e0e0}.mapping-tools__tabs{display:flex;border-bottom:1px solid #e0e0e0}.mapping-tools__tab{padding:6px 16px;border:none;background:transparent;font-size:.8em;cursor:pointer;border-bottom:2px solid transparent}.mapping-tools__tab--active{border-bottom-color:#0f62fe;font-weight:500}\n"] }]
|
|
2084
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$3.ProcessLinkStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { save$: [{
|
|
1834
2085
|
type: Input
|
|
1835
2086
|
}], disabled$: [{
|
|
1836
2087
|
type: Input
|
|
@@ -1858,9 +2109,17 @@ class CheckJobStatusConfigurationComponent {
|
|
|
1858
2109
|
saveSubscription;
|
|
1859
2110
|
formValue$ = new BehaviorSubject(null);
|
|
1860
2111
|
valid$ = new BehaviorSubject(false);
|
|
2112
|
+
/** Resolved synchronously before the v-form renders — see download-document-configuration for the why. */
|
|
2113
|
+
resolvedPrefill = {};
|
|
2114
|
+
prefillResolved$ = new BehaviorSubject(false);
|
|
1861
2115
|
safeDisabled$;
|
|
1862
2116
|
ngOnInit() {
|
|
1863
2117
|
this.safeDisabled$ = this.disabled$.pipe(startWith(true), delay(0));
|
|
2118
|
+
const prefill$ = this.prefillConfiguration$ ?? of({});
|
|
2119
|
+
prefill$.pipe(take(1)).subscribe((prefill) => {
|
|
2120
|
+
this.resolvedPrefill = prefill ?? {};
|
|
2121
|
+
this.prefillResolved$.next(true);
|
|
2122
|
+
});
|
|
1864
2123
|
this.openSaveSubscription();
|
|
1865
2124
|
}
|
|
1866
2125
|
ngOnDestroy() {
|
|
@@ -1888,11 +2147,11 @@ class CheckJobStatusConfigurationComponent {
|
|
|
1888
2147
|
});
|
|
1889
2148
|
}
|
|
1890
2149
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CheckJobStatusConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1891
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: CheckJobStatusConfigurationComponent, isStandalone: true, selector: "epistola-check-job-status-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form
|
|
2150
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: CheckJobStatusConfigurationComponent, isStandalone: true, selector: "epistola-check-job-status-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"requestIdVariable\"\n [title]=\"'requestIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'requestIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.requestIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"statusVariable\"\n [title]=\"'statusVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'statusVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.statusVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"errorMessageVariable\"\n [title]=\"'errorMessageVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'errorMessageVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.errorMessageVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
|
|
1892
2151
|
}
|
|
1893
2152
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CheckJobStatusConfigurationComponent, decorators: [{
|
|
1894
2153
|
type: Component,
|
|
1895
|
-
args: [{ selector: 'epistola-check-job-status-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form
|
|
2154
|
+
args: [{ selector: 'epistola-check-job-status-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"requestIdVariable\"\n [title]=\"'requestIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'requestIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.requestIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"statusVariable\"\n [title]=\"'statusVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'statusVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.statusVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"errorMessageVariable\"\n [title]=\"'errorMessageVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'errorMessageVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.errorMessageVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n" }]
|
|
1896
2155
|
}], propDecorators: { save$: [{
|
|
1897
2156
|
type: Input
|
|
1898
2157
|
}], disabled$: [{
|
|
@@ -1917,9 +2176,21 @@ class DownloadDocumentConfigurationComponent {
|
|
|
1917
2176
|
saveSubscription;
|
|
1918
2177
|
formValue$ = new BehaviorSubject(null);
|
|
1919
2178
|
valid$ = new BehaviorSubject(false);
|
|
2179
|
+
/**
|
|
2180
|
+
* Resolved prefill — populated synchronously before the v-form renders. Avoids the
|
|
2181
|
+
* v-input `[defaultValue]` async-binding race that otherwise drops one of the
|
|
2182
|
+
* fields when prefill arrives after mount.
|
|
2183
|
+
*/
|
|
2184
|
+
resolvedPrefill = {};
|
|
2185
|
+
prefillResolved$ = new BehaviorSubject(false);
|
|
1920
2186
|
safeDisabled$;
|
|
1921
2187
|
ngOnInit() {
|
|
1922
2188
|
this.safeDisabled$ = this.disabled$.pipe(startWith(true), delay(0));
|
|
2189
|
+
const prefill$ = this.prefillConfiguration$ ?? of({});
|
|
2190
|
+
prefill$.pipe(take(1)).subscribe((prefill) => {
|
|
2191
|
+
this.resolvedPrefill = prefill ?? {};
|
|
2192
|
+
this.prefillResolved$.next(true);
|
|
2193
|
+
});
|
|
1923
2194
|
this.openSaveSubscription();
|
|
1924
2195
|
}
|
|
1925
2196
|
ngOnDestroy() {
|
|
@@ -1931,7 +2202,7 @@ class DownloadDocumentConfigurationComponent {
|
|
|
1931
2202
|
this.handleValid(formValue);
|
|
1932
2203
|
}
|
|
1933
2204
|
handleValid(formValue) {
|
|
1934
|
-
const valid = !!(formValue?.
|
|
2205
|
+
const valid = !!(formValue?.documentVariable && formValue?.contentVariable);
|
|
1935
2206
|
this.valid$.next(valid);
|
|
1936
2207
|
this.valid.emit(valid);
|
|
1937
2208
|
}
|
|
@@ -1947,11 +2218,11 @@ class DownloadDocumentConfigurationComponent {
|
|
|
1947
2218
|
});
|
|
1948
2219
|
}
|
|
1949
2220
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1950
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: DownloadDocumentConfigurationComponent, isStandalone: true, selector: "epistola-download-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form
|
|
2221
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: DownloadDocumentConfigurationComponent, isStandalone: true, selector: "epistola-download-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"documentVariable\"\n [title]=\"'documentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"contentVariable\"\n [title]=\"'contentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'contentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.contentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
|
|
1951
2222
|
}
|
|
1952
2223
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, decorators: [{
|
|
1953
2224
|
type: Component,
|
|
1954
|
-
args: [{ selector: 'epistola-download-document-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form
|
|
2225
|
+
args: [{ selector: 'epistola-download-document-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"documentVariable\"\n [title]=\"'documentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"contentVariable\"\n [title]=\"'contentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'contentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.contentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n" }]
|
|
1955
2226
|
}], propDecorators: { save$: [{
|
|
1956
2227
|
type: Input
|
|
1957
2228
|
}], disabled$: [{
|
|
@@ -1966,35 +2237,89 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1966
2237
|
type: Output
|
|
1967
2238
|
}] } });
|
|
1968
2239
|
|
|
1969
|
-
|
|
1970
|
-
|
|
2240
|
+
/**
|
|
2241
|
+
* Unified Formio component for the after-generation Epistola PDF UX. Reads
|
|
2242
|
+
* the PDF id and tenant id from named process variables on the caller's
|
|
2243
|
+
* task via the {@code /documents/download} endpoint.
|
|
2244
|
+
*
|
|
2245
|
+
* <p>Three presentations, all backed by the same backend call:
|
|
2246
|
+
* <ul>
|
|
2247
|
+
* <li>{@code display="inline"} — render the PDF inline in a panel.</li>
|
|
2248
|
+
* <li>{@code display="button"} — show a click-to-save download button only.</li>
|
|
2249
|
+
* <li>{@code display="both"} (default) — inline panel with a download icon
|
|
2250
|
+
* in the header.</li>
|
|
2251
|
+
* </ul>
|
|
2252
|
+
*
|
|
2253
|
+
* <p>For the dry-run / what-would-be-generated UX use
|
|
2254
|
+
* {@code epistola-document-preview} instead.
|
|
2255
|
+
*/
|
|
2256
|
+
class EpistolaDocumentComponent {
|
|
2257
|
+
epistolaPluginService;
|
|
2258
|
+
sanitizer;
|
|
2259
|
+
formIoStateService;
|
|
2260
|
+
taskContext;
|
|
2261
|
+
cdr;
|
|
1971
2262
|
value;
|
|
1972
2263
|
valueChange = new EventEmitter();
|
|
1973
2264
|
disabled = false;
|
|
2265
|
+
label = 'Document';
|
|
2266
|
+
/** How to present the document. `both` (default) shows an inline panel with a download icon. */
|
|
2267
|
+
display = 'both';
|
|
2268
|
+
/**
|
|
2269
|
+
* Process-variable name holding the Epistola result. Default: `epistolaResult`.
|
|
2270
|
+
*
|
|
2271
|
+
* Type-tolerant on the backend: if the named variable holds a rich result object
|
|
2272
|
+
* (`Map<String, Object>` written by `generate-document` and updated by the result
|
|
2273
|
+
* collector), the backend digs out the `documentId` key. If it holds a plain
|
|
2274
|
+
* String (custom flow that wrote a bare id), the backend uses it as-is.
|
|
2275
|
+
*/
|
|
2276
|
+
documentVariable = 'epistolaResult';
|
|
2277
|
+
/** Process-variable name holding the Epistola tenant id. Default: `epistolaTenantId`. */
|
|
2278
|
+
tenantIdVariable = 'epistolaTenantId';
|
|
2279
|
+
/** Filename used for the download disposition. */
|
|
1974
2280
|
filename = 'document.pdf';
|
|
1975
|
-
|
|
2281
|
+
loading = false;
|
|
1976
2282
|
downloading = false;
|
|
1977
2283
|
error = null;
|
|
1978
|
-
|
|
1979
|
-
|
|
2284
|
+
previewUrl = null;
|
|
2285
|
+
currentBlobUrl = null;
|
|
2286
|
+
subscription;
|
|
2287
|
+
get designMode() {
|
|
2288
|
+
return !this.formIoStateService.documentId;
|
|
1980
2289
|
}
|
|
1981
|
-
constructor(
|
|
1982
|
-
this.
|
|
2290
|
+
constructor(epistolaPluginService, sanitizer, formIoStateService, taskContext, cdr) {
|
|
2291
|
+
this.epistolaPluginService = epistolaPluginService;
|
|
2292
|
+
this.sanitizer = sanitizer;
|
|
2293
|
+
this.formIoStateService = formIoStateService;
|
|
2294
|
+
this.taskContext = taskContext;
|
|
2295
|
+
this.cdr = cdr;
|
|
2296
|
+
}
|
|
2297
|
+
ngOnInit() {
|
|
2298
|
+
if (this.designMode) {
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
if (this.display !== 'button') {
|
|
2302
|
+
this.loadInline();
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
ngOnDestroy() {
|
|
2306
|
+
this.subscription?.unsubscribe();
|
|
2307
|
+
this.revokeBlobUrl();
|
|
1983
2308
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
2309
|
+
refresh() {
|
|
2310
|
+
this.loadInline();
|
|
1986
2311
|
}
|
|
1987
2312
|
download() {
|
|
1988
|
-
if (
|
|
2313
|
+
if (this.designMode || this.downloading) {
|
|
1989
2314
|
return;
|
|
1990
2315
|
}
|
|
2316
|
+
const request = this.buildRequest('attachment');
|
|
2317
|
+
if (!request)
|
|
2318
|
+
return;
|
|
1991
2319
|
this.downloading = true;
|
|
1992
2320
|
this.error = null;
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
`?tenantId=${encodeURIComponent(tenantId)}` +
|
|
1996
|
-
`&filename=${encodeURIComponent(this.filename)}`;
|
|
1997
|
-
this.http.get(url, { responseType: 'blob' }).subscribe({
|
|
2321
|
+
this.cdr.markForCheck();
|
|
2322
|
+
this.epistolaPluginService.downloadDocumentBlob(request).subscribe({
|
|
1998
2323
|
next: (blob) => {
|
|
1999
2324
|
const objectUrl = URL.createObjectURL(blob);
|
|
2000
2325
|
const anchor = document.createElement('a');
|
|
@@ -2003,66 +2328,246 @@ class EpistolaDownloadComponent {
|
|
|
2003
2328
|
anchor.click();
|
|
2004
2329
|
URL.revokeObjectURL(objectUrl);
|
|
2005
2330
|
this.downloading = false;
|
|
2331
|
+
this.cdr.markForCheck();
|
|
2006
2332
|
},
|
|
2007
2333
|
error: (err) => {
|
|
2008
|
-
|
|
2009
|
-
this.error = 'Download mislukt. Probeer opnieuw.';
|
|
2334
|
+
this.error = err.status === 404 ? 'Document is nog niet gegenereerd.' : 'Download mislukt.';
|
|
2010
2335
|
this.downloading = false;
|
|
2336
|
+
this.cdr.markForCheck();
|
|
2011
2337
|
},
|
|
2012
2338
|
});
|
|
2013
2339
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2340
|
+
loadInline() {
|
|
2341
|
+
const request = this.buildRequest('inline');
|
|
2342
|
+
if (!request)
|
|
2343
|
+
return;
|
|
2344
|
+
this.loading = true;
|
|
2345
|
+
this.error = null;
|
|
2346
|
+
this.cdr.markForCheck();
|
|
2347
|
+
this.revokeBlobUrl();
|
|
2348
|
+
this.subscription?.unsubscribe();
|
|
2349
|
+
this.subscription = this.epistolaPluginService.downloadDocumentBlob(request).subscribe({
|
|
2350
|
+
next: (blob) => {
|
|
2351
|
+
this.currentBlobUrl = URL.createObjectURL(blob);
|
|
2352
|
+
this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
|
|
2353
|
+
this.loading = false;
|
|
2354
|
+
this.cdr.markForCheck();
|
|
2355
|
+
},
|
|
2356
|
+
error: (err) => {
|
|
2357
|
+
this.previewUrl = null;
|
|
2358
|
+
this.error =
|
|
2359
|
+
err.status === 404
|
|
2360
|
+
? 'Document is nog niet gegenereerd.'
|
|
2361
|
+
: 'Document kon niet geladen worden.';
|
|
2362
|
+
this.loading = false;
|
|
2363
|
+
this.cdr.markForCheck();
|
|
2364
|
+
},
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
buildRequest(disposition) {
|
|
2368
|
+
const taskId = this.taskContext.taskInstanceId;
|
|
2369
|
+
const caseDocumentId = this.formIoStateService.documentId;
|
|
2370
|
+
if (!taskId || !caseDocumentId) {
|
|
2371
|
+
this.error = 'Document is alleen beschikbaar binnen een taak.';
|
|
2372
|
+
this.cdr.markForCheck();
|
|
2373
|
+
return null;
|
|
2374
|
+
}
|
|
2375
|
+
return {
|
|
2376
|
+
taskId,
|
|
2377
|
+
caseDocumentId,
|
|
2378
|
+
documentVariable: this.documentVariable,
|
|
2379
|
+
tenantIdVariable: this.tenantIdVariable,
|
|
2380
|
+
filename: this.filename,
|
|
2381
|
+
disposition,
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
revokeBlobUrl() {
|
|
2385
|
+
if (this.currentBlobUrl) {
|
|
2386
|
+
URL.revokeObjectURL(this.currentBlobUrl);
|
|
2387
|
+
this.currentBlobUrl = null;
|
|
2388
|
+
this.previewUrl = null;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentComponent, deps: [{ token: EpistolaPluginService }, { token: i2$4.DomSanitizer }, { token: i3.FormIoStateService }, { token: EpistolaTaskContextService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
2392
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaDocumentComponent, isStandalone: true, selector: "epistola-document-component", inputs: { value: "value", disabled: "disabled", label: "label", display: "display", documentVariable: "documentVariable", tenantIdVariable: "tenantIdVariable", filename: "filename" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: `
|
|
2393
|
+
<!-- Design-time placeholder -->
|
|
2394
|
+
<div *ngIf="designMode" class="epistola-doc-panel">
|
|
2395
|
+
<div class="doc-header">
|
|
2396
|
+
<span>{{ label || 'Document' }}</span>
|
|
2397
|
+
<span class="design-tag">design mode</span>
|
|
2398
|
+
</div>
|
|
2399
|
+
<div class="doc-body design-info">
|
|
2400
|
+
<div class="design-section">
|
|
2401
|
+
<div class="design-label">Display</div>
|
|
2402
|
+
<div class="design-value">{{ display }}</div>
|
|
2403
|
+
<div class="design-label">Document variable</div>
|
|
2404
|
+
<div class="design-value">{{ documentVariable }}</div>
|
|
2405
|
+
<div class="design-label">Tenant ID variable</div>
|
|
2406
|
+
<div class="design-value">{{ tenantIdVariable }}</div>
|
|
2407
|
+
</div>
|
|
2408
|
+
</div>
|
|
2409
|
+
</div>
|
|
2410
|
+
|
|
2411
|
+
<!-- Button-only display -->
|
|
2412
|
+
<div *ngIf="!designMode && display === 'button'">
|
|
2413
|
+
<button
|
|
2414
|
+
type="button"
|
|
2415
|
+
class="btn btn-outline-primary"
|
|
2416
|
+
[disabled]="disabled || downloading"
|
|
2417
|
+
(click)="download()"
|
|
2418
|
+
>
|
|
2419
|
+
<i class="mdi mdi-download mr-1"></i>
|
|
2420
|
+
{{ downloading ? 'Downloading...' : label || 'Download PDF' }}
|
|
2421
|
+
</button>
|
|
2422
|
+
<span *ngIf="error" class="text-danger ml-2">{{ error }}</span>
|
|
2423
|
+
</div>
|
|
2424
|
+
|
|
2425
|
+
<!-- Inline / both: panel with optional download icon -->
|
|
2426
|
+
<div *ngIf="!designMode && display !== 'button'" class="epistola-doc-panel">
|
|
2427
|
+
<div class="doc-header">
|
|
2428
|
+
<span>{{ label || 'Document' }}</span>
|
|
2429
|
+
<div class="doc-controls">
|
|
2430
|
+
<button
|
|
2431
|
+
*ngIf="display === 'both'"
|
|
2432
|
+
type="button"
|
|
2433
|
+
class="doc-icon-btn"
|
|
2434
|
+
[disabled]="disabled || downloading"
|
|
2435
|
+
(click)="download()"
|
|
2436
|
+
[title]="downloading ? 'Downloading...' : 'Download'"
|
|
2437
|
+
>
|
|
2438
|
+
<i class="mdi mdi-download"></i>
|
|
2439
|
+
</button>
|
|
2440
|
+
<button
|
|
2441
|
+
type="button"
|
|
2442
|
+
class="doc-icon-btn"
|
|
2443
|
+
[disabled]="loading"
|
|
2444
|
+
(click)="refresh()"
|
|
2445
|
+
title="Refresh"
|
|
2446
|
+
>
|
|
2447
|
+
<i class="mdi mdi-refresh"></i>
|
|
2448
|
+
</button>
|
|
2449
|
+
</div>
|
|
2450
|
+
</div>
|
|
2451
|
+
<div class="doc-body">
|
|
2452
|
+
<div *ngIf="loading" class="doc-loading">Loading document...</div>
|
|
2453
|
+
<div *ngIf="error && !loading" class="doc-unavailable">
|
|
2454
|
+
<i class="mdi mdi-information-outline"></i>
|
|
2455
|
+
{{ error }}
|
|
2456
|
+
</div>
|
|
2457
|
+
<object
|
|
2458
|
+
*ngIf="previewUrl && !loading"
|
|
2459
|
+
[data]="previewUrl"
|
|
2460
|
+
type="application/pdf"
|
|
2461
|
+
class="doc-pdf"
|
|
2462
|
+
>
|
|
2463
|
+
PDF preview is not supported in this browser.
|
|
2464
|
+
</object>
|
|
2465
|
+
</div>
|
|
2466
|
+
</div>
|
|
2467
|
+
`, isInline: true, 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"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2027
2468
|
}
|
|
2028
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
2469
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentComponent, decorators: [{
|
|
2029
2470
|
type: Component,
|
|
2030
|
-
args: [{
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
class="
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2471
|
+
args: [{ standalone: true, imports: [CommonModule], selector: 'epistola-document-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2472
|
+
<!-- Design-time placeholder -->
|
|
2473
|
+
<div *ngIf="designMode" class="epistola-doc-panel">
|
|
2474
|
+
<div class="doc-header">
|
|
2475
|
+
<span>{{ label || 'Document' }}</span>
|
|
2476
|
+
<span class="design-tag">design mode</span>
|
|
2477
|
+
</div>
|
|
2478
|
+
<div class="doc-body design-info">
|
|
2479
|
+
<div class="design-section">
|
|
2480
|
+
<div class="design-label">Display</div>
|
|
2481
|
+
<div class="design-value">{{ display }}</div>
|
|
2482
|
+
<div class="design-label">Document variable</div>
|
|
2483
|
+
<div class="design-value">{{ documentVariable }}</div>
|
|
2484
|
+
<div class="design-label">Tenant ID variable</div>
|
|
2485
|
+
<div class="design-value">{{ tenantIdVariable }}</div>
|
|
2486
|
+
</div>
|
|
2487
|
+
</div>
|
|
2488
|
+
</div>
|
|
2489
|
+
|
|
2490
|
+
<!-- Button-only display -->
|
|
2491
|
+
<div *ngIf="!designMode && display === 'button'">
|
|
2492
|
+
<button
|
|
2493
|
+
type="button"
|
|
2494
|
+
class="btn btn-outline-primary"
|
|
2495
|
+
[disabled]="disabled || downloading"
|
|
2496
|
+
(click)="download()"
|
|
2497
|
+
>
|
|
2498
|
+
<i class="mdi mdi-download mr-1"></i>
|
|
2499
|
+
{{ downloading ? 'Downloading...' : label || 'Download PDF' }}
|
|
2500
|
+
</button>
|
|
2501
|
+
<span *ngIf="error" class="text-danger ml-2">{{ error }}</span>
|
|
2502
|
+
</div>
|
|
2503
|
+
|
|
2504
|
+
<!-- Inline / both: panel with optional download icon -->
|
|
2505
|
+
<div *ngIf="!designMode && display !== 'button'" class="epistola-doc-panel">
|
|
2506
|
+
<div class="doc-header">
|
|
2507
|
+
<span>{{ label || 'Document' }}</span>
|
|
2508
|
+
<div class="doc-controls">
|
|
2509
|
+
<button
|
|
2510
|
+
*ngIf="display === 'both'"
|
|
2511
|
+
type="button"
|
|
2512
|
+
class="doc-icon-btn"
|
|
2513
|
+
[disabled]="disabled || downloading"
|
|
2514
|
+
(click)="download()"
|
|
2515
|
+
[title]="downloading ? 'Downloading...' : 'Download'"
|
|
2516
|
+
>
|
|
2517
|
+
<i class="mdi mdi-download"></i>
|
|
2518
|
+
</button>
|
|
2519
|
+
<button
|
|
2520
|
+
type="button"
|
|
2521
|
+
class="doc-icon-btn"
|
|
2522
|
+
[disabled]="loading"
|
|
2523
|
+
(click)="refresh()"
|
|
2524
|
+
title="Refresh"
|
|
2525
|
+
>
|
|
2526
|
+
<i class="mdi mdi-refresh"></i>
|
|
2527
|
+
</button>
|
|
2528
|
+
</div>
|
|
2529
|
+
</div>
|
|
2530
|
+
<div class="doc-body">
|
|
2531
|
+
<div *ngIf="loading" class="doc-loading">Loading document...</div>
|
|
2532
|
+
<div *ngIf="error && !loading" class="doc-unavailable">
|
|
2533
|
+
<i class="mdi mdi-information-outline"></i>
|
|
2534
|
+
{{ error }}
|
|
2535
|
+
</div>
|
|
2536
|
+
<object
|
|
2537
|
+
*ngIf="previewUrl && !loading"
|
|
2538
|
+
[data]="previewUrl"
|
|
2539
|
+
type="application/pdf"
|
|
2540
|
+
class="doc-pdf"
|
|
2541
|
+
>
|
|
2542
|
+
PDF preview is not supported in this browser.
|
|
2543
|
+
</object>
|
|
2544
|
+
</div>
|
|
2545
|
+
</div>
|
|
2546
|
+
`, 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"] }]
|
|
2547
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$4.DomSanitizer }, { type: i3.FormIoStateService }, { type: EpistolaTaskContextService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
2048
2548
|
type: Input
|
|
2049
2549
|
}], valueChange: [{
|
|
2050
2550
|
type: Output
|
|
2051
2551
|
}], disabled: [{
|
|
2052
2552
|
type: Input
|
|
2053
|
-
}], filename: [{
|
|
2054
|
-
type: Input
|
|
2055
2553
|
}], label: [{
|
|
2056
2554
|
type: Input
|
|
2555
|
+
}], display: [{
|
|
2556
|
+
type: Input
|
|
2557
|
+
}], documentVariable: [{
|
|
2558
|
+
type: Input
|
|
2559
|
+
}], tenantIdVariable: [{
|
|
2560
|
+
type: Input
|
|
2561
|
+
}], filename: [{
|
|
2562
|
+
type: Input
|
|
2057
2563
|
}] } });
|
|
2058
2564
|
|
|
2059
2565
|
class EpistolaRetryFormComponent {
|
|
2060
2566
|
epistolaPluginService;
|
|
2061
2567
|
formIoStateService;
|
|
2062
2568
|
cdr;
|
|
2063
|
-
http;
|
|
2064
2569
|
sanitizer;
|
|
2065
|
-
|
|
2570
|
+
taskContext;
|
|
2066
2571
|
value;
|
|
2067
2572
|
valueChange = new EventEmitter();
|
|
2068
2573
|
disabled = false;
|
|
@@ -2083,19 +2588,16 @@ class EpistolaRetryFormComponent {
|
|
|
2083
2588
|
currentBlobUrl = null;
|
|
2084
2589
|
resolvedSourceActivityId;
|
|
2085
2590
|
processDefinitionKey;
|
|
2086
|
-
apiEndpoint;
|
|
2087
2591
|
formOptions = {
|
|
2088
2592
|
noAlerts: true,
|
|
2089
2593
|
buttonSettings: { showCancel: false, showSubmit: false, showPrevious: false, showNext: false },
|
|
2090
2594
|
};
|
|
2091
|
-
constructor(epistolaPluginService, formIoStateService, cdr,
|
|
2595
|
+
constructor(epistolaPluginService, formIoStateService, cdr, sanitizer, taskContext) {
|
|
2092
2596
|
this.epistolaPluginService = epistolaPluginService;
|
|
2093
2597
|
this.formIoStateService = formIoStateService;
|
|
2094
2598
|
this.cdr = cdr;
|
|
2095
|
-
this.http = http;
|
|
2096
2599
|
this.sanitizer = sanitizer;
|
|
2097
|
-
this.
|
|
2098
|
-
this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
|
|
2600
|
+
this.taskContext = taskContext;
|
|
2099
2601
|
// Debounce preview calls
|
|
2100
2602
|
this.previewSubscription = this.previewSubject.pipe(debounceTime$1(1500)).subscribe((data) => {
|
|
2101
2603
|
this.loadPreview(data);
|
|
@@ -2132,6 +2634,12 @@ class EpistolaRetryFormComponent {
|
|
|
2132
2634
|
const processInstanceId = this.formIoStateService.processInstanceId;
|
|
2133
2635
|
if (!documentId || !processInstanceId)
|
|
2134
2636
|
return;
|
|
2637
|
+
const taskId = this.taskContext.taskInstanceId;
|
|
2638
|
+
if (!taskId) {
|
|
2639
|
+
this.previewError = 'Preview is only available from within a user task.';
|
|
2640
|
+
this.cdr.markForCheck();
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2135
2643
|
this.previewLoading = true;
|
|
2136
2644
|
this.previewError = null;
|
|
2137
2645
|
this.cdr.markForCheck();
|
|
@@ -2140,13 +2648,14 @@ class EpistolaRetryFormComponent {
|
|
|
2140
2648
|
URL.revokeObjectURL(this.currentBlobUrl);
|
|
2141
2649
|
this.currentBlobUrl = null;
|
|
2142
2650
|
}
|
|
2143
|
-
this.
|
|
2144
|
-
.
|
|
2651
|
+
this.epistolaPluginService
|
|
2652
|
+
.previewToBlob({
|
|
2653
|
+
taskId,
|
|
2145
2654
|
documentId,
|
|
2146
2655
|
processInstanceId,
|
|
2147
2656
|
sourceActivityId: this.sourceActivityId || null,
|
|
2148
2657
|
overrides: formData,
|
|
2149
|
-
}
|
|
2658
|
+
})
|
|
2150
2659
|
.subscribe({
|
|
2151
2660
|
next: (blob) => {
|
|
2152
2661
|
this.currentBlobUrl = URL.createObjectURL(blob);
|
|
@@ -2188,8 +2697,15 @@ class EpistolaRetryFormComponent {
|
|
|
2188
2697
|
this.cdr.markForCheck();
|
|
2189
2698
|
return;
|
|
2190
2699
|
}
|
|
2700
|
+
const taskId = this.taskContext.taskInstanceId;
|
|
2701
|
+
if (!taskId) {
|
|
2702
|
+
this.error = 'Retry form is only available from within a user task.';
|
|
2703
|
+
this.loading = false;
|
|
2704
|
+
this.cdr.markForCheck();
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2191
2707
|
this.loadSubscription = this.epistolaPluginService
|
|
2192
|
-
.getRetryForm(processInstanceId, documentId ?? undefined, this.sourceActivityId)
|
|
2708
|
+
.getRetryForm(taskId, processInstanceId, documentId ?? undefined, this.sourceActivityId)
|
|
2193
2709
|
.subscribe({
|
|
2194
2710
|
next: (form) => {
|
|
2195
2711
|
this.formDefinition = form;
|
|
@@ -2212,7 +2728,7 @@ class EpistolaRetryFormComponent {
|
|
|
2212
2728
|
},
|
|
2213
2729
|
});
|
|
2214
2730
|
}
|
|
2215
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, deps: [{ token: EpistolaPluginService }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }, { token:
|
|
2731
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, deps: [{ token: EpistolaPluginService }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }, { token: i2$4.DomSanitizer }, { token: EpistolaTaskContextService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2216
2732
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaRetryFormComponent, isStandalone: true, selector: "epistola-retry-form-component", inputs: { value: "value", disabled: "disabled", label: "label", sourceActivityId: "sourceActivityId" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2217
2733
|
<div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
|
|
2218
2734
|
<div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
|
|
@@ -2251,7 +2767,7 @@ class EpistolaRetryFormComponent {
|
|
|
2251
2767
|
</div>
|
|
2252
2768
|
</div>
|
|
2253
2769
|
</div>
|
|
2254
|
-
`, isInline: true, styles: [".epistola-retry-loading{padding:1rem;color:#6c757d}.epistola-retry-error{padding:.5rem;color:#dc3545}.epistola-retry-container{display:flex;gap:1rem}.epistola-retry-form{flex:2;min-width:0}.epistola-retry-preview{flex:1;min-width:0;border:1px solid #dee2e6;border-radius:4px;padding:1rem;background:#f8f9fa;display:flex;flex-direction:column}.preview-expanded .epistola-retry-preview{flex:1}.preview-header{display:flex;justify-content:space-between;align-items:center;font-weight:700;margin-bottom:.5rem;color:#495057}.preview-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.2rem .5rem;font-size:.75rem;cursor:pointer}.preview-toggle:hover{background:#e9ecef}.preview-loading{color:#6c757d;font-style:italic}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-expanded .preview-pdf{min-height:80vh}.preview-error{color:#dc3545}.preview-empty{color:#6c757d;font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormioModule }, { kind: "component", type:
|
|
2770
|
+
`, isInline: true, styles: [".epistola-retry-loading{padding:1rem;color:#6c757d}.epistola-retry-error{padding:.5rem;color:#dc3545}.epistola-retry-container{display:flex;gap:1rem}.epistola-retry-form{flex:2;min-width:0}.epistola-retry-preview{flex:1;min-width:0;border:1px solid #dee2e6;border-radius:4px;padding:1rem;background:#f8f9fa;display:flex;flex-direction:column}.preview-expanded .epistola-retry-preview{flex:1}.preview-header{display:flex;justify-content:space-between;align-items:center;font-weight:700;margin-bottom:.5rem;color:#495057}.preview-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.2rem .5rem;font-size:.75rem;cursor:pointer}.preview-toggle:hover{background:#e9ecef}.preview-loading{color:#6c757d;font-style:italic}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-expanded .preview-pdf{min-height:80vh}.preview-error{color:#dc3545}.preview-empty{color:#6c757d;font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormioModule }, { kind: "component", type: i5.FormioComponent, selector: "formio" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2255
2771
|
}
|
|
2256
2772
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, decorators: [{
|
|
2257
2773
|
type: Component,
|
|
@@ -2294,7 +2810,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2294
2810
|
</div>
|
|
2295
2811
|
</div>
|
|
2296
2812
|
`, 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"] }]
|
|
2297
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }, { type:
|
|
2813
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }, { type: i2$4.DomSanitizer }, { type: EpistolaTaskContextService }], propDecorators: { value: [{
|
|
2298
2814
|
type: Input
|
|
2299
2815
|
}], valueChange: [{
|
|
2300
2816
|
type: Output
|
|
@@ -2306,281 +2822,156 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2306
2822
|
type: Input
|
|
2307
2823
|
}] } });
|
|
2308
2824
|
|
|
2309
|
-
class
|
|
2310
|
-
|
|
2825
|
+
class EpistolaDocumentPreviewComponent {
|
|
2826
|
+
epistolaPluginService;
|
|
2311
2827
|
sanitizer;
|
|
2312
|
-
|
|
2828
|
+
formIoStateService;
|
|
2829
|
+
cdr;
|
|
2830
|
+
taskContext;
|
|
2313
2831
|
value;
|
|
2314
2832
|
valueChange = new EventEmitter();
|
|
2315
2833
|
disabled = false;
|
|
2316
|
-
label = 'Preview
|
|
2317
|
-
|
|
2834
|
+
label = 'Document Preview';
|
|
2835
|
+
processDefinitionKey;
|
|
2836
|
+
sourceActivityId;
|
|
2837
|
+
overrideMapping;
|
|
2318
2838
|
loading = false;
|
|
2319
|
-
|
|
2320
|
-
previewError = null;
|
|
2839
|
+
error = null;
|
|
2321
2840
|
previewUrl = null;
|
|
2841
|
+
designMode = false;
|
|
2842
|
+
initialized = false;
|
|
2322
2843
|
currentBlobUrl = null;
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
}
|
|
2327
|
-
constructor(http, sanitizer, configService) {
|
|
2328
|
-
this.http = http;
|
|
2844
|
+
previewSubscription;
|
|
2845
|
+
constructor(epistolaPluginService, sanitizer, formIoStateService, cdr, taskContext) {
|
|
2846
|
+
this.epistolaPluginService = epistolaPluginService;
|
|
2329
2847
|
this.sanitizer = sanitizer;
|
|
2330
|
-
this.
|
|
2331
|
-
this.
|
|
2848
|
+
this.formIoStateService = formIoStateService;
|
|
2849
|
+
this.cdr = cdr;
|
|
2850
|
+
this.taskContext = taskContext;
|
|
2332
2851
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2852
|
+
/**
|
|
2853
|
+
* Resolve the active task id from {@link EpistolaTaskContextService}, populated
|
|
2854
|
+
* by {@code EpistolaTaskContextInterceptor} on the canonical Valtimo task-open
|
|
2855
|
+
* call. Returns null when used outside a task context (e.g. Formio builder).
|
|
2856
|
+
*/
|
|
2857
|
+
get currentTaskId() {
|
|
2858
|
+
return this.taskContext.taskInstanceId;
|
|
2335
2859
|
}
|
|
2336
|
-
|
|
2337
|
-
return
|
|
2860
|
+
get overrideMappingScopes() {
|
|
2861
|
+
return this.overrideMapping ? Object.keys(this.overrideMapping) : [];
|
|
2338
2862
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
this.previewError = null;
|
|
2345
|
-
this.revokeBlobUrl();
|
|
2346
|
-
const { documentId, tenantId } = this.value;
|
|
2347
|
-
const url = `${this.apiEndpoint}/documents/${encodeURIComponent(documentId)}/download` +
|
|
2348
|
-
`?tenantId=${encodeURIComponent(tenantId)}` +
|
|
2349
|
-
`&filename=preview.pdf`;
|
|
2350
|
-
this.http.get(url, { responseType: 'blob' }).subscribe({
|
|
2351
|
-
next: (blob) => {
|
|
2352
|
-
this.currentBlobUrl = URL.createObjectURL(blob);
|
|
2353
|
-
this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
|
|
2354
|
-
this.previewLoading = false;
|
|
2355
|
-
},
|
|
2356
|
-
error: () => {
|
|
2357
|
-
this.previewError = 'Could not load the document.';
|
|
2358
|
-
this.previewLoading = false;
|
|
2359
|
-
},
|
|
2360
|
-
});
|
|
2361
|
-
}
|
|
2362
|
-
closePreview() {
|
|
2363
|
-
this.modalOpen = false;
|
|
2364
|
-
this.revokeBlobUrl();
|
|
2365
|
-
this.previewUrl = null;
|
|
2366
|
-
this.previewError = null;
|
|
2367
|
-
}
|
|
2368
|
-
revokeBlobUrl() {
|
|
2369
|
-
if (this.currentBlobUrl) {
|
|
2370
|
-
URL.revokeObjectURL(this.currentBlobUrl);
|
|
2371
|
-
this.currentBlobUrl = null;
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPreviewButtonComponent, deps: [{ token: i1.HttpClient }, { token: i4$1.DomSanitizer }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2375
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaPreviewButtonComponent, isStandalone: true, selector: "epistola-preview-button-component", inputs: { value: "value", disabled: "disabled", label: "label" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: `
|
|
2376
|
-
<button
|
|
2377
|
-
type="button"
|
|
2378
|
-
class="btn btn-outline-secondary"
|
|
2379
|
-
[disabled]="disabled || loading || !hasRequiredData()"
|
|
2380
|
-
(click)="openPreview()"
|
|
2381
|
-
>
|
|
2382
|
-
<i class="mdi mdi-eye mr-1"></i>
|
|
2383
|
-
{{ loading ? 'Loading...' : buttonLabel }}
|
|
2384
|
-
</button>
|
|
2385
|
-
|
|
2386
|
-
<div *ngIf="modalOpen" class="preview-modal-overlay" (click)="closePreview()">
|
|
2387
|
-
<div class="preview-modal-content" (click)="$event.stopPropagation()">
|
|
2388
|
-
<div class="preview-modal-header">
|
|
2389
|
-
<span>Document Preview</span>
|
|
2390
|
-
<button type="button" class="preview-modal-close" (click)="closePreview()">
|
|
2391
|
-
×
|
|
2392
|
-
</button>
|
|
2393
|
-
</div>
|
|
2394
|
-
<div class="preview-modal-body">
|
|
2395
|
-
<div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
|
|
2396
|
-
<div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
|
|
2397
|
-
<object
|
|
2398
|
-
*ngIf="previewUrl && !previewLoading"
|
|
2399
|
-
[data]="previewUrl"
|
|
2400
|
-
type="application/pdf"
|
|
2401
|
-
class="preview-pdf"
|
|
2402
|
-
>
|
|
2403
|
-
PDF preview is not supported in this browser.
|
|
2404
|
-
</object>
|
|
2405
|
-
</div>
|
|
2406
|
-
</div>
|
|
2407
|
-
</div>
|
|
2408
|
-
`, isInline: true, styles: [".preview-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:10000}.preview-modal-content{background:#fff;border-radius:8px;width:90vw;height:90vh;max-width:1200px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 32px #0000004d}.preview-modal-header{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;font-size:1rem}.preview-modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6c757d;line-height:1;padding:0 .25rem}.preview-modal-close:hover{color:#333}.preview-modal-body{flex:1;overflow:hidden;display:flex;flex-direction:column}.preview-loading,.preview-error{padding:2rem;text-align:center}.preview-error{color:#dc3545}.preview-pdf{width:100%;flex:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
2409
|
-
}
|
|
2410
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPreviewButtonComponent, decorators: [{
|
|
2411
|
-
type: Component,
|
|
2412
|
-
args: [{ standalone: true, imports: [CommonModule], selector: 'epistola-preview-button-component', template: `
|
|
2413
|
-
<button
|
|
2414
|
-
type="button"
|
|
2415
|
-
class="btn btn-outline-secondary"
|
|
2416
|
-
[disabled]="disabled || loading || !hasRequiredData()"
|
|
2417
|
-
(click)="openPreview()"
|
|
2418
|
-
>
|
|
2419
|
-
<i class="mdi mdi-eye mr-1"></i>
|
|
2420
|
-
{{ loading ? 'Loading...' : buttonLabel }}
|
|
2421
|
-
</button>
|
|
2422
|
-
|
|
2423
|
-
<div *ngIf="modalOpen" class="preview-modal-overlay" (click)="closePreview()">
|
|
2424
|
-
<div class="preview-modal-content" (click)="$event.stopPropagation()">
|
|
2425
|
-
<div class="preview-modal-header">
|
|
2426
|
-
<span>Document Preview</span>
|
|
2427
|
-
<button type="button" class="preview-modal-close" (click)="closePreview()">
|
|
2428
|
-
×
|
|
2429
|
-
</button>
|
|
2430
|
-
</div>
|
|
2431
|
-
<div class="preview-modal-body">
|
|
2432
|
-
<div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
|
|
2433
|
-
<div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
|
|
2434
|
-
<object
|
|
2435
|
-
*ngIf="previewUrl && !previewLoading"
|
|
2436
|
-
[data]="previewUrl"
|
|
2437
|
-
type="application/pdf"
|
|
2438
|
-
class="preview-pdf"
|
|
2439
|
-
>
|
|
2440
|
-
PDF preview is not supported in this browser.
|
|
2441
|
-
</object>
|
|
2442
|
-
</div>
|
|
2443
|
-
</div>
|
|
2444
|
-
</div>
|
|
2445
|
-
`, styles: [".preview-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:10000}.preview-modal-content{background:#fff;border-radius:8px;width:90vw;height:90vh;max-width:1200px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 32px #0000004d}.preview-modal-header{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;font-size:1rem}.preview-modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6c757d;line-height:1;padding:0 .25rem}.preview-modal-close:hover{color:#333}.preview-modal-body{flex:1;overflow:hidden;display:flex;flex-direction:column}.preview-loading,.preview-error{padding:2rem;text-align:center}.preview-error{color:#dc3545}.preview-pdf{width:100%;flex:1}\n"] }]
|
|
2446
|
-
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i4$1.DomSanitizer }, { type: i2.ConfigService }], propDecorators: { value: [{
|
|
2447
|
-
type: Input
|
|
2448
|
-
}], valueChange: [{
|
|
2449
|
-
type: Output
|
|
2450
|
-
}], disabled: [{
|
|
2451
|
-
type: Input
|
|
2452
|
-
}], label: [{
|
|
2453
|
-
type: Input
|
|
2454
|
-
}] } });
|
|
2455
|
-
|
|
2456
|
-
class EpistolaDocumentPreviewComponent {
|
|
2457
|
-
epistolaPluginService;
|
|
2458
|
-
http;
|
|
2459
|
-
sanitizer;
|
|
2460
|
-
configService;
|
|
2461
|
-
formIoStateService;
|
|
2462
|
-
cdr;
|
|
2463
|
-
value;
|
|
2464
|
-
valueChange = new EventEmitter();
|
|
2465
|
-
disabled = false;
|
|
2466
|
-
label = 'Document Preview';
|
|
2467
|
-
sources = [];
|
|
2468
|
-
selectedIndex = 0;
|
|
2469
|
-
discovering = false;
|
|
2470
|
-
loading = false;
|
|
2471
|
-
error = null;
|
|
2472
|
-
previewUrl = null;
|
|
2473
|
-
initialized = false;
|
|
2474
|
-
currentBlobUrl = null;
|
|
2475
|
-
discoverSubscription;
|
|
2476
|
-
previewSubscription;
|
|
2477
|
-
apiEndpoint;
|
|
2478
|
-
constructor(epistolaPluginService, http, sanitizer, configService, formIoStateService, cdr) {
|
|
2479
|
-
this.epistolaPluginService = epistolaPluginService;
|
|
2480
|
-
this.http = http;
|
|
2481
|
-
this.sanitizer = sanitizer;
|
|
2482
|
-
this.configService = configService;
|
|
2483
|
-
this.formIoStateService = formIoStateService;
|
|
2484
|
-
this.cdr = cdr;
|
|
2485
|
-
this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
|
|
2863
|
+
overrideMappingEntries(scope) {
|
|
2864
|
+
const fields = this.overrideMapping?.[scope];
|
|
2865
|
+
if (!fields || typeof fields !== 'object')
|
|
2866
|
+
return [];
|
|
2867
|
+
return Object.entries(fields).map(([path, field]) => ({ path, field: String(field) }));
|
|
2486
2868
|
}
|
|
2487
2869
|
ngOnChanges(changes) {
|
|
2488
2870
|
if (!this.initialized) {
|
|
2489
2871
|
this.initialized = true;
|
|
2490
|
-
|
|
2872
|
+
// Detect design mode: no runtime context (Formio builder)
|
|
2873
|
+
const documentId = this.formIoStateService.documentId;
|
|
2874
|
+
if (!documentId) {
|
|
2875
|
+
this.designMode = true;
|
|
2876
|
+
this.cdr.markForCheck();
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
if (!this.sourceActivityId) {
|
|
2880
|
+
this.error = 'Preview is not configured: set the source activity on the form component.';
|
|
2881
|
+
this.cdr.markForCheck();
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
this.loadPreview();
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
// React to value changes (input overrides from the Formio wrapper).
|
|
2888
|
+
if (changes['value']) {
|
|
2889
|
+
this.loadPreview();
|
|
2491
2890
|
}
|
|
2492
2891
|
}
|
|
2493
2892
|
ngOnDestroy() {
|
|
2494
|
-
this.discoverSubscription?.unsubscribe();
|
|
2495
2893
|
this.previewSubscription?.unsubscribe();
|
|
2496
2894
|
this.revokeBlobUrl();
|
|
2497
2895
|
}
|
|
2498
|
-
onSourceChange(event) {
|
|
2499
|
-
this.selectedIndex = +event.target.value;
|
|
2500
|
-
this.loadPreview();
|
|
2501
|
-
}
|
|
2502
2896
|
refresh() {
|
|
2503
2897
|
this.loadPreview();
|
|
2504
2898
|
}
|
|
2505
|
-
|
|
2899
|
+
/**
|
|
2900
|
+
* Preview using the explicitly configured process link + input overrides.
|
|
2901
|
+
* Requires a runtime task context — the backend authorizes the request against
|
|
2902
|
+
* the task's process instance and case document, so all three ids must match.
|
|
2903
|
+
*/
|
|
2904
|
+
loadPreview() {
|
|
2506
2905
|
const documentId = this.formIoStateService.documentId;
|
|
2507
2906
|
if (!documentId) {
|
|
2508
2907
|
this.error = 'Could not determine document ID from context.';
|
|
2509
2908
|
this.cdr.markForCheck();
|
|
2510
2909
|
return;
|
|
2511
2910
|
}
|
|
2512
|
-
this.
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
this.discoverSubscription = this.epistolaPluginService.getPreviewSources(documentId).subscribe({
|
|
2516
|
-
next: (sources) => {
|
|
2517
|
-
this.sources = sources;
|
|
2518
|
-
this.discovering = false;
|
|
2519
|
-
this.cdr.markForCheck();
|
|
2520
|
-
if (sources.length > 0) {
|
|
2521
|
-
this.selectedIndex = 0;
|
|
2522
|
-
this.loadPreview();
|
|
2523
|
-
}
|
|
2524
|
-
},
|
|
2525
|
-
error: (err) => {
|
|
2526
|
-
this.error = err.error?.error || 'Failed to discover preview sources';
|
|
2527
|
-
this.discovering = false;
|
|
2528
|
-
this.cdr.markForCheck();
|
|
2529
|
-
},
|
|
2530
|
-
});
|
|
2531
|
-
}
|
|
2532
|
-
loadPreview() {
|
|
2533
|
-
const source = this.sources[this.selectedIndex];
|
|
2534
|
-
if (!source)
|
|
2911
|
+
if (!this.sourceActivityId) {
|
|
2912
|
+
this.error = 'Preview is not configured: set the source activity on the form component.';
|
|
2913
|
+
this.cdr.markForCheck();
|
|
2535
2914
|
return;
|
|
2536
|
-
|
|
2537
|
-
|
|
2915
|
+
}
|
|
2916
|
+
const processInstanceId = this.formIoStateService.processInstanceId;
|
|
2917
|
+
if (!processInstanceId) {
|
|
2918
|
+
this.error = 'Preview is only available from within a running process.';
|
|
2919
|
+
this.cdr.markForCheck();
|
|
2920
|
+
return;
|
|
2921
|
+
}
|
|
2922
|
+
const taskId = this.currentTaskId;
|
|
2923
|
+
if (!taskId) {
|
|
2924
|
+
this.error = 'Preview is only available from within a user task.';
|
|
2925
|
+
this.cdr.markForCheck();
|
|
2538
2926
|
return;
|
|
2927
|
+
}
|
|
2539
2928
|
this.loading = true;
|
|
2540
2929
|
this.error = null;
|
|
2541
2930
|
this.cdr.markForCheck();
|
|
2542
2931
|
this.revokeBlobUrl();
|
|
2543
2932
|
this.previewSubscription?.unsubscribe();
|
|
2544
|
-
this.previewSubscription = this.
|
|
2545
|
-
.
|
|
2933
|
+
this.previewSubscription = this.epistolaPluginService
|
|
2934
|
+
.previewToBlob({
|
|
2935
|
+
taskId,
|
|
2546
2936
|
documentId,
|
|
2547
|
-
|
|
2548
|
-
|
|
2937
|
+
processDefinitionKey: this.processDefinitionKey || null,
|
|
2938
|
+
processInstanceId,
|
|
2939
|
+
sourceActivityId: this.sourceActivityId,
|
|
2940
|
+
inputOverrides: this.value || null,
|
|
2549
2941
|
overrides: null,
|
|
2550
|
-
}, {
|
|
2551
|
-
responseType: 'blob',
|
|
2552
|
-
headers: new HttpHeaders().set('X-Skip-Interceptor', '422'),
|
|
2553
2942
|
})
|
|
2554
2943
|
.subscribe({
|
|
2555
|
-
next: (blob) =>
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
this.loading = false;
|
|
2574
|
-
this.cdr.markForCheck();
|
|
2575
|
-
});
|
|
2944
|
+
next: (blob) => this.handlePreviewSuccess(blob),
|
|
2945
|
+
error: (err) => this.handlePreviewError(err),
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
handlePreviewSuccess(blob) {
|
|
2949
|
+
this.currentBlobUrl = URL.createObjectURL(blob);
|
|
2950
|
+
this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
|
|
2951
|
+
this.error = null;
|
|
2952
|
+
this.loading = false;
|
|
2953
|
+
this.cdr.markForCheck();
|
|
2954
|
+
}
|
|
2955
|
+
handlePreviewError(err) {
|
|
2956
|
+
this.previewUrl = null;
|
|
2957
|
+
if (err.error instanceof Blob) {
|
|
2958
|
+
err.error.text().then((text) => {
|
|
2959
|
+
try {
|
|
2960
|
+
const body = JSON.parse(text);
|
|
2961
|
+
this.error = body.details || body.error || 'Preview could not be generated';
|
|
2576
2962
|
}
|
|
2577
|
-
|
|
2578
|
-
this.error =
|
|
2579
|
-
this.loading = false;
|
|
2580
|
-
this.cdr.markForCheck();
|
|
2963
|
+
catch {
|
|
2964
|
+
this.error = 'Preview could not be generated';
|
|
2581
2965
|
}
|
|
2582
|
-
|
|
2583
|
-
|
|
2966
|
+
this.loading = false;
|
|
2967
|
+
this.cdr.markForCheck();
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
else {
|
|
2971
|
+
this.error = err.error?.error || 'Preview could not be generated';
|
|
2972
|
+
this.loading = false;
|
|
2973
|
+
this.cdr.markForCheck();
|
|
2974
|
+
}
|
|
2584
2975
|
}
|
|
2585
2976
|
revokeBlobUrl() {
|
|
2586
2977
|
if (this.currentBlobUrl) {
|
|
@@ -2589,111 +2980,127 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2589
2980
|
this.previewUrl = null;
|
|
2590
2981
|
}
|
|
2591
2982
|
}
|
|
2592
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, deps: [{ token: EpistolaPluginService }, { token:
|
|
2593
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaDocumentPreviewComponent, isStandalone: true, selector: "epistola-document-preview-component", inputs: { value: "value", disabled: "disabled", label: "label" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2594
|
-
|
|
2983
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, deps: [{ token: EpistolaPluginService }, { token: i2$4.DomSanitizer }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }, { token: EpistolaTaskContextService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2984
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaDocumentPreviewComponent, isStandalone: true, selector: "epistola-document-preview-component", inputs: { value: "value", disabled: "disabled", label: "label", processDefinitionKey: "processDefinitionKey", sourceActivityId: "sourceActivityId", overrideMapping: "overrideMapping" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2985
|
+
<!-- Design-time view: show configuration summary when no runtime context -->
|
|
2986
|
+
<div *ngIf="designMode" class="epistola-preview-panel">
|
|
2987
|
+
<div class="preview-header">
|
|
2988
|
+
<span>{{ label || 'Document Preview' }}</span>
|
|
2989
|
+
</div>
|
|
2990
|
+
<div class="preview-body design-info">
|
|
2991
|
+
<div class="design-section" *ngIf="sourceActivityId">
|
|
2992
|
+
<div class="design-label">Process</div>
|
|
2993
|
+
<div class="design-value">{{ processDefinitionKey || '(any)' }}</div>
|
|
2994
|
+
<div class="design-label">Activity</div>
|
|
2995
|
+
<div class="design-value">{{ sourceActivityId }}</div>
|
|
2996
|
+
</div>
|
|
2997
|
+
<div class="design-section" *ngIf="overrideMapping">
|
|
2998
|
+
<div class="design-label">Input Overrides</div>
|
|
2999
|
+
<div *ngFor="let scope of overrideMappingScopes" class="design-mapping">
|
|
3000
|
+
<div *ngFor="let entry of overrideMappingEntries(scope)" class="design-entry">
|
|
3001
|
+
<span class="design-scope">{{ scope }}</span
|
|
3002
|
+
>.{{ entry.path }}
|
|
3003
|
+
<i class="mdi mdi-arrow-left"></i>
|
|
3004
|
+
<span class="design-field">{{ entry.field }}</span>
|
|
3005
|
+
</div>
|
|
3006
|
+
</div>
|
|
3007
|
+
</div>
|
|
3008
|
+
<div *ngIf="!sourceActivityId" class="design-unconfigured">
|
|
3009
|
+
Auto-discover mode (no process link configured)
|
|
3010
|
+
</div>
|
|
3011
|
+
</div>
|
|
3012
|
+
</div>
|
|
3013
|
+
|
|
3014
|
+
<!-- Runtime view: actual preview -->
|
|
3015
|
+
<div *ngIf="!designMode" class="epistola-preview-panel">
|
|
2595
3016
|
<div class="preview-header">
|
|
2596
3017
|
<span>{{ label || 'Document Preview' }}</span>
|
|
2597
3018
|
<div class="preview-controls">
|
|
2598
|
-
<
|
|
2599
|
-
*ngIf="sources.length > 1"
|
|
2600
|
-
class="preview-select"
|
|
2601
|
-
[value]="selectedIndex"
|
|
2602
|
-
(change)="onSourceChange($event)"
|
|
2603
|
-
>
|
|
2604
|
-
<option *ngFor="let source of sources; let i = index" [value]="i">
|
|
2605
|
-
{{ source.templateName }} ({{ source.activityId }})
|
|
2606
|
-
</option>
|
|
2607
|
-
</select>
|
|
2608
|
-
<button
|
|
2609
|
-
type="button"
|
|
2610
|
-
class="preview-refresh"
|
|
2611
|
-
[disabled]="loading || discovering"
|
|
2612
|
-
(click)="refresh()"
|
|
2613
|
-
>
|
|
3019
|
+
<button type="button" class="preview-refresh" [disabled]="loading" (click)="refresh()">
|
|
2614
3020
|
<i class="mdi mdi-refresh mr-1"></i>
|
|
2615
3021
|
{{ loading ? 'Generating...' : 'Refresh' }}
|
|
2616
3022
|
</button>
|
|
2617
3023
|
</div>
|
|
2618
3024
|
</div>
|
|
2619
3025
|
<div class="preview-body">
|
|
2620
|
-
<div *ngIf="
|
|
2621
|
-
<div *ngIf="
|
|
2622
|
-
<div *ngIf="error && !loading && !discovering" class="preview-unavailable">
|
|
3026
|
+
<div *ngIf="loading" class="preview-loading">Generating preview...</div>
|
|
3027
|
+
<div *ngIf="error && !loading" class="preview-unavailable">
|
|
2623
3028
|
<i class="mdi mdi-information-outline"></i>
|
|
2624
|
-
|
|
3029
|
+
{{ error }}
|
|
2625
3030
|
</div>
|
|
2626
3031
|
<object
|
|
2627
|
-
*ngIf="previewUrl && !loading
|
|
3032
|
+
*ngIf="previewUrl && !loading"
|
|
2628
3033
|
[data]="previewUrl"
|
|
2629
3034
|
type="application/pdf"
|
|
2630
3035
|
class="preview-pdf"
|
|
2631
3036
|
>
|
|
2632
3037
|
PDF preview is not supported in this browser.
|
|
2633
3038
|
</object>
|
|
2634
|
-
<div
|
|
2635
|
-
*ngIf="!previewUrl && !loading && !discovering && !error && sources.length === 0"
|
|
2636
|
-
class="preview-empty"
|
|
2637
|
-
>
|
|
2638
|
-
No previewable documents found
|
|
2639
|
-
</div>
|
|
2640
3039
|
</div>
|
|
2641
3040
|
</div>
|
|
2642
|
-
`, isInline: true, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3041
|
+
`, isInline: true, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.design-info{padding:1rem;min-height:auto}.design-section{margin-bottom:.75rem}.design-label{font-size:.7rem;text-transform:uppercase;color:#868e96;font-weight:600;letter-spacing:.05em}.design-value{font-family:monospace;font-size:.85rem;color:#212529;margin-bottom:.25rem}.design-mapping{margin-top:.25rem}.design-entry{font-family:monospace;font-size:.8rem;color:#495057;padding:.15rem 0}.design-scope{color:#0d6efd}.design-field{color:#198754}.design-entry i{font-size:.7rem;margin:0 .25rem;color:#adb5bd}.design-unconfigured{color:#6c757d;font-style:italic;font-size:.85rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2643
3042
|
}
|
|
2644
3043
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, decorators: [{
|
|
2645
3044
|
type: Component,
|
|
2646
3045
|
args: [{ standalone: true, imports: [CommonModule], selector: 'epistola-document-preview-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2647
|
-
|
|
3046
|
+
<!-- Design-time view: show configuration summary when no runtime context -->
|
|
3047
|
+
<div *ngIf="designMode" class="epistola-preview-panel">
|
|
3048
|
+
<div class="preview-header">
|
|
3049
|
+
<span>{{ label || 'Document Preview' }}</span>
|
|
3050
|
+
</div>
|
|
3051
|
+
<div class="preview-body design-info">
|
|
3052
|
+
<div class="design-section" *ngIf="sourceActivityId">
|
|
3053
|
+
<div class="design-label">Process</div>
|
|
3054
|
+
<div class="design-value">{{ processDefinitionKey || '(any)' }}</div>
|
|
3055
|
+
<div class="design-label">Activity</div>
|
|
3056
|
+
<div class="design-value">{{ sourceActivityId }}</div>
|
|
3057
|
+
</div>
|
|
3058
|
+
<div class="design-section" *ngIf="overrideMapping">
|
|
3059
|
+
<div class="design-label">Input Overrides</div>
|
|
3060
|
+
<div *ngFor="let scope of overrideMappingScopes" class="design-mapping">
|
|
3061
|
+
<div *ngFor="let entry of overrideMappingEntries(scope)" class="design-entry">
|
|
3062
|
+
<span class="design-scope">{{ scope }}</span
|
|
3063
|
+
>.{{ entry.path }}
|
|
3064
|
+
<i class="mdi mdi-arrow-left"></i>
|
|
3065
|
+
<span class="design-field">{{ entry.field }}</span>
|
|
3066
|
+
</div>
|
|
3067
|
+
</div>
|
|
3068
|
+
</div>
|
|
3069
|
+
<div *ngIf="!sourceActivityId" class="design-unconfigured">
|
|
3070
|
+
Auto-discover mode (no process link configured)
|
|
3071
|
+
</div>
|
|
3072
|
+
</div>
|
|
3073
|
+
</div>
|
|
3074
|
+
|
|
3075
|
+
<!-- Runtime view: actual preview -->
|
|
3076
|
+
<div *ngIf="!designMode" class="epistola-preview-panel">
|
|
2648
3077
|
<div class="preview-header">
|
|
2649
3078
|
<span>{{ label || 'Document Preview' }}</span>
|
|
2650
3079
|
<div class="preview-controls">
|
|
2651
|
-
<
|
|
2652
|
-
*ngIf="sources.length > 1"
|
|
2653
|
-
class="preview-select"
|
|
2654
|
-
[value]="selectedIndex"
|
|
2655
|
-
(change)="onSourceChange($event)"
|
|
2656
|
-
>
|
|
2657
|
-
<option *ngFor="let source of sources; let i = index" [value]="i">
|
|
2658
|
-
{{ source.templateName }} ({{ source.activityId }})
|
|
2659
|
-
</option>
|
|
2660
|
-
</select>
|
|
2661
|
-
<button
|
|
2662
|
-
type="button"
|
|
2663
|
-
class="preview-refresh"
|
|
2664
|
-
[disabled]="loading || discovering"
|
|
2665
|
-
(click)="refresh()"
|
|
2666
|
-
>
|
|
3080
|
+
<button type="button" class="preview-refresh" [disabled]="loading" (click)="refresh()">
|
|
2667
3081
|
<i class="mdi mdi-refresh mr-1"></i>
|
|
2668
3082
|
{{ loading ? 'Generating...' : 'Refresh' }}
|
|
2669
3083
|
</button>
|
|
2670
3084
|
</div>
|
|
2671
3085
|
</div>
|
|
2672
3086
|
<div class="preview-body">
|
|
2673
|
-
<div *ngIf="
|
|
2674
|
-
<div *ngIf="
|
|
2675
|
-
<div *ngIf="error && !loading && !discovering" class="preview-unavailable">
|
|
3087
|
+
<div *ngIf="loading" class="preview-loading">Generating preview...</div>
|
|
3088
|
+
<div *ngIf="error && !loading" class="preview-unavailable">
|
|
2676
3089
|
<i class="mdi mdi-information-outline"></i>
|
|
2677
|
-
|
|
3090
|
+
{{ error }}
|
|
2678
3091
|
</div>
|
|
2679
3092
|
<object
|
|
2680
|
-
*ngIf="previewUrl && !loading
|
|
3093
|
+
*ngIf="previewUrl && !loading"
|
|
2681
3094
|
[data]="previewUrl"
|
|
2682
3095
|
type="application/pdf"
|
|
2683
3096
|
class="preview-pdf"
|
|
2684
3097
|
>
|
|
2685
3098
|
PDF preview is not supported in this browser.
|
|
2686
3099
|
</object>
|
|
2687
|
-
<div
|
|
2688
|
-
*ngIf="!previewUrl && !loading && !discovering && !error && sources.length === 0"
|
|
2689
|
-
class="preview-empty"
|
|
2690
|
-
>
|
|
2691
|
-
No previewable documents found
|
|
2692
|
-
</div>
|
|
2693
3100
|
</div>
|
|
2694
3101
|
</div>
|
|
2695
|
-
`, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}\n"] }]
|
|
2696
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type:
|
|
3102
|
+
`, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.design-info{padding:1rem;min-height:auto}.design-section{margin-bottom:.75rem}.design-label{font-size:.7rem;text-transform:uppercase;color:#868e96;font-weight:600;letter-spacing:.05em}.design-value{font-family:monospace;font-size:.85rem;color:#212529;margin-bottom:.25rem}.design-mapping{margin-top:.25rem}.design-entry{font-family:monospace;font-size:.8rem;color:#495057;padding:.15rem 0}.design-scope{color:#0d6efd}.design-field{color:#198754}.design-entry i{font-size:.7rem;margin:0 .25rem;color:#adb5bd}.design-unconfigured{color:#6c757d;font-style:italic;font-size:.85rem}\n"] }]
|
|
3103
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$4.DomSanitizer }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }, { type: EpistolaTaskContextService }], propDecorators: { value: [{
|
|
2697
3104
|
type: Input
|
|
2698
3105
|
}], valueChange: [{
|
|
2699
3106
|
type: Output
|
|
@@ -2701,6 +3108,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2701
3108
|
type: Input
|
|
2702
3109
|
}], label: [{
|
|
2703
3110
|
type: Input
|
|
3111
|
+
}], processDefinitionKey: [{
|
|
3112
|
+
type: Input
|
|
3113
|
+
}], sourceActivityId: [{
|
|
3114
|
+
type: Input
|
|
3115
|
+
}], overrideMapping: [{
|
|
3116
|
+
type: Input
|
|
2704
3117
|
}] } });
|
|
2705
3118
|
|
|
2706
3119
|
class EpistolaAdminPageComponent {
|
|
@@ -2710,8 +3123,12 @@ class EpistolaAdminPageComponent {
|
|
|
2710
3123
|
cards = [];
|
|
2711
3124
|
selectedCard = null;
|
|
2712
3125
|
activeTab = 'actions';
|
|
3126
|
+
overviewTab = 'configurations';
|
|
2713
3127
|
loading = false;
|
|
2714
3128
|
pluginVersion = null;
|
|
3129
|
+
validationViolations = [];
|
|
3130
|
+
reconcilingExecutionIds = new Set();
|
|
3131
|
+
reconcileFeedback = null;
|
|
2715
3132
|
connectionStatuses = [];
|
|
2716
3133
|
usageEntries = [];
|
|
2717
3134
|
pendingJobs = [];
|
|
@@ -2747,6 +3164,9 @@ class EpistolaAdminPageComponent {
|
|
|
2747
3164
|
this.activeTab = tab;
|
|
2748
3165
|
this.updateUrl(this.selectedCard?.configurationId ?? null, tab);
|
|
2749
3166
|
}
|
|
3167
|
+
setOverviewTab(tab) {
|
|
3168
|
+
this.overviewTab = tab;
|
|
3169
|
+
}
|
|
2750
3170
|
refresh() {
|
|
2751
3171
|
this.selectedCard = null;
|
|
2752
3172
|
this.loadData();
|
|
@@ -2763,6 +3183,53 @@ class EpistolaAdminPageComponent {
|
|
|
2763
3183
|
},
|
|
2764
3184
|
});
|
|
2765
3185
|
}
|
|
3186
|
+
/**
|
|
3187
|
+
* Manually reconcile a single stuck catch event. Pulls the current Epistola job
|
|
3188
|
+
* status and re-runs message correlation if the job is in a terminal state.
|
|
3189
|
+
* Refreshes the Pending list on success so the row drops out of the table.
|
|
3190
|
+
*/
|
|
3191
|
+
reconcilePending(job) {
|
|
3192
|
+
if (this.reconcilingExecutionIds.has(job.executionId)) {
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
this.reconcilingExecutionIds.add(job.executionId);
|
|
3196
|
+
this.reconcileFeedback = null;
|
|
3197
|
+
this.adminService.reconcilePending(job.executionId).subscribe({
|
|
3198
|
+
next: (result) => {
|
|
3199
|
+
this.reconcilingExecutionIds.delete(job.executionId);
|
|
3200
|
+
this.reconcileFeedback = {
|
|
3201
|
+
executionId: job.executionId,
|
|
3202
|
+
type: 'success',
|
|
3203
|
+
message: `OK (${result.epistolaStatus}, ${result.correlatedCount ?? 0} correlated)`,
|
|
3204
|
+
};
|
|
3205
|
+
this.loadData();
|
|
3206
|
+
},
|
|
3207
|
+
error: (err) => {
|
|
3208
|
+
this.reconcilingExecutionIds.delete(job.executionId);
|
|
3209
|
+
// 409 from the backend = job is still PENDING/IN_PROGRESS, surface as
|
|
3210
|
+
// informational rather than an error.
|
|
3211
|
+
if (err?.status === 409) {
|
|
3212
|
+
const status = err.error?.epistolaStatus ?? 'still pending';
|
|
3213
|
+
this.reconcileFeedback = {
|
|
3214
|
+
executionId: job.executionId,
|
|
3215
|
+
type: 'pending',
|
|
3216
|
+
message: `Epistola: ${status}. Try again in a moment.`,
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
else {
|
|
3220
|
+
const message = err?.error?.detail ?? err?.error?.message ?? err?.message ?? 'unknown error';
|
|
3221
|
+
this.reconcileFeedback = {
|
|
3222
|
+
executionId: job.executionId,
|
|
3223
|
+
type: 'error',
|
|
3224
|
+
message,
|
|
3225
|
+
};
|
|
3226
|
+
}
|
|
3227
|
+
},
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
isReconciling(job) {
|
|
3231
|
+
return this.reconcilingExecutionIds.has(job.executionId);
|
|
3232
|
+
}
|
|
2766
3233
|
updateUrl(configurationId, tab) {
|
|
2767
3234
|
this.router.navigate([], {
|
|
2768
3235
|
relativeTo: this.route,
|
|
@@ -2814,6 +3281,16 @@ class EpistolaAdminPageComponent {
|
|
|
2814
3281
|
this.tryBuildCards();
|
|
2815
3282
|
},
|
|
2816
3283
|
});
|
|
3284
|
+
// Validation violations are independent of cards — load alongside but don't
|
|
3285
|
+
// gate the loading flag on them.
|
|
3286
|
+
this.adminService.getValidationViolations().subscribe({
|
|
3287
|
+
next: (violations) => {
|
|
3288
|
+
this.validationViolations = violations;
|
|
3289
|
+
},
|
|
3290
|
+
error: () => {
|
|
3291
|
+
this.validationViolations = [];
|
|
3292
|
+
},
|
|
3293
|
+
});
|
|
2817
3294
|
}
|
|
2818
3295
|
tryBuildCards() {
|
|
2819
3296
|
if (!this.connectionLoaded || !this.usageLoaded || !this.pendingLoaded) {
|
|
@@ -2854,25 +3331,60 @@ class EpistolaAdminPageComponent {
|
|
|
2854
3331
|
},
|
|
2855
3332
|
});
|
|
2856
3333
|
}
|
|
2857
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, deps: [{ token: EpistolaAdminService }, { token: i2$
|
|
2858
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaAdminPageComponent, isStandalone: true, selector: "epistola-admin-page", ngImport: i0, template: "<div class=\"epistola-admin\">\n <!-- Overview: card grid (no configuration selected) -->\n <ng-container *ngIf=\"!selectedCard\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <div class=\"d-flex align-items-center\">\n <h5 class=\"mb-0\">{{ 'epistolaAdminOverview' | pluginTranslate: 'epistola' | async }}</h5>\n <span *ngIf=\"pluginVersion\" class=\"version-badge ms-2\">v{{ pluginVersion }}</span>\n </div>\n <button class=\"btn btn-outline-primary btn-sm\" (click)=\"refresh()\" [disabled]=\"loading\">\n {{ 'epistolaAdminRefresh' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div *ngIf=\"loading\" class=\"text-muted\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted\">\n {{ 'epistolaAdminNoConfigurations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length > 0\" class=\"card-grid\">\n <div\n *ngFor=\"let card of cards\"\n class=\"config-card\"\n [class.config-card--ok]=\"card.reachable && card.problemCount === 0\"\n [class.config-card--warning]=\"card.reachable && card.problemCount > 0\"\n [class.config-card--error]=\"!card.reachable\"\n (click)=\"selectConfiguration(card)\"\n >\n <div class=\"config-card__header\">\n <span\n class=\"status-dot\"\n [class.status-dot--ok]=\"card.reachable\"\n [class.status-dot--error]=\"!card.reachable\"\n >\n </span>\n <h5 class=\"config-card__title\">{{ card.configurationTitle }}</h5>\n </div>\n\n <div class=\"config-card__body\">\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async\n }}</span>\n <code class=\"config-card__value\">{{ card.tenantId }}</code>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" [type]=\"card.reachable ? 'green' : 'red'\">\n {{\n card.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n </div>\n <div *ngIf=\"card.serverVersion\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.serverVersion }}</span>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.usageCount }}</span>\n </div>\n <div *ngIf=\"card.pendingJobs.length > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">{{ card.pendingJobs.length }}</cds-tag>\n </div>\n <div *ngIf=\"card.problemCount > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"red\">{{ card.problemCount }}</cds-tag>\n </div>\n </div>\n\n <div class=\"config-card__footer\">\n <span class=\"config-card__latency\">{{ card.latencyMs }} ms</span>\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Detail view: selected configuration -->\n <ng-container *ngIf=\"selectedCard\">\n <div class=\"detail-header mb-3\">\n <button class=\"btn btn-link btn-sm p-0\" (click)=\"backToOverview()\">\n ← {{ 'epistolaAdminBackToOverview' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div class=\"detail-summary mb-4\">\n <h4>\n <span\n class=\"status-dot me-2\"\n [class.status-dot--ok]=\"selectedCard.reachable\"\n [class.status-dot--error]=\"!selectedCard.reachable\"\n >\n </span>\n {{ selectedCard.configurationTitle }}\n </h4>\n\n <table class=\"table table-sm detail-info-table\">\n <tbody>\n <tr>\n <th>{{ 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <code>{{ selectedCard.tenantId }}</code>\n </td>\n </tr>\n <tr>\n <th>{{ 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <cds-tag size=\"sm\" [type]=\"selectedCard.reachable ? 'green' : 'red'\">\n {{\n selectedCard.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n <span class=\"text-muted ms-2\">{{ selectedCard.latencyMs }} ms</span>\n </td>\n </tr>\n <tr *ngIf=\"selectedCard.serverVersion\">\n <th>{{ 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async }}</th>\n <td>{{ selectedCard.serverVersion }}</td>\n </tr>\n <tr *ngIf=\"selectedCard.errorMessage\">\n <th>{{ 'epistolaAdminError' | pluginTranslate: 'epistola' | async }}</th>\n <td class=\"text-danger\">{{ selectedCard.errorMessage }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Tabs -->\n <ng-template #actionsHeading>\n {{ 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ selectedCard.usageEntries.length }}</cds-tag>\n </ng-template>\n\n <ng-template #pendingHeading>\n {{ 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n size=\"sm\"\n [type]=\"selectedCard.pendingJobs.length > 0 ? 'blue' : 'gray'\"\n class=\"ms-1\"\n >\n {{ selectedCard.pendingJobs.length }}\n </cds-tag>\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"actionsHeading\"\n [active]=\"activeTab === 'actions'\"\n (selected)=\"setActiveTab('actions')\"\n >\n <div *ngIf=\"selectedCard.usageEntries.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoUsageForConfig' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.usageEntries.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCase' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminAction' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let entry of selectedCard.usageEntries\"\n [class.table-warning]=\"entry.problems.length > 0\"\n >\n <td>{{ entry.caseDefinitionKey || '-' }}</td>\n <td>\n <a\n *ngIf=\"entry.caseDefinitionKey && entry.caseDefinitionVersionTag\"\n [routerLink]=\"[\n '/case-management',\n 'case',\n entry.caseDefinitionKey,\n 'version',\n entry.caseDefinitionVersionTag,\n 'processes',\n entry.processDefinitionKey,\n ]\"\n class=\"usage-link\"\n >\n {{ entry.processDefinitionName }}\n </a>\n <span *ngIf=\"!entry.caseDefinitionKey || !entry.caseDefinitionVersionTag\">\n {{ entry.processDefinitionName }}\n </span>\n </td>\n <td>{{ entry.activityName }}</td>\n <td>\n <code>{{ entry.actionKey }}</code>\n </td>\n <td>\n <cds-tag *ngIf=\"entry.problems.length === 0\" size=\"sm\" type=\"green\">OK</cds-tag>\n <cds-tag\n *ngFor=\"let problem of entry.problems\"\n size=\"sm\"\n type=\"red\"\n class=\"d-block mb-1\"\n >\n {{ problem }}\n </cds-tag>\n </td>\n <td>\n <button\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"exportProcessLink(entry)\"\n [title]=\"'epistolaAdminExport' | pluginTranslate: 'epistola' | async\"\n >\n ⤓\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"pendingHeading\"\n [active]=\"activeTab === 'pending'\"\n (selected)=\"setActiveTab('pending')\"\n >\n <div *ngIf=\"selectedCard.pendingJobs.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoPendingJobs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.pendingJobs.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminRequestId' | pluginTranslate: 'epistola' | async }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let job of selectedCard.pendingJobs\">\n <td>{{ job.processDefinitionName }}</td>\n <td>{{ job.activityName }}</td>\n <td>\n <code>{{ job.requestId }}</code>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n </cds-tabs>\n </ng-container>\n</div>\n", styles: [".epistola-admin{padding:1.5rem}.epistola-admin .version-badge{font-size:.75rem;font-weight:500;padding:.2em .6em;border-radius:4px;background-color:#e8e8e8;color:#525252}.epistola-admin .badge{font-size:.85em;padding:.35em .65em}.epistola-admin code{font-size:.9em;color:#525252}.epistola-admin table th{font-weight:600;white-space:nowrap}.epistola-admin .status-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:.5rem;flex-shrink:0;background-color:#adb5bd}.epistola-admin .status-dot--ok{background-color:#198754}.epistola-admin .status-dot--error{background-color:#dc3545}.epistola-admin .card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}.epistola-admin .config-card{border:1px solid #dee2e6;border-radius:8px;padding:1.25rem;cursor:pointer;transition:box-shadow .15s ease,border-color .15s ease;background:#fff}.epistola-admin .config-card:hover{box-shadow:0 2px 8px #0000001a}.epistola-admin .config-card--ok{border-left:4px solid #198754}.epistola-admin .config-card--warning{border-left:4px solid #ffc107}.epistola-admin .config-card--error{border-left:4px solid #dc3545}.epistola-admin .config-card__header{display:flex;align-items:center;margin-bottom:1rem}.epistola-admin .config-card__title{margin:0;font-size:1.05rem;font-weight:600;color:#161616}.epistola-admin .config-card__body{display:flex;flex-direction:column;gap:.5rem}.epistola-admin .config-card__field{display:flex;justify-content:space-between;align-items:center}.epistola-admin .config-card__label{font-size:.875rem;color:#6c757d}.epistola-admin .config-card__value{font-size:.875rem;color:#161616}.epistola-admin .config-card__footer{margin-top:1rem;padding-top:.75rem;border-top:1px solid #f0f0f0;text-align:right}.epistola-admin .config-card__latency{font-size:.8rem;color:#adb5bd}.epistola-admin .usage-link{color:#0f62fe;text-decoration:none}.epistola-admin .usage-link:hover{text-decoration:underline}.epistola-admin .detail-info-table{max-width:500px}.epistola-admin .detail-info-table th{width:140px}.epistola-admin .detail-summary{padding:1rem 0;border-bottom:1px solid #dee2e6}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$3.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i5.Tabs, selector: "cds-tabs, ibm-tabs", inputs: ["position", "cacheActive", "followFocus", "isNavigation", "ariaLabel", "ariaLabelledby", "type", "theme", "skeleton"] }, { kind: "component", type: i5.Tab, selector: "cds-tab, ibm-tab", inputs: ["heading", "title", "context", "active", "disabled", "tabIndex", "id", "cacheActive", "tabContent", "templateContext"], outputs: ["selected"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i6.Tag, selector: "cds-tag, ibm-tag", inputs: ["type", "size", "class", "skeleton"] }] });
|
|
3334
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, deps: [{ token: EpistolaAdminService }, { token: i2$5.ActivatedRoute }, { token: i2$5.Router }], target: i0.ɵɵFactoryTarget.Component });
|
|
3335
|
+
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 <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"configurationsHeading\"\n [active]=\"overviewTab === 'configurations'\"\n (selected)=\"setOverviewTab('configurations')\"\n >\n <div *ngIf=\"loading\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminNoConfigurations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length > 0\" class=\"card-grid\">\n <div\n *ngFor=\"let card of cards\"\n class=\"config-card\"\n [class.config-card--ok]=\"card.reachable && card.problemCount === 0\"\n [class.config-card--warning]=\"card.reachable && card.problemCount > 0\"\n [class.config-card--error]=\"!card.reachable\"\n (click)=\"selectConfiguration(card)\"\n >\n <div class=\"config-card__header\">\n <span\n class=\"status-dot\"\n [class.status-dot--ok]=\"card.reachable\"\n [class.status-dot--error]=\"!card.reachable\"\n >\n </span>\n <h5 class=\"config-card__title\">{{ card.configurationTitle }}</h5>\n </div>\n\n <div class=\"config-card__body\">\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async\n }}</span>\n <code class=\"config-card__value\">{{ card.tenantId }}</code>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" [type]=\"card.reachable ? 'green' : 'red'\">\n {{\n card.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n </div>\n <div *ngIf=\"card.serverVersion\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.serverVersion }}</span>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.usageCount }}</span>\n </div>\n <div *ngIf=\"card.pendingJobs.length > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">{{ card.pendingJobs.length }}</cds-tag>\n </div>\n <div *ngIf=\"card.problemCount > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"red\">{{ card.problemCount }}</cds-tag>\n </div>\n </div>\n\n <div class=\"config-card__footer\">\n <span class=\"config-card__latency\">{{ card.latencyMs }} ms</span>\n </div>\n </div>\n </div>\n </cds-tab>\n\n <cds-tab\n [heading]=\"validationsHeading\"\n [active]=\"overviewTab === 'validations'\"\n (selected)=\"setOverviewTab('validations')\"\n >\n <div *ngIf=\"validationViolations.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoValidations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"validationViolations.length > 0\" class=\"mt-3\">\n <p class=\"text-muted mb-3\">\n {{ 'epistolaAdminValidationWarningBody' | pluginTranslate: 'epistola' | async }}\n </p>\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminValidationCode' | pluginTranslate: 'epistola' | async }}</th>\n <th>\n {{ 'epistolaAdminValidationMessage' | pluginTranslate: 'epistola' | async }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let v of validationViolations\">\n <td>\n {{ v.processDefinitionName || v.processDefinitionKey }}\n <div *ngIf=\"v.processDefinitionName\" class=\"text-muted small\">\n <code>{{ v.processDefinitionKey }}</code>\n </div>\n </td>\n <td>\n <code>{{ v.activityId }}</code>\n </td>\n <td>\n <cds-tag size=\"sm\" type=\"red\">{{ v.code }}</cds-tag>\n </td>\n <td>{{ v.message }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </cds-tab>\n </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 <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 </cds-tabs>\n </ng-container>\n</div>\n", styles: [".epistola-admin{padding:1.5rem}.epistola-admin .version-badge{font-size:.75rem;font-weight:500;padding:.2em .6em;border-radius:4px;background-color:#e8e8e8;color:#525252}.epistola-admin .badge{font-size:.85em;padding:.35em .65em}.epistola-admin code{font-size:.9em;color:#525252}.epistola-admin table th{font-weight:600;white-space:nowrap}.epistola-admin .status-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:.5rem;flex-shrink:0;background-color:#adb5bd}.epistola-admin .status-dot--ok{background-color:#198754}.epistola-admin .status-dot--error{background-color:#dc3545}.epistola-admin .card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}.epistola-admin .config-card{border:1px solid #dee2e6;border-radius:8px;padding:1.25rem;cursor:pointer;transition:box-shadow .15s ease,border-color .15s ease;background:#fff}.epistola-admin .config-card:hover{box-shadow:0 2px 8px #0000001a}.epistola-admin .config-card--ok{border-left:4px solid #198754}.epistola-admin .config-card--warning{border-left:4px solid #ffc107}.epistola-admin .config-card--error{border-left:4px solid #dc3545}.epistola-admin .config-card__header{display:flex;align-items:center;margin-bottom:1rem}.epistola-admin .config-card__title{margin:0;font-size:1.05rem;font-weight:600;color:#161616}.epistola-admin .config-card__body{display:flex;flex-direction:column;gap:.5rem}.epistola-admin .config-card__field{display:flex;justify-content:space-between;align-items:center}.epistola-admin .config-card__label{font-size:.875rem;color:#6c757d}.epistola-admin .config-card__value{font-size:.875rem;color:#161616}.epistola-admin .config-card__footer{margin-top:1rem;padding-top:.75rem;border-top:1px solid #f0f0f0;text-align:right}.epistola-admin .config-card__latency{font-size:.8rem;color:#adb5bd}.epistola-admin .usage-link{color:#0f62fe;text-decoration:none}.epistola-admin .usage-link:hover{text-decoration:underline}.epistola-admin .detail-info-table{max-width:500px}.epistola-admin .detail-info-table th{width:140px}.epistola-admin .detail-summary{padding:1rem 0;border-bottom:1px solid #dee2e6}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$5.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i5$1.Tabs, selector: "cds-tabs, ibm-tabs", inputs: ["position", "cacheActive", "followFocus", "isNavigation", "ariaLabel", "ariaLabelledby", "type", "theme", "skeleton"] }, { kind: "component", type: i5$1.Tab, selector: "cds-tab, ibm-tab", inputs: ["heading", "title", "context", "active", "disabled", "tabIndex", "id", "cacheActive", "tabContent", "templateContext"], outputs: ["selected"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i6.Tag, selector: "cds-tag, ibm-tag", inputs: ["type", "size", "class", "skeleton"] }] });
|
|
2859
3336
|
}
|
|
2860
3337
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, decorators: [{
|
|
2861
3338
|
type: Component,
|
|
2862
|
-
args: [{ selector: 'epistola-admin-page', standalone: true, imports: [CommonModule, RouterModule, PluginTranslatePipeModule, TabsModule, TagModule], template: "<div class=\"epistola-admin\">\n <!-- Overview: card grid (no configuration selected) -->\n <ng-container *ngIf=\"!selectedCard\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <div class=\"d-flex align-items-center\">\n <h5 class=\"mb-0\">{{ 'epistolaAdminOverview' | pluginTranslate: 'epistola' | async }}</h5>\n <span *ngIf=\"pluginVersion\" class=\"version-badge ms-2\">v{{ pluginVersion }}</span>\n </div>\n <button class=\"btn btn-outline-primary btn-sm\" (click)=\"refresh()\" [disabled]=\"loading\">\n {{ 'epistolaAdminRefresh' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div *ngIf=\"loading\" class=\"text-muted\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted\">\n {{ 'epistolaAdminNoConfigurations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length > 0\" class=\"card-grid\">\n <div\n *ngFor=\"let card of cards\"\n class=\"config-card\"\n [class.config-card--ok]=\"card.reachable && card.problemCount === 0\"\n [class.config-card--warning]=\"card.reachable && card.problemCount > 0\"\n [class.config-card--error]=\"!card.reachable\"\n (click)=\"selectConfiguration(card)\"\n >\n <div class=\"config-card__header\">\n <span\n class=\"status-dot\"\n [class.status-dot--ok]=\"card.reachable\"\n [class.status-dot--error]=\"!card.reachable\"\n >\n </span>\n <h5 class=\"config-card__title\">{{ card.configurationTitle }}</h5>\n </div>\n\n <div class=\"config-card__body\">\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async\n }}</span>\n <code class=\"config-card__value\">{{ card.tenantId }}</code>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" [type]=\"card.reachable ? 'green' : 'red'\">\n {{\n card.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n </div>\n <div *ngIf=\"card.serverVersion\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.serverVersion }}</span>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.usageCount }}</span>\n </div>\n <div *ngIf=\"card.pendingJobs.length > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">{{ card.pendingJobs.length }}</cds-tag>\n </div>\n <div *ngIf=\"card.problemCount > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"red\">{{ card.problemCount }}</cds-tag>\n </div>\n </div>\n\n <div class=\"config-card__footer\">\n <span class=\"config-card__latency\">{{ card.latencyMs }} ms</span>\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Detail view: selected configuration -->\n <ng-container *ngIf=\"selectedCard\">\n <div class=\"detail-header mb-3\">\n <button class=\"btn btn-link btn-sm p-0\" (click)=\"backToOverview()\">\n ← {{ 'epistolaAdminBackToOverview' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div class=\"detail-summary mb-4\">\n <h4>\n <span\n class=\"status-dot me-2\"\n [class.status-dot--ok]=\"selectedCard.reachable\"\n [class.status-dot--error]=\"!selectedCard.reachable\"\n >\n </span>\n {{ selectedCard.configurationTitle }}\n </h4>\n\n <table class=\"table table-sm detail-info-table\">\n <tbody>\n <tr>\n <th>{{ 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <code>{{ selectedCard.tenantId }}</code>\n </td>\n </tr>\n <tr>\n <th>{{ 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <cds-tag size=\"sm\" [type]=\"selectedCard.reachable ? 'green' : 'red'\">\n {{\n selectedCard.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n <span class=\"text-muted ms-2\">{{ selectedCard.latencyMs }} ms</span>\n </td>\n </tr>\n <tr *ngIf=\"selectedCard.serverVersion\">\n <th>{{ 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async }}</th>\n <td>{{ selectedCard.serverVersion }}</td>\n </tr>\n <tr *ngIf=\"selectedCard.errorMessage\">\n <th>{{ 'epistolaAdminError' | pluginTranslate: 'epistola' | async }}</th>\n <td class=\"text-danger\">{{ selectedCard.errorMessage }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Tabs -->\n <ng-template #actionsHeading>\n {{ 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ selectedCard.usageEntries.length }}</cds-tag>\n </ng-template>\n\n <ng-template #pendingHeading>\n {{ 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n size=\"sm\"\n [type]=\"selectedCard.pendingJobs.length > 0 ? 'blue' : 'gray'\"\n class=\"ms-1\"\n >\n {{ selectedCard.pendingJobs.length }}\n </cds-tag>\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"actionsHeading\"\n [active]=\"activeTab === 'actions'\"\n (selected)=\"setActiveTab('actions')\"\n >\n <div *ngIf=\"selectedCard.usageEntries.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoUsageForConfig' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.usageEntries.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCase' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminAction' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let entry of selectedCard.usageEntries\"\n [class.table-warning]=\"entry.problems.length > 0\"\n >\n <td>{{ entry.caseDefinitionKey || '-' }}</td>\n <td>\n <a\n *ngIf=\"entry.caseDefinitionKey && entry.caseDefinitionVersionTag\"\n [routerLink]=\"[\n '/case-management',\n 'case',\n entry.caseDefinitionKey,\n 'version',\n entry.caseDefinitionVersionTag,\n 'processes',\n entry.processDefinitionKey,\n ]\"\n class=\"usage-link\"\n >\n {{ entry.processDefinitionName }}\n </a>\n <span *ngIf=\"!entry.caseDefinitionKey || !entry.caseDefinitionVersionTag\">\n {{ entry.processDefinitionName }}\n </span>\n </td>\n <td>{{ entry.activityName }}</td>\n <td>\n <code>{{ entry.actionKey }}</code>\n </td>\n <td>\n <cds-tag *ngIf=\"entry.problems.length === 0\" size=\"sm\" type=\"green\">OK</cds-tag>\n <cds-tag\n *ngFor=\"let problem of entry.problems\"\n size=\"sm\"\n type=\"red\"\n class=\"d-block mb-1\"\n >\n {{ problem }}\n </cds-tag>\n </td>\n <td>\n <button\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"exportProcessLink(entry)\"\n [title]=\"'epistolaAdminExport' | pluginTranslate: 'epistola' | async\"\n >\n ⤓\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"pendingHeading\"\n [active]=\"activeTab === 'pending'\"\n (selected)=\"setActiveTab('pending')\"\n >\n <div *ngIf=\"selectedCard.pendingJobs.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoPendingJobs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.pendingJobs.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminRequestId' | pluginTranslate: 'epistola' | async }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let job of selectedCard.pendingJobs\">\n <td>{{ job.processDefinitionName }}</td>\n <td>{{ job.activityName }}</td>\n <td>\n <code>{{ job.requestId }}</code>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n </cds-tabs>\n </ng-container>\n</div>\n", styles: [".epistola-admin{padding:1.5rem}.epistola-admin .version-badge{font-size:.75rem;font-weight:500;padding:.2em .6em;border-radius:4px;background-color:#e8e8e8;color:#525252}.epistola-admin .badge{font-size:.85em;padding:.35em .65em}.epistola-admin code{font-size:.9em;color:#525252}.epistola-admin table th{font-weight:600;white-space:nowrap}.epistola-admin .status-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:.5rem;flex-shrink:0;background-color:#adb5bd}.epistola-admin .status-dot--ok{background-color:#198754}.epistola-admin .status-dot--error{background-color:#dc3545}.epistola-admin .card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}.epistola-admin .config-card{border:1px solid #dee2e6;border-radius:8px;padding:1.25rem;cursor:pointer;transition:box-shadow .15s ease,border-color .15s ease;background:#fff}.epistola-admin .config-card:hover{box-shadow:0 2px 8px #0000001a}.epistola-admin .config-card--ok{border-left:4px solid #198754}.epistola-admin .config-card--warning{border-left:4px solid #ffc107}.epistola-admin .config-card--error{border-left:4px solid #dc3545}.epistola-admin .config-card__header{display:flex;align-items:center;margin-bottom:1rem}.epistola-admin .config-card__title{margin:0;font-size:1.05rem;font-weight:600;color:#161616}.epistola-admin .config-card__body{display:flex;flex-direction:column;gap:.5rem}.epistola-admin .config-card__field{display:flex;justify-content:space-between;align-items:center}.epistola-admin .config-card__label{font-size:.875rem;color:#6c757d}.epistola-admin .config-card__value{font-size:.875rem;color:#161616}.epistola-admin .config-card__footer{margin-top:1rem;padding-top:.75rem;border-top:1px solid #f0f0f0;text-align:right}.epistola-admin .config-card__latency{font-size:.8rem;color:#adb5bd}.epistola-admin .usage-link{color:#0f62fe;text-decoration:none}.epistola-admin .usage-link:hover{text-decoration:underline}.epistola-admin .detail-info-table{max-width:500px}.epistola-admin .detail-info-table th{width:140px}.epistola-admin .detail-summary{padding:1rem 0;border-bottom:1px solid #dee2e6}\n"] }]
|
|
2863
|
-
}], ctorParameters: () => [{ type: EpistolaAdminService }, { type: i2$
|
|
3339
|
+
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 <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"configurationsHeading\"\n [active]=\"overviewTab === 'configurations'\"\n (selected)=\"setOverviewTab('configurations')\"\n >\n <div *ngIf=\"loading\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminNoConfigurations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length > 0\" class=\"card-grid\">\n <div\n *ngFor=\"let card of cards\"\n class=\"config-card\"\n [class.config-card--ok]=\"card.reachable && card.problemCount === 0\"\n [class.config-card--warning]=\"card.reachable && card.problemCount > 0\"\n [class.config-card--error]=\"!card.reachable\"\n (click)=\"selectConfiguration(card)\"\n >\n <div class=\"config-card__header\">\n <span\n class=\"status-dot\"\n [class.status-dot--ok]=\"card.reachable\"\n [class.status-dot--error]=\"!card.reachable\"\n >\n </span>\n <h5 class=\"config-card__title\">{{ card.configurationTitle }}</h5>\n </div>\n\n <div class=\"config-card__body\">\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async\n }}</span>\n <code class=\"config-card__value\">{{ card.tenantId }}</code>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" [type]=\"card.reachable ? 'green' : 'red'\">\n {{\n card.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n </div>\n <div *ngIf=\"card.serverVersion\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.serverVersion }}</span>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.usageCount }}</span>\n </div>\n <div *ngIf=\"card.pendingJobs.length > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">{{ card.pendingJobs.length }}</cds-tag>\n </div>\n <div *ngIf=\"card.problemCount > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"red\">{{ card.problemCount }}</cds-tag>\n </div>\n </div>\n\n <div class=\"config-card__footer\">\n <span class=\"config-card__latency\">{{ card.latencyMs }} ms</span>\n </div>\n </div>\n </div>\n </cds-tab>\n\n <cds-tab\n [heading]=\"validationsHeading\"\n [active]=\"overviewTab === 'validations'\"\n (selected)=\"setOverviewTab('validations')\"\n >\n <div *ngIf=\"validationViolations.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoValidations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"validationViolations.length > 0\" class=\"mt-3\">\n <p class=\"text-muted mb-3\">\n {{ 'epistolaAdminValidationWarningBody' | pluginTranslate: 'epistola' | async }}\n </p>\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminValidationCode' | pluginTranslate: 'epistola' | async }}</th>\n <th>\n {{ 'epistolaAdminValidationMessage' | pluginTranslate: 'epistola' | async }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let v of validationViolations\">\n <td>\n {{ v.processDefinitionName || v.processDefinitionKey }}\n <div *ngIf=\"v.processDefinitionName\" class=\"text-muted small\">\n <code>{{ v.processDefinitionKey }}</code>\n </div>\n </td>\n <td>\n <code>{{ v.activityId }}</code>\n </td>\n <td>\n <cds-tag size=\"sm\" type=\"red\">{{ v.code }}</cds-tag>\n </td>\n <td>{{ v.message }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </cds-tab>\n </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 <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 </cds-tabs>\n </ng-container>\n</div>\n", styles: [".epistola-admin{padding:1.5rem}.epistola-admin .version-badge{font-size:.75rem;font-weight:500;padding:.2em .6em;border-radius:4px;background-color:#e8e8e8;color:#525252}.epistola-admin .badge{font-size:.85em;padding:.35em .65em}.epistola-admin code{font-size:.9em;color:#525252}.epistola-admin table th{font-weight:600;white-space:nowrap}.epistola-admin .status-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:.5rem;flex-shrink:0;background-color:#adb5bd}.epistola-admin .status-dot--ok{background-color:#198754}.epistola-admin .status-dot--error{background-color:#dc3545}.epistola-admin .card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}.epistola-admin .config-card{border:1px solid #dee2e6;border-radius:8px;padding:1.25rem;cursor:pointer;transition:box-shadow .15s ease,border-color .15s ease;background:#fff}.epistola-admin .config-card:hover{box-shadow:0 2px 8px #0000001a}.epistola-admin .config-card--ok{border-left:4px solid #198754}.epistola-admin .config-card--warning{border-left:4px solid #ffc107}.epistola-admin .config-card--error{border-left:4px solid #dc3545}.epistola-admin .config-card__header{display:flex;align-items:center;margin-bottom:1rem}.epistola-admin .config-card__title{margin:0;font-size:1.05rem;font-weight:600;color:#161616}.epistola-admin .config-card__body{display:flex;flex-direction:column;gap:.5rem}.epistola-admin .config-card__field{display:flex;justify-content:space-between;align-items:center}.epistola-admin .config-card__label{font-size:.875rem;color:#6c757d}.epistola-admin .config-card__value{font-size:.875rem;color:#161616}.epistola-admin .config-card__footer{margin-top:1rem;padding-top:.75rem;border-top:1px solid #f0f0f0;text-align:right}.epistola-admin .config-card__latency{font-size:.8rem;color:#adb5bd}.epistola-admin .usage-link{color:#0f62fe;text-decoration:none}.epistola-admin .usage-link:hover{text-decoration:underline}.epistola-admin .detail-info-table{max-width:500px}.epistola-admin .detail-info-table th{width:140px}.epistola-admin .detail-summary{padding:1rem 0;border-bottom:1px solid #dee2e6}\n"] }]
|
|
3340
|
+
}], ctorParameters: () => [{ type: EpistolaAdminService }, { type: i2$5.ActivatedRoute }, { type: i2$5.Router }] });
|
|
3341
|
+
|
|
3342
|
+
function isRuntimeWindow(value) {
|
|
3343
|
+
return typeof value === 'object' && value !== null;
|
|
3344
|
+
}
|
|
3345
|
+
/**
|
|
3346
|
+
* Reads the runtime feature flag that decides whether the Epistola plugin
|
|
3347
|
+
* surfaces (admin menu, /epistola route, plugin specification, Formio
|
|
3348
|
+
* components) should activate in the host Valtimo app.
|
|
3349
|
+
*
|
|
3350
|
+
* The flag is sourced from `window['env']['epistolaEnabled']`, populated at
|
|
3351
|
+
* container start by `envsubst` against `assets/config.template.js` (the
|
|
3352
|
+
* standard Valtimo runtime-config pattern). Defaults to enabled — only the
|
|
3353
|
+
* literal `false` or string `'false'` disables the plugin, matching the
|
|
3354
|
+
* backend's `epistola.enabled` `matchIfMissing = true` semantics.
|
|
3355
|
+
*
|
|
3356
|
+
* Exposed as a runtime helper rather than evaluated directly in `@NgModule`
|
|
3357
|
+
* decorator metadata because Angular's AOT compiler cannot statically resolve
|
|
3358
|
+
* `window` accesses (NG1010). Read from runtime code such as specification
|
|
3359
|
+
* property getters, route guards, or the environment initializer instead.
|
|
3360
|
+
*/
|
|
3361
|
+
function isEpistolaEnabled() {
|
|
3362
|
+
const runtimeWindow = Reflect.get(globalThis, 'window');
|
|
3363
|
+
if (!runtimeWindow)
|
|
3364
|
+
return true;
|
|
3365
|
+
if (!isRuntimeWindow(runtimeWindow))
|
|
3366
|
+
return true;
|
|
3367
|
+
const flag = runtimeWindow.env ? runtimeWindow.env.epistolaEnabled : undefined;
|
|
3368
|
+
return flag !== false && flag !== 'false';
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
const epistolaEnabledGuard = () => {
|
|
3372
|
+
if (isEpistolaEnabled())
|
|
3373
|
+
return true;
|
|
3374
|
+
return inject(Router).parseUrl('/');
|
|
3375
|
+
};
|
|
2864
3376
|
|
|
2865
3377
|
const routes = [
|
|
2866
3378
|
{
|
|
2867
3379
|
path: 'epistola',
|
|
2868
3380
|
component: EpistolaAdminPageComponent,
|
|
2869
|
-
canActivate: [AuthGuardService],
|
|
3381
|
+
canActivate: [epistolaEnabledGuard, AuthGuardService],
|
|
2870
3382
|
data: { title: 'Epistola', roles: ['ROLE_ADMIN'] },
|
|
2871
3383
|
},
|
|
2872
3384
|
];
|
|
2873
3385
|
class EpistolaAdminRoutingModule {
|
|
2874
3386
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
2875
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [i2$
|
|
3387
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [i2$5.RouterModule], exports: [RouterModule] });
|
|
2876
3388
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [RouterModule.forChild(routes), RouterModule] });
|
|
2877
3389
|
}
|
|
2878
3390
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, decorators: [{
|
|
@@ -2883,19 +3395,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2883
3395
|
}]
|
|
2884
3396
|
}] });
|
|
2885
3397
|
|
|
2886
|
-
const
|
|
2887
|
-
type: 'epistola-
|
|
2888
|
-
selector: 'epistola-
|
|
2889
|
-
title: 'Epistola
|
|
3398
|
+
const EPISTOLA_DOCUMENT_OPTIONS = {
|
|
3399
|
+
type: 'epistola-document',
|
|
3400
|
+
selector: 'epistola-document-element',
|
|
3401
|
+
title: 'Epistola Document',
|
|
2890
3402
|
group: 'basic',
|
|
2891
|
-
icon: '
|
|
3403
|
+
icon: 'file-pdf-o',
|
|
2892
3404
|
emptyValue: null,
|
|
2893
|
-
fieldOptions: ['
|
|
3405
|
+
fieldOptions: ['label', 'display', 'documentVariable', 'tenantIdVariable', 'filename'],
|
|
2894
3406
|
};
|
|
2895
|
-
function
|
|
2896
|
-
if (
|
|
2897
|
-
|
|
3407
|
+
function registerEpistolaDocumentComponent(injector) {
|
|
3408
|
+
if (customElements.get(EPISTOLA_DOCUMENT_OPTIONS.selector)) {
|
|
3409
|
+
return;
|
|
2898
3410
|
}
|
|
3411
|
+
registerCustomFormioComponent(EPISTOLA_DOCUMENT_OPTIONS, EpistolaDocumentComponent, injector);
|
|
2899
3412
|
}
|
|
2900
3413
|
|
|
2901
3414
|
const EPISTOLA_RETRY_FORM_OPTIONS = {
|
|
@@ -2913,33 +3426,548 @@ function registerEpistolaRetryFormComponent(injector) {
|
|
|
2913
3426
|
}
|
|
2914
3427
|
}
|
|
2915
3428
|
|
|
2916
|
-
const
|
|
2917
|
-
type: 'epistola-preview
|
|
2918
|
-
selector: 'epistola-preview-
|
|
2919
|
-
title: 'Epistola Preview',
|
|
3429
|
+
const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
|
|
3430
|
+
type: 'epistola-document-preview',
|
|
3431
|
+
selector: 'epistola-document-preview-element',
|
|
3432
|
+
title: 'Epistola Document Preview',
|
|
2920
3433
|
group: 'basic',
|
|
2921
|
-
icon: '
|
|
3434
|
+
icon: 'file-pdf-o',
|
|
2922
3435
|
emptyValue: null,
|
|
2923
|
-
fieldOptions: ['label'],
|
|
3436
|
+
fieldOptions: ['label', 'processDefinitionKey', 'sourceActivityId', 'overrideMapping'],
|
|
3437
|
+
editForm: () => ({
|
|
3438
|
+
components: [
|
|
3439
|
+
{
|
|
3440
|
+
type: 'epistola-process-link-selector',
|
|
3441
|
+
key: 'processLinkSelection',
|
|
3442
|
+
label: 'Process Link',
|
|
3443
|
+
weight: 10,
|
|
3444
|
+
validate: { required: true },
|
|
3445
|
+
},
|
|
3446
|
+
{
|
|
3447
|
+
type: 'epistola-override-builder',
|
|
3448
|
+
key: 'overrideMapping',
|
|
3449
|
+
label: 'Input Overrides',
|
|
3450
|
+
weight: 20,
|
|
3451
|
+
},
|
|
3452
|
+
],
|
|
3453
|
+
}),
|
|
2924
3454
|
};
|
|
2925
|
-
function
|
|
2926
|
-
if (
|
|
2927
|
-
|
|
3455
|
+
function registerEpistolaDocumentPreviewComponent(injector) {
|
|
3456
|
+
if (customElements.get(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS.selector)) {
|
|
3457
|
+
return;
|
|
2928
3458
|
}
|
|
3459
|
+
// Register the base component (Angular element + Formio component class)
|
|
3460
|
+
registerCustomFormioComponent(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EpistolaDocumentPreviewComponent, injector);
|
|
3461
|
+
// Get the Formio Components registry and the registered base class
|
|
3462
|
+
const Formio = window.Formio;
|
|
3463
|
+
if (!Formio?.Components)
|
|
3464
|
+
return;
|
|
3465
|
+
const BasePreviewComponent = Formio.Components.components[EPISTOLA_DOCUMENT_PREVIEW_OPTIONS.type];
|
|
3466
|
+
if (!BasePreviewComponent)
|
|
3467
|
+
return;
|
|
3468
|
+
// Extend the base class to listen for form data changes and compute input overrides
|
|
3469
|
+
class PreviewWithOverrides extends BasePreviewComponent {
|
|
3470
|
+
_debounceTimer = null;
|
|
3471
|
+
_changeListenerAttached = false;
|
|
3472
|
+
attach(element) {
|
|
3473
|
+
// Bidirectional sync between processLinkSelection object and separate properties.
|
|
3474
|
+
// The editForm uses processLinkSelection (single field), while the component
|
|
3475
|
+
// config and Angular inputs use processDefinitionKey + sourceActivityId.
|
|
3476
|
+
if (this.component?.processLinkSelection) {
|
|
3477
|
+
const sel = this.component.processLinkSelection;
|
|
3478
|
+
this.component.processDefinitionKey = sel.processDefinitionKey || '';
|
|
3479
|
+
this.component.sourceActivityId = sel.sourceActivityId || '';
|
|
3480
|
+
}
|
|
3481
|
+
else if (this.component?.processDefinitionKey && this.component?.sourceActivityId) {
|
|
3482
|
+
this.component.processLinkSelection = {
|
|
3483
|
+
processDefinitionKey: this.component.processDefinitionKey,
|
|
3484
|
+
sourceActivityId: this.component.sourceActivityId,
|
|
3485
|
+
};
|
|
3486
|
+
}
|
|
3487
|
+
const result = super.attach(element);
|
|
3488
|
+
if (this._customAngularElement) {
|
|
3489
|
+
this._customAngularElement['processDefinitionKey'] =
|
|
3490
|
+
this.component.processDefinitionKey || '';
|
|
3491
|
+
this._customAngularElement['sourceActivityId'] = this.component.sourceActivityId || '';
|
|
3492
|
+
}
|
|
3493
|
+
// Listen to form changes and compute input overrides from the mapping
|
|
3494
|
+
if (this.root && this.component?.overrideMapping && !this._changeListenerAttached) {
|
|
3495
|
+
this._changeListenerAttached = true;
|
|
3496
|
+
this.root.on('change', () => {
|
|
3497
|
+
this._computeAndSetOverrides();
|
|
3498
|
+
});
|
|
3499
|
+
// Compute initial value
|
|
3500
|
+
this._computeAndSetOverrides();
|
|
3501
|
+
}
|
|
3502
|
+
return result;
|
|
3503
|
+
}
|
|
3504
|
+
_computeAndSetOverrides() {
|
|
3505
|
+
if (this._debounceTimer) {
|
|
3506
|
+
clearTimeout(this._debounceTimer);
|
|
3507
|
+
}
|
|
3508
|
+
this._debounceTimer = setTimeout(() => {
|
|
3509
|
+
const mapping = this.component?.overrideMapping;
|
|
3510
|
+
const formData = this.root?.data;
|
|
3511
|
+
if (mapping && formData) {
|
|
3512
|
+
const overrides = computeInputOverrides(mapping, formData);
|
|
3513
|
+
if (Object.keys(overrides).length > 0) {
|
|
3514
|
+
this.setValue(overrides);
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
}, 1500);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
// Re-register with the extended class
|
|
3521
|
+
Formio.Components.setComponent(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS.type, PreviewWithOverrides);
|
|
2929
3522
|
}
|
|
2930
3523
|
|
|
2931
|
-
const
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
3524
|
+
const FORM_REF_PREFIX = 'form:';
|
|
3525
|
+
class EpistolaOverrideBuilderComponent {
|
|
3526
|
+
cdr;
|
|
3527
|
+
value;
|
|
3528
|
+
valueChange = new EventEmitter();
|
|
3529
|
+
disabled = false;
|
|
3530
|
+
label = 'Input Overrides';
|
|
3531
|
+
availableFields = [];
|
|
3532
|
+
rows = [];
|
|
3533
|
+
advancedMode = false;
|
|
3534
|
+
jsonText = '';
|
|
3535
|
+
jsonError = null;
|
|
3536
|
+
initialized = false;
|
|
3537
|
+
constructor(cdr) {
|
|
3538
|
+
this.cdr = cdr;
|
|
3539
|
+
}
|
|
3540
|
+
ngOnChanges() {
|
|
3541
|
+
if (!this.initialized && this.value) {
|
|
3542
|
+
this.initialized = true;
|
|
3543
|
+
this.rows = this.mappingToRows(this.value);
|
|
3544
|
+
this.jsonText = JSON.stringify(this.value, null, 2);
|
|
3545
|
+
}
|
|
3546
|
+
this.cdr.markForCheck();
|
|
3547
|
+
}
|
|
3548
|
+
toggleMode() {
|
|
3549
|
+
this.advancedMode = !this.advancedMode;
|
|
3550
|
+
if (this.advancedMode) {
|
|
3551
|
+
const mapping = this.rowsToMapping();
|
|
3552
|
+
this.jsonText = Object.keys(mapping).length > 0 ? JSON.stringify(mapping, null, 2) : '';
|
|
3553
|
+
this.jsonError = null;
|
|
3554
|
+
}
|
|
3555
|
+
else {
|
|
3556
|
+
try {
|
|
3557
|
+
const parsed = this.jsonText.trim() ? JSON.parse(this.jsonText) : {};
|
|
3558
|
+
this.rows = this.mappingToRows(parsed);
|
|
3559
|
+
this.jsonError = null;
|
|
3560
|
+
}
|
|
3561
|
+
catch {
|
|
3562
|
+
// Keep current rows if JSON is invalid
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
addRow() {
|
|
3567
|
+
this.rows.push({ scope: 'doc', inputPath: '', formFieldKey: '' });
|
|
3568
|
+
}
|
|
3569
|
+
removeRow(index) {
|
|
3570
|
+
this.rows.splice(index, 1);
|
|
3571
|
+
this.emitChange();
|
|
3572
|
+
}
|
|
3573
|
+
emitChange() {
|
|
3574
|
+
const mapping = this.rowsToMapping();
|
|
3575
|
+
this.value = Object.keys(mapping).length > 0 ? mapping : null;
|
|
3576
|
+
this.valueChange.emit(this.value);
|
|
3577
|
+
}
|
|
3578
|
+
onJsonChange(text) {
|
|
3579
|
+
this.jsonText = text;
|
|
3580
|
+
if (!text.trim()) {
|
|
3581
|
+
this.jsonError = null;
|
|
3582
|
+
this.value = null;
|
|
3583
|
+
this.valueChange.emit(null);
|
|
3584
|
+
return;
|
|
3585
|
+
}
|
|
3586
|
+
try {
|
|
3587
|
+
const parsed = JSON.parse(text);
|
|
3588
|
+
this.jsonError = null;
|
|
3589
|
+
this.value = parsed;
|
|
3590
|
+
this.valueChange.emit(parsed);
|
|
3591
|
+
}
|
|
3592
|
+
catch (e) {
|
|
3593
|
+
this.jsonError = 'Invalid JSON';
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
rowsToMapping() {
|
|
3597
|
+
const mapping = {};
|
|
3598
|
+
for (const row of this.rows) {
|
|
3599
|
+
if (row.inputPath && row.formFieldKey) {
|
|
3600
|
+
if (!mapping[row.scope]) {
|
|
3601
|
+
mapping[row.scope] = {};
|
|
3602
|
+
}
|
|
3603
|
+
mapping[row.scope][row.inputPath] = FORM_REF_PREFIX + row.formFieldKey;
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
return mapping;
|
|
3607
|
+
}
|
|
3608
|
+
mappingToRows(mapping) {
|
|
3609
|
+
const rows = [];
|
|
3610
|
+
for (const [scope, fields] of Object.entries(mapping)) {
|
|
3611
|
+
if (scope === 'doc' || scope === 'pv') {
|
|
3612
|
+
for (const [path, ref] of Object.entries(fields)) {
|
|
3613
|
+
const formFieldKey = String(ref).startsWith(FORM_REF_PREFIX)
|
|
3614
|
+
? String(ref).substring(FORM_REF_PREFIX.length)
|
|
3615
|
+
: String(ref);
|
|
3616
|
+
rows.push({ scope, inputPath: path, formFieldKey });
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
return rows;
|
|
3621
|
+
}
|
|
3622
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaOverrideBuilderComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3623
|
+
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: `
|
|
3624
|
+
<div class="override-builder">
|
|
3625
|
+
<div class="builder-header">
|
|
3626
|
+
<span class="builder-label">{{ label || 'Input Overrides' }}</span>
|
|
3627
|
+
<button type="button" class="mode-toggle" (click)="toggleMode()">
|
|
3628
|
+
{{ advancedMode ? 'Simple' : 'Advanced' }}
|
|
3629
|
+
</button>
|
|
3630
|
+
</div>
|
|
3631
|
+
|
|
3632
|
+
<!-- Simple mode: table -->
|
|
3633
|
+
<div *ngIf="!advancedMode" class="builder-table">
|
|
3634
|
+
<div *ngIf="rows.length > 0" class="table-header">
|
|
3635
|
+
<span class="col-scope">Scope</span>
|
|
3636
|
+
<span class="col-path">Input Path</span>
|
|
3637
|
+
<span class="col-field">Form Field</span>
|
|
3638
|
+
<span class="col-action"></span>
|
|
3639
|
+
</div>
|
|
3640
|
+
<div *ngFor="let row of rows; let i = index" class="table-row">
|
|
3641
|
+
<select class="col-scope" [(ngModel)]="row.scope" (ngModelChange)="emitChange()">
|
|
3642
|
+
<option value="doc">doc</option>
|
|
3643
|
+
<option value="pv">pv</option>
|
|
3644
|
+
</select>
|
|
3645
|
+
<input
|
|
3646
|
+
class="col-path"
|
|
3647
|
+
type="text"
|
|
3648
|
+
[(ngModel)]="row.inputPath"
|
|
3649
|
+
(ngModelChange)="emitChange()"
|
|
3650
|
+
placeholder="e.g. beslissing.tekst"
|
|
3651
|
+
/>
|
|
3652
|
+
<!-- Dropdown when form fields are available, text input as fallback -->
|
|
3653
|
+
<select
|
|
3654
|
+
*ngIf="availableFields.length > 0"
|
|
3655
|
+
class="col-field"
|
|
3656
|
+
[(ngModel)]="row.formFieldKey"
|
|
3657
|
+
(ngModelChange)="emitChange()"
|
|
3658
|
+
>
|
|
3659
|
+
<option value="">-- Select field --</option>
|
|
3660
|
+
<option *ngFor="let field of availableFields" [value]="field.key">
|
|
3661
|
+
{{ field.label }}
|
|
3662
|
+
</option>
|
|
3663
|
+
</select>
|
|
3664
|
+
<input
|
|
3665
|
+
*ngIf="availableFields.length === 0"
|
|
3666
|
+
class="col-field"
|
|
3667
|
+
type="text"
|
|
3668
|
+
[(ngModel)]="row.formFieldKey"
|
|
3669
|
+
(ngModelChange)="emitChange()"
|
|
3670
|
+
placeholder="form field key"
|
|
3671
|
+
/>
|
|
3672
|
+
<button type="button" class="col-action remove-btn" (click)="removeRow(i)">
|
|
3673
|
+
<i class="mdi mdi-close"></i>
|
|
3674
|
+
</button>
|
|
3675
|
+
</div>
|
|
3676
|
+
<button type="button" class="add-btn" (click)="addRow()">
|
|
3677
|
+
<i class="mdi mdi-plus mr-1"></i> Add override
|
|
3678
|
+
</button>
|
|
3679
|
+
</div>
|
|
3680
|
+
|
|
3681
|
+
<!-- Advanced mode: JSON editor -->
|
|
3682
|
+
<div *ngIf="advancedMode" class="builder-advanced">
|
|
3683
|
+
<textarea
|
|
3684
|
+
class="json-editor"
|
|
3685
|
+
[ngModel]="jsonText"
|
|
3686
|
+
(ngModelChange)="onJsonChange($event)"
|
|
3687
|
+
placeholder='{ "pv": { "motivation": "form:pv:motivation" } }'
|
|
3688
|
+
rows="6"
|
|
3689
|
+
></textarea>
|
|
3690
|
+
<div *ngIf="jsonError" class="json-error">{{ jsonError }}</div>
|
|
3691
|
+
</div>
|
|
3692
|
+
</div>
|
|
3693
|
+
`, isInline: true, styles: [".override-builder{border:1px solid #dee2e6;border-radius:4px;padding:.75rem;background:#f8f9fa}.builder-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem}.builder-label{font-weight:600;font-size:.85rem;color:#495057}.mode-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.15rem .5rem;font-size:.75rem;cursor:pointer}.mode-toggle:hover{background:#e9ecef}.table-header{display:flex;gap:.5rem;padding:.25rem 0;font-size:.75rem;color:#6c757d;font-weight:600}.table-row{display:flex;gap:.5rem;margin-bottom:.25rem;align-items:center}.col-scope{width:70px;flex-shrink:0}.col-path,.col-field{flex:1;min-width:0}.col-action{width:30px;flex-shrink:0}.table-row select,.table-row input{border:1px solid #ced4da;border-radius:4px;padding:.25rem .4rem;font-size:.8rem;background:#fff}.remove-btn{background:none;border:none;color:#dc3545;cursor:pointer;padding:.25rem;font-size:.9rem}.remove-btn:hover{color:#a71d2a}.add-btn{background:none;border:1px dashed #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;margin-top:.25rem;display:flex;align-items:center}.add-btn:hover{background:#e9ecef;border-color:#495057}.json-editor{width:100%;border:1px solid #ced4da;border-radius:4px;padding:.5rem;font-family:monospace;font-size:.8rem;resize:vertical;background:#fff}.json-error{color:#dc3545;font-size:.75rem;margin-top:.25rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3694
|
+
}
|
|
3695
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaOverrideBuilderComponent, decorators: [{
|
|
3696
|
+
type: Component,
|
|
3697
|
+
args: [{ standalone: true, imports: [CommonModule, FormsModule], selector: 'epistola-override-builder-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3698
|
+
<div class="override-builder">
|
|
3699
|
+
<div class="builder-header">
|
|
3700
|
+
<span class="builder-label">{{ label || 'Input Overrides' }}</span>
|
|
3701
|
+
<button type="button" class="mode-toggle" (click)="toggleMode()">
|
|
3702
|
+
{{ advancedMode ? 'Simple' : 'Advanced' }}
|
|
3703
|
+
</button>
|
|
3704
|
+
</div>
|
|
3705
|
+
|
|
3706
|
+
<!-- Simple mode: table -->
|
|
3707
|
+
<div *ngIf="!advancedMode" class="builder-table">
|
|
3708
|
+
<div *ngIf="rows.length > 0" class="table-header">
|
|
3709
|
+
<span class="col-scope">Scope</span>
|
|
3710
|
+
<span class="col-path">Input Path</span>
|
|
3711
|
+
<span class="col-field">Form Field</span>
|
|
3712
|
+
<span class="col-action"></span>
|
|
3713
|
+
</div>
|
|
3714
|
+
<div *ngFor="let row of rows; let i = index" class="table-row">
|
|
3715
|
+
<select class="col-scope" [(ngModel)]="row.scope" (ngModelChange)="emitChange()">
|
|
3716
|
+
<option value="doc">doc</option>
|
|
3717
|
+
<option value="pv">pv</option>
|
|
3718
|
+
</select>
|
|
3719
|
+
<input
|
|
3720
|
+
class="col-path"
|
|
3721
|
+
type="text"
|
|
3722
|
+
[(ngModel)]="row.inputPath"
|
|
3723
|
+
(ngModelChange)="emitChange()"
|
|
3724
|
+
placeholder="e.g. beslissing.tekst"
|
|
3725
|
+
/>
|
|
3726
|
+
<!-- Dropdown when form fields are available, text input as fallback -->
|
|
3727
|
+
<select
|
|
3728
|
+
*ngIf="availableFields.length > 0"
|
|
3729
|
+
class="col-field"
|
|
3730
|
+
[(ngModel)]="row.formFieldKey"
|
|
3731
|
+
(ngModelChange)="emitChange()"
|
|
3732
|
+
>
|
|
3733
|
+
<option value="">-- Select field --</option>
|
|
3734
|
+
<option *ngFor="let field of availableFields" [value]="field.key">
|
|
3735
|
+
{{ field.label }}
|
|
3736
|
+
</option>
|
|
3737
|
+
</select>
|
|
3738
|
+
<input
|
|
3739
|
+
*ngIf="availableFields.length === 0"
|
|
3740
|
+
class="col-field"
|
|
3741
|
+
type="text"
|
|
3742
|
+
[(ngModel)]="row.formFieldKey"
|
|
3743
|
+
(ngModelChange)="emitChange()"
|
|
3744
|
+
placeholder="form field key"
|
|
3745
|
+
/>
|
|
3746
|
+
<button type="button" class="col-action remove-btn" (click)="removeRow(i)">
|
|
3747
|
+
<i class="mdi mdi-close"></i>
|
|
3748
|
+
</button>
|
|
3749
|
+
</div>
|
|
3750
|
+
<button type="button" class="add-btn" (click)="addRow()">
|
|
3751
|
+
<i class="mdi mdi-plus mr-1"></i> Add override
|
|
3752
|
+
</button>
|
|
3753
|
+
</div>
|
|
3754
|
+
|
|
3755
|
+
<!-- Advanced mode: JSON editor -->
|
|
3756
|
+
<div *ngIf="advancedMode" class="builder-advanced">
|
|
3757
|
+
<textarea
|
|
3758
|
+
class="json-editor"
|
|
3759
|
+
[ngModel]="jsonText"
|
|
3760
|
+
(ngModelChange)="onJsonChange($event)"
|
|
3761
|
+
placeholder='{ "pv": { "motivation": "form:pv:motivation" } }'
|
|
3762
|
+
rows="6"
|
|
3763
|
+
></textarea>
|
|
3764
|
+
<div *ngIf="jsonError" class="json-error">{{ jsonError }}</div>
|
|
3765
|
+
</div>
|
|
3766
|
+
</div>
|
|
3767
|
+
`, styles: [".override-builder{border:1px solid #dee2e6;border-radius:4px;padding:.75rem;background:#f8f9fa}.builder-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem}.builder-label{font-weight:600;font-size:.85rem;color:#495057}.mode-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.15rem .5rem;font-size:.75rem;cursor:pointer}.mode-toggle:hover{background:#e9ecef}.table-header{display:flex;gap:.5rem;padding:.25rem 0;font-size:.75rem;color:#6c757d;font-weight:600}.table-row{display:flex;gap:.5rem;margin-bottom:.25rem;align-items:center}.col-scope{width:70px;flex-shrink:0}.col-path,.col-field{flex:1;min-width:0}.col-action{width:30px;flex-shrink:0}.table-row select,.table-row input{border:1px solid #ced4da;border-radius:4px;padding:.25rem .4rem;font-size:.8rem;background:#fff}.remove-btn{background:none;border:none;color:#dc3545;cursor:pointer;padding:.25rem;font-size:.9rem}.remove-btn:hover{color:#a71d2a}.add-btn{background:none;border:1px dashed #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;margin-top:.25rem;display:flex;align-items:center}.add-btn:hover{background:#e9ecef;border-color:#495057}.json-editor{width:100%;border:1px solid #ced4da;border-radius:4px;padding:.5rem;font-family:monospace;font-size:.8rem;resize:vertical;background:#fff}.json-error{color:#dc3545;font-size:.75rem;margin-top:.25rem}\n"] }]
|
|
3768
|
+
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
3769
|
+
type: Input
|
|
3770
|
+
}], valueChange: [{
|
|
3771
|
+
type: Output
|
|
3772
|
+
}], disabled: [{
|
|
3773
|
+
type: Input
|
|
3774
|
+
}], label: [{
|
|
3775
|
+
type: Input
|
|
3776
|
+
}], availableFields: [{
|
|
3777
|
+
type: Input
|
|
3778
|
+
}] } });
|
|
3779
|
+
|
|
3780
|
+
const EPISTOLA_OVERRIDE_BUILDER_OPTIONS = {
|
|
3781
|
+
type: 'epistola-override-builder',
|
|
3782
|
+
selector: 'epistola-override-builder-element',
|
|
3783
|
+
title: 'Epistola Override Builder',
|
|
2935
3784
|
group: 'basic',
|
|
2936
|
-
icon: '
|
|
3785
|
+
icon: 'list',
|
|
3786
|
+
emptyValue: null,
|
|
3787
|
+
fieldOptions: ['label', 'availableFields'],
|
|
3788
|
+
};
|
|
3789
|
+
/**
|
|
3790
|
+
* Recursively collect input field keys and labels from a Formio component tree.
|
|
3791
|
+
* Skips epistola custom components (which are builder UI, not form fields).
|
|
3792
|
+
*/
|
|
3793
|
+
function collectFormFields(components) {
|
|
3794
|
+
const fields = [];
|
|
3795
|
+
for (const comp of components) {
|
|
3796
|
+
if (comp.input && comp.key && comp.type !== 'button' && !comp.type?.startsWith('epistola-')) {
|
|
3797
|
+
fields.push({ key: comp.key, label: comp.label || comp.key });
|
|
3798
|
+
}
|
|
3799
|
+
if (comp.components) {
|
|
3800
|
+
fields.push(...collectFormFields(comp.components));
|
|
3801
|
+
}
|
|
3802
|
+
if (comp.columns) {
|
|
3803
|
+
for (const col of comp.columns) {
|
|
3804
|
+
if (col.components) {
|
|
3805
|
+
fields.push(...collectFormFields(col.components));
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
return fields;
|
|
3811
|
+
}
|
|
3812
|
+
function registerEpistolaOverrideBuilderComponent(injector) {
|
|
3813
|
+
if (customElements.get(EPISTOLA_OVERRIDE_BUILDER_OPTIONS.selector)) {
|
|
3814
|
+
return;
|
|
3815
|
+
}
|
|
3816
|
+
// Register the base component (Angular element + Formio component class)
|
|
3817
|
+
registerCustomFormioComponent(EPISTOLA_OVERRIDE_BUILDER_OPTIONS, EpistolaOverrideBuilderComponent, injector);
|
|
3818
|
+
// Get the Formio Components registry and the registered base class
|
|
3819
|
+
const Formio = window.Formio;
|
|
3820
|
+
if (!Formio?.Components)
|
|
3821
|
+
return;
|
|
3822
|
+
const BaseComponent = Formio.Components.components[EPISTOLA_OVERRIDE_BUILDER_OPTIONS.type];
|
|
3823
|
+
if (!BaseComponent)
|
|
3824
|
+
return;
|
|
3825
|
+
// Extend the base class to pass available form fields to the Angular component
|
|
3826
|
+
class OverrideBuilderWithFields extends BaseComponent {
|
|
3827
|
+
attach(element) {
|
|
3828
|
+
// Set form fields on the component BEFORE super.attach() reads fieldOptions
|
|
3829
|
+
this.component.availableFields = this._extractFormFields();
|
|
3830
|
+
return super.attach(element);
|
|
3831
|
+
}
|
|
3832
|
+
_extractFormFields() {
|
|
3833
|
+
// The Formio builder passes the main form schema as options.editForm
|
|
3834
|
+
// when opening the edit dialog (editFormOptions.editForm = this.form).
|
|
3835
|
+
const components = this.options?.editForm?.components;
|
|
3836
|
+
if (Array.isArray(components)) {
|
|
3837
|
+
return collectFormFields(components);
|
|
3838
|
+
}
|
|
3839
|
+
return [];
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
// Re-register with the extended class
|
|
3843
|
+
Formio.Components.setComponent(EPISTOLA_OVERRIDE_BUILDER_OPTIONS.type, OverrideBuilderWithFields);
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
class EpistolaProcessLinkSelectorComponent {
|
|
3847
|
+
adminService;
|
|
3848
|
+
cdr;
|
|
3849
|
+
value;
|
|
3850
|
+
valueChange = new EventEmitter();
|
|
3851
|
+
disabled = false;
|
|
3852
|
+
label = 'Process Link';
|
|
3853
|
+
filteredEntries = [];
|
|
3854
|
+
selectedKey = '';
|
|
3855
|
+
loading = false;
|
|
3856
|
+
error = null;
|
|
3857
|
+
initialized = false;
|
|
3858
|
+
loadSubscription;
|
|
3859
|
+
constructor(adminService, cdr) {
|
|
3860
|
+
this.adminService = adminService;
|
|
3861
|
+
this.cdr = cdr;
|
|
3862
|
+
}
|
|
3863
|
+
ngOnChanges(changes) {
|
|
3864
|
+
if (!this.initialized) {
|
|
3865
|
+
this.initialized = true;
|
|
3866
|
+
this.loadEntries();
|
|
3867
|
+
}
|
|
3868
|
+
// Restore selection whenever value changes (Formio may set it after init)
|
|
3869
|
+
if (changes['value'] && this.value) {
|
|
3870
|
+
this.selectedKey = `${this.value.processDefinitionKey}::${this.value.sourceActivityId}`;
|
|
3871
|
+
this.cdr.markForCheck();
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
ngOnDestroy() {
|
|
3875
|
+
this.loadSubscription?.unsubscribe();
|
|
3876
|
+
}
|
|
3877
|
+
onSelect(key) {
|
|
3878
|
+
this.selectedKey = key;
|
|
3879
|
+
if (!key) {
|
|
3880
|
+
this.value = null;
|
|
3881
|
+
this.valueChange.emit(null);
|
|
3882
|
+
return;
|
|
3883
|
+
}
|
|
3884
|
+
const [processDefinitionKey, sourceActivityId] = key.split('::');
|
|
3885
|
+
this.value = { processDefinitionKey, sourceActivityId };
|
|
3886
|
+
this.valueChange.emit(this.value);
|
|
3887
|
+
}
|
|
3888
|
+
entryKey(entry) {
|
|
3889
|
+
return `${entry.processDefinitionKey}::${entry.activityId}`;
|
|
3890
|
+
}
|
|
3891
|
+
loadEntries() {
|
|
3892
|
+
this.loading = true;
|
|
3893
|
+
this.cdr.markForCheck();
|
|
3894
|
+
this.loadSubscription = this.adminService.getPluginUsage().subscribe({
|
|
3895
|
+
next: (entries) => {
|
|
3896
|
+
this.filteredEntries = entries.filter((e) => e.actionKey === 'generate-document');
|
|
3897
|
+
this.loading = false;
|
|
3898
|
+
// Restore selection from value
|
|
3899
|
+
if (this.value) {
|
|
3900
|
+
this.selectedKey = `${this.value.processDefinitionKey}::${this.value.sourceActivityId}`;
|
|
3901
|
+
}
|
|
3902
|
+
this.cdr.markForCheck();
|
|
3903
|
+
},
|
|
3904
|
+
error: (err) => {
|
|
3905
|
+
this.error = 'Failed to load process links';
|
|
3906
|
+
this.loading = false;
|
|
3907
|
+
this.cdr.markForCheck();
|
|
3908
|
+
},
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaProcessLinkSelectorComponent, deps: [{ token: EpistolaAdminService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3912
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaProcessLinkSelectorComponent, isStandalone: true, selector: "epistola-process-link-selector-component", inputs: { value: "value", disabled: "disabled", label: "label" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
3913
|
+
<div class="process-link-selector">
|
|
3914
|
+
<label class="selector-label">{{ label || 'Process Link' }}</label>
|
|
3915
|
+
<select
|
|
3916
|
+
class="selector-dropdown"
|
|
3917
|
+
[ngModel]="selectedKey"
|
|
3918
|
+
(ngModelChange)="onSelect($event)"
|
|
3919
|
+
[disabled]="disabled || loading"
|
|
3920
|
+
>
|
|
3921
|
+
<option value="">{{ loading ? 'Loading...' : '-- Select a process link --' }}</option>
|
|
3922
|
+
<option *ngFor="let entry of filteredEntries" [value]="entryKey(entry)">
|
|
3923
|
+
{{ entry.processDefinitionName }} / {{ entry.activityName }} ({{ entry.activityId }})
|
|
3924
|
+
</option>
|
|
3925
|
+
</select>
|
|
3926
|
+
<div *ngIf="error" class="selector-error">{{ error }}</div>
|
|
3927
|
+
</div>
|
|
3928
|
+
`, isInline: true, styles: [".process-link-selector{margin-bottom:.5rem}.selector-label{display:block;font-weight:600;font-size:.85rem;color:#495057;margin-bottom:.25rem}.selector-dropdown{width:100%;border:1px solid #ced4da;border-radius:4px;padding:.4rem .5rem;font-size:.85rem;background:#fff}.selector-error{color:#dc3545;font-size:.75rem;margin-top:.25rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3929
|
+
}
|
|
3930
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaProcessLinkSelectorComponent, decorators: [{
|
|
3931
|
+
type: Component,
|
|
3932
|
+
args: [{ standalone: true, imports: [CommonModule, FormsModule], selector: 'epistola-process-link-selector-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3933
|
+
<div class="process-link-selector">
|
|
3934
|
+
<label class="selector-label">{{ label || 'Process Link' }}</label>
|
|
3935
|
+
<select
|
|
3936
|
+
class="selector-dropdown"
|
|
3937
|
+
[ngModel]="selectedKey"
|
|
3938
|
+
(ngModelChange)="onSelect($event)"
|
|
3939
|
+
[disabled]="disabled || loading"
|
|
3940
|
+
>
|
|
3941
|
+
<option value="">{{ loading ? 'Loading...' : '-- Select a process link --' }}</option>
|
|
3942
|
+
<option *ngFor="let entry of filteredEntries" [value]="entryKey(entry)">
|
|
3943
|
+
{{ entry.processDefinitionName }} / {{ entry.activityName }} ({{ entry.activityId }})
|
|
3944
|
+
</option>
|
|
3945
|
+
</select>
|
|
3946
|
+
<div *ngIf="error" class="selector-error">{{ error }}</div>
|
|
3947
|
+
</div>
|
|
3948
|
+
`, 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"] }]
|
|
3949
|
+
}], ctorParameters: () => [{ type: EpistolaAdminService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
3950
|
+
type: Input
|
|
3951
|
+
}], valueChange: [{
|
|
3952
|
+
type: Output
|
|
3953
|
+
}], disabled: [{
|
|
3954
|
+
type: Input
|
|
3955
|
+
}], label: [{
|
|
3956
|
+
type: Input
|
|
3957
|
+
}] } });
|
|
3958
|
+
|
|
3959
|
+
const EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS = {
|
|
3960
|
+
type: 'epistola-process-link-selector',
|
|
3961
|
+
selector: 'epistola-process-link-selector-element',
|
|
3962
|
+
title: 'Epistola Process Link Selector',
|
|
3963
|
+
group: 'basic',
|
|
3964
|
+
icon: 'link',
|
|
2937
3965
|
emptyValue: null,
|
|
2938
3966
|
fieldOptions: ['label'],
|
|
2939
3967
|
};
|
|
2940
|
-
function
|
|
2941
|
-
if (!customElements.get(
|
|
2942
|
-
registerCustomFormioComponent(
|
|
3968
|
+
function registerEpistolaProcessLinkSelectorComponent(injector) {
|
|
3969
|
+
if (!customElements.get(EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS.selector)) {
|
|
3970
|
+
registerCustomFormioComponent(EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS, EpistolaProcessLinkSelectorComponent, injector);
|
|
2943
3971
|
}
|
|
2944
3972
|
}
|
|
2945
3973
|
|
|
@@ -2949,14 +3977,22 @@ class EpistolaPluginModule {
|
|
|
2949
3977
|
ngModule: EpistolaPluginModule,
|
|
2950
3978
|
providers: [
|
|
2951
3979
|
EpistolaMenuService,
|
|
3980
|
+
{
|
|
3981
|
+
provide: HTTP_INTERCEPTORS,
|
|
3982
|
+
useClass: EpistolaTaskContextInterceptor,
|
|
3983
|
+
multi: true,
|
|
3984
|
+
},
|
|
2952
3985
|
{
|
|
2953
3986
|
provide: ENVIRONMENT_INITIALIZER,
|
|
2954
3987
|
multi: true,
|
|
2955
3988
|
useValue: () => {
|
|
3989
|
+
if (!isEpistolaEnabled())
|
|
3990
|
+
return;
|
|
2956
3991
|
const injector = inject(Injector);
|
|
2957
|
-
|
|
3992
|
+
registerEpistolaDocumentComponent(injector);
|
|
2958
3993
|
registerEpistolaRetryFormComponent(injector);
|
|
2959
|
-
|
|
3994
|
+
registerEpistolaOverrideBuilderComponent(injector);
|
|
3995
|
+
registerEpistolaProcessLinkSelectorComponent(injector);
|
|
2960
3996
|
registerEpistolaDocumentPreviewComponent(injector);
|
|
2961
3997
|
// Eagerly create EpistolaMenuService to trigger menu registration
|
|
2962
3998
|
inject(EpistolaMenuService);
|
|
@@ -2977,17 +4013,15 @@ class EpistolaPluginModule {
|
|
|
2977
4013
|
GenerateDocumentConfigurationComponent,
|
|
2978
4014
|
CheckJobStatusConfigurationComponent,
|
|
2979
4015
|
DownloadDocumentConfigurationComponent,
|
|
2980
|
-
|
|
4016
|
+
EpistolaDocumentComponent,
|
|
2981
4017
|
EpistolaRetryFormComponent,
|
|
2982
|
-
EpistolaPreviewButtonComponent,
|
|
2983
4018
|
EpistolaDocumentPreviewComponent,
|
|
2984
4019
|
EpistolaAdminPageComponent], exports: [EpistolaConfigurationComponent,
|
|
2985
4020
|
GenerateDocumentConfigurationComponent,
|
|
2986
4021
|
CheckJobStatusConfigurationComponent,
|
|
2987
4022
|
DownloadDocumentConfigurationComponent,
|
|
2988
|
-
|
|
4023
|
+
EpistolaDocumentComponent,
|
|
2989
4024
|
EpistolaRetryFormComponent,
|
|
2990
|
-
EpistolaPreviewButtonComponent,
|
|
2991
4025
|
EpistolaDocumentPreviewComponent,
|
|
2992
4026
|
EpistolaAdminPageComponent] });
|
|
2993
4027
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, providers: [EpistolaPluginService, EpistolaAdminService], imports: [CommonModule,
|
|
@@ -3001,9 +4035,8 @@ class EpistolaPluginModule {
|
|
|
3001
4035
|
GenerateDocumentConfigurationComponent,
|
|
3002
4036
|
CheckJobStatusConfigurationComponent,
|
|
3003
4037
|
DownloadDocumentConfigurationComponent,
|
|
3004
|
-
|
|
4038
|
+
EpistolaDocumentComponent,
|
|
3005
4039
|
EpistolaRetryFormComponent,
|
|
3006
|
-
EpistolaPreviewButtonComponent,
|
|
3007
4040
|
EpistolaDocumentPreviewComponent,
|
|
3008
4041
|
EpistolaAdminPageComponent] });
|
|
3009
4042
|
}
|
|
@@ -3022,9 +4055,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3022
4055
|
GenerateDocumentConfigurationComponent,
|
|
3023
4056
|
CheckJobStatusConfigurationComponent,
|
|
3024
4057
|
DownloadDocumentConfigurationComponent,
|
|
3025
|
-
|
|
4058
|
+
EpistolaDocumentComponent,
|
|
3026
4059
|
EpistolaRetryFormComponent,
|
|
3027
|
-
EpistolaPreviewButtonComponent,
|
|
3028
4060
|
EpistolaDocumentPreviewComponent,
|
|
3029
4061
|
EpistolaAdminPageComponent,
|
|
3030
4062
|
],
|
|
@@ -3033,9 +4065,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3033
4065
|
GenerateDocumentConfigurationComponent,
|
|
3034
4066
|
CheckJobStatusConfigurationComponent,
|
|
3035
4067
|
DownloadDocumentConfigurationComponent,
|
|
3036
|
-
|
|
4068
|
+
EpistolaDocumentComponent,
|
|
3037
4069
|
EpistolaRetryFormComponent,
|
|
3038
|
-
EpistolaPreviewButtonComponent,
|
|
3039
4070
|
EpistolaDocumentPreviewComponent,
|
|
3040
4071
|
EpistolaAdminPageComponent,
|
|
3041
4072
|
],
|
|
@@ -3043,22 +4074,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3043
4074
|
}]
|
|
3044
4075
|
}] });
|
|
3045
4076
|
|
|
3046
|
-
|
|
3047
|
-
// TODO: Replace with actual Epistola logo
|
|
3048
|
-
const EPISTOLA_PLUGIN_LOGO_BASE64 = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzMzNjZjYyI+PHBhdGggZD0iTTE0IDJINmMtMS4xIDAtMiAuOS0yIDJ2MTZjMCAxLjEuOSAyIDIgMmgxMmMxLjEgMCAyLS45IDItMlY4bC02LTZ6bTQgMThINlY0aDd2NWg1djExeiIvPjxwYXRoIGQ9Ik04IDEyaDh2Mkg4em0wIDRoOHYtMkg4em0wLThWNmg0djJ6Ii8+PC9zdmc+';
|
|
4077
|
+
const EPISTOLA_PLUGIN_LOGO_BASE64 = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAgMTIwIiB3aWR0aD0iMTIwIiBoZWlnaHQ9IjEyMCI+CiAgPCEtLSBTdGFjayBiYXNlIC0tPgogIDxyZWN0IHg9IjM2IiB5PSIxNiIgd2lkdGg9IjU0IiBoZWlnaHQ9IjcwIiByeD0iMyIgZmlsbD0iI2U2YzJiMCIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIiIHRyYW5zZm9ybT0icm90YXRlKDUgNjMgNTEpIi8+CiAgPHJlY3QgeD0iMzIiIHk9IjIyIiB3aWR0aD0iNTQiIGhlaWdodD0iNzAiIHJ4PSIzIiBmaWxsPSIjZjBkOGM4IiBzdHJva2U9IiM0ZjJmMmIiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxyZWN0IHg9IjI4IiB5PSIyOCIgd2lkdGg9IjU0IiBoZWlnaHQ9IjcwIiByeD0iMyIgZmlsbD0iI2Y1ZWJlMyIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIuNSIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjQ0IiB4Mj0iNzIiIHkyPSI0NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjU0IiB4Mj0iNzIiIHkyPSI1NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDxsaW5lIHgxPSIzOCIgeTE9IjY0IiB4Mj0iNTgiIHkyPSI2NCIgc3Ryb2tlPSIjYzRhODgyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgogIDwhLS0gV2F4IHNlYWwgd2l0aCBjaGVja21hcmsgLS0+CiAgPGNpcmNsZSBjeD0iNTUiIGN5PSI4NCIgcj0iMTUiIGZpbGw9IiNiODVjM2MiIHN0cm9rZT0iIzRmMmYyYiIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPGNpcmNsZSBjeD0iNTUiIGN5PSI4NCIgcj0iMTAuNSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDQ4MzZhIiBzdHJva2Utd2lkdGg9IjEiLz4KICA8cG9seWxpbmUgcG9pbnRzPSI0OSw4NCA1Myw4OSA2Miw3OCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNGYyZjJiIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo=';
|
|
3049
4078
|
|
|
4079
|
+
const EPISTOLA_PLUGIN_ID = 'epistola';
|
|
4080
|
+
const DISABLED_EPISTOLA_PLUGIN_ID = '__epistola_disabled__';
|
|
3050
4081
|
const epistolaPluginSpecification = {
|
|
3051
|
-
|
|
3052
|
-
|
|
4082
|
+
get pluginId() {
|
|
4083
|
+
return isEpistolaEnabled() ? EPISTOLA_PLUGIN_ID : DISABLED_EPISTOLA_PLUGIN_ID;
|
|
4084
|
+
},
|
|
3053
4085
|
// Component for plugin-level configuration (tenantId)
|
|
3054
4086
|
pluginConfigurationComponent: EpistolaConfigurationComponent,
|
|
3055
4087
|
// Plugin logo
|
|
3056
4088
|
pluginLogoBase64: EPISTOLA_PLUGIN_LOGO_BASE64,
|
|
3057
4089
|
// Map action keys to their configuration components
|
|
3058
4090
|
functionConfigurationComponents: {
|
|
3059
|
-
'generate-document': GenerateDocumentConfigurationComponent,
|
|
3060
|
-
'check-job-status': CheckJobStatusConfigurationComponent,
|
|
3061
|
-
'download-document': DownloadDocumentConfigurationComponent,
|
|
4091
|
+
'epistola-generate-document': GenerateDocumentConfigurationComponent,
|
|
4092
|
+
'epistola-check-job-status': CheckJobStatusConfigurationComponent,
|
|
4093
|
+
'epistola-download-document': DownloadDocumentConfigurationComponent,
|
|
3062
4094
|
},
|
|
3063
4095
|
// Translations
|
|
3064
4096
|
pluginTranslations: {
|
|
@@ -3076,7 +4108,7 @@ const epistolaPluginSpecification = {
|
|
|
3076
4108
|
defaultEnvironmentIdTooltip: 'De standaard omgeving voor documentgeneratie (3-30 tekens, alleen kleine letters, cijfers en koppeltekens, bijv. "productie")',
|
|
3077
4109
|
templateSyncEnabled: 'Template synchronisatie',
|
|
3078
4110
|
templateSyncEnabledTooltip: 'Synchroniseer template definities automatisch van het classpath naar Epistola bij het opstarten',
|
|
3079
|
-
'generate-document': 'Genereer Document',
|
|
4111
|
+
'epistola-generate-document': 'Genereer Document',
|
|
3080
4112
|
catalogId: 'Catalogus',
|
|
3081
4113
|
catalogIdTooltip: 'Selecteer de catalogus waaruit een template gekozen wordt',
|
|
3082
4114
|
templateId: 'Template',
|
|
@@ -3139,6 +4171,7 @@ const epistolaPluginSpecification = {
|
|
|
3139
4171
|
requiredFieldsMissing: 'Niet alle verplichte velden zijn gekoppeld',
|
|
3140
4172
|
requiredFieldsComplete: 'Alle verplichte velden zijn gekoppeld',
|
|
3141
4173
|
validationSummary: 'verplichte velden gekoppeld',
|
|
4174
|
+
jsonataValidationErrorsHeading: 'Ongeldige JSONata-expressies:',
|
|
3142
4175
|
fieldRequired: 'Verplicht',
|
|
3143
4176
|
fieldOptional: 'Optioneel',
|
|
3144
4177
|
mapCollectionTo: 'Koppel collectie aan',
|
|
@@ -3152,7 +4185,7 @@ const epistolaPluginSpecification = {
|
|
|
3152
4185
|
sourceFieldPlaceholder: 'Bronveldnaam',
|
|
3153
4186
|
noTemplateFields: 'Geen template velden beschikbaar',
|
|
3154
4187
|
// Check job status action
|
|
3155
|
-
'check-job-status': 'Controleer Taakstatus',
|
|
4188
|
+
'epistola-check-job-status': 'Controleer Taakstatus',
|
|
3156
4189
|
requestIdVariable: 'Request ID Variabele',
|
|
3157
4190
|
requestIdVariableTooltip: 'Naam van de procesvariabele met het Epistola request ID',
|
|
3158
4191
|
statusVariable: 'Status Variabele',
|
|
@@ -3162,7 +4195,9 @@ const epistolaPluginSpecification = {
|
|
|
3162
4195
|
errorMessageVariable: 'Foutmelding Variabele',
|
|
3163
4196
|
errorMessageVariableTooltip: 'Naam van de procesvariabele waarin de foutmelding wordt opgeslagen (bij fout)',
|
|
3164
4197
|
// Download document action
|
|
3165
|
-
'download-document': 'Download Document',
|
|
4198
|
+
'epistola-download-document': 'Download Document',
|
|
4199
|
+
documentVariable: 'Document Variabele',
|
|
4200
|
+
documentVariableTooltip: 'Naam van de procesvariabele met het Epistola resultaat. Mag een String document ID zijn (legacy) of een rich-result object met een documentId-veld; de actie haalt het document ID eruit.',
|
|
3166
4201
|
contentVariable: 'Inhoud Variabele',
|
|
3167
4202
|
contentVariableTooltip: 'Naam van de procesvariabele waarin de documentinhoud (Base64) wordt opgeslagen',
|
|
3168
4203
|
// Admin page
|
|
@@ -3189,6 +4224,16 @@ const epistolaPluginSpecification = {
|
|
|
3189
4224
|
epistolaAdminNoPendingJobs: 'Geen wachtende taken voor deze verbinding.',
|
|
3190
4225
|
epistolaAdminConfiguration: 'Configuratie',
|
|
3191
4226
|
epistolaAdminRequestId: 'Request ID',
|
|
4227
|
+
epistolaAdminReconcile: 'Hersynchroniseer',
|
|
4228
|
+
epistolaAdminReconciling: 'Bezig...',
|
|
4229
|
+
epistolaAdminReconcileTooltip: 'Vraag de huidige status op bij Epistola en hervat het wachtende proces als het klaar is.',
|
|
4230
|
+
epistolaAdminConfigurations: 'Configuraties',
|
|
4231
|
+
epistolaAdminValidations: 'BPMN-validatie',
|
|
4232
|
+
epistolaAdminNoValidations: 'Geen race-onveilige procesdefinities gevonden. Alles ziet er goed uit.',
|
|
4233
|
+
epistolaAdminValidationWarningTitle: 'BPMN configuratie waarschuwing',
|
|
4234
|
+
epistolaAdminValidationWarningBody: 'In deze procesdefinities is de grens tussen de generate-document service task en de EpistolaDocumentGenerated catch event niet synchroon. Resultaten kunnen verloren gaan; gebruik in dat geval de Hersynchroniseer-knop in de Wachtende taken-tab.',
|
|
4235
|
+
epistolaAdminValidationCode: 'Code',
|
|
4236
|
+
epistolaAdminValidationMessage: 'Bericht',
|
|
3192
4237
|
},
|
|
3193
4238
|
en: {
|
|
3194
4239
|
title: 'Epistola Document Suite',
|
|
@@ -3204,7 +4249,7 @@ const epistolaPluginSpecification = {
|
|
|
3204
4249
|
defaultEnvironmentIdTooltip: 'The default environment for document generation (3-30 chars, lowercase letters, digits and hyphens only, e.g. "production")',
|
|
3205
4250
|
templateSyncEnabled: 'Template sync',
|
|
3206
4251
|
templateSyncEnabledTooltip: 'Automatically synchronize template definitions from classpath to Epistola on startup',
|
|
3207
|
-
'generate-document': 'Generate Document',
|
|
4252
|
+
'epistola-generate-document': 'Generate Document',
|
|
3208
4253
|
catalogId: 'Catalog',
|
|
3209
4254
|
catalogIdTooltip: 'Select the catalog to choose a template from',
|
|
3210
4255
|
templateId: 'Template',
|
|
@@ -3267,6 +4312,7 @@ const epistolaPluginSpecification = {
|
|
|
3267
4312
|
requiredFieldsMissing: 'Not all required fields are mapped',
|
|
3268
4313
|
requiredFieldsComplete: 'All required fields are mapped',
|
|
3269
4314
|
validationSummary: 'required fields mapped',
|
|
4315
|
+
jsonataValidationErrorsHeading: 'Invalid JSONata expressions:',
|
|
3270
4316
|
fieldRequired: 'Required',
|
|
3271
4317
|
fieldOptional: 'Optional',
|
|
3272
4318
|
mapCollectionTo: 'Map collection to',
|
|
@@ -3280,7 +4326,7 @@ const epistolaPluginSpecification = {
|
|
|
3280
4326
|
sourceFieldPlaceholder: 'Source field name',
|
|
3281
4327
|
noTemplateFields: 'No template fields available',
|
|
3282
4328
|
// Check job status action
|
|
3283
|
-
'check-job-status': 'Check Job Status',
|
|
4329
|
+
'epistola-check-job-status': 'Check Job Status',
|
|
3284
4330
|
requestIdVariable: 'Request ID Variable',
|
|
3285
4331
|
requestIdVariableTooltip: 'Name of the process variable containing the Epistola request ID',
|
|
3286
4332
|
statusVariable: 'Status Variable',
|
|
@@ -3290,7 +4336,9 @@ const epistolaPluginSpecification = {
|
|
|
3290
4336
|
errorMessageVariable: 'Error Message Variable',
|
|
3291
4337
|
errorMessageVariableTooltip: 'Name of the process variable to store the error message in (when failed)',
|
|
3292
4338
|
// Download document action
|
|
3293
|
-
'download-document': 'Download Document',
|
|
4339
|
+
'epistola-download-document': 'Download Document',
|
|
4340
|
+
documentVariable: 'Document Variable',
|
|
4341
|
+
documentVariableTooltip: 'Name of the process variable holding the Epistola result. May be a String document id (legacy) or a rich result object with a `documentId` key — the action extracts the document id from it.',
|
|
3294
4342
|
contentVariable: 'Content Variable',
|
|
3295
4343
|
contentVariableTooltip: 'Name of the process variable to store the document content (Base64) in',
|
|
3296
4344
|
// Admin page
|
|
@@ -3317,6 +4365,16 @@ const epistolaPluginSpecification = {
|
|
|
3317
4365
|
epistolaAdminNoPendingJobs: 'No pending jobs for this connection.',
|
|
3318
4366
|
epistolaAdminConfiguration: 'Configuration',
|
|
3319
4367
|
epistolaAdminRequestId: 'Request ID',
|
|
4368
|
+
epistolaAdminReconcile: 'Reconcile',
|
|
4369
|
+
epistolaAdminReconciling: 'Reconciling...',
|
|
4370
|
+
epistolaAdminReconcileTooltip: "Ask Epistola for this job's current status and resume the waiting process if it has finished.",
|
|
4371
|
+
epistolaAdminConfigurations: 'Configurations',
|
|
4372
|
+
epistolaAdminValidations: 'BPMN validation',
|
|
4373
|
+
epistolaAdminNoValidations: 'No race-unsafe process definitions detected. Everything looks good.',
|
|
4374
|
+
epistolaAdminValidationWarningTitle: 'BPMN configuration warning',
|
|
4375
|
+
epistolaAdminValidationWarningBody: 'These process definitions have a non-synchronous boundary between the generate-document service task and the EpistolaDocumentGenerated catch event. Results can be missed; use the Reconcile button on the Pending Jobs tab to recover.',
|
|
4376
|
+
epistolaAdminValidationCode: 'Code',
|
|
4377
|
+
epistolaAdminValidationMessage: 'Message',
|
|
3320
4378
|
},
|
|
3321
4379
|
},
|
|
3322
4380
|
};
|
|
@@ -3329,5 +4387,5 @@ const epistolaPluginSpecification = {
|
|
|
3329
4387
|
* Generated bundle index. Do not edit.
|
|
3330
4388
|
*/
|
|
3331
4389
|
|
|
3332
|
-
export { CheckJobStatusConfigurationComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOCUMENT_PREVIEW_OPTIONS,
|
|
4390
|
+
export { CheckJobStatusConfigurationComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOCUMENT_OPTIONS, EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EPISTOLA_OVERRIDE_BUILDER_OPTIONS, EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS, EPISTOLA_RETRY_FORM_OPTIONS, EpistolaAdminPageComponent, EpistolaAdminRoutingModule, EpistolaAdminService, EpistolaConfigurationComponent, EpistolaDocumentComponent, EpistolaDocumentPreviewComponent, EpistolaMenuService, EpistolaOverrideBuilderComponent, EpistolaPluginModule, EpistolaPluginService, EpistolaProcessLinkSelectorComponent, EpistolaRetryFormComponent, EpistolaTaskContextInterceptor, EpistolaTaskContextService, GenerateDocumentConfigurationComponent, JsonataEditorComponent, MappingBuilderComponent, epistolaPluginSpecification, errorResource, initialResource, isEpistolaEnabled, loadingResource, registerEpistolaDocumentComponent, registerEpistolaDocumentPreviewComponent, registerEpistolaOverrideBuilderComponent, registerEpistolaProcessLinkSelectorComponent, registerEpistolaRetryFormComponent, successResource };
|
|
3333
4391
|
//# sourceMappingURL=epistola.app-valtimo-plugin.mjs.map
|