@scion/workbench 20.0.0-beta.6 → 20.0.0-beta.8
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/scion-workbench.mjs +181 -78
- package/fesm2022/scion-workbench.mjs.map +1 -1
- package/index.d.ts +13 -11
- package/package.json +2 -2
|
@@ -3,10 +3,10 @@ import { effect, untracked, Injectable, inject, Injector, signal, booleanAttribu
|
|
|
3
3
|
import { Router, ChildrenOutletContexts, NavigationStart, ActivationStart, ActivationEnd, PRIMARY_OUTLET, UrlSegment, RouterOutlet, RouterEvent, NavigationEnd, NavigationCancel, NavigationError, ActivatedRoute } from '@angular/router';
|
|
4
4
|
import { Arrays, Objects as Objects$1, Dictionaries, Defined, Observables, Maps } from '@scion/toolkit/util';
|
|
5
5
|
import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
6
|
-
import { skipUntil, mergeMap, filter, observeOn, catchError, startWith, take, map, switchMap as switchMap$1, skip, subscribeOn, finalize, distinctUntilChanged,
|
|
6
|
+
import { skipUntil, mergeMap, filter, observeOn, catchError, startWith, take, map, switchMap as switchMap$1, skip, subscribeOn, finalize, distinctUntilChanged, takeUntil, first, combineLatestWith } from 'rxjs/operators';
|
|
7
7
|
import { coerceElement } from '@angular/cdk/coercion';
|
|
8
8
|
import { UUID } from '@scion/toolkit/uuid';
|
|
9
|
-
import { Observable, Subject, asapScheduler, AsyncSubject, lastValueFrom, iif, switchMap, race, pairwise, EMPTY, of, firstValueFrom, from, animationFrameScheduler, merge, fromEvent, BehaviorSubject, delay, identity,
|
|
9
|
+
import { Observable, Subject, asapScheduler, AsyncSubject, lastValueFrom, iif, switchMap, race, pairwise, EMPTY, of, firstValueFrom, from, animationFrameScheduler, merge, fromEvent, BehaviorSubject, delay, identity, noop, map as map$1, concatWith, withLatestFrom, mergeMap as mergeMap$1, mergeWith, NEVER, share, timer, ReplaySubject, combineLatest, asyncScheduler } from 'rxjs';
|
|
10
10
|
import { fromMutation$, fromResize$ } from '@scion/toolkit/observable';
|
|
11
11
|
import { subscribeIn, observeIn, filterArray, tapFirst } from '@scion/toolkit/operators';
|
|
12
12
|
import { SciViewportComponent } from '@scion/components/viewport';
|
|
@@ -19,7 +19,7 @@ import * as i1 from '@angular/forms';
|
|
|
19
19
|
import { NonNullableFormBuilder, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
20
20
|
import { FocusMonitor, CdkTrapFocus } from '@angular/cdk/a11y';
|
|
21
21
|
import { trigger, transition, style, animate } from '@angular/animations';
|
|
22
|
-
import { ManifestService, MicrofrontendPlatform, PlatformState, MessageHeaders, MessageClient, ResponseStatusCodes, OutletRouter, MicrofrontendPlatformConfig, APP_IDENTITY,
|
|
22
|
+
import { ManifestService, MicrofrontendPlatform, PlatformState, MessageHeaders, MessageClient, ResponseStatusCodes, mapToBody, OutletRouter, MicrofrontendPlatformConfig, APP_IDENTITY, HostManifestInterceptor, ObservableDecorator, IntentInterceptor, CapabilityInterceptor, MicrofrontendPlatformHost, IntentClient, PlatformPropertyService } from '@scion/microfrontend-platform';
|
|
23
23
|
import { Beans } from '@scion/toolkit/bean-manager';
|
|
24
24
|
import { ɵMicrofrontendRouteParams as _MicrofrontendRouteParams, WorkbenchCapabilities, WorkbenchTextService, WorkbenchMessageBox, eMESSAGE_BOX_MESSAGE_PARAM, ɵTHEME_CONTEXT_KEY as _THEME_CONTEXT_KEY, ɵWorkbenchCommands as _WorkbenchCommands, ɵPOPUP_CONTEXT as _POPUP_CONTEXT, ɵWorkbenchPopupMessageHeaders as _WorkbenchPopupMessageHeaders, WorkbenchPopup, ɵMESSAGE_BOX_CONTEXT as _MESSAGE_BOX_CONTEXT, ɵDIALOG_CONTEXT as _DIALOG_CONTEXT, ɵWorkbenchDialogMessageHeaders as _WorkbenchDialogMessageHeaders, WorkbenchDialog as WorkbenchDialog$1, WorkbenchRouter as WorkbenchRouter$1, WorkbenchPopupService, WorkbenchMessageBoxService as WorkbenchMessageBoxService$1, ɵWorkbenchMessageBoxService as _WorkbenchMessageBoxService, WorkbenchDialogService as WorkbenchDialogService$1, ɵWorkbenchDialogService as _WorkbenchDialogService, WorkbenchNotificationService, ɵWorkbenchTextService as _WorkbenchTextService, ɵVIEW_ID_CONTEXT_KEY as _VIEW_ID_CONTEXT_KEY, WorkbenchClient } from '@scion/workbench-client';
|
|
25
25
|
import { SciThrobberComponent } from '@scion/components/throbber';
|
|
@@ -5570,22 +5570,22 @@ function parseMatrixParams(matrixParams) {
|
|
|
5570
5570
|
return {};
|
|
5571
5571
|
}
|
|
5572
5572
|
const params = {};
|
|
5573
|
-
for (const match of
|
|
5573
|
+
for (const match of encodeEscapedSemicolons(matrixParams).matchAll(/(?<paramName>[^=;]+)=(?<paramValue>[^;]*)/g)) {
|
|
5574
5574
|
const { paramName, paramValue } = match.groups;
|
|
5575
|
-
params[
|
|
5575
|
+
params[paramName] = decodeSemicolons(paramValue);
|
|
5576
5576
|
}
|
|
5577
5577
|
return params;
|
|
5578
5578
|
/**
|
|
5579
|
-
*
|
|
5579
|
+
* Encodes escaped semicolons (`\\;`) as `;` (Unicode) to prevent interpretation as interpolation parameter separators.
|
|
5580
5580
|
*/
|
|
5581
|
-
function
|
|
5582
|
-
return value.replaceAll('\\;', '
|
|
5581
|
+
function encodeEscapedSemicolons(value) {
|
|
5582
|
+
return value.replaceAll('\\;', ';');
|
|
5583
5583
|
}
|
|
5584
5584
|
/**
|
|
5585
|
-
*
|
|
5585
|
+
* Decodes encoded semicolons (`;`) back to semicolons (`;`).
|
|
5586
5586
|
*/
|
|
5587
|
-
function
|
|
5588
|
-
return value.replaceAll('
|
|
5587
|
+
function decodeSemicolons(value) {
|
|
5588
|
+
return value.replaceAll(';', ';');
|
|
5589
5589
|
}
|
|
5590
5590
|
}
|
|
5591
5591
|
|
|
@@ -6399,7 +6399,7 @@ class ViewDropZoneDirective {
|
|
|
6399
6399
|
_zone = inject(NgZone);
|
|
6400
6400
|
_id = UUID.randomUUID();
|
|
6401
6401
|
_boundingClientRect = boundingClientRect(inject(ElementRef));
|
|
6402
|
-
_dropRegion$ =
|
|
6402
|
+
_dropRegion$ = new BehaviorSubject(null);
|
|
6403
6403
|
_dropRegionSize = computed(() => ({
|
|
6404
6404
|
maxHeight: coercePixelValue(this.dropRegionSize(), { containerSize: this._boundingClientRect().height }),
|
|
6405
6405
|
maxWidth: coercePixelValue(this.dropRegionSize(), { containerSize: this._boundingClientRect().width }),
|
|
@@ -6484,7 +6484,9 @@ class ViewDropZoneDirective {
|
|
|
6484
6484
|
});
|
|
6485
6485
|
// Enable drop zone based on dragging over an allowed drop region.
|
|
6486
6486
|
// If enabled, drag events are not received by nested drop zones.
|
|
6487
|
-
const dropZoneActivator = this._dropRegion
|
|
6487
|
+
const dropZoneActivator = this._dropRegion$
|
|
6488
|
+
.pipe(distinctUntilChanged())
|
|
6489
|
+
.subscribe(dropRegion => {
|
|
6488
6490
|
setStyle(dropZoneElement, { 'pointer-events': dropRegion ? null : 'none' });
|
|
6489
6491
|
setAttribute(dropZoneElement, { 'data-region': dropRegion });
|
|
6490
6492
|
});
|
|
@@ -6509,7 +6511,7 @@ class ViewDropZoneDirective {
|
|
|
6509
6511
|
// order rather than in the order of tracked signal changes.
|
|
6510
6512
|
const dragging = this._viewDragService.dragging;
|
|
6511
6513
|
this._dropRegion$
|
|
6512
|
-
.pipe(
|
|
6514
|
+
.pipe(distinctUntilChanged(),
|
|
6513
6515
|
// When leaving the drop zone (no drop region) but continue dragging, remove the drop zone asynchronously
|
|
6514
6516
|
// to animate transition of the placeholder to another drop zone. If ended dragging, remove the placeholder
|
|
6515
6517
|
// immediately.
|
|
@@ -6613,28 +6615,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImpor
|
|
|
6613
6615
|
function coercePixelValue(pixelOrRatio, options) {
|
|
6614
6616
|
return (pixelOrRatio <= 1) ? options.containerSize * pixelOrRatio : pixelOrRatio;
|
|
6615
6617
|
}
|
|
6616
|
-
/**
|
|
6617
|
-
* Creates a subject-like observable to observe and set the drop region,
|
|
6618
|
-
* multicasting the drop zone to many observers while throttling emission
|
|
6619
|
-
* to the latest drop region per animation frame.
|
|
6620
|
-
*/
|
|
6621
|
-
function createDropRegionObservable() {
|
|
6622
|
-
const zone = inject(NgZone);
|
|
6623
|
-
const region$ = new BehaviorSubject(null);
|
|
6624
|
-
const observe$ = region$
|
|
6625
|
-
.pipe(subscribeIn(fn => zone.runOutsideAngular(fn)),
|
|
6626
|
-
// Ensure not to be in Angular.
|
|
6627
|
-
tap(() => NgZone.assertNotInAngularZone()),
|
|
6628
|
-
// Throttle emission to a single event per animation frame.
|
|
6629
|
-
audit(() => nextAnimationFrame$()), share());
|
|
6630
|
-
// Add notifier function.
|
|
6631
|
-
observe$.next = (region) => {
|
|
6632
|
-
if (region$.value !== region) {
|
|
6633
|
-
zone.runOutsideAngular(() => region$.next(region));
|
|
6634
|
-
}
|
|
6635
|
-
};
|
|
6636
|
-
return observe$;
|
|
6637
|
-
}
|
|
6638
6618
|
|
|
6639
6619
|
/*
|
|
6640
6620
|
* Copyright (c) 2018-2024 Swiss Federal Railways
|
|
@@ -14359,86 +14339,185 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImpor
|
|
|
14359
14339
|
/**
|
|
14360
14340
|
* Registers a text provider for the SCION Workbench to get texts from micro apps.
|
|
14361
14341
|
*
|
|
14362
|
-
*
|
|
14363
|
-
* If not, the key is ignored and `undefined` is returned.
|
|
14364
|
-
*
|
|
14365
|
-
* Remote key format: "workbench.external.~<APP_SYMBOLIC_NAME>~.<TEXT_KEY>".
|
|
14342
|
+
* This text provider provides texts for keys matching the format: "workbench.external.scion-workbench-client.<APP_SYMBOLIC_NAME>.<TRANSLATABLE>".
|
|
14366
14343
|
*
|
|
14367
14344
|
* @see createRemoteTranslatable
|
|
14368
14345
|
*/
|
|
14369
14346
|
function provideRemoteTextProvider() {
|
|
14370
|
-
const
|
|
14347
|
+
const REMOTE_TRANSLATION_KEY = /^workbench\.external\.scion-workbench-client\.(?<provider>[^\\.]+)\.%(?<key>.+)$/;
|
|
14348
|
+
const REMOTE_TEXT = /^workbench\.external\.scion-workbench-client\.(?<provider>[^\\.]+)\.(?<text>[^%].+)$/;
|
|
14349
|
+
const cache = new Map();
|
|
14371
14350
|
return makeEnvironmentProviders([
|
|
14372
14351
|
{
|
|
14373
14352
|
provide: WORKBENCH_TEXT_PROVIDER,
|
|
14374
|
-
useValue:
|
|
14353
|
+
useValue: provideRemoteText,
|
|
14354
|
+
multi: true,
|
|
14355
|
+
},
|
|
14356
|
+
{
|
|
14357
|
+
provide: WORKBENCH_TEXT_PROVIDER,
|
|
14358
|
+
useValue: interpolateRemoteText,
|
|
14375
14359
|
multi: true,
|
|
14376
14360
|
},
|
|
14377
14361
|
]);
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14362
|
+
/**
|
|
14363
|
+
* Provides text from a remote app.
|
|
14364
|
+
*/
|
|
14365
|
+
function provideRemoteText(translationKey, params) {
|
|
14366
|
+
// Test if the key matches a remote translation key.
|
|
14367
|
+
const match = REMOTE_TRANSLATION_KEY.exec(translationKey);
|
|
14381
14368
|
if (!match) {
|
|
14382
|
-
return undefined;
|
|
14369
|
+
return undefined;
|
|
14383
14370
|
}
|
|
14384
|
-
// Parse key and provider from the remote key.
|
|
14371
|
+
// Parse key and provider from the remote translation key.
|
|
14385
14372
|
const { key, provider } = match.groups;
|
|
14373
|
+
// Return cached text if cached.
|
|
14374
|
+
const cacheKey = createCacheKey(translationKey, params, provider);
|
|
14375
|
+
if (cache.has(cacheKey)) {
|
|
14376
|
+
return toSignal(cache.get(cacheKey), { initialValue: '' });
|
|
14377
|
+
}
|
|
14386
14378
|
// Request the text by intent. Parameters starting with the topic protocol 'topic://' are resolved via messaging.
|
|
14387
|
-
const text$ = observeParams$(params)
|
|
14388
|
-
.pipe(
|
|
14379
|
+
const text$ = observeParams$(params, { provider })
|
|
14380
|
+
.pipe(
|
|
14381
|
+
// Ensure the observable to never complete independent of text provider request completion, simplifying cache cleanup as
|
|
14382
|
+
// finalize is only called when the last subscriber unsubscribes, after the specified TTL.
|
|
14383
|
+
concatWith(NEVER), map$1(params => params.reduce((translatable, [param, value]) => `${translatable};${param}=${encodeSemicolons$1(value)}`, `%${key}`)), switchMap(translatable => Beans.get(WorkbenchTextService).text$(translatable, { provider })), map$1(text => text ?? `%${key}`),
|
|
14384
|
+
// Remove cached text when an error occurs, or when the subscriber count drops to zero, after the specified TTL.
|
|
14385
|
+
finalize(() => cache.delete(cacheKey)), share({
|
|
14386
|
+
connector: () => new ReplaySubject(1),
|
|
14387
|
+
resetOnRefCountZero: () => timer(0, animationFrameScheduler), // reset asynchronously to prevent flickering of translated texts on re-layout
|
|
14388
|
+
}));
|
|
14389
|
+
cache.set(cacheKey, text$);
|
|
14390
|
+
return toSignal(text$, { initialValue: '' });
|
|
14391
|
+
}
|
|
14392
|
+
/**
|
|
14393
|
+
* Substitutes named parameters in a remote text.
|
|
14394
|
+
*/
|
|
14395
|
+
function interpolateRemoteText(translationKey, params) {
|
|
14396
|
+
// Test if the key matches a remote text.
|
|
14397
|
+
const match = REMOTE_TEXT.exec(translationKey);
|
|
14398
|
+
if (!match) {
|
|
14399
|
+
return undefined;
|
|
14400
|
+
}
|
|
14401
|
+
// Parse text and provider from the remote text.
|
|
14402
|
+
const { text, provider } = match.groups;
|
|
14403
|
+
// Return cached text if cached.
|
|
14404
|
+
const cacheKey = createCacheKey(translationKey, params, provider);
|
|
14405
|
+
if (cache.has(cacheKey)) {
|
|
14406
|
+
return toSignal(cache.get(cacheKey), { initialValue: '' });
|
|
14407
|
+
}
|
|
14408
|
+
// Substitute params. Parameters starting with the topic protocol 'topic://' are resolved via messaging.
|
|
14409
|
+
const text$ = observeParams$(params, { provider })
|
|
14410
|
+
.pipe(
|
|
14411
|
+
// Never complete the observable, simplifying cache cleanup as `finalize` is only called when the last subscriber unsubscribes, after the specified TTL.
|
|
14412
|
+
concatWith(NEVER), map$1(params => params.reduce((text, [param, value]) => text.replaceAll(`:${param}`, value), decodeSemicolons(text))),
|
|
14413
|
+
// Remove cached text when an error occurs, or when the subscriber count drops to zero, after the specified TTL.
|
|
14414
|
+
finalize(() => cache.delete(cacheKey)), share({
|
|
14415
|
+
connector: () => new ReplaySubject(1),
|
|
14416
|
+
resetOnRefCountZero: () => timer(0, animationFrameScheduler), // reset asynchronously to prevent flickering of translated texts on re-layout
|
|
14417
|
+
}));
|
|
14418
|
+
cache.set(cacheKey, text$);
|
|
14389
14419
|
return toSignal(text$, { initialValue: '' });
|
|
14390
14420
|
}
|
|
14391
14421
|
/**
|
|
14392
|
-
* Creates an Observable that emits tuples of name
|
|
14422
|
+
* Creates an Observable that emits tuples of name-value pairs from the passed parameters.
|
|
14393
14423
|
*
|
|
14394
14424
|
* Parameters starting with the topic protocol 'topic://' are resolved via topic-based messaging.
|
|
14395
14425
|
*/
|
|
14396
|
-
function observeParams$(params) {
|
|
14397
|
-
const observableParams = Object.entries(params).map(([
|
|
14426
|
+
function observeParams$(params, options) {
|
|
14427
|
+
const observableParams = Object.entries(params).map(([param, value]) => {
|
|
14398
14428
|
if (!value.startsWith(TOPIC_PROTOCOL)) {
|
|
14399
|
-
return of([
|
|
14429
|
+
return of([param, value]);
|
|
14400
14430
|
}
|
|
14401
14431
|
const topic = value.substring(TOPIC_PROTOCOL.length);
|
|
14402
|
-
return Beans.get(MessageClient).request$(topic, undefined, { retain: true })
|
|
14432
|
+
return Beans.get(MessageClient).request$(topic, undefined, { retain: true })
|
|
14433
|
+
.pipe(mapToBody(),
|
|
14434
|
+
// Resolve text if the resolver returns a translatable.
|
|
14435
|
+
switchMap(resolved => resolved?.startsWith('%') ? Beans.get(WorkbenchTextService).text$(resolved, options) : of(resolved)), map$1(resolved => [param, resolved ?? '']));
|
|
14403
14436
|
});
|
|
14404
14437
|
return observableParams.length ? combineLatest(observableParams) : of([]);
|
|
14405
14438
|
}
|
|
14439
|
+
/**
|
|
14440
|
+
* Creates the cache key for specified translatable.
|
|
14441
|
+
*/
|
|
14442
|
+
function createCacheKey(key, params, provider) {
|
|
14443
|
+
const translatable = Object.entries(params).reduce((translatable, [param, value]) => `${translatable};${param}=${value}`, `%${key}`);
|
|
14444
|
+
return `${translatable}@${provider}`;
|
|
14445
|
+
}
|
|
14406
14446
|
}
|
|
14407
14447
|
/**
|
|
14408
14448
|
* Creates a translatable for the SCION Workbench to request the text from a micro app.
|
|
14409
14449
|
*
|
|
14410
|
-
* Passed parameters are used to substitute named
|
|
14450
|
+
* Passed parameters are used to substitute named parameters in the text or interpolation parameters of the translation key.
|
|
14411
14451
|
*
|
|
14412
|
-
*
|
|
14452
|
+
* A named parameter starts with a colon (`:`) followed by the parameter name and can be substituted by an explicit value (passed as value param) or topic (passed as topic param).
|
|
14453
|
+
* Topic params define a topic with the actual value requested when resolving the translatable. A topic can also reference value params as named params in topic segments.
|
|
14413
14454
|
*
|
|
14414
|
-
*
|
|
14415
|
-
*
|
|
14416
|
-
*
|
|
14455
|
+
* @example - Translation Key
|
|
14456
|
+
* `%translationKey;param1=:namedParam1;param2=:namedParam2`
|
|
14457
|
+
*
|
|
14458
|
+
* @example - Text
|
|
14459
|
+
* `Text with :namedParam1 and :namedParam2`
|
|
14417
14460
|
*
|
|
14418
14461
|
* @param translatable - Specifies the translatable.
|
|
14419
|
-
* @param config - Specifies the text provider and values for substituting named
|
|
14462
|
+
* @param config - Specifies the text provider and values for substituting named parameters.
|
|
14420
14463
|
* @return Translatable that can be passed to the workbench's {@link text()} function for translation.
|
|
14421
14464
|
*
|
|
14422
14465
|
* @see provideRemoteTextProvider
|
|
14423
14466
|
*/
|
|
14424
14467
|
function createRemoteTranslatable(translatable, config) {
|
|
14425
|
-
if (!translatable
|
|
14468
|
+
if (!translatable) {
|
|
14469
|
+
return translatable;
|
|
14470
|
+
}
|
|
14471
|
+
// Create Map of value params.
|
|
14472
|
+
const valueParams = new Map(Object.entries(Dictionaries.coerce(config.valueParams)).map(([param, value]) => [param, encodeSemicolons$1(value)]));
|
|
14473
|
+
// Create Map of topic params, substituting referenced value params.
|
|
14474
|
+
const topicParams = new Map(Object.entries(Dictionaries.coerce(config.topicParams)).map(([param, topic]) => [param, toTopicParam(topic)]));
|
|
14475
|
+
// Return text as-is if not a translation key nor referencing parameters.
|
|
14476
|
+
if (!translatable.startsWith('%') && !containsParam(translatable, valueParams) && !containsParam(translatable, topicParams)) {
|
|
14426
14477
|
return translatable;
|
|
14427
14478
|
}
|
|
14428
|
-
const
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14479
|
+
const remoteTranslatablePrefix = `%workbench.external.scion-workbench-client.${config.appSymbolicName}`;
|
|
14480
|
+
if (translatable.startsWith('%')) {
|
|
14481
|
+
// Substitute named parameters in interpolation params.
|
|
14482
|
+
return `${remoteTranslatablePrefix}.${translatable}`.replace(/(?<==):(?<namedParam>[^;]+)/g, (match, param) => valueParams.get(param) ?? topicParams.get(param) ?? match);
|
|
14483
|
+
}
|
|
14484
|
+
else {
|
|
14485
|
+
// Append referenced parameters in matrix notation.
|
|
14486
|
+
return [...valueParams, ...topicParams]
|
|
14487
|
+
.filter(([param]) => translatable.includes(`:${param}`))
|
|
14488
|
+
.reduce((translatable, [param, value]) => `${translatable};${param}=${value}`, `${remoteTranslatablePrefix}.${encodeSemicolons$1(translatable)}`);
|
|
14489
|
+
}
|
|
14490
|
+
/**
|
|
14491
|
+
* Adds the topic protocol to indicate resolution via topic-based messaging and substitutes named topic segments.
|
|
14492
|
+
*/
|
|
14493
|
+
function toTopicParam(topic) {
|
|
14494
|
+
return `${TOPIC_PROTOCOL}${topic.replace(/(?<=\/|^):(?<namedParam>[^/]+)/g, (match, namedParam) => {
|
|
14495
|
+
return valueParams.get(namedParam) ?? match;
|
|
14496
|
+
})}`;
|
|
14497
|
+
}
|
|
14498
|
+
/**
|
|
14499
|
+
* Tests whether the passed text references any of the passed params.
|
|
14500
|
+
*/
|
|
14501
|
+
function containsParam(text, params) {
|
|
14502
|
+
return Array.from(params.keys()).some(param => text.includes(`:${param}`));
|
|
14503
|
+
}
|
|
14437
14504
|
}
|
|
14438
14505
|
/**
|
|
14439
14506
|
* Prefix of topic params.
|
|
14440
14507
|
*/
|
|
14441
14508
|
const TOPIC_PROTOCOL = 'topic://';
|
|
14509
|
+
/**
|
|
14510
|
+
* Encodes semicolons (`;`) as `;` (Unicode) to prevent interpretation as interpolation parameter separators.
|
|
14511
|
+
*/
|
|
14512
|
+
function encodeSemicolons$1(value) {
|
|
14513
|
+
return `${value}`.replaceAll(';', ';');
|
|
14514
|
+
}
|
|
14515
|
+
/**
|
|
14516
|
+
* Decodes encoded semicolons (`;`) back to semicolons (`;`).
|
|
14517
|
+
*/
|
|
14518
|
+
function decodeSemicolons(value) {
|
|
14519
|
+
return value.replaceAll(';', ';');
|
|
14520
|
+
}
|
|
14442
14521
|
|
|
14443
14522
|
/*
|
|
14444
14523
|
* Copyright (c) 2018-2024 Swiss Federal Railways
|
|
@@ -16625,7 +16704,10 @@ class MicrofrontendViewComponent {
|
|
|
16625
16704
|
}
|
|
16626
16705
|
installNavigator() {
|
|
16627
16706
|
this._route.params
|
|
16628
|
-
.pipe(switchMap(params => this.fetchCapability$(params[_MicrofrontendRouteParams.ɵVIEW_CAPABILITY_ID]).pipe(map(capability => ({ capability, params })))),
|
|
16707
|
+
.pipe(switchMap(params => this.fetchCapability$(params[_MicrofrontendRouteParams.ɵVIEW_CAPABILITY_ID]).pipe(map(capability => ({ capability, params })))), executeOn({
|
|
16708
|
+
onEach: context => this.setViewTitleAndHeading(context),
|
|
16709
|
+
onCapabilityChange: context => this.onCapabilityChange(context),
|
|
16710
|
+
}), filterNullCapability(), delayIfLazy(), serializeExecution(context => this.onNavigate(context)), subscribeOn(asyncScheduler), // subscribe asynchronously to prevent manual change detection in `onCapabilityChange` during component construction.
|
|
16629
16711
|
takeUntilDestroyed())
|
|
16630
16712
|
.subscribe();
|
|
16631
16713
|
}
|
|
@@ -16736,13 +16818,23 @@ class MicrofrontendViewComponent {
|
|
|
16736
16818
|
.catch((error) => this._messageClient.publish(replyTo, stringifyError(error), { headers: new Map().set(MessageHeaders.Status, ResponseStatusCodes.ERROR) }));
|
|
16737
16819
|
});
|
|
16738
16820
|
}
|
|
16821
|
+
/**
|
|
16822
|
+
* Sets view title and heading as defined on the capability.
|
|
16823
|
+
*/
|
|
16824
|
+
setViewTitleAndHeading(context) {
|
|
16825
|
+
const { capability, params } = context;
|
|
16826
|
+
if (capability?.properties.title) {
|
|
16827
|
+
this.view.title = createRemoteTranslatable(capability.properties.title, { appSymbolicName: capability.metadata.appSymbolicName, valueParams: params, topicParams: capability.properties.resolve });
|
|
16828
|
+
}
|
|
16829
|
+
if (capability?.properties.heading) {
|
|
16830
|
+
this.view.heading = createRemoteTranslatable(capability.properties.heading, { appSymbolicName: capability.metadata.appSymbolicName, valueParams: params, topicParams: capability.properties.resolve });
|
|
16831
|
+
}
|
|
16832
|
+
}
|
|
16739
16833
|
/**
|
|
16740
16834
|
* Updates the properties of this view, such as the view title, as defined by the capability.
|
|
16741
16835
|
*/
|
|
16742
16836
|
setViewProperties(context) {
|
|
16743
|
-
const { capability
|
|
16744
|
-
this.view.title = (capability && createRemoteTranslatable(capability.properties.title, { appSymbolicName: capability.metadata.appSymbolicName, valueParams: params, topicParams: capability.properties.resolve })) ?? null;
|
|
16745
|
-
this.view.heading = (capability && createRemoteTranslatable(capability.properties.heading, { appSymbolicName: capability.metadata.appSymbolicName, valueParams: params, topicParams: capability.properties.resolve })) ?? null;
|
|
16837
|
+
const { capability } = context;
|
|
16746
16838
|
this.view.classList.application = capability?.properties.cssClass;
|
|
16747
16839
|
this.view.closable = capability?.properties.closable ?? true;
|
|
16748
16840
|
this.view.dirty = false;
|
|
@@ -16876,14 +16968,17 @@ function configureMicrofrontendGlassPane() {
|
|
|
16876
16968
|
];
|
|
16877
16969
|
}
|
|
16878
16970
|
/**
|
|
16879
|
-
* Executes passed
|
|
16971
|
+
* Executes passed functions based on the current state.
|
|
16880
16972
|
*/
|
|
16881
|
-
function
|
|
16973
|
+
function executeOn(executeOn) {
|
|
16974
|
+
const { onEach, onCapabilityChange } = executeOn;
|
|
16882
16975
|
let prevCapability = null;
|
|
16883
16976
|
return map(({ capability, params }) => {
|
|
16884
16977
|
const context = { capability, prevCapability, params };
|
|
16978
|
+
onEach?.(context);
|
|
16979
|
+
// Test if the capability has changed.
|
|
16885
16980
|
if (prevCapability?.metadata.id !== capability?.metadata.id) {
|
|
16886
|
-
onCapabilityChange(context);
|
|
16981
|
+
onCapabilityChange?.(context);
|
|
16887
16982
|
prevCapability = capability;
|
|
16888
16983
|
}
|
|
16889
16984
|
return context;
|
|
@@ -17055,7 +17150,7 @@ function provideHostTextProvider() {
|
|
|
17055
17150
|
provideMicrofrontendPlatformInitializer(() => {
|
|
17056
17151
|
const injector = inject(Injector);
|
|
17057
17152
|
WorkbenchClient.registerTextProvider((key, params) => {
|
|
17058
|
-
const translatable = Object.entries(params).reduce((translatable, [name, value]) => `${translatable};${name}=${value}`, `%${key}`);
|
|
17153
|
+
const translatable = Object.entries(params).reduce((translatable, [name, value]) => `${translatable};${name}=${encodeSemicolons(value)}`, `%${key}`);
|
|
17059
17154
|
const environmentInjector = createEnvironmentInjector([], injector.get(EnvironmentInjector));
|
|
17060
17155
|
return toObservable(text(translatable, { injector: environmentInjector }), { injector: environmentInjector })
|
|
17061
17156
|
.pipe(map$1(text => text !== '' && text !== key ? text : undefined), // emit `undefined` if not found the text
|
|
@@ -17064,6 +17159,14 @@ function provideHostTextProvider() {
|
|
|
17064
17159
|
}),
|
|
17065
17160
|
]);
|
|
17066
17161
|
}
|
|
17162
|
+
/**
|
|
17163
|
+
* Encodes semicolons (`;`) as `\\;` to prevent interpretation as interpolation parameter separators.
|
|
17164
|
+
*
|
|
17165
|
+
* @see Translatable
|
|
17166
|
+
*/
|
|
17167
|
+
function encodeSemicolons(value) {
|
|
17168
|
+
return value.replaceAll(';', '\\;');
|
|
17169
|
+
}
|
|
17067
17170
|
|
|
17068
17171
|
/*
|
|
17069
17172
|
* Copyright (c) 2018-2024 Swiss Federal Railways
|