@scion/workbench 20.0.0-beta.7 → 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.
|
@@ -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';
|
|
@@ -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
|
|
@@ -14366,6 +14346,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImpor
|
|
|
14366
14346
|
function provideRemoteTextProvider() {
|
|
14367
14347
|
const REMOTE_TRANSLATION_KEY = /^workbench\.external\.scion-workbench-client\.(?<provider>[^\\.]+)\.%(?<key>.+)$/;
|
|
14368
14348
|
const REMOTE_TEXT = /^workbench\.external\.scion-workbench-client\.(?<provider>[^\\.]+)\.(?<text>[^%].+)$/;
|
|
14349
|
+
const cache = new Map();
|
|
14369
14350
|
return makeEnvironmentProviders([
|
|
14370
14351
|
{
|
|
14371
14352
|
provide: WORKBENCH_TEXT_PROVIDER,
|
|
@@ -14389,9 +14370,23 @@ function provideRemoteTextProvider() {
|
|
|
14389
14370
|
}
|
|
14390
14371
|
// Parse key and provider from the remote translation key.
|
|
14391
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
|
+
}
|
|
14392
14378
|
// Request the text by intent. Parameters starting with the topic protocol 'topic://' are resolved via messaging.
|
|
14393
14379
|
const text$ = observeParams$(params, { provider })
|
|
14394
|
-
.pipe(
|
|
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$);
|
|
14395
14390
|
return toSignal(text$, { initialValue: '' });
|
|
14396
14391
|
}
|
|
14397
14392
|
/**
|
|
@@ -14405,9 +14400,22 @@ function provideRemoteTextProvider() {
|
|
|
14405
14400
|
}
|
|
14406
14401
|
// Parse text and provider from the remote text.
|
|
14407
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
14408
|
// Substitute params. Parameters starting with the topic protocol 'topic://' are resolved via messaging.
|
|
14409
14409
|
const text$ = observeParams$(params, { provider })
|
|
14410
|
-
.pipe(
|
|
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$);
|
|
14411
14419
|
return toSignal(text$, { initialValue: '' });
|
|
14412
14420
|
}
|
|
14413
14421
|
/**
|
|
@@ -14428,6 +14436,13 @@ function provideRemoteTextProvider() {
|
|
|
14428
14436
|
});
|
|
14429
14437
|
return observableParams.length ? combineLatest(observableParams) : of([]);
|
|
14430
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
|
+
}
|
|
14431
14446
|
}
|
|
14432
14447
|
/**
|
|
14433
14448
|
* Creates a translatable for the SCION Workbench to request the text from a micro app.
|
|
@@ -16689,7 +16704,10 @@ class MicrofrontendViewComponent {
|
|
|
16689
16704
|
}
|
|
16690
16705
|
installNavigator() {
|
|
16691
16706
|
this._route.params
|
|
16692
|
-
.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.
|
|
16693
16711
|
takeUntilDestroyed())
|
|
16694
16712
|
.subscribe();
|
|
16695
16713
|
}
|
|
@@ -16800,13 +16818,23 @@ class MicrofrontendViewComponent {
|
|
|
16800
16818
|
.catch((error) => this._messageClient.publish(replyTo, stringifyError(error), { headers: new Map().set(MessageHeaders.Status, ResponseStatusCodes.ERROR) }));
|
|
16801
16819
|
});
|
|
16802
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
|
+
}
|
|
16803
16833
|
/**
|
|
16804
16834
|
* Updates the properties of this view, such as the view title, as defined by the capability.
|
|
16805
16835
|
*/
|
|
16806
16836
|
setViewProperties(context) {
|
|
16807
|
-
const { capability
|
|
16808
|
-
this.view.title = (capability && createRemoteTranslatable(capability.properties.title, { appSymbolicName: capability.metadata.appSymbolicName, valueParams: params, topicParams: capability.properties.resolve })) ?? null;
|
|
16809
|
-
this.view.heading = (capability && createRemoteTranslatable(capability.properties.heading, { appSymbolicName: capability.metadata.appSymbolicName, valueParams: params, topicParams: capability.properties.resolve })) ?? null;
|
|
16837
|
+
const { capability } = context;
|
|
16810
16838
|
this.view.classList.application = capability?.properties.cssClass;
|
|
16811
16839
|
this.view.closable = capability?.properties.closable ?? true;
|
|
16812
16840
|
this.view.dirty = false;
|
|
@@ -16940,14 +16968,17 @@ function configureMicrofrontendGlassPane() {
|
|
|
16940
16968
|
];
|
|
16941
16969
|
}
|
|
16942
16970
|
/**
|
|
16943
|
-
* Executes passed
|
|
16971
|
+
* Executes passed functions based on the current state.
|
|
16944
16972
|
*/
|
|
16945
|
-
function
|
|
16973
|
+
function executeOn(executeOn) {
|
|
16974
|
+
const { onEach, onCapabilityChange } = executeOn;
|
|
16946
16975
|
let prevCapability = null;
|
|
16947
16976
|
return map(({ capability, params }) => {
|
|
16948
16977
|
const context = { capability, prevCapability, params };
|
|
16978
|
+
onEach?.(context);
|
|
16979
|
+
// Test if the capability has changed.
|
|
16949
16980
|
if (prevCapability?.metadata.id !== capability?.metadata.id) {
|
|
16950
|
-
onCapabilityChange(context);
|
|
16981
|
+
onCapabilityChange?.(context);
|
|
16951
16982
|
prevCapability = capability;
|
|
16952
16983
|
}
|
|
16953
16984
|
return context;
|