@scion/workbench 18.0.0-beta.1 → 18.0.0-beta.3

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.
Files changed (111) hide show
  1. package/_index.scss +3 -3
  2. package/design/_workbench-icon-font.scss +1 -1
  3. package/esm2022/lib/common/objects.util.mjs +7 -1
  4. package/esm2022/lib/common/uid.util.mjs +22 -0
  5. package/esm2022/lib/content-projection/content-projection.directive.mjs +16 -17
  6. package/esm2022/lib/dialog//311/265workbench-dialog.mjs +3 -3
  7. package/esm2022/lib/filter-field/filter-field.component.mjs +5 -5
  8. package/esm2022/lib/layout/grid-element/grid-element.component.mjs +6 -16
  9. package/esm2022/lib/layout/migration/model/workbench-layout-migration-v5.model.mjs +11 -0
  10. package/esm2022/lib/layout/migration/workbench-layout-migration-v3.service.mjs +2 -2
  11. package/esm2022/lib/layout/migration/workbench-layout-migration-v5.service.mjs +67 -0
  12. package/esm2022/lib/layout/stringifier.mjs +70 -0
  13. package/esm2022/lib/layout/workbench-layout.model.mjs +1 -1
  14. package/esm2022/lib/layout/workench-layout-serializer.service.mjs +16 -25
  15. package/esm2022/lib/layout//311/265workbench-layout.mjs +14 -18
  16. package/esm2022/lib/microfrontend-platform/common/microfrontend.util.mjs +18 -1
  17. package/esm2022/lib/microfrontend-platform/initialization/microfrontend-platform-initializer.service.mjs +28 -21
  18. package/esm2022/lib/microfrontend-platform/initialization/workbench-host-manifest-interceptor.service.mjs +11 -1
  19. package/esm2022/lib/microfrontend-platform/manifest-object-cache.service.mjs +63 -0
  20. package/esm2022/lib/microfrontend-platform/microfrontend-dialog/microfrontend-dialog-capability-validator.interceptor.mjs +5 -4
  21. package/esm2022/lib/microfrontend-platform/microfrontend-dialog/microfrontend-dialog.component.mjs +2 -2
  22. package/esm2022/lib/microfrontend-platform/microfrontend-host-message-box/text-message/text-message.component.mjs +3 -3
  23. package/esm2022/lib/microfrontend-platform/microfrontend-host-popup/microfrontend-host-popup.component.mjs +3 -2
  24. package/esm2022/lib/microfrontend-platform/microfrontend-message-box/microfrontend-message-box-capability-validator.interceptor.mjs +3 -2
  25. package/esm2022/lib/microfrontend-platform/microfrontend-message-box/microfrontend-message-box.component.mjs +2 -2
  26. package/esm2022/lib/microfrontend-platform/microfrontend-perspective/microfrontend-perspective-capability-validator.interceptor.mjs +39 -0
  27. package/esm2022/lib/microfrontend-platform/microfrontend-perspective/microfrontend-perspective-installer.service.mjs +120 -0
  28. package/esm2022/lib/microfrontend-platform/microfrontend-perspective/microfrontend-perspective-intent-handler.interceptor.mjs +55 -0
  29. package/esm2022/lib/microfrontend-platform/microfrontend-perspective/workbench-perspective-data.mjs +19 -0
  30. package/esm2022/lib/microfrontend-platform/microfrontend-popup/microfrontend-popup-capability-validator.interceptor.mjs +4 -3
  31. package/esm2022/lib/microfrontend-platform/microfrontend-popup/microfrontend-popup.component.mjs +2 -13
  32. package/esm2022/lib/microfrontend-platform/microfrontend-view/microfrontend-view-command-handler.service.mjs +1 -16
  33. package/esm2022/lib/microfrontend-platform/microfrontend-view/microfrontend-view.component.mjs +42 -17
  34. package/esm2022/lib/microfrontend-platform/public_api.mjs +2 -1
  35. package/esm2022/lib/microfrontend-platform/routing/microfrontend-view-capability-validator.interceptor.mjs +5 -4
  36. package/esm2022/lib/microfrontend-platform/routing/microfrontend-view-intent-handler.interceptor.mjs +7 -4
  37. package/esm2022/lib/microfrontend-platform/routing/microfrontend-view-routes.mjs +26 -4
  38. package/esm2022/lib/microfrontend-platform/stable-capability-id-assigner.interceptor.mjs +32 -0
  39. package/esm2022/lib/microfrontend-platform/workbench-microfrontend-support.mjs +23 -5
  40. package/esm2022/lib/page-not-found/format-url.pipe.mjs +2 -2
  41. package/esm2022/lib/page-not-found/page-not-found.component.mjs +3 -3
  42. package/esm2022/lib/part/part.component.mjs +3 -5
  43. package/esm2022/lib/perspective/workbench-grid-merger.service.mjs +3 -3
  44. package/esm2022/lib/perspective/workbench-perspective.model.mjs +1 -1
  45. package/esm2022/lib/perspective/workbench-perspective.service.mjs +61 -51
  46. package/esm2022/lib/perspective//311/265workbench-perspective.model.mjs +11 -2
  47. package/esm2022/lib/popup/popup.config.mjs +3 -3
  48. package/esm2022/lib/portal/wb-component-portal.mjs +3 -1
  49. package/esm2022/lib/routing/public_api.mjs +1 -2
  50. package/esm2022/lib/routing/routing.model.mjs +1 -1
  51. package/esm2022/lib/routing/workbench-auxiliary-route-installer.service.mjs +94 -0
  52. package/esm2022/lib/routing/workbench-layout-differ.mjs +3 -10
  53. package/esm2022/lib/routing/workbench-url-observer.service.mjs +31 -27
  54. package/esm2022/lib/routing/workbench-view-outlet-differ.mjs +58 -0
  55. package/esm2022/lib/routing//311/265workbench-router.service.mjs +2 -2
  56. package/esm2022/lib/view/view-move-handler.service.mjs +5 -5
  57. package/esm2022/lib/view/view.component.mjs +38 -25
  58. package/esm2022/lib/view/workbench-view-route-guards.mjs +2 -2
  59. package/esm2022/lib/view//311/265workbench-view.model.mjs +25 -19
  60. package/esm2022/lib/view-dnd/grid-drop-targets.util.mjs +2 -2
  61. package/esm2022/lib/workbench-config.mjs +1 -1
  62. package/esm2022/lib/workbench-id.mjs +3 -3
  63. package/esm2022/lib/workbench.component.mjs +2 -2
  64. package/esm2022/lib/workbench.constants.mjs +1 -5
  65. package/esm2022/lib/workbench.provider.mjs +3 -9
  66. package/fesm2022/scion-workbench.mjs +855 -326
  67. package/fesm2022/scion-workbench.mjs.map +1 -1
  68. package/lib/common/objects.util.d.ts +4 -0
  69. package/lib/common/uid.util.d.ts +9 -0
  70. package/lib/dialog//311/265workbench-dialog.d.ts +1 -1
  71. package/lib/filter-field/filter-field.component.d.ts +1 -1
  72. package/lib/layout/grid-element/grid-element.component.d.ts +1 -8
  73. package/lib/layout/migration/model/workbench-layout-migration-v5.model.d.ts +32 -0
  74. package/lib/layout/migration/workbench-layout-migration-v5.service.d.ts +12 -0
  75. package/lib/layout/stringifier.d.ts +26 -0
  76. package/lib/layout/workbench-layout.model.d.ts +3 -3
  77. package/lib/layout/workench-layout-serializer.service.d.ts +13 -15
  78. package/lib/layout//311/265workbench-layout.d.ts +3 -3
  79. package/lib/microfrontend-platform/common/microfrontend.util.d.ts +5 -1
  80. package/lib/microfrontend-platform/initialization/microfrontend-platform-initializer.service.d.ts +7 -3
  81. package/lib/microfrontend-platform/manifest-object-cache.service.d.ts +33 -0
  82. package/lib/microfrontend-platform/microfrontend-host-message-box/text-message/text-message.component.d.ts +1 -1
  83. package/lib/microfrontend-platform/microfrontend-perspective/microfrontend-perspective-capability-validator.interceptor.d.ts +10 -0
  84. package/lib/microfrontend-platform/microfrontend-perspective/microfrontend-perspective-installer.service.d.ts +24 -0
  85. package/lib/microfrontend-platform/microfrontend-perspective/microfrontend-perspective-intent-handler.interceptor.d.ts +20 -0
  86. package/lib/microfrontend-platform/microfrontend-perspective/workbench-perspective-data.d.ts +9 -0
  87. package/lib/microfrontend-platform/microfrontend-popup/microfrontend-popup.component.d.ts +0 -4
  88. package/lib/microfrontend-platform/microfrontend-view/microfrontend-view-command-handler.service.d.ts +0 -4
  89. package/lib/microfrontend-platform/microfrontend-view/microfrontend-view.component.d.ts +7 -2
  90. package/lib/microfrontend-platform/public_api.d.ts +1 -0
  91. package/lib/microfrontend-platform/routing/microfrontend-view-capability-validator.interceptor.d.ts +1 -1
  92. package/lib/microfrontend-platform/routing/microfrontend-view-routes.d.ts +9 -2
  93. package/lib/microfrontend-platform/stable-capability-id-assigner.interceptor.d.ts +11 -0
  94. package/lib/perspective/workbench-perspective.model.d.ts +16 -17
  95. package/lib/perspective/workbench-perspective.service.d.ts +15 -7
  96. package/lib/perspective//311/265workbench-perspective.model.d.ts +4 -0
  97. package/lib/routing/public_api.d.ts +0 -1
  98. package/lib/routing/{workbench-auxiliary-routes-registrator.service.d.ts → workbench-auxiliary-route-installer.service.d.ts} +3 -3
  99. package/lib/routing/workbench-layout-differ.d.ts +1 -2
  100. package/lib/routing/workbench-url-observer.service.d.ts +5 -3
  101. package/lib/routing/workbench-view-outlet-differ.d.ts +32 -0
  102. package/lib/view/view.component.d.ts +8 -7
  103. package/lib/view//311/265workbench-view.model.d.ts +11 -7
  104. package/lib/workbench-config.d.ts +2 -2
  105. package/lib/workbench.constants.d.ts +0 -5
  106. package/package.json +3 -3
  107. package/esm2022/lib/common/uuid.util.mjs +0 -17
  108. package/esm2022/lib/microfrontend-platform/routing/microfrontend-view-capability-id-assigner.interceptor.mjs +0 -41
  109. package/esm2022/lib/routing/workbench-auxiliary-routes-registrator.service.mjs +0 -94
  110. package/lib/common/uuid.util.d.ts +0 -8
  111. package/lib/microfrontend-platform/routing/microfrontend-view-capability-id-assigner.interceptor.d.ts +0 -10
@@ -1,13 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Component, ChangeDetectionStrategy, Injectable, Inject, Optional, makeEnvironmentProviders, Pipe, NgZone, inject, DestroyRef, runInInjectionContext, HostBinding, Input, EventEmitter, Directive, Output, Injector, HostListener, ElementRef, isDevMode, EnvironmentInjector, ApplicationInitStatus, APP_INITIALIZER, ViewContainerRef, ViewChild, createComponent, forwardRef, ViewChildren, booleanAttribute, CUSTOM_ELEMENTS_SCHEMA, ApplicationRef, ENVIRONMENT_INITIALIZER, NgModule } from '@angular/core';
2
+ import { InjectionToken, Component, ChangeDetectionStrategy, Injectable, Inject, Optional, makeEnvironmentProviders, Pipe, NgZone, inject, DestroyRef, runInInjectionContext, HostBinding, Input, EventEmitter, Directive, Output, Injector, HostListener, ElementRef, isDevMode, EnvironmentInjector, ApplicationInitStatus, APP_INITIALIZER, ViewContainerRef, ViewChild, createComponent, forwardRef, ViewChildren, booleanAttribute, CUSTOM_ELEMENTS_SCHEMA, IterableDiffers, ApplicationRef, ENVIRONMENT_INITIALIZER, NgModule } from '@angular/core';
3
3
  import { BehaviorSubject, Subject, merge, fromEvent, Observable, EMPTY, zip, of, skip, audit, concat, noop, asapScheduler, AsyncSubject, lastValueFrom, firstValueFrom, timer, combineLatest, mergeWith, identity, share, ReplaySubject, switchMap as switchMap$1, race, map as map$1, concatWith, delay, animationFrameScheduler, combineLatestWith, pairwise, withLatestFrom, interval, from, mergeMap as mergeMap$1 } from 'rxjs';
4
4
  import { SciThrobberComponent } from '@scion/components/throbber';
5
5
  import * as i2 from '@angular/router';
6
6
  import { NavigationStart, Router, PRIMARY_OUTLET, RouterOutlet, NavigationEnd, UrlSegment, ChildrenOutletContexts, ActivationStart, RouterEvent, NavigationCancel, NavigationError, GuardsCheckEnd, ROUTES } from '@angular/router';
7
7
  import { startWith, filter, map, take, mergeMap, observeOn, catchError, takeUntil, switchMap, distinctUntilChanged, first, shareReplay, expand, debounceTime } from 'rxjs/operators';
8
8
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
9
- import * as i3$1 from '@angular/common';
10
- import { NgIf, NgFor, AsyncPipe, DOCUMENT, NgClass, NgComponentOutlet, NgTemplateOutlet, KeyValuePipe, Location, LocationStrategy } from '@angular/common';
9
+ import * as i5 from '@angular/common';
10
+ import { NgIf, AsyncPipe, DOCUMENT, NgFor, NgClass, NgComponentOutlet, NgTemplateOutlet, KeyValuePipe, Location, LocationStrategy } from '@angular/common';
11
11
  import { Arrays, Defined, Objects as Objects$1, Dictionaries, Observables, Maps } from '@scion/toolkit/util';
12
12
  import * as i3 from '@angular/cdk/portal';
13
13
  import { PortalModule, ComponentPortal, TemplatePortal, DomPortalOutlet } from '@angular/cdk/portal';
@@ -27,7 +27,7 @@ import { SciDimensionDirective, SciDimensionModule } from '@scion/components/dim
27
27
  import * as i2$2 from '@angular/forms';
28
28
  import { FormControl, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
29
29
  import * as i2$3 from '@scion/microfrontend-platform';
30
- import { MessageHeaders, MessageClient, ResponseStatusCodes, MicrofrontendPlatformConfig, APP_IDENTITY, mapToBody, HostManifestInterceptor, ObservableDecorator, IntentInterceptor, CapabilityInterceptor, MicrofrontendPlatformHost, MicrofrontendPlatform, IntentClient, OutletRouter, ManifestService, PlatformPropertyService } from '@scion/microfrontend-platform';
30
+ import { MessageHeaders, MessageClient, ResponseStatusCodes, MicrofrontendPlatform, PlatformState, MicrofrontendPlatformConfig, APP_IDENTITY, mapToBody, HostManifestInterceptor, ObservableDecorator, IntentInterceptor, CapabilityInterceptor, MicrofrontendPlatformHost, IntentClient, OutletRouter, ManifestService, PlatformPropertyService } from '@scion/microfrontend-platform';
31
31
  import { Beans } from '@scion/toolkit/bean-manager';
32
32
  import * as i1$1 from '@scion/workbench-client';
33
33
  import { eMESSAGE_BOX_MESSAGE_PARAM, WorkbenchCapabilities, ɵMicrofrontendRouteParams as _MicrofrontendRouteParams, ɵTHEME_CONTEXT_KEY as _THEME_CONTEXT_KEY, ɵWorkbenchCommands as _WorkbenchCommands, ɵWorkbenchPopupMessageHeaders as _WorkbenchPopupMessageHeaders, ɵPOPUP_CONTEXT as _POPUP_CONTEXT, WorkbenchPopup, ɵDIALOG_CONTEXT as _DIALOG_CONTEXT, ɵWorkbenchDialogMessageHeaders as _WorkbenchDialogMessageHeaders, WorkbenchDialog as WorkbenchDialog$1, WorkbenchMessageBox, ɵMESSAGE_BOX_CONTEXT as _MESSAGE_BOX_CONTEXT, WorkbenchRouter as WorkbenchRouter$1, WorkbenchPopupService, WorkbenchMessageBoxService as WorkbenchMessageBoxService$1, ɵWorkbenchMessageBoxService as _WorkbenchMessageBoxService, WorkbenchDialogService as WorkbenchDialogService$1, ɵWorkbenchDialogService as _WorkbenchDialogService, WorkbenchNotificationService, ɵVIEW_ID_CONTEXT_KEY as _VIEW_ID_CONTEXT_KEY } from '@scion/workbench-client';
@@ -1054,10 +1054,6 @@ const MAIN_AREA_LAYOUT_QUERY_PARAM = 'main_area';
1054
1054
  * DI token to inject the context in which the view tab is rendered.
1055
1055
  */
1056
1056
  const VIEW_TAB_RENDERING_CONTEXT = new InjectionToken('VIEW_TAB_RENDERING_CONTEXT');
1057
- /**
1058
- * DI token representing the configured workbench layout.
1059
- */
1060
- const WORKBENCH_LAYOUT_CONFIG = new InjectionToken('WORKBENCH_LAYOUT_CONFIG');
1061
1057
  /**
1062
1058
  * Prefix used to identify an anonymous perspective that the workbench creates for views moved to a new window.
1063
1059
  */
@@ -1468,6 +1464,12 @@ const Objects = {
1468
1464
  withoutUndefinedEntries: (object) => {
1469
1465
  return Dictionaries.withoutUndefinedEntries(object);
1470
1466
  },
1467
+ /**
1468
+ * Stringifies given object to matrix notation: a=b;c=d;e=f
1469
+ */
1470
+ toMatrixNotation: (object) => {
1471
+ return Object.entries(object ?? {}).map(([key, value]) => `${key}=${value}`).join(';');
1472
+ },
1471
1473
  };
1472
1474
 
1473
1475
  /*
@@ -1775,7 +1777,7 @@ function createNavigationFromCommands(commands, extras) {
1775
1777
  * Creates navigation extras with workbench navigation instructions.
1776
1778
  */
1777
1779
  function createNavigationExtras(layout, extras) {
1778
- const { workbenchGrid, mainAreaGrid } = layout.serialize();
1780
+ const { workbenchGrid, mainAreaGrid } = layout.serialize({ excludeViewMarkedForRemoval: true });
1779
1781
  return {
1780
1782
  ...extras,
1781
1783
  // Instruct the Angular router to process the navigation even if the URL does not change, e.g., when changing the workbench grid which is not contained in the URL.
@@ -1849,18 +1851,11 @@ class GridElementComponent {
1849
1851
  this.MTreeNode = MTreeNode;
1850
1852
  this.MPart = MPart;
1851
1853
  this.children = new Array();
1852
- /**
1853
- * Each layout change creates new model object instances since the layout is deserialized from the URL.
1854
- * Therefore, the "correct" track-by function is critical to help Angular identify DOM elements for reuse, which significantly can
1855
- * improve performance. Note that we use the index instead of the object identity because a different identity may be computed for
1856
- * nodes when re-arranging parts. Using the index is like not using *ngFor at all.
1857
- */
1858
- this.indexTrackByFn = (index) => index;
1859
1854
  }
1860
1855
  ngOnChanges(changes) {
1861
1856
  this.children = this.element instanceof MTreeNode ? this.computeChildren(this.element) : [];
1862
- this.parentNodeId = this.element.parent?.nodeId;
1863
- this.nodeId = this.element instanceof MTreeNode ? this.element.nodeId : undefined;
1857
+ this.parentNodeId = this.element.parent?.id;
1858
+ this.nodeId = this.element instanceof MTreeNode ? this.element.id : undefined;
1864
1859
  this.partId = this.element instanceof MPart ? this.element.id : undefined;
1865
1860
  }
1866
1861
  onSashStart() {
@@ -1869,7 +1864,7 @@ class GridElementComponent {
1869
1864
  onSashEnd(treeNode, [sashSize1, sashSize2]) {
1870
1865
  const ratio = sashSize1 / (sashSize1 + sashSize2);
1871
1866
  this._workbenchLayoutService.notifyDragEnding();
1872
- this._workbenchRouter.navigate(layout => layout.setSplitRatio(treeNode.nodeId, ratio)).then();
1867
+ this._workbenchRouter.navigate(layout => layout.setSplitRatio(treeNode.id, ratio)).then();
1873
1868
  }
1874
1869
  computeChildren(treeNode) {
1875
1870
  const child1Visible = WorkbenchLayouts.isGridElementVisible(treeNode.child1);
@@ -1890,19 +1885,17 @@ class GridElementComponent {
1890
1885
  return [];
1891
1886
  }
1892
1887
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: GridElementComponent, deps: [{ token: ɵWorkbenchRouter }, { token: WorkbenchLayoutService }], target: i0.ɵɵFactoryTarget.Component }); }
1893
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.2", type: GridElementComponent, isStandalone: true, selector: "wb-grid-element", inputs: { element: "element" }, host: { properties: { "attr.data-parentnodeid": "this.parentNodeId", "attr.data-nodeid": "this.nodeId", "attr.data-partid": "this.partId" } }, usesOnChanges: true, ngImport: i0, template: "<!-- MPart (leaf) -->\n<ng-container *ngIf=\"element | wbInstanceof:MPart as part\" [cdkPortalOutlet]=\"part.id | wbPartPortal\"></ng-container>\n\n<!-- MTreeNode -->\n<ng-container *ngIf=\"element | wbInstanceof:MTreeNode as treeNode\">\n <!-- Node with a single visible child. -->\n <wb-grid-element *ngIf=\"children.length === 1\" [element]=\"children[0].element\"></wb-grid-element>\n\n <!-- Node with multiple visible children. -->\n <sci-sashbox *ngIf=\"children.length > 1\"\n [direction]=\"treeNode.direction\"\n [attr.data-nodeid]=\"treeNode.nodeId\"\n (sashStart)=\"onSashStart()\"\n (sashEnd)=\"onSashEnd(treeNode, $event)\">\n <ng-template *ngFor=\"let child of children; index as i; trackBy: indexTrackByFn\" sciSash [size]=\"child.size\">\n <wb-grid-element [element]=\"child.element\" [class]=\"'sash-' + (i + 1)\"></wb-grid-element>\n </ng-template>\n </sci-sashbox>\n</ng-container>\n", styles: [":host{display:grid}:host>sci-sashbox{z-index:auto;--sci-sashbox-gap: 0;--sci-sashbox-splitter-background-color: var(--sci-workbench-part-divider-color);--sci-sashbox-splitter-background-color-hover: var(--sci-workbench-part-divider-color-hover);--sci-sashbox-splitter-size: var(--sci-workbench-part-divider-size);--sci-sashbox-splitter-size-hover: var(--sci-workbench-part-divider-size-hover);--sci-sashbox-splitter-touch-target-size: var(--sci-workbench-part-divider-touch-target-size);--sci-sashbox-splitter-border-radius: 0;--sci-sashbox-splitter-opacity-active: var(--sci-workbench-part-divider-opacity-active);--sci-sashbox-splitter-opacity-hover: var(--sci-workbench-part-divider-opacity-hover)}\n"], dependencies: [{ kind: "component", type: GridElementComponent, selector: "wb-grid-element", inputs: ["element"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: InstanceofPipe, name: "wbInstanceof" }, { kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i3.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "pipe", type: PartPortalPipe, name: "wbPartPortal" }, { kind: "component", type: SciSashboxComponent, selector: "sci-sashbox", inputs: ["direction"], outputs: ["sashStart", "sashEnd"] }, { kind: "directive", type: SciSashDirective, selector: "ng-template[sciSash]", inputs: ["size", "minSize"], exportAs: ["sciSash"] }] }); }
1888
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.2", type: GridElementComponent, isStandalone: true, selector: "wb-grid-element", inputs: { element: "element" }, host: { properties: { "attr.data-parentnodeid": "this.parentNodeId", "attr.data-nodeid": "this.nodeId", "attr.data-partid": "this.partId" } }, usesOnChanges: true, ngImport: i0, template: "<!-- MPart (leaf) -->\n@if (element | wbInstanceof:MPart; as part) {\n <ng-container [cdkPortalOutlet]=\"part.id | wbPartPortal\"/>\n}\n\n<!-- MTreeNode -->\n@if (element | wbInstanceof:MTreeNode; as treeNode) {\n @if (children.length === 1) {\n <!-- Node with a single visible child. -->\n <wb-grid-element [element]=\"children[0].element\"/>\n }\n @if (children.length > 1) {\n <!-- Node with multiple visible children. -->\n <sci-sashbox [direction]=\"treeNode.direction\"\n [attr.data-nodeid]=\"treeNode.id\"\n (sashStart)=\"onSashStart()\"\n (sashEnd)=\"onSashEnd(treeNode, $event)\">\n @for (child of children; track child.element.id) {\n <ng-template sciSash [size]=\"child.size\">\n <wb-grid-element [element]=\"child.element\" [class]=\"'sash-' + ($index + 1)\"/>\n </ng-template>\n }\n </sci-sashbox>\n }\n}\n", styles: [":host{display:grid}:host>sci-sashbox{z-index:auto;--sci-sashbox-gap: 0;--sci-sashbox-splitter-background-color: var(--sci-workbench-part-divider-color);--sci-sashbox-splitter-background-color-hover: var(--sci-workbench-part-divider-color-hover);--sci-sashbox-splitter-size: var(--sci-workbench-part-divider-size);--sci-sashbox-splitter-size-hover: var(--sci-workbench-part-divider-size-hover);--sci-sashbox-splitter-touch-target-size: var(--sci-workbench-part-divider-touch-target-size);--sci-sashbox-splitter-border-radius: 0;--sci-sashbox-splitter-opacity-active: var(--sci-workbench-part-divider-opacity-active);--sci-sashbox-splitter-opacity-hover: var(--sci-workbench-part-divider-opacity-hover)}\n"], dependencies: [{ kind: "component", type: GridElementComponent, selector: "wb-grid-element", inputs: ["element"] }, { kind: "pipe", type: InstanceofPipe, name: "wbInstanceof" }, { kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i3.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "pipe", type: PartPortalPipe, name: "wbPartPortal" }, { kind: "component", type: SciSashboxComponent, selector: "sci-sashbox", inputs: ["direction"], outputs: ["sashStart", "sashEnd"] }, { kind: "directive", type: SciSashDirective, selector: "ng-template[sciSash]", inputs: ["size", "minSize"], exportAs: ["sciSash"] }] }); }
1894
1889
  }
1895
1890
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: GridElementComponent, decorators: [{
1896
1891
  type: Component,
1897
1892
  args: [{ selector: 'wb-grid-element', standalone: true, imports: [
1898
- NgIf,
1899
- NgFor,
1900
1893
  InstanceofPipe,
1901
1894
  PortalModule,
1902
1895
  PartPortalPipe,
1903
1896
  SciSashboxComponent,
1904
1897
  SciSashDirective,
1905
- ], template: "<!-- MPart (leaf) -->\n<ng-container *ngIf=\"element | wbInstanceof:MPart as part\" [cdkPortalOutlet]=\"part.id | wbPartPortal\"></ng-container>\n\n<!-- MTreeNode -->\n<ng-container *ngIf=\"element | wbInstanceof:MTreeNode as treeNode\">\n <!-- Node with a single visible child. -->\n <wb-grid-element *ngIf=\"children.length === 1\" [element]=\"children[0].element\"></wb-grid-element>\n\n <!-- Node with multiple visible children. -->\n <sci-sashbox *ngIf=\"children.length > 1\"\n [direction]=\"treeNode.direction\"\n [attr.data-nodeid]=\"treeNode.nodeId\"\n (sashStart)=\"onSashStart()\"\n (sashEnd)=\"onSashEnd(treeNode, $event)\">\n <ng-template *ngFor=\"let child of children; index as i; trackBy: indexTrackByFn\" sciSash [size]=\"child.size\">\n <wb-grid-element [element]=\"child.element\" [class]=\"'sash-' + (i + 1)\"></wb-grid-element>\n </ng-template>\n </sci-sashbox>\n</ng-container>\n", styles: [":host{display:grid}:host>sci-sashbox{z-index:auto;--sci-sashbox-gap: 0;--sci-sashbox-splitter-background-color: var(--sci-workbench-part-divider-color);--sci-sashbox-splitter-background-color-hover: var(--sci-workbench-part-divider-color-hover);--sci-sashbox-splitter-size: var(--sci-workbench-part-divider-size);--sci-sashbox-splitter-size-hover: var(--sci-workbench-part-divider-size-hover);--sci-sashbox-splitter-touch-target-size: var(--sci-workbench-part-divider-touch-target-size);--sci-sashbox-splitter-border-radius: 0;--sci-sashbox-splitter-opacity-active: var(--sci-workbench-part-divider-opacity-active);--sci-sashbox-splitter-opacity-hover: var(--sci-workbench-part-divider-opacity-hover)}\n"] }]
1898
+ ], template: "<!-- MPart (leaf) -->\n@if (element | wbInstanceof:MPart; as part) {\n <ng-container [cdkPortalOutlet]=\"part.id | wbPartPortal\"/>\n}\n\n<!-- MTreeNode -->\n@if (element | wbInstanceof:MTreeNode; as treeNode) {\n @if (children.length === 1) {\n <!-- Node with a single visible child. -->\n <wb-grid-element [element]=\"children[0].element\"/>\n }\n @if (children.length > 1) {\n <!-- Node with multiple visible children. -->\n <sci-sashbox [direction]=\"treeNode.direction\"\n [attr.data-nodeid]=\"treeNode.id\"\n (sashStart)=\"onSashStart()\"\n (sashEnd)=\"onSashEnd(treeNode, $event)\">\n @for (child of children; track child.element.id) {\n <ng-template sciSash [size]=\"child.size\">\n <wb-grid-element [element]=\"child.element\" [class]=\"'sash-' + ($index + 1)\"/>\n </ng-template>\n }\n </sci-sashbox>\n }\n}\n", styles: [":host{display:grid}:host>sci-sashbox{z-index:auto;--sci-sashbox-gap: 0;--sci-sashbox-splitter-background-color: var(--sci-workbench-part-divider-color);--sci-sashbox-splitter-background-color-hover: var(--sci-workbench-part-divider-color-hover);--sci-sashbox-splitter-size: var(--sci-workbench-part-divider-size);--sci-sashbox-splitter-size-hover: var(--sci-workbench-part-divider-size-hover);--sci-sashbox-splitter-touch-target-size: var(--sci-workbench-part-divider-touch-target-size);--sci-sashbox-splitter-border-radius: 0;--sci-sashbox-splitter-opacity-active: var(--sci-workbench-part-divider-opacity-active);--sci-sashbox-splitter-opacity-hover: var(--sci-workbench-part-divider-opacity-hover)}\n"] }]
1906
1899
  }], ctorParameters: () => [{ type: ɵWorkbenchRouter }, { type: WorkbenchLayoutService }], propDecorators: { parentNodeId: [{
1907
1900
  type: HostBinding,
1908
1901
  args: ['attr.data-parentnodeid']
@@ -2363,22 +2356,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
2363
2356
  args: [{ name: 'wbGridElementIfVisible', standalone: true }]
2364
2357
  }] });
2365
2358
 
2366
- /*
2367
- * Copyright (c) 2018-2024 Swiss Federal Railways
2368
- *
2369
- * This program and the accompanying materials are made
2370
- * available under the terms of the Eclipse Public License 2.0
2371
- * which is available at https://www.eclipse.org/legal/epl-2.0/
2372
- *
2373
- * SPDX-License-Identifier: EPL-2.0
2374
- */
2375
- /**
2376
- * Generates a UUID (universally unique identifier) compliant with the RFC 4122 version 4.
2377
- */
2378
- function randomUUID() {
2379
- return UUID.randomUUID();
2380
- }
2381
-
2382
2359
  /*
2383
2360
  * Copyright (c) 2018-2024 Swiss Federal Railways
2384
2361
  *
@@ -2395,7 +2372,7 @@ function randomUUID() {
2395
2372
  */
2396
2373
  const WORKBENCH_ID = new InjectionToken('WORKBENCH_ID', {
2397
2374
  providedIn: 'root',
2398
- factory: () => randomUUID(),
2375
+ factory: () => UUID.randomUUID(),
2399
2376
  });
2400
2377
 
2401
2378
  /*
@@ -2435,7 +2412,7 @@ const GridDropTargets = {
2435
2412
  }
2436
2413
  default: {
2437
2414
  return {
2438
- elementId: grid.root instanceof MPart ? grid.root.id : grid.root.nodeId,
2415
+ elementId: grid.root.id,
2439
2416
  region: dropRegion,
2440
2417
  workbenchId,
2441
2418
  newPart: { ratio: .2 },
@@ -3128,7 +3105,7 @@ class WorkbenchView {
3128
3105
  */
3129
3106
  class FormatUrlPipe {
3130
3107
  transform(url) {
3131
- return url.map(segment => segment.path).join('/');
3108
+ return url.map(segment => `${segment}`).join('/');
3132
3109
  }
3133
3110
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: FormatUrlPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3134
3111
  static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.0.2", ngImport: i0, type: FormatUrlPipe, isStandalone: true, name: "appFormatUrl" }); }
@@ -3153,13 +3130,13 @@ class PageNotFoundComponent {
3153
3130
  this.view = inject(WorkbenchView);
3154
3131
  }
3155
3132
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: PageNotFoundComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3156
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.2", type: PageNotFoundComponent, isStandalone: true, selector: "wb-page-not-found", ngImport: i0, template: "<header>Page Not Found</header>\n\n<section class=\"message\">\n The requested page <span class=\"url\">{{view.urlSegments | appFormatUrl}}</span> was not found.\n <br>\n The URL may have changed. Try to open the view again.\n</section>\n\n<button (click)=\"view.close()\">Close</button>\n\n@if (isDevMode) {\n <section class=\"developer-hint\">\n You can create a custom \"Page Not Found\" component and register it in the workbench configuration to personalize this page.\n </section>\n}\n", styles: [":host{display:flex;flex-direction:column;gap:2em;padding:1em;align-items:center}:host>header{font-weight:700;font-size:1.3rem}:host>section.message{text-align:center;line-height:1.75}:host>section.message>span.url{font-weight:700}:host>section.developer-hint{border:1px solid var(--sci-color-accent);border-radius:var(--sci-corner);padding:1em;max-width:550px;color:var(--sci-color-accent);font-family:monospace;text-align:center}:host>button{all:unset;cursor:pointer;padding:.5em 1.5em;color:var(--sci-color-accent-inverse);background-color:var(--sci-color-accent);background-clip:padding-box;border:1px solid var(--sci-color-accent);border-radius:var(--sci-corner);text-align:center}:host>button:focus,:host>button:active{border-color:transparent;outline:1px solid var(--sci-color-accent);color:var(--sci-color-accent-inverse)}\n"], dependencies: [{ kind: "pipe", type: FormatUrlPipe, name: "appFormatUrl" }] }); }
3133
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.2", type: PageNotFoundComponent, isStandalone: true, selector: "wb-page-not-found", ngImport: i0, template: "<header>Page Not Found</header>\n\n<section class=\"message\">\n The requested page <span class=\"url\">{{view.urlSegments | appFormatUrl}}</span> was not found.\n <br>\n The URL may have changed. Try to open the view again.\n</section>\n\n<button (click)=\"view.close()\">Close</button>\n\n@if (isDevMode) {\n <section class=\"developer-hint\">\n You can create a custom \"Not Found\" page component and register it in the workbench configuration to personalize this page.\n </section>\n}\n", styles: [":host{display:flex;flex-direction:column;gap:2em;padding:1em;align-items:center}:host>header{font-weight:700;font-size:1.3rem}:host>section.message{text-align:center;line-height:1.75}:host>section.message>span.url{font-weight:700}:host>section.developer-hint{border:1px solid var(--sci-color-accent);border-radius:var(--sci-corner);padding:1em;max-width:550px;color:var(--sci-color-accent);font-family:monospace;text-align:center}:host>button{all:unset;cursor:pointer;padding:.5em 1.5em;color:var(--sci-color-accent-inverse);background-color:var(--sci-color-accent);background-clip:padding-box;border:1px solid var(--sci-color-accent);border-radius:var(--sci-corner);text-align:center}:host>button:focus,:host>button:active{border-color:transparent;outline:1px solid var(--sci-color-accent);color:var(--sci-color-accent-inverse)}\n"], dependencies: [{ kind: "pipe", type: FormatUrlPipe, name: "appFormatUrl" }] }); }
3157
3134
  }
3158
3135
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: PageNotFoundComponent, decorators: [{
3159
3136
  type: Component,
3160
3137
  args: [{ selector: 'wb-page-not-found', standalone: true, imports: [
3161
3138
  FormatUrlPipe,
3162
- ], template: "<header>Page Not Found</header>\n\n<section class=\"message\">\n The requested page <span class=\"url\">{{view.urlSegments | appFormatUrl}}</span> was not found.\n <br>\n The URL may have changed. Try to open the view again.\n</section>\n\n<button (click)=\"view.close()\">Close</button>\n\n@if (isDevMode) {\n <section class=\"developer-hint\">\n You can create a custom \"Page Not Found\" component and register it in the workbench configuration to personalize this page.\n </section>\n}\n", styles: [":host{display:flex;flex-direction:column;gap:2em;padding:1em;align-items:center}:host>header{font-weight:700;font-size:1.3rem}:host>section.message{text-align:center;line-height:1.75}:host>section.message>span.url{font-weight:700}:host>section.developer-hint{border:1px solid var(--sci-color-accent);border-radius:var(--sci-corner);padding:1em;max-width:550px;color:var(--sci-color-accent);font-family:monospace;text-align:center}:host>button{all:unset;cursor:pointer;padding:.5em 1.5em;color:var(--sci-color-accent-inverse);background-color:var(--sci-color-accent);background-clip:padding-box;border:1px solid var(--sci-color-accent);border-radius:var(--sci-corner);text-align:center}:host>button:focus,:host>button:active{border-color:transparent;outline:1px solid var(--sci-color-accent);color:var(--sci-color-accent-inverse)}\n"] }]
3139
+ ], template: "<header>Page Not Found</header>\n\n<section class=\"message\">\n The requested page <span class=\"url\">{{view.urlSegments | appFormatUrl}}</span> was not found.\n <br>\n The URL may have changed. Try to open the view again.\n</section>\n\n<button (click)=\"view.close()\">Close</button>\n\n@if (isDevMode) {\n <section class=\"developer-hint\">\n You can create a custom \"Not Found\" page component and register it in the workbench configuration to personalize this page.\n </section>\n}\n", styles: [":host{display:flex;flex-direction:column;gap:2em;padding:1em;align-items:center}:host>header{font-weight:700;font-size:1.3rem}:host>section.message{text-align:center;line-height:1.75}:host>section.message>span.url{font-weight:700}:host>section.developer-hint{border:1px solid var(--sci-color-accent);border-radius:var(--sci-corner);padding:1em;max-width:550px;color:var(--sci-color-accent);font-family:monospace;text-align:center}:host>button{all:unset;cursor:pointer;padding:.5em 1.5em;color:var(--sci-color-accent-inverse);background-color:var(--sci-color-accent);background-clip:padding-box;border:1px solid var(--sci-color-accent);border-radius:var(--sci-corner);text-align:center}:host>button:focus,:host>button:active{border-color:transparent;outline:1px solid var(--sci-color-accent);color:var(--sci-color-accent-inverse)}\n"] }]
3163
3140
  }] });
3164
3141
 
3165
3142
  /*
@@ -3241,7 +3218,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
3241
3218
  /**
3242
3219
  * Facilitates the registration of auxiliary routes of top-level routes.
3243
3220
  */
3244
- class WorkbenchAuxiliaryRoutesRegistrator {
3221
+ class WorkbenchAuxiliaryRouteInstaller {
3245
3222
  constructor(_workbenchConfig, _router) {
3246
3223
  this._workbenchConfig = _workbenchConfig;
3247
3224
  this._router = _router;
@@ -3265,7 +3242,7 @@ class WorkbenchAuxiliaryRoutesRegistrator {
3265
3242
  ...this._router.config
3266
3243
  .filter(route => !route.outlet || route.outlet === PRIMARY_OUTLET)
3267
3244
  .map(route => ({ ...route, data: { ...route.data, [WorkbenchRouteData.ɵoutlet]: outlet } })),
3268
- // Register "Page Not Found" route as the last route of the outlet.
3245
+ // Register "Not Found" page route as the last route of the outlet.
3269
3246
  {
3270
3247
  path: '**',
3271
3248
  loadComponent: () => this._workbenchConfig.pageNotFoundComponent ?? PageNotFoundComponent,
@@ -3301,10 +3278,10 @@ class WorkbenchAuxiliaryRoutesRegistrator {
3301
3278
  const newRoutes = [...config];
3302
3279
  this._router.config.splice(0, this._router.config.length, ...newRoutes);
3303
3280
  }
3304
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchAuxiliaryRoutesRegistrator, deps: [{ token: WorkbenchConfig }, { token: i2.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
3305
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchAuxiliaryRoutesRegistrator, providedIn: 'root' }); }
3281
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchAuxiliaryRouteInstaller, deps: [{ token: WorkbenchConfig }, { token: i2.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
3282
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchAuxiliaryRouteInstaller, providedIn: 'root' }); }
3306
3283
  }
3307
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchAuxiliaryRoutesRegistrator, decorators: [{
3284
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchAuxiliaryRouteInstaller, decorators: [{
3308
3285
  type: Injectable,
3309
3286
  args: [{ providedIn: 'root' }]
3310
3287
  }], ctorParameters: () => [{ type: WorkbenchConfig }, { type: i2.Router }] });
@@ -3792,13 +3769,13 @@ class WorkbenchRouterLinkDirective {
3792
3769
  ngOnDestroy() {
3793
3770
  this._ngOnDestroy$.next();
3794
3771
  }
3795
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchRouterLinkDirective, deps: [{ token: WorkbenchRouter }, { token: i2.Router }, { token: i2.ActivatedRoute }, { token: i3$1.LocationStrategy }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: WorkbenchView, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
3772
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchRouterLinkDirective, deps: [{ token: WorkbenchRouter }, { token: i2.Router }, { token: i2.ActivatedRoute }, { token: i5.LocationStrategy }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: WorkbenchView, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
3796
3773
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.0.2", type: WorkbenchRouterLinkDirective, isStandalone: true, selector: "[wbRouterLink]", inputs: { wbRouterLink: "wbRouterLink", extras: ["wbRouterLinkExtras", "extras"] }, host: { listeners: { "click": "onClick($event.button,$event.ctrlKey,$event.metaKey)" }, properties: { "attr.href": "this.href" } }, usesOnChanges: true, ngImport: i0 }); }
3797
3774
  }
3798
3775
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchRouterLinkDirective, decorators: [{
3799
3776
  type: Directive,
3800
3777
  args: [{ selector: '[wbRouterLink]', standalone: true }]
3801
- }], ctorParameters: () => [{ type: WorkbenchRouter }, { type: i2.Router }, { type: i2.ActivatedRoute }, { type: i3$1.LocationStrategy }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: WorkbenchView, decorators: [{
3778
+ }], ctorParameters: () => [{ type: WorkbenchRouter }, { type: i2.Router }, { type: i2.ActivatedRoute }, { type: i5.LocationStrategy }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: WorkbenchView, decorators: [{
3802
3779
  type: Optional
3803
3780
  }] }], propDecorators: { href: [{
3804
3781
  type: HostBinding,
@@ -3924,6 +3901,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
3924
3901
  args: [{ providedIn: 'root' }]
3925
3902
  }] });
3926
3903
 
3904
+ /*
3905
+ * Copyright (c) 2018-2024 Swiss Federal Railways
3906
+ *
3907
+ * This program and the accompanying materials are made
3908
+ * available under the terms of the Eclipse Public License 2.0
3909
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
3910
+ *
3911
+ * SPDX-License-Identifier: EPL-2.0
3912
+ */
3913
+ /**
3914
+ * Generates a UID (unique identifier).
3915
+ */
3916
+ const UID = {
3917
+ /**
3918
+ * Generates a UID (unique identifier) with length 8.
3919
+ */
3920
+ randomUID: () => {
3921
+ return UUID.randomUUID().substring(0, 8);
3922
+ },
3923
+ };
3924
+
3927
3925
  /*
3928
3926
  * Copyright (c) 2018-2024 Swiss Federal Railways
3929
3927
  *
@@ -3958,7 +3956,7 @@ class WorkbenchLayoutMigrationV3 {
3958
3956
  // Consider the ids of views contained in the URL as already used.
3959
3957
  // Otherwise, when migrating the main area and using a view id already present in the perspective,
3960
3958
  // the view outlet would not be removed from the URL, resulting the migrated view to display
3961
- // "Page Not Found" or incorrect content.
3959
+ // "Not Found" page or incorrect content.
3962
3960
  const viewOutlets = RouterUtils.parseViewOutlets(this.getCurrentUrl());
3963
3961
  const usedViewIds = new Set([...viewOutlets.keys(), ...collectViewIds(partGridV2.root)]);
3964
3962
  // Migrate the grid.
@@ -4140,6 +4138,140 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
4140
4138
  args: [{ providedIn: 'root' }]
4141
4139
  }] });
4142
4140
 
4141
+ /*
4142
+ * Copyright (c) 2018-2024 Swiss Federal Railways
4143
+ *
4144
+ * This program and the accompanying materials are made
4145
+ * available under the terms of the Eclipse Public License 2.0
4146
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
4147
+ *
4148
+ * SPDX-License-Identifier: EPL-2.0
4149
+ */
4150
+ /**
4151
+ * Migrates the workbench layout from version 4 to version 5.
4152
+ *
4153
+ * TODO [Angular 20] Remove migrator.
4154
+ */
4155
+ class WorkbenchLayoutMigrationV5 {
4156
+ migrate(json) {
4157
+ const partGridV4 = JSON.parse(json);
4158
+ // Migrate the grid.
4159
+ const partGridV5 = {
4160
+ ...partGridV4,
4161
+ root: migrateGridElement(partGridV4.root),
4162
+ };
4163
+ return JSON.stringify(partGridV5);
4164
+ function migrateGridElement(elementV4) {
4165
+ switch (elementV4.type) {
4166
+ case 'MTreeNode':
4167
+ return migrateNode(elementV4);
4168
+ case 'MPart':
4169
+ return migratePart(elementV4);
4170
+ default:
4171
+ throw Error(`[WorkbenchLayoutError] Unable to migrate to the latest version. Expected element to be of type 'MPart' or 'MTreeNode'. [version=3, element=${JSON.stringify(elementV4)}]`);
4172
+ }
4173
+ }
4174
+ function migrateNode(nodeV4) {
4175
+ return {
4176
+ ...nodeV4,
4177
+ id: UID.randomUID(),
4178
+ child1: migrateGridElement(nodeV4.child1),
4179
+ child2: migrateGridElement(nodeV4.child2),
4180
+ };
4181
+ }
4182
+ function migratePart(partV4) {
4183
+ return { ...partV4, views: partV4.views.map(migrateView) };
4184
+ }
4185
+ function migrateView(viewV4) {
4186
+ const viewV5 = {
4187
+ ...viewV4,
4188
+ uid: UID.randomUID(),
4189
+ navigation: viewV4.navigation ? { ...viewV4.navigation, id: UID.randomUID() } : undefined,
4190
+ };
4191
+ if (!viewV5.navigation) {
4192
+ delete viewV5.navigation;
4193
+ }
4194
+ return viewV5;
4195
+ }
4196
+ }
4197
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchLayoutMigrationV5, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4198
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchLayoutMigrationV5, providedIn: 'root' }); }
4199
+ }
4200
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchLayoutMigrationV5, decorators: [{
4201
+ type: Injectable,
4202
+ args: [{ providedIn: 'root' }]
4203
+ }] });
4204
+
4205
+ /*
4206
+ * Copyright (c) 2018-2024 Swiss Federal Railways
4207
+ *
4208
+ * This program and the accompanying materials are made
4209
+ * available under the terms of the Eclipse Public License 2.0
4210
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
4211
+ *
4212
+ * SPDX-License-Identifier: EPL-2.0
4213
+ */
4214
+ /**
4215
+ * Stringifies given data, exluding specified fields from serialization.
4216
+ *
4217
+ * To exclude a field, specify the path to the field in the object tree,
4218
+ * using the slash as delimiter. The path supports the asterisk (`*`) to match a
4219
+ * single segment or the globstar (`**`) to match multiple segments.
4220
+ */
4221
+ function stringify(data, exclusions) {
4222
+ if (!exclusions?.length) {
4223
+ return JSON.stringify(data);
4224
+ }
4225
+ const objectStack = new Array();
4226
+ const compiledExclusions = exclusions.map(exclusion => new CompiledExclusion(exclusion));
4227
+ return JSON.stringify(data, (key, value) => {
4228
+ if (key === '') { // root node
4229
+ return value;
4230
+ }
4231
+ // Remove object(s) from the stack if finished their serialization.
4232
+ while (objectStack.at(-1)?.fieldsToSerialize.size === 0) {
4233
+ objectStack.pop();
4234
+ }
4235
+ // Mark current field as serialized.
4236
+ objectStack.at(-1)?.fieldsToSerialize.delete(key);
4237
+ // Check if to exclude the current field from serialization.
4238
+ const exclude = compiledExclusions.some(exclusion => exclusion.matches(objectStack, key, value));
4239
+ // Push object to stack if type of object.
4240
+ if (!exclude && typeof value === 'object') {
4241
+ objectStack.push({ key, value, fieldsToSerialize: new Set(Object.keys(value)) });
4242
+ }
4243
+ return exclude ? undefined : value;
4244
+ });
4245
+ }
4246
+ /**
4247
+ * Represents an exclusion, with the path compiled to a regex.
4248
+ */
4249
+ class CompiledExclusion {
4250
+ constructor(exclusion) {
4251
+ exclusion = typeof exclusion === 'string' ? ({ path: exclusion, predicate: () => true }) : exclusion;
4252
+ const path = exclusion.path
4253
+ .replaceAll('/*/', '/[^/]+/') // replace asterisk to match single segment
4254
+ .replaceAll('**/', '.+/') // replace globstar to match multiple root segments
4255
+ .replaceAll('/**/', '/.+/'); // replace globstar to match multiple segments
4256
+ this._regex = new RegExp(`^${path}$`);
4257
+ this._predicate = exclusion.predicate;
4258
+ }
4259
+ /**
4260
+ * Tests if given field matches this exclusion.
4261
+ */
4262
+ matches(objectStack, key, value) {
4263
+ const path = objectStack.map(objectValue => objectValue.key).concat(key).join('/');
4264
+ if (!this._regex.test(path)) {
4265
+ return false;
4266
+ }
4267
+ const objectPath = objectStack.map(objectValue => objectValue.value);
4268
+ if (!this._predicate(objectPath, key, value)) {
4269
+ return false;
4270
+ }
4271
+ return true;
4272
+ }
4273
+ }
4274
+
4143
4275
  /*
4144
4276
  * Copyright (c) 2018-2023 Swiss Federal Railways
4145
4277
  *
@@ -4156,25 +4288,20 @@ class WorkbenchLayoutSerializer {
4156
4288
  constructor() {
4157
4289
  this._workbenchLayoutMigrator = new WorkbenchMigrator()
4158
4290
  .registerMigration(2, inject(WorkbenchLayoutMigrationV3))
4159
- .registerMigration(3, inject(WorkbenchLayoutMigrationV4));
4291
+ .registerMigration(3, inject(WorkbenchLayoutMigrationV4))
4292
+ .registerMigration(4, inject(WorkbenchLayoutMigrationV5));
4160
4293
  }
4161
- serializeGrid(grid, options) {
4294
+ serializeGrid(grid, flags) {
4162
4295
  if (grid === null || grid === undefined) {
4163
4296
  return null;
4164
4297
  }
4165
- const transientFields = new Set(TRANSIENT_FIELDS);
4166
- if (!options?.includeNodeId) {
4167
- transientFields.add('nodeId');
4168
- }
4169
- if (!options?.includeUid) {
4170
- transientFields.add('uid');
4171
- }
4172
- if (!options?.includeMarkedForRemovalFlag) {
4173
- transientFields.add('markedForRemoval');
4174
- }
4175
- const json = JSON.stringify(grid, (key, value) => {
4176
- return transientFields.has(key) ? undefined : value;
4177
- });
4298
+ const json = stringify(grid, new Array()
4299
+ .concat('**/parent')
4300
+ .concat('migrated')
4301
+ .concat(flags?.excludeTreeNodeId ? ({ path: '**/id', predicate: context => context.at(-1) instanceof MTreeNode }) : [])
4302
+ .concat(flags?.excludeViewUid ? '**/views/*/uid' : [])
4303
+ .concat(flags?.excludeViewMarkedForRemoval ? '**/views/*/markedForRemoval' : [])
4304
+ .concat(flags?.excludeViewNavigationId ? '**/views/*/navigation/id' : []));
4178
4305
  return window.btoa(`${json}${VERSION_SEPARATOR$1}${WORKBENCH_LAYOUT_VERSION}`);
4179
4306
  }
4180
4307
  /**
@@ -4187,11 +4314,10 @@ class WorkbenchLayoutSerializer {
4187
4314
  // Parse the JSON.
4188
4315
  const grid = JSON.parse(migratedJsonGrid, (key, value) => {
4189
4316
  if (MPart.isMPart(value)) {
4190
- const views = value.views.map(view => ({ ...view, uid: view.uid ?? randomUUID() }));
4191
- return new MPart({ ...value, views }); // create a class object from the object literal
4317
+ return new MPart(value); // create a class object from the object literal
4192
4318
  }
4193
4319
  if (MTreeNode.isMTreeNode(value)) {
4194
- return new MTreeNode({ ...value, nodeId: value.nodeId ?? randomUUID() }); // create a class object from the object literal
4320
+ return new MTreeNode(value); // create a class object from the object literal
4195
4321
  }
4196
4322
  return value;
4197
4323
  });
@@ -4238,11 +4364,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
4238
4364
  *
4239
4365
  * @see WorkbenchMigrator
4240
4366
  */
4241
- const WORKBENCH_LAYOUT_VERSION = 4;
4242
- /**
4243
- * Fields not serialized into JSON representation.
4244
- */
4245
- const TRANSIENT_FIELDS = new Set().add('parent').add('migrated');
4367
+ const WORKBENCH_LAYOUT_VERSION = 5;
4246
4368
  /**
4247
4369
  * Separates the serialized JSON model and its version in the base64-encoded string.
4248
4370
  *
@@ -4491,10 +4613,10 @@ class ɵWorkbenchLayout {
4491
4613
  addView(id, options) {
4492
4614
  const workingCopy = this.workingCopy();
4493
4615
  if (WorkbenchLayouts.isViewId(id)) {
4494
- workingCopy.__addView({ id, uid: randomUUID() }, options);
4616
+ workingCopy.__addView({ id, uid: UID.randomUID() }, options);
4495
4617
  }
4496
4618
  else {
4497
- workingCopy.__addView({ id: this.computeNextViewId(), alternativeId: id, uid: randomUUID() }, options);
4619
+ workingCopy.__addView({ id: this.computeNextViewId(), alternativeId: id, uid: UID.randomUID() }, options);
4498
4620
  }
4499
4621
  return workingCopy;
4500
4622
  }
@@ -4587,11 +4709,11 @@ class ɵWorkbenchLayout {
4587
4709
  /**
4588
4710
  * Serializes this layout into a URL-safe base64 string.
4589
4711
  */
4590
- serialize() {
4712
+ serialize(flags) {
4591
4713
  const isMainAreaEmpty = (this.mainAreaGrid?.root instanceof MPart && this.mainAreaGrid.root.views.length === 0) ?? true;
4592
4714
  return {
4593
- workbenchGrid: this._serializer.serializeGrid(this.workbenchGrid),
4594
- mainAreaGrid: isMainAreaEmpty ? null : this._serializer.serializeGrid(this._grids.mainArea),
4715
+ workbenchGrid: this._serializer.serializeGrid(this.workbenchGrid, flags),
4716
+ mainAreaGrid: isMainAreaEmpty ? null : this._serializer.serializeGrid(this._grids.mainArea, flags),
4595
4717
  workbenchViewOutlets: this._serializer.serializeViewOutlets(this.viewOutlets({ grid: 'workbench' })),
4596
4718
  mainAreaViewOutlets: this._serializer.serializeViewOutlets(this.viewOutlets({ grid: 'mainArea' })),
4597
4719
  };
@@ -4616,7 +4738,7 @@ class ɵWorkbenchLayout {
4616
4738
  const ratio = relativeTo.ratio ?? .5;
4617
4739
  // Create a new tree node.
4618
4740
  const newTreeNode = new MTreeNode({
4619
- nodeId: randomUUID(),
4741
+ id: UID.randomUID(),
4620
4742
  child1: addBefore ? newPart : referenceElement,
4621
4743
  child2: addBefore ? referenceElement : newPart,
4622
4744
  direction: relativeTo.align === 'left' || relativeTo.align === 'right' ? 'row' : 'column',
@@ -4722,6 +4844,7 @@ class ɵWorkbenchLayout {
4722
4844
  this._viewStates.delete(view.id);
4723
4845
  }
4724
4846
  view.navigation = Objects.withoutUndefinedEntries({
4847
+ id: UID.randomUID(),
4725
4848
  hint: extras?.hint,
4726
4849
  cssClass: extras?.cssClass ? Arrays.coerce(extras.cssClass) : undefined,
4727
4850
  });
@@ -4864,12 +4987,7 @@ class ɵWorkbenchLayout {
4864
4987
  return this.findTreeElements((element) => element === findBy.element, { findFirst: true, grid: gridName }).length > 0;
4865
4988
  });
4866
4989
  if (!gridName) {
4867
- if (findBy.element instanceof MPart) {
4868
- throw Error(`[NullGridError] No grid found that contains the part '${findBy.element.id}'".`);
4869
- }
4870
- else {
4871
- throw Error(`[NullGridError] No grid found that contains the node '${findBy.element.nodeId}'".`);
4872
- }
4990
+ throw Error(`[NullGridError] No grid found that contains the ${findBy.element instanceof MPart ? 'part' : 'node'} '${findBy.element.id}'".`);
4873
4991
  }
4874
4992
  return this._grids[gridName];
4875
4993
  }
@@ -4882,7 +5000,7 @@ class ɵWorkbenchLayout {
4882
5000
  */
4883
5001
  findTreeElement(findBy) {
4884
5002
  const element = this.findTreeElements((element) => {
4885
- return element instanceof MPart ? element.id === findBy.id : element.nodeId === findBy.id;
5003
+ return element.id === findBy.id;
4886
5004
  }, { findFirst: true }).at(0);
4887
5005
  if (!element) {
4888
5006
  throw Error(`[NullElementError] No element found with id '${findBy.id}'.`);
@@ -4932,8 +5050,8 @@ class ɵWorkbenchLayout {
4932
5050
  */
4933
5051
  workingCopy() {
4934
5052
  return runInInjectionContext(this._injector, () => new ɵWorkbenchLayout({
4935
- workbenchGrid: this._serializer.serializeGrid(this.workbenchGrid, { includeNodeId: true, includeUid: true, includeMarkedForRemovalFlag: true }),
4936
- mainAreaGrid: this._serializer.serializeGrid(this._grids.mainArea, { includeNodeId: true, includeUid: true, includeMarkedForRemovalFlag: true }),
5053
+ workbenchGrid: this._serializer.serializeGrid(this.workbenchGrid),
5054
+ mainAreaGrid: this._serializer.serializeGrid(this._grids.mainArea),
4937
5055
  viewOutlets: Object.fromEntries(this._viewOutlets),
4938
5056
  viewStates: Object.fromEntries(this._viewStates),
4939
5057
  maximized: this._maximized,
@@ -5031,7 +5149,7 @@ function coercePosition(position, part) {
5031
5149
  */
5032
5150
  const MAIN_AREA_INITIAL_PART_ID = new InjectionToken('MAIN_AREA_INITIAL_PART_ID', {
5033
5151
  providedIn: 'root',
5034
- factory: () => randomUUID(),
5152
+ factory: () => UID.randomUID(),
5035
5153
  });
5036
5154
  /**
5037
5155
  * Provides the instant when a part was last activated.
@@ -5169,8 +5287,8 @@ class WorkbenchGridMerger {
5169
5287
  * Performs a merge of given local and remote layouts, using the base layout as the common ancestor.
5170
5288
  */
5171
5289
  merge(grids) {
5172
- const serializedBaseLayout = grids.base.serialize();
5173
- const serializedRemoteLayout = grids.remote.serialize();
5290
+ const serializedBaseLayout = grids.base.serialize({ excludeTreeNodeId: true, excludeViewUid: true, excludeViewNavigationId: true });
5291
+ const serializedRemoteLayout = grids.remote.serialize({ excludeTreeNodeId: true, excludeViewUid: true, excludeViewNavigationId: true });
5174
5292
  if (serializedBaseLayout.workbenchGrid !== serializedRemoteLayout.workbenchGrid) {
5175
5293
  return grids.remote;
5176
5294
  }
@@ -5563,7 +5681,16 @@ class ɵWorkbenchPerspective {
5563
5681
  * Creates the initial layout of this perspective as defined in the perspective definition.
5564
5682
  */
5565
5683
  async createInitialPerspectiveLayout() {
5566
- return await runInInjectionContext(this._environmentInjector, () => this._initialLayoutFn(this._workbenchLayoutFactory));
5684
+ const initialLayout = await runInInjectionContext(this._environmentInjector, () => this._initialLayoutFn(this._workbenchLayoutFactory));
5685
+ return this.ensureActiveView(initialLayout);
5686
+ }
5687
+ /**
5688
+ * Activates the first view of each part if not specified.
5689
+ */
5690
+ ensureActiveView(layout) {
5691
+ return layout.parts()
5692
+ .filter(part => part.views?.length)
5693
+ .reduce((acc, part) => part.activeViewId ? acc : acc.activateView(part.views[0].id), layout);
5567
5694
  }
5568
5695
  /**
5569
5696
  * Subscribes to workbench layout changes, invoking the given callback on layout change, but only if this perspective is active.
@@ -5643,32 +5770,37 @@ class ɵWorkbenchPerspective {
5643
5770
  * Enables registration and activation of perspectives.
5644
5771
  */
5645
5772
  class WorkbenchPerspectiveService {
5646
- constructor(_layoutConfig, _perspectiveRegistry, _environmentInjector, _workbenchPerspectiveStorageService, workbenchStartup, logger) {
5647
- this._layoutConfig = _layoutConfig;
5773
+ constructor(_workbenchConfig, _perspectiveRegistry, _environmentInjector, _applicationInitStatus, _workbenchPerspectiveStorageService) {
5774
+ this._workbenchConfig = _workbenchConfig;
5648
5775
  this._perspectiveRegistry = _perspectiveRegistry;
5649
5776
  this._environmentInjector = _environmentInjector;
5777
+ this._applicationInitStatus = _applicationInitStatus;
5650
5778
  this._workbenchPerspectiveStorageService = _workbenchPerspectiveStorageService;
5651
- workbenchStartup.whenStarted
5652
- .then(() => this.activateInitialPerspective())
5653
- .catch(error => logger.error(() => 'Failed to initialize perspectives', error));
5654
5779
  }
5655
5780
  async init() {
5656
5781
  await this.registerPerspectivesFromConfig();
5657
5782
  await this.registerAnonymousPerspectiveFromWindowName();
5783
+ await this.activateInitialPerspective();
5658
5784
  }
5659
5785
  /**
5660
5786
  * Registers perspectives configured in {@link WorkbenchConfig}.
5661
5787
  */
5662
5788
  async registerPerspectivesFromConfig() {
5663
- // Register perspective either from function or object config.
5664
- if (typeof this._layoutConfig === 'function') {
5665
- await this.registerPerspective({ id: DEFAULT_WORKBENCH_PERSPECTIVE_ID, layout: this._layoutConfig });
5666
- }
5667
- else {
5668
- for (const perspective of this._layoutConfig.perspectives) {
5789
+ const layout = this._workbenchConfig.layout;
5790
+ // Create perspective from layout (if any).
5791
+ if (typeof layout === 'function') {
5792
+ await this.registerPerspective({ id: DEFAULT_WORKBENCH_PERSPECTIVE_ID, layout });
5793
+ }
5794
+ // Register configured perspectives (if any).
5795
+ else if (layout?.perspectives?.length) {
5796
+ for (const perspective of layout.perspectives) {
5669
5797
  await this.registerPerspective(perspective);
5670
5798
  }
5671
5799
  }
5800
+ // Register default perspective if no perspective is registered.
5801
+ else if (!this._perspectiveRegistry.perspectives.length) {
5802
+ await this.registerPerspective({ id: DEFAULT_WORKBENCH_PERSPECTIVE_ID, layout: ((factory) => factory.addPart(MAIN_AREA)) });
5803
+ }
5672
5804
  }
5673
5805
  /**
5674
5806
  * Registers an anonymous perspective if the window name indicates it.
@@ -5724,55 +5856,62 @@ class WorkbenchPerspectiveService {
5724
5856
  await this.activePerspective?.reset();
5725
5857
  }
5726
5858
  /**
5727
- * Activates the initial perspective, if any.
5859
+ * Activates the initial perspective.
5728
5860
  */
5729
5861
  async activateInitialPerspective() {
5730
- // Determine the initial perspective.
5731
- const initialPerspectiveId = await (async () => {
5732
- // Determine the initial perspective using information from the window name.
5733
- const perspectiveFromWindow = parsePerspectiveIdFromWindowName();
5734
- if (perspectiveFromWindow && this._perspectiveRegistry.has(perspectiveFromWindow)) {
5735
- return perspectiveFromWindow;
5736
- }
5737
- // Determine the initial perspective using information from the storage.
5738
- const perspectiveFromStorage = await this._workbenchPerspectiveStorageService.loadActivePerspectiveId();
5739
- if (perspectiveFromStorage && this._perspectiveRegistry.has(perspectiveFromStorage)) {
5740
- return perspectiveFromStorage;
5741
- }
5742
- // Determine the initial perspective using information from the config.
5743
- const perspectiveFromConfig = typeof this._layoutConfig === 'object' ? this._layoutConfig.initialPerspective : null;
5744
- if (!perspectiveFromConfig) {
5745
- return this._perspectiveRegistry.perspectives[0]?.id;
5746
- }
5747
- if (typeof perspectiveFromConfig === 'string') {
5748
- return perspectiveFromConfig;
5749
- }
5750
- if (typeof perspectiveFromConfig === 'function') {
5751
- return (await runInInjectionContext(this._environmentInjector, () => perspectiveFromConfig([...this._perspectiveRegistry.perspectives])))?.id;
5752
- }
5753
- return undefined;
5754
- })();
5755
- // Select initial perspective.
5756
- if (initialPerspectiveId) {
5757
- await this.switchPerspective(initialPerspectiveId, { storePerspectiveAsActive: false });
5862
+ if (!this._perspectiveRegistry.perspectives.length) {
5863
+ throw Error('[NullPerspectiveError] No perspective found to activate.');
5864
+ }
5865
+ const perspectiveId = await this.determineInitialPerspective() ?? this._perspectiveRegistry.perspectives[0].id;
5866
+ const activation = this.switchPerspective(perspectiveId, { storePerspectiveAsActive: false });
5867
+ // Switching perspective blocks until the initial navigation has been performed. By default, Angular performs
5868
+ // the initial navigation after running app initializers. Therefore, do not await perspective activation when
5869
+ // starting the workbench during app initialization. Otherwise, Angular would never complete app initialization.
5870
+ if (this._applicationInitStatus.done) {
5871
+ await activation;
5758
5872
  }
5759
5873
  }
5874
+ /**
5875
+ * Determines which perspective to activate, with the following precedence:
5876
+ *
5877
+ * 1. Perspective defined as window name.
5878
+ * 2. Perspective defined in storage.
5879
+ * 3. Perspective configured in the workbench config.
5880
+ */
5881
+ async determineInitialPerspective() {
5882
+ // Find perspective in window name.
5883
+ const perspectiveFromWindow = parsePerspectiveIdFromWindowName();
5884
+ if (perspectiveFromWindow && this._perspectiveRegistry.has(perspectiveFromWindow)) {
5885
+ return perspectiveFromWindow;
5886
+ }
5887
+ // Find perspective in storage.
5888
+ const perspectiveFromStorage = await this._workbenchPerspectiveStorageService.loadActivePerspectiveId();
5889
+ if (perspectiveFromStorage && this._perspectiveRegistry.has(perspectiveFromStorage)) {
5890
+ return perspectiveFromStorage;
5891
+ }
5892
+ // Find perspective in config.
5893
+ const perspectiveFromConfig = typeof this._workbenchConfig.layout === 'object' && this._workbenchConfig.layout.initialPerspective;
5894
+ if (typeof perspectiveFromConfig === 'string') {
5895
+ return perspectiveFromConfig;
5896
+ }
5897
+ if (typeof perspectiveFromConfig === 'function') {
5898
+ return (await runInInjectionContext(this._environmentInjector, () => perspectiveFromConfig([...this._perspectiveRegistry.perspectives])));
5899
+ }
5900
+ return undefined;
5901
+ }
5760
5902
  /**
5761
5903
  * Returns the currently active perspective, or `null` if there is no current perspective.
5762
5904
  */
5763
5905
  get activePerspective() {
5764
5906
  return this._perspectiveRegistry.perspectives.find(perspective => perspective.active) ?? null;
5765
5907
  }
5766
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchPerspectiveService, deps: [{ token: WORKBENCH_LAYOUT_CONFIG }, { token: WorkbenchPerspectiveRegistry }, { token: i0.EnvironmentInjector }, { token: WorkbenchPerspectiveStorageService }, { token: WorkbenchStartup }, { token: Logger }], target: i0.ɵɵFactoryTarget.Injectable }); }
5908
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchPerspectiveService, deps: [{ token: WorkbenchConfig }, { token: WorkbenchPerspectiveRegistry }, { token: i0.EnvironmentInjector }, { token: i0.ApplicationInitStatus }, { token: WorkbenchPerspectiveStorageService }], target: i0.ɵɵFactoryTarget.Injectable }); }
5767
5909
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchPerspectiveService, providedIn: 'root' }); }
5768
5910
  }
5769
5911
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchPerspectiveService, decorators: [{
5770
5912
  type: Injectable,
5771
5913
  args: [{ providedIn: 'root' }]
5772
- }], ctorParameters: () => [{ type: undefined, decorators: [{
5773
- type: Inject,
5774
- args: [WORKBENCH_LAYOUT_CONFIG]
5775
- }] }, { type: WorkbenchPerspectiveRegistry }, { type: i0.EnvironmentInjector }, { type: WorkbenchPerspectiveStorageService }, { type: WorkbenchStartup }, { type: Logger }] });
5914
+ }], ctorParameters: () => [{ type: WorkbenchConfig }, { type: WorkbenchPerspectiveRegistry }, { type: i0.EnvironmentInjector }, { type: i0.ApplicationInitStatus }, { type: WorkbenchPerspectiveStorageService }] });
5776
5915
  /**
5777
5916
  * Identifier for the "default" perspective that the workbench creates if not providing a named layout.
5778
5917
  */
@@ -6328,6 +6467,8 @@ class WbComponentPortal {
6328
6467
  throw Error(`[PortalConstructError] Component already constructed. [component=${this._componentType}]`);
6329
6468
  }
6330
6469
  this._componentRef = this.createComponent(injectionContext);
6470
+ // Trigger change detection to complete the initialization of the component, important for components detached from the Angular component tree.
6471
+ this._componentRef.changeDetectorRef.detectChanges();
6331
6472
  }
6332
6473
  /**
6333
6474
  * Attaches this portal to the given {@link ViewContainerRef} according to the following rules:
@@ -7367,7 +7508,7 @@ class ɵWorkbenchDialog {
7367
7508
  /**
7368
7509
  * Unique identity of this dialog.
7369
7510
  */
7370
- this.id = randomUUID();
7511
+ this.id = UUID.randomUUID();
7371
7512
  /**
7372
7513
  * Indicates whether this dialog is blocked by other dialog(s) that overlay this dialog.
7373
7514
  */
@@ -8138,7 +8279,7 @@ class ɵPopup {
8138
8279
  * Indicates whether this popup is blocked by dialog(s) that overlay it.
8139
8280
  */
8140
8281
  this.blockedBy$ = new BehaviorSubject(null);
8141
- this.id = this._config.id ?? randomUUID();
8282
+ this.id = this._config.id ?? UUID.randomUUID();
8142
8283
  this.cssClasses = Arrays.coerce(this._config.cssClass);
8143
8284
  this.blockWhenDialogOpened();
8144
8285
  }
@@ -8767,7 +8908,6 @@ class ɵWorkbenchView {
8767
8908
  this._viewDragService = inject(ViewDragService);
8768
8909
  this._activationInstantProvider = inject(ActivationInstantProvider);
8769
8910
  this._workbenchDialogRegistry = inject(WorkbenchDialogRegistry);
8770
- this._part$ = new BehaviorSubject(undefined);
8771
8911
  this._menuItemProviders$ = new BehaviorSubject([]);
8772
8912
  this._scrolledIntoView$ = new BehaviorSubject(true);
8773
8913
  this._adapters = new Map();
@@ -8780,13 +8920,17 @@ class ɵWorkbenchView {
8780
8920
  this.dirty = false;
8781
8921
  this.scrollTop = 0;
8782
8922
  this.scrollLeft = 0;
8783
- this.active$ = new BehaviorSubject(false);
8784
8923
  this.blockedBy$ = new BehaviorSubject(null);
8785
8924
  this.classList = new ClassList();
8786
8925
  this.menuItems$ = combineLatest([this._menuItemProviders$, this._workbenchService.viewMenuItemProviders$])
8787
8926
  .pipe(map(([localMenuItemProviders, globalMenuItemProviders]) => localMenuItemProviders.concat(globalMenuItemProviders)), mapArray(menuItemFactoryFn => menuItemFactoryFn(this)), filterArray((menuItem) => menuItem !== null));
8927
+ const mView = options.layout.view({ viewId: this.id });
8928
+ const mPart = options.layout.part({ viewId: this.id });
8929
+ this.uid = mView.uid;
8930
+ this.alternativeId = mView.alternativeId;
8931
+ this.partId$ = new BehaviorSubject(mPart.id);
8932
+ this.active$ = new BehaviorSubject(mPart.activeViewId === this.id);
8788
8933
  this.portal = this.createPortal(options.component);
8789
- this.trackViewActivation();
8790
8934
  this.touchOnActivate();
8791
8935
  this.blockWhenDialogOpened();
8792
8936
  this.detectRouteActivation();
@@ -8826,14 +8970,28 @@ class ɵWorkbenchView {
8826
8970
  onLayoutChange(layout) {
8827
8971
  const mPart = layout.part({ viewId: this.id });
8828
8972
  const mView = layout.view({ viewId: this.id });
8973
+ const prevNavigationId = this.navigationId;
8829
8974
  this.uid = mView.uid;
8830
8975
  this.alternativeId = mView.alternativeId;
8831
8976
  this.urlSegments = layout.urlSegments({ viewId: this.id });
8977
+ this.navigationId = mView.navigation?.id;
8832
8978
  this.navigationHint = mView.navigation?.hint;
8833
8979
  this.state = layout.viewState({ viewId: this.id });
8834
8980
  this.classList.set(mView.cssClass, { scope: 'layout' });
8835
8981
  this.classList.set(mView.navigation?.cssClass, { scope: 'navigation' });
8836
- this._part$.next(this._partRegistry.get(mPart.id));
8982
+ if (mPart.id !== this.partId$.value) {
8983
+ this.partId$.next(mPart.id);
8984
+ }
8985
+ const active = mPart.activeViewId === this.id;
8986
+ if (active !== this.active) {
8987
+ this.active$.next(active);
8988
+ }
8989
+ // Inactive views are not checked for changes since detached from the Angular component tree.
8990
+ // To complete the initialization of the routed content (to ensure that `ngOnInit` is called),
8991
+ // we manually trigger change detection.
8992
+ if (!this.active && this.portal.isConstructed && prevNavigationId !== this.navigationId) {
8993
+ this.portal.componentRef.changeDetectorRef.detectChanges();
8994
+ }
8837
8995
  }
8838
8996
  /**
8839
8997
  * Returns the component of this view. Returns `null` if not navigated the view, or before it was activated for the first time.
@@ -8913,7 +9071,7 @@ class ɵWorkbenchView {
8913
9071
  }
8914
9072
  /** @inheritDoc */
8915
9073
  get part() {
8916
- return Defined.orElseThrow(this._part$.value, () => Error(`[NullPartError] Part reference missing for view '${this.id}'.`));
9074
+ return this._partRegistry.get(this.partId$.value);
8917
9075
  }
8918
9076
  /** @inheritDoc */
8919
9077
  close(target) {
@@ -8998,15 +9156,6 @@ class ɵWorkbenchView {
8998
9156
  get destroyed() {
8999
9157
  return this.portal.isDestroyed;
9000
9158
  }
9001
- /**
9002
- * Monitors the associated part to check if this view is currently active, updating the active state of this view accordingly.
9003
- */
9004
- trackViewActivation() {
9005
- this._part$
9006
- .pipe(switchMap$1(part => part?.activeViewId$ ?? EMPTY), map(activeViewId => activeViewId === this.id), bufferLatestUntilLayoutChange(), // Prevent the (de-)activation of potentially wrong views while updating the layout.
9007
- distinctUntilChanged(), takeUntilDestroyed(this._destroyRef))
9008
- .subscribe(this.active$);
9009
- }
9010
9159
  /**
9011
9160
  * Updates the activation instant when this view is activated.
9012
9161
  */
@@ -9579,25 +9728,18 @@ class ViewComponent {
9579
9728
  get viewId() {
9580
9729
  return this._view.id;
9581
9730
  }
9582
- get cssClasses() {
9583
- return this._view.classList.value.join(' ');
9584
- }
9585
9731
  get isViewDragActive() {
9586
9732
  return this._viewDragService.viewDragData !== null;
9587
9733
  }
9588
- constructor(_view, _logger, _host, _viewDragService, viewContextMenuService) {
9734
+ constructor(_view, _viewDragService, _logger) {
9589
9735
  this._view = _view;
9590
- this._logger = _logger;
9591
- this._host = _host;
9592
9736
  this._viewDragService = _viewDragService;
9737
+ this._logger = _logger;
9593
9738
  this._viewport$ = new AsyncSubject();
9594
9739
  this._logger.debug(() => `Constructing ViewComponent. [viewId=${this.viewId}]`, LoggerNames.LIFECYCLE);
9595
- viewContextMenuService.installMenuItemAccelerators$(this._host, this._view)
9596
- .pipe(takeUntilDestroyed())
9597
- .subscribe();
9598
- combineLatest([this._view.active$, this._viewport$])
9599
- .pipe(takeUntilDestroyed())
9600
- .subscribe(([active, viewport]) => active ? this.onActivateView(viewport) : this.onDeactivateView(viewport));
9740
+ this.installMenuItemAccelerators();
9741
+ this.subscribeForViewActivation();
9742
+ this.addViewClassesToHost();
9601
9743
  }
9602
9744
  onActivateView(viewport) {
9603
9745
  viewport.focus();
@@ -9608,13 +9750,33 @@ class ViewComponent {
9608
9750
  this._view.scrollTop = viewport.scrollTop;
9609
9751
  this._view.scrollLeft = viewport.scrollLeft;
9610
9752
  }
9753
+ installMenuItemAccelerators() {
9754
+ inject(ViewMenuService).installMenuItemAccelerators$(inject((ElementRef)), this._view)
9755
+ .pipe(takeUntilDestroyed())
9756
+ .subscribe();
9757
+ }
9758
+ subscribeForViewActivation() {
9759
+ combineLatest([this._view.active$, this._viewport$])
9760
+ .pipe(takeUntilDestroyed())
9761
+ .subscribe(([active, viewport]) => {
9762
+ active ? this.onActivateView(viewport) : this.onDeactivateView(viewport);
9763
+ });
9764
+ }
9765
+ addViewClassesToHost() {
9766
+ const ngClass = inject(NgClass);
9767
+ this._view.classList.value$
9768
+ .pipe(takeUntilDestroyed())
9769
+ .subscribe(cssClasses => {
9770
+ ngClass.ngClass = cssClasses;
9771
+ });
9772
+ }
9611
9773
  ngOnDestroy() {
9612
9774
  this._logger.debug(() => `Destroying ViewComponent [viewId=${this.viewId}]'`, LoggerNames.LIFECYCLE);
9613
9775
  }
9614
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: ViewComponent, deps: [{ token: ɵWorkbenchView }, { token: Logger }, { token: i0.ElementRef }, { token: ViewDragService }, { token: ViewMenuService }], target: i0.ɵɵFactoryTarget.Component }); }
9615
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.2", type: ViewComponent, isStandalone: true, selector: "wb-view", host: { properties: { "attr.data-viewid": "this.viewId", "attr.class": "this.cssClasses", "class.view-drag": "this.isViewDragActive" } }, providers: [
9776
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: ViewComponent, deps: [{ token: ɵWorkbenchView }, { token: ViewDragService }, { token: Logger }], target: i0.ɵɵFactoryTarget.Component }); }
9777
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.2", type: ViewComponent, isStandalone: true, selector: "wb-view", host: { properties: { "attr.data-viewid": "this.viewId", "class.view-drag": "this.isViewDragActive" } }, providers: [
9616
9778
  configureViewGlassPane(),
9617
- ], viewQueries: [{ propertyName: "setViewport", first: true, predicate: SciViewportComponent, descendants: true }], hostDirectives: [{ directive: GlassPaneDirective }], ngImport: i0, template: "<sci-viewport cdkTrapFocus>\n <router-outlet [name]=\"viewId\"/>\n</sci-viewport>\n", styles: [":host{display:flex;flex-direction:column;background-color:var(--sci-workbench-view-background-color);color:var(--sci-color-text)}wb-workbench:has(wb-main-area-layout) wb-part:not(.main-area) :host{background-color:var(--sci-workbench-view-peripheral-background-color)}:host.view-drag{pointer-events:none}:host>sci-viewport{flex:1 1 0}:host>sci-viewport>router-outlet{position:absolute}\n"], dependencies: [{ kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2$1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: SciViewportComponent, selector: "sci-viewport", inputs: ["scrollbarStyle"], outputs: ["scroll"] }] }); }
9779
+ ], viewQueries: [{ propertyName: "setViewport", first: true, predicate: SciViewportComponent, descendants: true }], hostDirectives: [{ directive: GlassPaneDirective }, { directive: i5.NgClass }], ngImport: i0, template: "<sci-viewport cdkTrapFocus>\n <router-outlet [name]=\"viewId\"/>\n</sci-viewport>\n", styles: [":host{display:flex;flex-direction:column;background-color:var(--sci-workbench-view-background-color);color:var(--sci-color-text)}wb-workbench:has(wb-main-area-layout) wb-part:not(.main-area) :host{background-color:var(--sci-workbench-view-peripheral-background-color)}:host.view-drag{pointer-events:none}:host>sci-viewport{flex:1 1 0}:host>sci-viewport>router-outlet{position:absolute}\n"], dependencies: [{ kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2$1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: SciViewportComponent, selector: "sci-viewport", inputs: ["scrollbarStyle"], outputs: ["scroll"] }] }); }
9618
9780
  }
9619
9781
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: ViewComponent, decorators: [{
9620
9782
  type: Component,
@@ -9624,18 +9786,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
9624
9786
  SciViewportComponent,
9625
9787
  ], hostDirectives: [
9626
9788
  GlassPaneDirective,
9789
+ NgClass,
9627
9790
  ], providers: [
9628
9791
  configureViewGlassPane(),
9629
9792
  ], template: "<sci-viewport cdkTrapFocus>\n <router-outlet [name]=\"viewId\"/>\n</sci-viewport>\n", styles: [":host{display:flex;flex-direction:column;background-color:var(--sci-workbench-view-background-color);color:var(--sci-color-text)}wb-workbench:has(wb-main-area-layout) wb-part:not(.main-area) :host{background-color:var(--sci-workbench-view-peripheral-background-color)}:host.view-drag{pointer-events:none}:host>sci-viewport{flex:1 1 0}:host>sci-viewport>router-outlet{position:absolute}\n"] }]
9630
- }], ctorParameters: () => [{ type: ɵWorkbenchView }, { type: Logger }, { type: i0.ElementRef }, { type: ViewDragService }, { type: ViewMenuService }], propDecorators: { setViewport: [{
9793
+ }], ctorParameters: () => [{ type: ɵWorkbenchView }, { type: ViewDragService }, { type: Logger }], propDecorators: { setViewport: [{
9631
9794
  type: ViewChild,
9632
9795
  args: [SciViewportComponent]
9633
9796
  }], viewId: [{
9634
9797
  type: HostBinding,
9635
9798
  args: ['attr.data-viewid']
9636
- }], cssClasses: [{
9637
- type: HostBinding,
9638
- args: ['attr.class']
9639
9799
  }], isViewDragActive: [{
9640
9800
  type: HostBinding,
9641
9801
  args: ['class.view-drag']
@@ -10342,7 +10502,7 @@ class FilterFieldComponent {
10342
10502
  this._cd = _cd;
10343
10503
  this._cvaChangeFn = noop;
10344
10504
  this._cvaTouchedFn = noop;
10345
- this.id = randomUUID();
10505
+ this.id = UUID.randomUUID();
10346
10506
  /**
10347
10507
  * Emits on filter change.
10348
10508
  */
@@ -10406,13 +10566,13 @@ class FilterFieldComponent {
10406
10566
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: FilterFieldComponent, deps: [{ token: i0.ElementRef }, { token: i2$1.FocusMonitor }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
10407
10567
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.2", type: FilterFieldComponent, isStandalone: true, selector: "wb-filter-field", inputs: { tabindex: "tabindex", placeholder: "placeholder", disabled: "disabled" }, outputs: { filter: "filter" }, host: { listeners: { "focus": "focus()" }, properties: { "attr.tabindex": "this.componentTabindex", "class.empty": "this.empty" } }, providers: [
10408
10568
  { provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => FilterFieldComponent) },
10409
- ], viewQueries: [{ propertyName: "_inputElement", first: true, predicate: ["input"], descendants: true, static: true }], ngImport: i0, template: "<label [for]=\"id\" class=\"filter-icon scion-workbench-icons\">search</label>\n<input #input\n [attr.id]=\"id\"\n autocomplete=\"off\"\n [formControl]=\"formControl\"\n [tabindex]=\"tabindex ?? 0\"\n [placeholder]=\"placeholder ?? ''\">\n<button class=\"clear scion-workbench-icons\" tabindex=\"-1\" (click)=\"onClear()\">\n clear\n</button>\n", styles: [":host{display:inline-flex;flex-direction:row;gap:.5em;padding:.25em .5em}:host>label.filter-icon{flex:none;align-self:center;-webkit-user-select:none;user-select:none;font-size:1.5em}:host>input{all:unset;flex:auto;min-width:0}:host>button.clear{all:unset;flex:none;align-self:center;opacity:.75;cursor:pointer}:host>button.clear:hover{opacity:1}:host:not(:focus-within):not(:hover)>button.clear,:host:has(>input:disabled)>button.clear,:host.empty>button.clear{visibility:hidden}:host:has(>input:disabled)>label.filter-icon{color:var(--sci-color-text-subtlest)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10569
+ ], viewQueries: [{ propertyName: "_inputElement", first: true, predicate: ["input"], descendants: true, static: true }], ngImport: i0, template: "<label [for]=\"id\" class=\"filter-icon scion-workbench-icons\">search</label>\n<input #input\n [attr.id]=\"id\"\n autocomplete=\"off\"\n [formControl]=\"formControl\"\n [tabindex]=\"tabindex ?? 0\"\n [placeholder]=\"placeholder ?? ''\">\n<button class=\"clear scion-workbench-icons\" tabindex=\"-1\" (click)=\"onClear()\">\n clear\n</button>\n", styles: [":host{display:inline-flex;flex-direction:row;gap:.5em;padding:.25em .5em}:host>label.filter-icon{flex:none;align-self:center;-webkit-user-select:none;user-select:none;font-size:1.5em}:host>input{all:unset;flex:auto;min-width:0}:host>input::placeholder{color:var(--sci-color-gray-400)}:host>button.clear{all:unset;flex:none;align-self:center;opacity:.75;cursor:pointer}:host>button.clear:hover{opacity:1}:host:not(:focus-within):not(:hover)>button.clear,:host:has(>input:disabled)>button.clear,:host.empty>button.clear{visibility:hidden}:host:has(>input:disabled)>label.filter-icon{color:var(--sci-color-text-subtlest)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10410
10570
  }
10411
10571
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: FilterFieldComponent, decorators: [{
10412
10572
  type: Component,
10413
10573
  args: [{ selector: 'wb-filter-field', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ReactiveFormsModule], providers: [
10414
10574
  { provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => FilterFieldComponent) },
10415
- ], template: "<label [for]=\"id\" class=\"filter-icon scion-workbench-icons\">search</label>\n<input #input\n [attr.id]=\"id\"\n autocomplete=\"off\"\n [formControl]=\"formControl\"\n [tabindex]=\"tabindex ?? 0\"\n [placeholder]=\"placeholder ?? ''\">\n<button class=\"clear scion-workbench-icons\" tabindex=\"-1\" (click)=\"onClear()\">\n clear\n</button>\n", styles: [":host{display:inline-flex;flex-direction:row;gap:.5em;padding:.25em .5em}:host>label.filter-icon{flex:none;align-self:center;-webkit-user-select:none;user-select:none;font-size:1.5em}:host>input{all:unset;flex:auto;min-width:0}:host>button.clear{all:unset;flex:none;align-self:center;opacity:.75;cursor:pointer}:host>button.clear:hover{opacity:1}:host:not(:focus-within):not(:hover)>button.clear,:host:has(>input:disabled)>button.clear,:host.empty>button.clear{visibility:hidden}:host:has(>input:disabled)>label.filter-icon{color:var(--sci-color-text-subtlest)}\n"] }]
10575
+ ], template: "<label [for]=\"id\" class=\"filter-icon scion-workbench-icons\">search</label>\n<input #input\n [attr.id]=\"id\"\n autocomplete=\"off\"\n [formControl]=\"formControl\"\n [tabindex]=\"tabindex ?? 0\"\n [placeholder]=\"placeholder ?? ''\">\n<button class=\"clear scion-workbench-icons\" tabindex=\"-1\" (click)=\"onClear()\">\n clear\n</button>\n", styles: [":host{display:inline-flex;flex-direction:row;gap:.5em;padding:.25em .5em}:host>label.filter-icon{flex:none;align-self:center;-webkit-user-select:none;user-select:none;font-size:1.5em}:host>input{all:unset;flex:auto;min-width:0}:host>input::placeholder{color:var(--sci-color-gray-400)}:host>button.clear{all:unset;flex:none;align-self:center;opacity:.75;cursor:pointer}:host>button.clear:hover{opacity:1}:host:not(:focus-within):not(:hover)>button.clear,:host:has(>input:disabled)>button.clear,:host.empty>button.clear{visibility:hidden}:host:has(>input:disabled)>label.filter-icon{color:var(--sci-color-text-subtlest)}\n"] }]
10416
10576
  }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i2$1.FocusMonitor }, { type: i0.ChangeDetectorRef }], propDecorators: { tabindex: [{
10417
10577
  type: Input
10418
10578
  }], placeholder: [{
@@ -11329,8 +11489,6 @@ class PartComponent {
11329
11489
  .pipe(mapArray(viewId => this._viewRegistry.get(viewId)), filterArray(view => !view.active && !view.portal.isConstructed), mergeMap$1(views => from(views)))
11330
11490
  .subscribe(inactiveView => {
11331
11491
  inactiveView.portal.createComponentFromInjectionContext(this._injector);
11332
- // Trigger manual change detection cycle because the view is not yet added to the Angular component tree. Otherwise, routed content would not be attached.
11333
- inactiveView.portal.componentRef.changeDetectorRef.detectChanges();
11334
11492
  });
11335
11493
  }
11336
11494
  /**
@@ -11353,7 +11511,7 @@ class PartComponent {
11353
11511
  this._logger.debug(() => `Destroying PartComponent [partId=${this.partId}]'`, LoggerNames.LIFECYCLE);
11354
11512
  }
11355
11513
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: PartComponent, deps: [{ token: WORKBENCH_ID }, { token: WorkbenchViewRegistry }, { token: ViewDragService }, { token: i0.Injector }, { token: Logger }, { token: i0.ChangeDetectorRef }, { token: ɵWorkbenchPart }], target: i0.ɵɵFactoryTarget.Component }); }
11356
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.2", type: PartComponent, isStandalone: true, selector: "wb-part", host: { properties: { "attr.tabindex": "this.tabIndex", "attr.data-partid": "this.partId", "class.e2e-main-area": "this.isInMainArea", "class.main-area": "this.isMainArea", "class.active": "this.isActive" } }, ngImport: i0, template: "<wb-part-bar></wb-part-bar>\n\n<div wbViewDropZone\n [wbViewDropZoneSize]=\".3\"\n [wbViewDropZonePlaceholderSize]=\".5\"\n [wbViewDropZoneCssClass]=\"'e2e-part'\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"active-view e2e-active-view\">\n <ng-container *wbPortalOutlet=\"part.activeViewId$ | async | wbViewPortal\"></ng-container>\n</div>\n", styles: [":host{display:flex;flex-direction:column;outline:none}:host>wb-part-bar{flex:none}:host>div.active-view{flex:auto;display:grid;position:relative}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "component", type: PartBarComponent, selector: "wb-part-bar" }, { kind: "directive", type: ViewDropZoneDirective, selector: "[wbViewDropZone]", inputs: ["wbViewDropZoneRegions", "wbViewDropZoneCssClass", "wbViewDropZoneSize", "wbViewDropZonePlaceholderSize"], outputs: ["wbViewDropZoneDrop"] }, { kind: "directive", type: WorkbenchPortalOutletDirective, selector: "ng-template[wbPortalOutlet]", inputs: ["wbPortalOutlet"] }, { kind: "pipe", type: ViewPortalPipe, name: "wbViewPortal" }] }); }
11514
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.2", type: PartComponent, isStandalone: true, selector: "wb-part", host: { properties: { "attr.tabindex": "this.tabIndex", "attr.data-partid": "this.partId", "class.e2e-main-area": "this.isInMainArea", "class.main-area": "this.isMainArea", "class.active": "this.isActive" } }, ngImport: i0, template: "<wb-part-bar/>\n\n<div wbViewDropZone\n [wbViewDropZoneSize]=\".3\"\n [wbViewDropZonePlaceholderSize]=\".5\"\n [wbViewDropZoneCssClass]=\"'e2e-part'\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"active-view e2e-active-view\">\n <ng-container *wbPortalOutlet=\"part.activeViewId$ | async | wbViewPortal\"/>\n</div>\n", styles: [":host{display:flex;flex-direction:column;outline:none}:host>wb-part-bar{flex:none}:host>div.active-view{flex:auto;display:grid;position:relative}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "component", type: PartBarComponent, selector: "wb-part-bar" }, { kind: "directive", type: ViewDropZoneDirective, selector: "[wbViewDropZone]", inputs: ["wbViewDropZoneRegions", "wbViewDropZoneCssClass", "wbViewDropZoneSize", "wbViewDropZonePlaceholderSize"], outputs: ["wbViewDropZoneDrop"] }, { kind: "directive", type: WorkbenchPortalOutletDirective, selector: "ng-template[wbPortalOutlet]", inputs: ["wbPortalOutlet"] }, { kind: "pipe", type: ViewPortalPipe, name: "wbViewPortal" }] }); }
11357
11515
  }
11358
11516
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: PartComponent, decorators: [{
11359
11517
  type: Component,
@@ -11364,7 +11522,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
11364
11522
  ViewDropZoneDirective,
11365
11523
  WorkbenchPortalOutletDirective,
11366
11524
  ViewPortalPipe,
11367
- ], template: "<wb-part-bar></wb-part-bar>\n\n<div wbViewDropZone\n [wbViewDropZoneSize]=\".3\"\n [wbViewDropZonePlaceholderSize]=\".5\"\n [wbViewDropZoneCssClass]=\"'e2e-part'\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"active-view e2e-active-view\">\n <ng-container *wbPortalOutlet=\"part.activeViewId$ | async | wbViewPortal\"></ng-container>\n</div>\n", styles: [":host{display:flex;flex-direction:column;outline:none}:host>wb-part-bar{flex:none}:host>div.active-view{flex:auto;display:grid;position:relative}\n"] }]
11525
+ ], template: "<wb-part-bar/>\n\n<div wbViewDropZone\n [wbViewDropZoneSize]=\".3\"\n [wbViewDropZonePlaceholderSize]=\".5\"\n [wbViewDropZoneCssClass]=\"'e2e-part'\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"active-view e2e-active-view\">\n <ng-container *wbPortalOutlet=\"part.activeViewId$ | async | wbViewPortal\"/>\n</div>\n", styles: [":host{display:flex;flex-direction:column;outline:none}:host>wb-part-bar{flex:none}:host>div.active-view{flex:auto;display:grid;position:relative}\n"] }]
11368
11526
  }], ctorParameters: () => [{ type: undefined, decorators: [{
11369
11527
  type: Inject,
11370
11528
  args: [WORKBENCH_ID]
@@ -11448,15 +11606,9 @@ class WorkbenchLayoutDiffer {
11448
11606
  /**
11449
11607
  * Computes differences in the layout since last time {@link WorkbenchLayoutDiffer#diff} was invoked.
11450
11608
  */
11451
- diff(workbenchLayout, urlTree) {
11609
+ diff(workbenchLayout) {
11452
11610
  const parts = workbenchLayout?.parts().map(part => part.id);
11453
- // Create view diff based on views in the layout plus outlets in the URL, required because the layout is unavailable during initial navigation.
11454
- // Otherwise, the initial navigation for a URL with outlets of views contained in the workbench grid would fail as their view routes would be
11455
- // registered too late.
11456
- const views = new Set([
11457
- ...RouterUtils.parseViewOutlets(urlTree).keys(),
11458
- ...(workbenchLayout?.views().map(view => view.id) ?? []),
11459
- ]);
11611
+ const views = workbenchLayout?.views().map(view => view.id);
11460
11612
  return new WorkbenchLayoutDiff({
11461
11613
  parts: this._partsDiffer.diff(parts),
11462
11614
  views: this._viewsDiffer.diff(views),
@@ -11493,6 +11645,61 @@ class WorkbenchLayoutDiff {
11493
11645
  }
11494
11646
  }
11495
11647
 
11648
+ /*
11649
+ * Copyright (c) 2018-2023 Swiss Federal Railways
11650
+ *
11651
+ * This program and the accompanying materials are made
11652
+ * available under the terms of the Eclipse Public License 2.0
11653
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
11654
+ *
11655
+ * SPDX-License-Identifier: EPL-2.0
11656
+ */
11657
+ /**
11658
+ * Stateful differ to compute view outlets.
11659
+ *
11660
+ * Use this differ to register/unregister view auxiliary routes.
11661
+ *
11662
+ * Because the layout is not available during initial navigation and empty-path views do not have
11663
+ * an outlet in the URL, this differ uses both, outlets in the URL and views in the layout.
11664
+ */
11665
+ class WorkbenchViewOutletDiffer {
11666
+ constructor(differs) {
11667
+ this._differ = differs.find([]).create();
11668
+ }
11669
+ /**
11670
+ * Computes differences since last time {@link WorkbenchViewOutletDiffer#diff} was invoked.
11671
+ */
11672
+ diff(workbenchLayout, urlTree) {
11673
+ const views = workbenchLayout?.views().map(view => view.id) ?? [];
11674
+ const viewOutlets = RouterUtils.parseViewOutlets(urlTree).keys();
11675
+ const changes = this._differ.diff(new Set([...viewOutlets, ...views]));
11676
+ return new WorkbenchViewOutletDiff(changes);
11677
+ }
11678
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchViewOutletDiffer, deps: [{ token: i0.IterableDiffers }], target: i0.ɵɵFactoryTarget.Injectable }); }
11679
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchViewOutletDiffer, providedIn: 'root' }); }
11680
+ }
11681
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchViewOutletDiffer, decorators: [{
11682
+ type: Injectable,
11683
+ args: [{ providedIn: 'root' }]
11684
+ }], ctorParameters: () => [{ type: i0.IterableDiffers }] });
11685
+ /**
11686
+ * Lists the view outlets added/removed in the current navigation.
11687
+ */
11688
+ class WorkbenchViewOutletDiff {
11689
+ constructor(changes) {
11690
+ this.addedViewOutlets = new Array();
11691
+ this.removedViewOutlets = new Array();
11692
+ changes?.forEachAddedItem(({ item }) => this.addedViewOutlets.push(item));
11693
+ changes?.forEachRemovedItem(({ item }) => this.removedViewOutlets.push(item));
11694
+ }
11695
+ toString() {
11696
+ return `${new Array()
11697
+ .concat(this.addedViewOutlets.length ? `addedViewOutlets=[${this.addedViewOutlets}]` : [])
11698
+ .concat(this.removedViewOutlets.length ? `removedViewOutlets=[${this.removedViewOutlets}]` : [])
11699
+ .join(', ')}`;
11700
+ }
11701
+ }
11702
+
11496
11703
  /*
11497
11704
  * Copyright (c) 2018-2023 Swiss Federal Railways
11498
11705
  *
@@ -11656,9 +11863,9 @@ class WorkbenchMessageBoxDiff {
11656
11863
  * - Parses the serialized layout and injects it into {@link WorkbenchLayoutService}.
11657
11864
  */
11658
11865
  class WorkbenchUrlObserver {
11659
- constructor(_router, _auxiliaryRoutesRegistrator, _viewRegistry, _partRegistry, _workbenchLayoutService, _environmentInjector, _workbenchRouter, _workbenchLayoutFactory, _workbenchLayoutDiffer, _workbenchPopupDiffer, _workbenchDialogDiffer, _workbenchMessageBoxDiffer, _logger) {
11866
+ constructor(_router, _auxiliaryRouteInstaller, _viewRegistry, _partRegistry, _workbenchLayoutService, _environmentInjector, _workbenchRouter, _workbenchLayoutFactory, _workbenchLayoutDiffer, _workbenchViewOutletDiffer, _workbenchPopupDiffer, _workbenchDialogDiffer, _workbenchMessageBoxDiffer, _logger) {
11660
11867
  this._router = _router;
11661
- this._auxiliaryRoutesRegistrator = _auxiliaryRoutesRegistrator;
11868
+ this._auxiliaryRouteInstaller = _auxiliaryRouteInstaller;
11662
11869
  this._viewRegistry = _viewRegistry;
11663
11870
  this._partRegistry = _partRegistry;
11664
11871
  this._workbenchLayoutService = _workbenchLayoutService;
@@ -11666,6 +11873,7 @@ class WorkbenchUrlObserver {
11666
11873
  this._workbenchRouter = _workbenchRouter;
11667
11874
  this._workbenchLayoutFactory = _workbenchLayoutFactory;
11668
11875
  this._workbenchLayoutDiffer = _workbenchLayoutDiffer;
11876
+ this._workbenchViewOutletDiffer = _workbenchViewOutletDiffer;
11669
11877
  this._workbenchPopupDiffer = _workbenchPopupDiffer;
11670
11878
  this._workbenchDialogDiffer = _workbenchDialogDiffer;
11671
11879
  this._workbenchMessageBoxDiffer = _workbenchMessageBoxDiffer;
@@ -11739,7 +11947,8 @@ class WorkbenchUrlObserver {
11739
11947
  });
11740
11948
  return {
11741
11949
  layout,
11742
- layoutDiff: this._workbenchLayoutDiffer.diff(layout, urlTree),
11950
+ layoutDiff: this._workbenchLayoutDiffer.diff(layout),
11951
+ viewOutletDiff: this._workbenchViewOutletDiffer.diff(layout, urlTree),
11743
11952
  popupDiff: this._workbenchPopupDiffer.diff(urlTree),
11744
11953
  dialogDiff: this._workbenchDialogDiffer.diff(urlTree),
11745
11954
  messageBoxDiff: this._workbenchMessageBoxDiffer.diff(urlTree),
@@ -11751,27 +11960,27 @@ class WorkbenchUrlObserver {
11751
11960
  registerAddedOutletAuxiliaryRoutes() {
11752
11961
  const navigationContext = this._workbenchRouter.getCurrentNavigationContext();
11753
11962
  // Register view auxiliary routes.
11754
- const addedViews = navigationContext.layoutDiff.addedViews;
11755
- if (addedViews.length) {
11756
- const auxiliaryRoutes = this._auxiliaryRoutesRegistrator.registerAuxiliaryRoutes(addedViews, { canMatchNotFoundPage: [canMatchNotFoundPage] });
11757
- this._logger.debug(() => `Registered auxiliary routes for views: ${addedViews}`, LoggerNames.ROUTING, auxiliaryRoutes);
11963
+ const addedViewOutlets = navigationContext.viewOutletDiff.addedViewOutlets;
11964
+ if (addedViewOutlets.length) {
11965
+ const auxiliaryRoutes = this._auxiliaryRouteInstaller.registerAuxiliaryRoutes(addedViewOutlets, { canMatchNotFoundPage: [canMatchNotFoundPage] });
11966
+ this._logger.debug(() => `Registered auxiliary routes for views: ${addedViewOutlets}`, LoggerNames.ROUTING, auxiliaryRoutes);
11758
11967
  }
11759
11968
  // Register popup auxiliary routes.
11760
11969
  const addedPopupOutlets = navigationContext.popupDiff.addedPopupOutlets;
11761
11970
  if (addedPopupOutlets.length) {
11762
- const auxiliaryRoutes = this._auxiliaryRoutesRegistrator.registerAuxiliaryRoutes(addedPopupOutlets);
11971
+ const auxiliaryRoutes = this._auxiliaryRouteInstaller.registerAuxiliaryRoutes(addedPopupOutlets);
11763
11972
  this._logger.debug(() => `Registered auxiliary routes for popups: ${addedPopupOutlets}`, LoggerNames.ROUTING, auxiliaryRoutes);
11764
11973
  }
11765
11974
  // Register dialog auxiliary routes.
11766
11975
  const addedDialogOutlets = navigationContext.dialogDiff.addedDialogOutlets;
11767
11976
  if (addedDialogOutlets.length) {
11768
- const auxiliaryRoutes = this._auxiliaryRoutesRegistrator.registerAuxiliaryRoutes(addedDialogOutlets);
11977
+ const auxiliaryRoutes = this._auxiliaryRouteInstaller.registerAuxiliaryRoutes(addedDialogOutlets);
11769
11978
  this._logger.debug(() => `Registered auxiliary routes for dialogs: ${addedDialogOutlets}`, LoggerNames.ROUTING, auxiliaryRoutes);
11770
11979
  }
11771
11980
  // Register message box auxiliary routes.
11772
11981
  const addedMessageBoxOutlets = navigationContext.messageBoxDiff.addedMessageBoxOutlets;
11773
11982
  if (addedMessageBoxOutlets.length) {
11774
- const auxiliaryRoutes = this._auxiliaryRoutesRegistrator.registerAuxiliaryRoutes(addedMessageBoxOutlets);
11983
+ const auxiliaryRoutes = this._auxiliaryRouteInstaller.registerAuxiliaryRoutes(addedMessageBoxOutlets);
11775
11984
  this._logger.debug(() => `Registered auxiliary routes for message boxes: ${addedMessageBoxOutlets}`, LoggerNames.ROUTING, auxiliaryRoutes);
11776
11985
  }
11777
11986
  }
@@ -11783,7 +11992,8 @@ class WorkbenchUrlObserver {
11783
11992
  undoWorkbenchDiffers() {
11784
11993
  const prevNavigateLayout = this._workbenchLayoutService.layout; // Layout in `WorkbenchLayoutService` is only updated after successful navigation
11785
11994
  const prevNavigateUrl = this._router.parseUrl(this._router.url); // Browser URL is only updated after successful navigation
11786
- this._workbenchLayoutDiffer.diff(prevNavigateLayout, prevNavigateUrl);
11995
+ this._workbenchLayoutDiffer.diff(prevNavigateLayout);
11996
+ this._workbenchViewOutletDiffer.diff(prevNavigateLayout, prevNavigateUrl);
11787
11997
  this._workbenchPopupDiffer.diff(prevNavigateUrl);
11788
11998
  this._workbenchDialogDiffer.diff(prevNavigateUrl);
11789
11999
  this._workbenchMessageBoxDiffer.diff(prevNavigateUrl);
@@ -11796,13 +12006,13 @@ class WorkbenchUrlObserver {
11796
12006
  undoAuxiliaryRoutesRegistration() {
11797
12007
  const navigationContext = this._workbenchRouter.getCurrentNavigationContext();
11798
12008
  const addedOutlets = [
11799
- ...navigationContext.layoutDiff.addedViews,
12009
+ ...navigationContext.viewOutletDiff.addedViewOutlets,
11800
12010
  ...navigationContext.popupDiff.addedPopupOutlets,
11801
12011
  ...navigationContext.dialogDiff.addedDialogOutlets,
11802
12012
  ...navigationContext.messageBoxDiff.addedMessageBoxOutlets,
11803
12013
  ];
11804
12014
  if (addedOutlets.length) {
11805
- this._auxiliaryRoutesRegistrator.unregisterAuxiliaryRoutes(addedOutlets);
12015
+ this._auxiliaryRouteInstaller.unregisterAuxiliaryRoutes(addedOutlets);
11806
12016
  this._logger.debug(() => `Undo auxiliary routes registration for outlet(s): ${addedOutlets}`, LoggerNames.ROUTING);
11807
12017
  }
11808
12018
  }
@@ -11845,14 +12055,14 @@ class WorkbenchUrlObserver {
11845
12055
  unregisterRemovedOutletAuxiliaryRoutes() {
11846
12056
  const navigationContext = this._workbenchRouter.getCurrentNavigationContext();
11847
12057
  const removedOutlets = [
11848
- ...navigationContext.layoutDiff.removedViews,
12058
+ ...navigationContext.viewOutletDiff.removedViewOutlets,
11849
12059
  ...navigationContext.popupDiff.removedPopupOutlets,
11850
12060
  ...navigationContext.dialogDiff.removedDialogOutlets,
11851
12061
  ...navigationContext.messageBoxDiff.removedMessageBoxOutlets,
11852
12062
  ];
11853
12063
  if (removedOutlets.length) {
11854
12064
  this._logger.debug(() => 'Unregistering outlet auxiliary routes: ', LoggerNames.ROUTING, removedOutlets);
11855
- this._auxiliaryRoutesRegistrator.unregisterAuxiliaryRoutes(removedOutlets);
12065
+ this._auxiliaryRouteInstaller.unregisterAuxiliaryRoutes(removedOutlets);
11856
12066
  }
11857
12067
  }
11858
12068
  /**
@@ -11860,10 +12070,10 @@ class WorkbenchUrlObserver {
11860
12070
  * - For each removed view, destroys the {@link WorkbenchView} and unregisters it in {@link WorkbenchViewRegistry}
11861
12071
  */
11862
12072
  updateViewRegistry() {
11863
- const { layoutDiff } = this._workbenchRouter.getCurrentNavigationContext();
12073
+ const { layoutDiff, layout } = this._workbenchRouter.getCurrentNavigationContext();
11864
12074
  layoutDiff.addedViews.forEach(viewId => {
11865
12075
  this._logger.debug(() => `Constructing ɵWorkbenchView [viewId=${viewId}]`, LoggerNames.LIFECYCLE);
11866
- this._viewRegistry.register(this.createWorkbenchView(viewId));
12076
+ this._viewRegistry.register(this.createWorkbenchView(viewId, layout));
11867
12077
  });
11868
12078
  layoutDiff.removedViews.forEach(viewId => {
11869
12079
  this._logger.debug(() => `Destroying ɵWorkbenchView [viewId=${viewId}]`, LoggerNames.LIFECYCLE);
@@ -11890,8 +12100,8 @@ class WorkbenchUrlObserver {
11890
12100
  component: partId === MAIN_AREA ? MainAreaLayoutComponent : PartComponent,
11891
12101
  }));
11892
12102
  }
11893
- createWorkbenchView(viewId) {
11894
- return runInInjectionContext(this._environmentInjector, () => new ɵWorkbenchView(viewId, { component: ViewComponent }));
12103
+ createWorkbenchView(viewId, layout) {
12104
+ return runInInjectionContext(this._environmentInjector, () => new ɵWorkbenchView(viewId, { component: ViewComponent, layout }));
11895
12105
  }
11896
12106
  installRouterEventListeners() {
11897
12107
  this._router.events
@@ -11914,13 +12124,13 @@ class WorkbenchUrlObserver {
11914
12124
  }
11915
12125
  });
11916
12126
  }
11917
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchUrlObserver, deps: [{ token: i2.Router }, { token: WorkbenchAuxiliaryRoutesRegistrator }, { token: WorkbenchViewRegistry }, { token: WorkbenchPartRegistry }, { token: WorkbenchLayoutService }, { token: i0.EnvironmentInjector }, { token: ɵWorkbenchRouter }, { token: ɵWorkbenchLayoutFactory }, { token: WorkbenchLayoutDiffer }, { token: WorkbenchPopupDiffer }, { token: WorkbenchDialogDiffer }, { token: WorkbenchMessageBoxDiffer }, { token: Logger }], target: i0.ɵɵFactoryTarget.Injectable }); }
12127
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchUrlObserver, deps: [{ token: i2.Router }, { token: WorkbenchAuxiliaryRouteInstaller }, { token: WorkbenchViewRegistry }, { token: WorkbenchPartRegistry }, { token: WorkbenchLayoutService }, { token: i0.EnvironmentInjector }, { token: ɵWorkbenchRouter }, { token: ɵWorkbenchLayoutFactory }, { token: WorkbenchLayoutDiffer }, { token: WorkbenchViewOutletDiffer }, { token: WorkbenchPopupDiffer }, { token: WorkbenchDialogDiffer }, { token: WorkbenchMessageBoxDiffer }, { token: Logger }], target: i0.ɵɵFactoryTarget.Injectable }); }
11918
12128
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchUrlObserver, providedIn: 'root' }); }
11919
12129
  }
11920
12130
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchUrlObserver, decorators: [{
11921
12131
  type: Injectable,
11922
12132
  args: [{ providedIn: 'root' }]
11923
- }], ctorParameters: () => [{ type: i2.Router }, { type: WorkbenchAuxiliaryRoutesRegistrator }, { type: WorkbenchViewRegistry }, { type: WorkbenchPartRegistry }, { type: WorkbenchLayoutService }, { type: i0.EnvironmentInjector }, { type: ɵWorkbenchRouter }, { type: ɵWorkbenchLayoutFactory }, { type: WorkbenchLayoutDiffer }, { type: WorkbenchPopupDiffer }, { type: WorkbenchDialogDiffer }, { type: WorkbenchMessageBoxDiffer }, { type: Logger }] });
12133
+ }], ctorParameters: () => [{ type: i2.Router }, { type: WorkbenchAuxiliaryRouteInstaller }, { type: WorkbenchViewRegistry }, { type: WorkbenchPartRegistry }, { type: WorkbenchLayoutService }, { type: i0.EnvironmentInjector }, { type: ɵWorkbenchRouter }, { type: ɵWorkbenchLayoutFactory }, { type: WorkbenchLayoutDiffer }, { type: WorkbenchViewOutletDiffer }, { type: WorkbenchPopupDiffer }, { type: WorkbenchDialogDiffer }, { type: WorkbenchMessageBoxDiffer }, { type: Logger }] });
11924
12134
 
11925
12135
  /**
11926
12136
  * Updates the workbench layout when the user moves a view.
@@ -11971,7 +12181,7 @@ class ViewMoveHandler {
11971
12181
  await this._workbenchRouter.navigate(layout => {
11972
12182
  const newViewId = event.source.alternativeViewId ?? layout.computeNextViewId();
11973
12183
  if (addToNewPart) {
11974
- const newPartId = event.target.newPart?.id ?? randomUUID();
12184
+ const newPartId = event.target.newPart?.id ?? UID.randomUID();
11975
12185
  return layout
11976
12186
  .addPart(newPartId, { relativeTo: event.target.elementId, align: coerceAlignProperty(region), ratio: event.target.newPart?.ratio }, { structural: false })
11977
12187
  .addView(newViewId, { partId: newPartId, activateView: true, activatePart: true, cssClass: event.source.classList?.get('layout') })
@@ -12004,7 +12214,7 @@ class ViewMoveHandler {
12004
12214
  })
12005
12215
  .navigateView(newViewId, commands, { hint: event.source.navigationHint, cssClass: event.source.classList?.get('navigation') });
12006
12216
  });
12007
- const target = generatePerspectiveWindowName(`${ANONYMOUS_PERSPECTIVE_ID_PREFIX}${randomUUID()}`);
12217
+ const target = generatePerspectiveWindowName(`${ANONYMOUS_PERSPECTIVE_ID_PREFIX}${UID.randomUID()}`);
12008
12218
  if (window.open(this._locationStrategy.prepareExternalUrl(this._router.serializeUrl(urlTree)), target)) {
12009
12219
  await this.removeView(event);
12010
12220
  }
@@ -12015,7 +12225,7 @@ class ViewMoveHandler {
12015
12225
  async moveView(event) {
12016
12226
  const addToNewPart = !!event.target.region;
12017
12227
  if (addToNewPart) {
12018
- const newPartId = event.target.newPart?.id ?? randomUUID();
12228
+ const newPartId = event.target.newPart?.id ?? UID.randomUID();
12019
12229
  await this._workbenchRouter.navigate(layout => layout
12020
12230
  .addPart(newPartId, { relativeTo: event.target.elementId, align: coerceAlignProperty(event.target.region), ratio: event.target.newPart?.ratio }, { structural: false })
12021
12231
  .moveView(event.source.viewId, newPartId, { activatePart: true, activateView: true }));
@@ -12029,7 +12239,7 @@ class ViewMoveHandler {
12029
12239
  }));
12030
12240
  }
12031
12241
  }
12032
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: ViewMoveHandler, deps: [{ token: WORKBENCH_ID }, { token: ɵWorkbenchRouter }, { token: ɵWorkbenchLayoutFactory }, { token: ViewDragService }, { token: i2.Router }, { token: i3$1.LocationStrategy }], target: i0.ɵɵFactoryTarget.Injectable }); }
12242
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: ViewMoveHandler, deps: [{ token: WORKBENCH_ID }, { token: ɵWorkbenchRouter }, { token: ɵWorkbenchLayoutFactory }, { token: ViewDragService }, { token: i2.Router }, { token: i5.LocationStrategy }], target: i0.ɵɵFactoryTarget.Injectable }); }
12033
12243
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: ViewMoveHandler }); }
12034
12244
  }
12035
12245
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: ViewMoveHandler, decorators: [{
@@ -12037,7 +12247,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
12037
12247
  }], ctorParameters: () => [{ type: undefined, decorators: [{
12038
12248
  type: Inject,
12039
12249
  args: [WORKBENCH_ID]
12040
- }] }, { type: ɵWorkbenchRouter }, { type: ɵWorkbenchLayoutFactory }, { type: ViewDragService }, { type: i2.Router }, { type: i3$1.LocationStrategy }] });
12250
+ }] }, { type: ɵWorkbenchRouter }, { type: ɵWorkbenchLayoutFactory }, { type: ViewDragService }, { type: i2.Router }, { type: i5.LocationStrategy }] });
12041
12251
  function coerceAlignProperty(region) {
12042
12252
  switch (region) {
12043
12253
  case 'west':
@@ -12110,7 +12320,7 @@ const TEXT_MESSAGE_BOX_CAPABILITY_IDENTITY_PROPERTY = 'ɵidentity';
12110
12320
  /**
12111
12321
  * Value to identify the built-in text message box capability.
12112
12322
  */
12113
- const TEXT_MESSAGE_BOX_CAPABILITY_IDENTITY = randomUUID();
12323
+ const TEXT_MESSAGE_BOX_CAPABILITY_IDENTITY = UUID.randomUUID();
12114
12324
 
12115
12325
  var textMessage_component = /*#__PURE__*/Object.freeze({
12116
12326
  __proto__: null,
@@ -12129,6 +12339,7 @@ class WorkbenchHostManifestInterceptor {
12129
12339
  intercept(hostManifest) {
12130
12340
  hostManifest.intentions = [
12131
12341
  ...hostManifest.intentions || [],
12342
+ providePerspectiveIntention(),
12132
12343
  provideViewIntention(),
12133
12344
  ];
12134
12345
  hostManifest.capabilities = [
@@ -12143,6 +12354,15 @@ class WorkbenchHostManifestInterceptor {
12143
12354
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: WorkbenchHostManifestInterceptor, decorators: [{
12144
12355
  type: Injectable
12145
12356
  }] });
12357
+ /**
12358
+ * Provides a wildcard perspective intention for the workbench to register perspective capabilities as workbench perspectives.
12359
+ */
12360
+ function providePerspectiveIntention() {
12361
+ return {
12362
+ type: WorkbenchCapabilities.Perspective,
12363
+ qualifier: { '*': '*' },
12364
+ };
12365
+ }
12146
12366
  /**
12147
12367
  * Provides a wildcard view intention for the workbench to read view capabilities during microfrontend view routing.
12148
12368
  */
@@ -12222,6 +12442,110 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
12222
12442
  type: Injectable
12223
12443
  }], ctorParameters: () => [{ type: i0.NgZone }] });
12224
12444
 
12445
+ /*
12446
+ * Copyright (c) 2018-2024 Swiss Federal Railways
12447
+ *
12448
+ * This program and the accompanying materials are made
12449
+ * available under the terms of the Eclipse Public License 2.0
12450
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
12451
+ *
12452
+ * SPDX-License-Identifier: EPL-2.0
12453
+ */
12454
+ /**
12455
+ * Handles perspective intents, instructing the workbench to switch perspective.
12456
+ */
12457
+ class MicrofrontendPerspectiveIntentHandler {
12458
+ constructor(_workbenchService, _logger) {
12459
+ this._workbenchService = _workbenchService;
12460
+ this._logger = _logger;
12461
+ }
12462
+ /**
12463
+ * Perspective intents are handled in this interceptor and then swallowed.
12464
+ */
12465
+ intercept(intentMessage, next) {
12466
+ if (intentMessage.intent.type === WorkbenchCapabilities.Perspective) {
12467
+ return this.consumePerspectiveIntent(intentMessage);
12468
+ }
12469
+ else {
12470
+ return next.handle(intentMessage);
12471
+ }
12472
+ }
12473
+ async consumePerspectiveIntent(message) {
12474
+ const replyTo = message.headers.get(MessageHeaders.ReplyTo);
12475
+ const success = await this.switchPerspective(message);
12476
+ if (replyTo) {
12477
+ await Beans.get(MessageClient).publish(replyTo, success, { headers: new Map().set(MessageHeaders.Status, ResponseStatusCodes.TERMINAL) });
12478
+ }
12479
+ }
12480
+ switchPerspective(message) {
12481
+ const perspectiveId = message.capability.metadata.id;
12482
+ this._logger.debug(() => `Switching to perspective ${perspectiveId}.`, LoggerNames.MICROFRONTEND);
12483
+ return this._workbenchService.switchPerspective(perspectiveId);
12484
+ }
12485
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPerspectiveIntentHandler, deps: [{ token: WorkbenchService }, { token: Logger }], target: i0.ɵɵFactoryTarget.Injectable }); }
12486
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPerspectiveIntentHandler }); }
12487
+ }
12488
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPerspectiveIntentHandler, decorators: [{
12489
+ type: Injectable
12490
+ }], ctorParameters: () => [{ type: WorkbenchService }, { type: Logger }] });
12491
+
12492
+ /*
12493
+ * Copyright (c) 2018-2024 Swiss Federal Railways
12494
+ *
12495
+ * This program and the accompanying materials are made
12496
+ * available under the terms of the Eclipse Public License 2.0
12497
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
12498
+ *
12499
+ * SPDX-License-Identifier: EPL-2.0
12500
+ */
12501
+ /**
12502
+ * Provides cached access to manifest objects.
12503
+ */
12504
+ class ManifestObjectCache {
12505
+ constructor(_manifestService) {
12506
+ this._manifestService = _manifestService;
12507
+ this._capabilities$ = new BehaviorSubject(new Map());
12508
+ this.installCapabilityLookup();
12509
+ }
12510
+ async init() {
12511
+ await firstValueFrom(this._capabilities$);
12512
+ }
12513
+ /**
12514
+ * Tests if given capability exists.
12515
+ */
12516
+ hasCapability(capabilityId) {
12517
+ return this._capabilities$.value.has(capabilityId);
12518
+ }
12519
+ getCapability(capabilityId, options) {
12520
+ const capability = this._capabilities$.value.get(capabilityId);
12521
+ if (!capability && !options) {
12522
+ throw Error(`[NullCapabilityError] No capability found with id '${capabilityId}'.`);
12523
+ }
12524
+ return capability ?? null;
12525
+ }
12526
+ /**
12527
+ * Looks up specified capability.
12528
+ *
12529
+ * Upon subscription, emits the requested capability, and then emits continuously when it changes. It never completes.
12530
+ */
12531
+ observeCapability$(capabilityId) {
12532
+ return this._capabilities$
12533
+ .pipe(startWith(undefined), map(() => this.getCapability(capabilityId, { orElse: null })), distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)));
12534
+ }
12535
+ installCapabilityLookup() {
12536
+ this._manifestService.lookupCapabilities$()
12537
+ .pipe(takeUntilDestroyed())
12538
+ .subscribe(capabilities => {
12539
+ this._capabilities$.next(capabilities.reduce((acc, capability) => acc.set(capability.metadata.id, capability), new Map()));
12540
+ });
12541
+ }
12542
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: ManifestObjectCache, deps: [{ token: i2$3.ManifestService }], target: i0.ɵɵFactoryTarget.Injectable }); }
12543
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: ManifestObjectCache }); }
12544
+ }
12545
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: ManifestObjectCache, decorators: [{
12546
+ type: Injectable
12547
+ }], ctorParameters: () => [{ type: i2$3.ManifestService }] });
12548
+
12225
12549
  /*
12226
12550
  * Copyright (c) 2018-2024 Swiss Federal Railways
12227
12551
  *
@@ -12260,7 +12584,8 @@ const MicrofrontendViewRoutes = {
12260
12584
  const injector = inject(Injector);
12261
12585
  return (segments, group, route) => {
12262
12586
  // Test if the path matches.
12263
- if (!MicrofrontendViewRoutes.isMicrofrontendRoute(segments)) {
12587
+ const microfrontendRoute = MicrofrontendViewRoutes.parseMicrofrontendRoute(segments);
12588
+ if (!microfrontendRoute) {
12264
12589
  return null;
12265
12590
  }
12266
12591
  // Test if navigating a view.
@@ -12299,8 +12624,14 @@ const MicrofrontendViewRoutes = {
12299
12624
  /**
12300
12625
  * Tests given URL to be a microfrontend route.
12301
12626
  */
12302
- isMicrofrontendRoute: (segments) => {
12303
- return segments.length === 2 && segments[0].path === MicrofrontendViewRoutes.ROUTE_PREFIX;
12627
+ parseMicrofrontendRoute: (segments) => {
12628
+ if (segments.length === 2 && segments[0].path === MicrofrontendViewRoutes.ROUTE_PREFIX) {
12629
+ return {
12630
+ capabilityId: segments[1].path,
12631
+ params: segments[1].parameters,
12632
+ };
12633
+ }
12634
+ return null;
12304
12635
  },
12305
12636
  /**
12306
12637
  * Splits given params into URL and transient params.
@@ -12317,6 +12648,19 @@ const MicrofrontendViewRoutes = {
12317
12648
  return groups;
12318
12649
  }, { urlParams: {}, transientParams: {} });
12319
12650
  },
12651
+ /**
12652
+ * Matches the route if target of a view capability (microfrontend) and the capability exists.
12653
+ */
12654
+ canMatchViewCapability: ((_route, segments) => {
12655
+ const microfrontendRoute = MicrofrontendViewRoutes.parseMicrofrontendRoute(segments);
12656
+ if (!microfrontendRoute) {
12657
+ return false;
12658
+ }
12659
+ if (MicrofrontendPlatform.state !== PlatformState.Started) {
12660
+ return true; // match until started the microfrontend platform to avoid flickering.
12661
+ }
12662
+ return inject(ManifestObjectCache).hasCapability(microfrontendRoute.capabilityId);
12663
+ }),
12320
12664
  };
12321
12665
 
12322
12666
  /*
@@ -12388,10 +12732,12 @@ class MicrofrontendViewIntentHandler {
12388
12732
  const { urlParams, transientParams } = MicrofrontendViewRoutes.splitParams(intentParams, viewCapability);
12389
12733
  const targets = this.resolveTargets(message, extras);
12390
12734
  const commands = extras.close ? [] : MicrofrontendViewRoutes.createMicrofrontendNavigateCommands(viewCapability.metadata.id, urlParams);
12735
+ const partId = extras.close ? undefined : extras.partId;
12391
12736
  this._logger.debug(() => `Navigating to: ${viewCapability.properties.path}`, LoggerNames.MICROFRONTEND_ROUTING, commands, viewCapability, transientParams);
12392
12737
  const navigations = await Promise.all(Arrays.coerce(targets).map(target => {
12393
12738
  return this._workbenchRouter.navigate(commands, {
12394
12739
  target,
12740
+ partId,
12395
12741
  activate: extras.activate,
12396
12742
  close: extras.close,
12397
12743
  position: extras.position ?? extras.blankInsertionIndex,
@@ -12412,10 +12758,10 @@ class MicrofrontendViewIntentHandler {
12412
12758
  throw Error(`[NavigateError] The target must be empty if closing a view [target=${(extras.target)}]`);
12413
12759
  }
12414
12760
  if (extras.close) {
12415
- return this.resolvePresentViewIds(intentMessage, { matchWildcardParams: true }) ?? [];
12761
+ return this.resolvePresentViewIds(intentMessage, extras, { matchWildcardParams: true }) ?? [];
12416
12762
  }
12417
12763
  if (!extras.target || extras.target === 'auto') {
12418
- return this.resolvePresentViewIds(intentMessage) ?? 'blank';
12764
+ return this.resolvePresentViewIds(intentMessage, extras) ?? 'blank';
12419
12765
  }
12420
12766
  return extras.target;
12421
12767
  }
@@ -12425,10 +12771,11 @@ class MicrofrontendViewIntentHandler {
12425
12771
  *
12426
12772
  * Allows matching wildcard parameters by setting the option `matchWildcardParameters` to `true`.
12427
12773
  */
12428
- resolvePresentViewIds(intentMessage, options) {
12774
+ resolvePresentViewIds(intentMessage, extras, options) {
12429
12775
  const requiredParams = intentMessage.capability.params?.filter(param => param.required).map(param => param.name) ?? [];
12430
12776
  const matchWildcardParams = options?.matchWildcardParams ?? false;
12431
12777
  const viewIds = this._viewRegistry.views
12778
+ .filter(view => !extras.partId || extras.partId === view.part.id)
12432
12779
  .filter(view => {
12433
12780
  const microfrontendWorkbenchView = view.adapt(MicrofrontendWorkbenchView);
12434
12781
  if (!microfrontendWorkbenchView) {
@@ -12505,6 +12852,22 @@ const Microfrontends = {
12505
12852
  }
12506
12853
  });
12507
12854
  },
12855
+ /**
12856
+ * Creates a stable identifier for given capability.
12857
+ */
12858
+ createStableIdentifier: async (capability) => {
12859
+ const qualifier = capability.qualifier;
12860
+ const application = capability.metadata.appSymbolicName;
12861
+ // Create identifier consisting of vendor and sorted qualifier entries.
12862
+ const identifier = Object.entries(qualifier)
12863
+ .sort(([key1], [key2]) => key1.localeCompare(key2))
12864
+ .reduce((acc, [key, value]) => acc.concat(key).concat(`${value}`), [application])
12865
+ .join(';');
12866
+ // Hash the identifier.
12867
+ const identifierHash = await Crypto.digest(identifier);
12868
+ // Use the first 7 digits of the hash.
12869
+ return identifierHash.substring(0, 7);
12870
+ },
12508
12871
  /**
12509
12872
  * Replaces named parameters in the given value with values contained in the given {@link Map}.
12510
12873
  * Named parameters begin with a colon (`:`).
@@ -12559,12 +12922,6 @@ class MicrofrontendPopupComponent {
12559
12922
  this.splash = inject(MicrofrontendPlatformConfig).splash ?? MicrofrontendSplashComponent;
12560
12923
  }
12561
12924
  ngOnInit() {
12562
- // Obtain the capability provider.
12563
- const application = this.lookupApplication(this.popupCapability.metadata.appSymbolicName);
12564
- if (!application) {
12565
- this.popup.closeWithError(`[NullApplicationError] Unexpected. Cannot resolve application '${this.popupCapability.metadata.appSymbolicName}'.`);
12566
- return;
12567
- }
12568
12925
  // Listen to popup close requests.
12569
12926
  this._messageClient.observe$(_WorkbenchCommands.popupCloseTopic(this.popup.id))
12570
12927
  .pipe(takeUntilDestroyed(this._destroyRef))
@@ -12581,6 +12938,7 @@ class MicrofrontendPopupComponent {
12581
12938
  // Propagate workbench and color theme to the microfrontend.
12582
12939
  this.propagateWorkbenchTheme();
12583
12940
  // Navigate to the microfrontend.
12941
+ const application = this._manifestService.getApplication(this.popupCapability.metadata.appSymbolicName);
12584
12942
  this._logger.debug(() => `Loading microfrontend into workbench popup [app=${this.popupCapability.metadata.appSymbolicName}, baseUrl=${application.baseUrl}, path=${(this.popupCapability.properties.path)}].`, LoggerNames.MICROFRONTEND, this._popupContext.params, this.popupCapability);
12585
12943
  this._outletRouter.navigate(this.popupCapability.properties.path, {
12586
12944
  outlet: this.popup.id,
@@ -12600,12 +12958,6 @@ class MicrofrontendPopupComponent {
12600
12958
  this._host.nativeElement.dispatchEvent(new CustomEvent('sci-microfrontend-focusin', { bubbles: true }));
12601
12959
  }
12602
12960
  }
12603
- /**
12604
- * Looks up the application registered under the given symbolic name. Returns `undefined` if not found.
12605
- */
12606
- lookupApplication(symbolicName) {
12607
- return this._manifestService.applications.find(app => app.symbolicName === symbolicName);
12608
- }
12609
12961
  /**
12610
12962
  * Sets the {@link isWorkbenchDrag} property when a workbench drag operation is detected,
12611
12963
  * such as when dragging a view or moving a sash.
@@ -12678,7 +13030,7 @@ class MicrofrontendHostPopupComponent {
12678
13030
  this._singleNavigationExecutor = inject(SINGLE_NAVIGATION_EXECUTOR);
12679
13031
  const popupContext = popup.input;
12680
13032
  const capability = popupContext.capability;
12681
- const path = Defined.orElseThrow(capability.properties.path, () => Error(`[PopupProviderError] Missing required path for popup capability: ${JSON.stringify(capability)}`));
13033
+ const path = Defined.orElseThrow(capability.properties.path, () => Error(`[PopupProviderError] Missing required path for popup capability [application="${capability.metadata.appSymbolicName}", capability=${Objects.toMatrixNotation(capability.qualifier)}]`));
12682
13034
  const params = popupContext.params;
12683
13035
  this.outletName = POPUP_ID_PREFIX.concat(popup.id);
12684
13036
  this.outletInjector = Injector.create({
@@ -12901,7 +13253,7 @@ class MicrofrontendDialogComponent {
12901
13253
  this.navigate();
12902
13254
  }
12903
13255
  navigate() {
12904
- const application = this._manifestService.applications.find(app => app.symbolicName === this.capability.metadata.appSymbolicName);
13256
+ const application = this._manifestService.getApplication(this.capability.metadata.appSymbolicName);
12905
13257
  this._logger.debug(() => `Loading microfrontend into workbench dialog [app=${this.capability.metadata.appSymbolicName}, baseUrl=${application.baseUrl}, path=${this.capability.properties.path}].`, LoggerNames.MICROFRONTEND, this.params, this.capability);
12906
13258
  this._outletRouter.navigate(this.capability.properties.path, {
12907
13259
  outlet: this.dialog.id,
@@ -13307,7 +13659,7 @@ class MicrofrontendMessageBoxComponent {
13307
13659
  this.navigate();
13308
13660
  }
13309
13661
  navigate() {
13310
- const application = this._manifestService.applications.find(app => app.symbolicName === this.capability.metadata.appSymbolicName);
13662
+ const application = this._manifestService.getApplication(this.capability.metadata.appSymbolicName);
13311
13663
  this._logger.debug(() => `Loading microfrontend into workbench message box [app=${this.capability.metadata.appSymbolicName}, baseUrl=${application.baseUrl}, path=${this.capability.properties.path}].`, LoggerNames.MICROFRONTEND, this.params, this.capability);
13312
13664
  this._outletRouter.navigate(this.capability.properties.path, {
13313
13665
  outlet: this.outletName,
@@ -13483,7 +13835,7 @@ function hasLegacyContent(intentMessage) {
13483
13835
  }
13484
13836
 
13485
13837
  /**
13486
- * Asserts view capabilities to have required properties and assigns each view capability a stable identifer required for persistent navigation.
13838
+ * Asserts view capabilities to have required properties.
13487
13839
  */
13488
13840
  class MicrofrontendViewCapabilityValidator {
13489
13841
  async intercept(capability) {
@@ -13492,13 +13844,13 @@ class MicrofrontendViewCapabilityValidator {
13492
13844
  }
13493
13845
  const viewCapability = capability;
13494
13846
  // Assert the view capability to have a qualifier set.
13495
- if (!viewCapability.qualifier || !Object.keys(viewCapability.qualifier).length) {
13847
+ if (!Object.keys(viewCapability.qualifier ?? {}).length) {
13496
13848
  throw Error(`[NullQualifierError] View capability requires a qualifier [capability=${JSON.stringify(viewCapability)}]`);
13497
13849
  }
13498
13850
  // Assert the view capability to have a path set.
13499
13851
  const path = viewCapability.properties?.path;
13500
13852
  if (path === undefined || path === null) {
13501
- throw Error(`[NullPathError] View capability requires a path to the microfrontend in its properties [capability=${JSON.stringify(viewCapability)}]`);
13853
+ throw Error(`[NullPathError] View capability requires a path to the microfrontend in its properties [application="${viewCapability.metadata.appSymbolicName}", capability="${Objects.toMatrixNotation(viewCapability.qualifier)}"]`);
13502
13854
  }
13503
13855
  return capability;
13504
13856
  }
@@ -13510,41 +13862,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
13510
13862
  }] });
13511
13863
 
13512
13864
  /**
13513
- * Assigns each view capability a stable identifer required for persistent navigation.
13865
+ * Asserts perspective capabilities to have required properties.
13514
13866
  */
13515
- class MicrofrontendViewCapabilityIdAssigner {
13867
+ class MicrofrontendPerspectiveCapabilityValidator {
13516
13868
  async intercept(capability) {
13517
- if (capability.type !== WorkbenchCapabilities.View) {
13869
+ if (capability.type !== WorkbenchCapabilities.Perspective) {
13518
13870
  return capability;
13519
13871
  }
13520
- const stableIdentifier = await createStableViewIdentifier(capability);
13521
- return {
13522
- ...capability,
13523
- metadata: { ...capability.metadata, id: stableIdentifier },
13524
- };
13872
+ const perspectiveCapability = capability;
13873
+ // Assert the perspective capability to have a qualifier set.
13874
+ if (!Object.keys(perspectiveCapability.qualifier ?? {}).length) {
13875
+ throw Error(`[NullQualifierError] Perspective capability requires a qualifier [capability=${JSON.stringify(perspectiveCapability)}]`);
13876
+ }
13877
+ // Assert the perspective capability to have a layout set.
13878
+ if (!perspectiveCapability.properties?.layout) {
13879
+ throw Error(`[NullLayoutError] Perspective capability requires a layout [application="${perspectiveCapability.metadata.appSymbolicName}", capability="${Objects.toMatrixNotation(perspectiveCapability.qualifier)}"]`);
13880
+ }
13881
+ // Assert the perspective capability to define parts.
13882
+ if (!perspectiveCapability.properties.layout.length) {
13883
+ throw Error(`[NullLayoutError] Perspective capability requires parts [application="${perspectiveCapability.metadata.appSymbolicName}", capability="${Objects.toMatrixNotation(perspectiveCapability.qualifier)}"]`);
13884
+ }
13885
+ // Assert no views to be added to the main area.
13886
+ if (perspectiveCapability.properties.layout.find(part => part.id === MAIN_AREA)?.views?.length) {
13887
+ throw Error(`[PerspectiveLayoutError] Perspective capability cannot add views to the main area [application="${perspectiveCapability.metadata.appSymbolicName}", capability="${Objects.toMatrixNotation(perspectiveCapability.qualifier)}"]`);
13888
+ }
13889
+ return capability;
13525
13890
  }
13526
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendViewCapabilityIdAssigner, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
13527
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendViewCapabilityIdAssigner }); }
13891
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPerspectiveCapabilityValidator, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
13892
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPerspectiveCapabilityValidator }); }
13528
13893
  }
13529
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendViewCapabilityIdAssigner, decorators: [{
13894
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPerspectiveCapabilityValidator, decorators: [{
13530
13895
  type: Injectable
13531
13896
  }] });
13532
- /**
13533
- * Creates a stable identifier for given view capability.
13534
- */
13535
- async function createStableViewIdentifier(capability) {
13536
- const qualifier = capability.qualifier;
13537
- const vendor = capability.metadata.appSymbolicName;
13538
- // Create identifier consisting of vendor and sorted qualifier entries.
13539
- const identifier = Object.keys(qualifier)
13540
- .sort()
13541
- .reduce((acc, qualifierKey) => acc.concat(qualifierKey).concat(`${qualifier[qualifierKey]}`), [vendor])
13542
- .join(';');
13543
- // Hash the identifier.
13544
- const identifierHash = await Crypto.digest(identifier);
13545
- // Use the first 7 digits of the hash.
13546
- return identifierHash.substring(0, 7);
13547
- }
13548
13897
 
13549
13898
  /**
13550
13899
  * Asserts popup capabilities to have required properties.
@@ -13556,13 +13905,13 @@ class MicrofrontendPopupCapabilityValidator {
13556
13905
  }
13557
13906
  const popupCapability = capability;
13558
13907
  // Assert the popup capability to have a qualifier set.
13559
- if (!popupCapability.qualifier || !Object.keys(popupCapability.qualifier).length) {
13908
+ if (!Object.keys(popupCapability.qualifier ?? {}).length) {
13560
13909
  throw Error(`[NullQualifierError] Popup capability requires a qualifier [capability=${JSON.stringify(popupCapability)}]`);
13561
13910
  }
13562
13911
  // Assert the popup capability to have a path set.
13563
13912
  const path = popupCapability.properties?.path;
13564
13913
  if (path === undefined || path === null) {
13565
- throw Error(`[NullPathError] Popup capability requires a path to the microfrontend in its properties [capability=${JSON.stringify(popupCapability)}]`);
13914
+ throw Error(`[NullPathError] Popup capability requires a path to the microfrontend in its properties [application="${popupCapability.metadata.appSymbolicName}", capability="${Objects.toMatrixNotation(popupCapability.qualifier)}"]`);
13566
13915
  }
13567
13916
  return popupCapability;
13568
13917
  }
@@ -13592,13 +13941,13 @@ class MicrofrontendDialogCapabilityValidator {
13592
13941
  }
13593
13942
  const dialogCapability = capability;
13594
13943
  // Assert the dialog capability to have a qualifier.
13595
- if (!dialogCapability.qualifier || !Object.keys(dialogCapability.qualifier).length) {
13944
+ if (!Object.keys(dialogCapability.qualifier ?? {}).length) {
13596
13945
  throw Error(`[NullQualifierError] Dialog capability requires a qualifier [capability=${JSON.stringify(dialogCapability)}]`);
13597
13946
  }
13598
13947
  // Assert the dialog capability to have a path.
13599
13948
  const path = dialogCapability.properties?.path;
13600
13949
  if (path === undefined || path === null) {
13601
- throw Error(`[NullPathError] Dialog capability requires a path to the microfrontend in its properties [capability=${JSON.stringify(dialogCapability)}]`);
13950
+ throw Error(`[NullPathError] Dialog capability requires a path to the microfrontend in its properties [application="${dialogCapability.metadata.appSymbolicName}", capability="${Objects.toMatrixNotation(dialogCapability.qualifier)}"]`);
13602
13951
  }
13603
13952
  // Assert the dialog capability to have a height and width, unless provided by the host application.
13604
13953
  this.assertSize(dialogCapability);
@@ -13611,7 +13960,7 @@ class MicrofrontendDialogCapabilityValidator {
13611
13960
  }
13612
13961
  const size = capability.properties?.size;
13613
13962
  if (!size?.width || !size?.height) {
13614
- throw Error(`[NullSizeError] Dialog capability requires width and height in its size properties [capability=${JSON.stringify(capability)}]`);
13963
+ throw Error(`[NullSizeError] Dialog capability requires width and height in its size properties [application="${capability.metadata.appSymbolicName}", capability="${Objects.toMatrixNotation(capability.qualifier)}"]`);
13615
13964
  }
13616
13965
  }
13617
13966
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendDialogCapabilityValidator, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -13633,7 +13982,7 @@ class MicrofrontendMessageBoxCapabilityValidator {
13633
13982
  // Assert capability to have a path.
13634
13983
  const path = messageBoxCapability.properties?.path;
13635
13984
  if (path === undefined || path === null) {
13636
- throw Error(`[NullPathError] MessageBox capability requires a path to the microfrontend in its properties [capability=${JSON.stringify(messageBoxCapability)}]`);
13985
+ throw Error(`[NullPathError] MessageBox capability requires a path to the microfrontend in its properties [application="${messageBoxCapability.metadata.appSymbolicName}", capability="${Objects.toMatrixNotation(messageBoxCapability.qualifier)}"]`);
13637
13986
  }
13638
13987
  // Assert capability other than the built-in text messsage box capability to have a qualifier.
13639
13988
  if (!isBuiltInTextMessageBoxCapability(messageBoxCapability) && !Object.keys(messageBoxCapability.qualifier ?? {}).length) {
@@ -13651,6 +14000,34 @@ function isBuiltInTextMessageBoxCapability(capability) {
13651
14000
  return capability.properties[TEXT_MESSAGE_BOX_CAPABILITY_IDENTITY_PROPERTY] === TEXT_MESSAGE_BOX_CAPABILITY_IDENTITY;
13652
14001
  }
13653
14002
 
14003
+ /**
14004
+ * Assigns perspective and view capabilities a stable identifer based on the qualifier and application.
14005
+ */
14006
+ class StableCapabilityIdAssigner {
14007
+ constructor() {
14008
+ this._types = new Set()
14009
+ .add(WorkbenchCapabilities.Perspective)
14010
+ .add(WorkbenchCapabilities.View);
14011
+ }
14012
+ async intercept(capability) {
14013
+ if (!this._types.has(capability.type)) {
14014
+ return capability;
14015
+ }
14016
+ return {
14017
+ ...capability,
14018
+ metadata: {
14019
+ ...capability.metadata,
14020
+ id: await Microfrontends.createStableIdentifier(capability),
14021
+ },
14022
+ };
14023
+ }
14024
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: StableCapabilityIdAssigner, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
14025
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: StableCapabilityIdAssigner }); }
14026
+ }
14027
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: StableCapabilityIdAssigner, decorators: [{
14028
+ type: Injectable
14029
+ }] });
14030
+
13654
14031
  /*
13655
14032
  * Copyright (c) 2018-2024 Swiss Federal Railways
13656
14033
  *
@@ -13664,20 +14041,22 @@ function isBuiltInTextMessageBoxCapability(capability) {
13664
14041
  * Initializes and starts the SCION Microfrontend Platform in host mode.
13665
14042
  */
13666
14043
  class MicrofrontendPlatformInitializer {
13667
- constructor(_microfrontendPlatformConfigLoader, _hostManifestInterceptor, _ngZoneObservableDecorator, _viewIntentHandler, _popupIntentHandler, _dialogIntentHandler, _messageBoxIntentHandler, _messageBoxLegacyIntentTranslator, _viewCapabilityValidator, _viewCapabilityIdAssigner, _popupCapabilityValidator, _dialogCapabilityValidator, _messageBoxCapabilityValidator, _injector, _zone, _logger) {
14044
+ constructor(_microfrontendPlatformConfigLoader, _hostManifestInterceptor, _ngZoneObservableDecorator, _perspectiveIntentHandler, _viewIntentHandler, _popupIntentHandler, _dialogIntentHandler, _messageBoxIntentHandler, _messageBoxLegacyIntentTranslator, _viewCapabilityValidator, _perspectiveCapabilityValidator, _popupCapabilityValidator, _dialogCapabilityValidator, _messageBoxCapabilityValidator, _stableCapabilityIdAssigner, _injector, _zone, _logger) {
13668
14045
  this._microfrontendPlatformConfigLoader = _microfrontendPlatformConfigLoader;
13669
14046
  this._hostManifestInterceptor = _hostManifestInterceptor;
13670
14047
  this._ngZoneObservableDecorator = _ngZoneObservableDecorator;
14048
+ this._perspectiveIntentHandler = _perspectiveIntentHandler;
13671
14049
  this._viewIntentHandler = _viewIntentHandler;
13672
14050
  this._popupIntentHandler = _popupIntentHandler;
13673
14051
  this._dialogIntentHandler = _dialogIntentHandler;
13674
14052
  this._messageBoxIntentHandler = _messageBoxIntentHandler;
13675
14053
  this._messageBoxLegacyIntentTranslator = _messageBoxLegacyIntentTranslator;
13676
14054
  this._viewCapabilityValidator = _viewCapabilityValidator;
13677
- this._viewCapabilityIdAssigner = _viewCapabilityIdAssigner;
14055
+ this._perspectiveCapabilityValidator = _perspectiveCapabilityValidator;
13678
14056
  this._popupCapabilityValidator = _popupCapabilityValidator;
13679
14057
  this._dialogCapabilityValidator = _dialogCapabilityValidator;
13680
14058
  this._messageBoxCapabilityValidator = _messageBoxCapabilityValidator;
14059
+ this._stableCapabilityIdAssigner = _stableCapabilityIdAssigner;
13681
14060
  this._injector = _injector;
13682
14061
  this._zone = _zone;
13683
14062
  this._logger = _logger;
@@ -13706,6 +14085,8 @@ class MicrofrontendPlatformInitializer {
13706
14085
  Beans.register(HostManifestInterceptor, { useValue: this._hostManifestInterceptor, multi: true });
13707
14086
  // Synchronize emissions of Observables exposed by the SCION Microfrontend Platform with the Angular zone.
13708
14087
  Beans.register(ObservableDecorator, { useValue: this._ngZoneObservableDecorator });
14088
+ // Register perspective interceptor to switch perspective.
14089
+ Beans.register(IntentInterceptor, { useValue: this._perspectiveIntentHandler, multi: true });
13709
14090
  // Register view intent interceptor to open the corresponding view.
13710
14091
  Beans.register(IntentInterceptor, { useValue: this._viewIntentHandler, multi: true });
13711
14092
  // Register popup intent interceptor to open the corresponding popup.
@@ -13716,16 +14097,18 @@ class MicrofrontendPlatformInitializer {
13716
14097
  Beans.register(IntentInterceptor, { useValue: this._messageBoxLegacyIntentTranslator, multi: true });
13717
14098
  // Register message box intent interceptor to open the corresponding message box.
13718
14099
  Beans.register(IntentInterceptor, { useValue: this._messageBoxIntentHandler, multi: true });
14100
+ // Register perspective capability interceptor to assert required perspective capability properties.
14101
+ Beans.register(CapabilityInterceptor, { useValue: this._perspectiveCapabilityValidator, multi: true });
13719
14102
  // Register view capability interceptor to assert required view capability properties.
13720
14103
  Beans.register(CapabilityInterceptor, { useValue: this._viewCapabilityValidator, multi: true });
13721
- // Register view capability interceptor to assign view capabilities a stable identifier required for persistent navigation.
13722
- Beans.register(CapabilityInterceptor, { useValue: this._viewCapabilityIdAssigner, multi: true });
13723
14104
  // Register popup capability interceptor to assert required popup capability properties.
13724
14105
  Beans.register(CapabilityInterceptor, { useValue: this._popupCapabilityValidator, multi: true });
13725
14106
  // Register dialog capability interceptor to assert required dialog capability properties.
13726
14107
  Beans.register(CapabilityInterceptor, { useValue: this._dialogCapabilityValidator, multi: true });
13727
14108
  // Register message box capability interceptor to assert required capability properties.
13728
14109
  Beans.register(CapabilityInterceptor, { useValue: this._messageBoxCapabilityValidator, multi: true });
14110
+ // Register capability interceptor to assign perspective and view capabilities a stable identifier.
14111
+ Beans.register(CapabilityInterceptor, { useValue: this._stableCapabilityIdAssigner, multi: true });
13729
14112
  // Inject services registered under {MICROFRONTEND_PLATFORM_POST_STARTUP} DI token;
13730
14113
  // must be done in runlevel 2, i.e., before activator microfrontends are installed.
13731
14114
  Beans.registerInitializer({
@@ -13741,13 +14124,12 @@ class MicrofrontendPlatformInitializer {
13741
14124
  ngOnDestroy() {
13742
14125
  MicrofrontendPlatform.destroy().then();
13743
14126
  }
13744
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPlatformInitializer, deps: [{ token: MicrofrontendPlatformConfigLoader }, { token: WorkbenchHostManifestInterceptor }, { token: NgZoneObservableDecorator }, { token: MicrofrontendViewIntentHandler }, { token: MicrofrontendPopupIntentHandler }, { token: MicrofrontendDialogIntentHandler }, { token: MicrofrontendMessageBoxIntentHandler }, { token: MicrofrontendMessageBoxLegacyIntentTranslator }, { token: MicrofrontendViewCapabilityValidator }, { token: MicrofrontendViewCapabilityIdAssigner }, { token: MicrofrontendPopupCapabilityValidator }, { token: MicrofrontendDialogCapabilityValidator }, { token: MicrofrontendMessageBoxCapabilityValidator }, { token: i0.Injector }, { token: i0.NgZone }, { token: Logger }], target: i0.ɵɵFactoryTarget.Injectable }); }
13745
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPlatformInitializer, providedIn: 'root' }); }
14127
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPlatformInitializer, deps: [{ token: MicrofrontendPlatformConfigLoader }, { token: WorkbenchHostManifestInterceptor }, { token: NgZoneObservableDecorator }, { token: MicrofrontendPerspectiveIntentHandler }, { token: MicrofrontendViewIntentHandler }, { token: MicrofrontendPopupIntentHandler }, { token: MicrofrontendDialogIntentHandler }, { token: MicrofrontendMessageBoxIntentHandler }, { token: MicrofrontendMessageBoxLegacyIntentTranslator }, { token: MicrofrontendViewCapabilityValidator }, { token: MicrofrontendPerspectiveCapabilityValidator }, { token: MicrofrontendPopupCapabilityValidator }, { token: MicrofrontendDialogCapabilityValidator }, { token: MicrofrontendMessageBoxCapabilityValidator }, { token: StableCapabilityIdAssigner }, { token: i0.Injector }, { token: i0.NgZone }, { token: Logger }], target: i0.ɵɵFactoryTarget.Injectable }); }
14128
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPlatformInitializer }); }
13746
14129
  }
13747
14130
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPlatformInitializer, decorators: [{
13748
- type: Injectable,
13749
- args: [{ providedIn: 'root' }]
13750
- }], ctorParameters: () => [{ type: MicrofrontendPlatformConfigLoader }, { type: WorkbenchHostManifestInterceptor }, { type: NgZoneObservableDecorator }, { type: MicrofrontendViewIntentHandler }, { type: MicrofrontendPopupIntentHandler }, { type: MicrofrontendDialogIntentHandler }, { type: MicrofrontendMessageBoxIntentHandler }, { type: MicrofrontendMessageBoxLegacyIntentTranslator }, { type: MicrofrontendViewCapabilityValidator }, { type: MicrofrontendViewCapabilityIdAssigner }, { type: MicrofrontendPopupCapabilityValidator }, { type: MicrofrontendDialogCapabilityValidator }, { type: MicrofrontendMessageBoxCapabilityValidator }, { type: i0.Injector }, { type: i0.NgZone }, { type: Logger }] });
14131
+ type: Injectable
14132
+ }], ctorParameters: () => [{ type: MicrofrontendPlatformConfigLoader }, { type: WorkbenchHostManifestInterceptor }, { type: NgZoneObservableDecorator }, { type: MicrofrontendPerspectiveIntentHandler }, { type: MicrofrontendViewIntentHandler }, { type: MicrofrontendPopupIntentHandler }, { type: MicrofrontendDialogIntentHandler }, { type: MicrofrontendMessageBoxIntentHandler }, { type: MicrofrontendMessageBoxLegacyIntentTranslator }, { type: MicrofrontendViewCapabilityValidator }, { type: MicrofrontendPerspectiveCapabilityValidator }, { type: MicrofrontendPopupCapabilityValidator }, { type: MicrofrontendDialogCapabilityValidator }, { type: MicrofrontendMessageBoxCapabilityValidator }, { type: StableCapabilityIdAssigner }, { type: i0.Injector }, { type: i0.NgZone }, { type: Logger }] });
13751
14133
 
13752
14134
  /*
13753
14135
  * Copyright (c) 2018-2022 Swiss Federal Railways
@@ -13769,24 +14151,12 @@ class MicrofrontendViewCommandHandler {
13769
14151
  this._viewRegistry = _viewRegistry;
13770
14152
  this._logger = _logger;
13771
14153
  this._subscriptions = new Set();
13772
- this.installViewActiveStatePublisher();
13773
14154
  this._subscriptions.add(this.installViewTitleCommandHandler());
13774
14155
  this._subscriptions.add(this.installViewHeadingCommandHandler());
13775
14156
  this._subscriptions.add(this.installViewDirtyCommandHandler());
13776
14157
  this._subscriptions.add(this.installViewClosableCommandHandler());
13777
14158
  this._subscriptions.add(this.installViewCloseCommandHandler());
13778
14159
  }
13779
- /**
13780
- * Notifies microfrontends about the active state of the embedding view.
13781
- */
13782
- installViewActiveStatePublisher() {
13783
- this._viewRegistry.views$
13784
- .pipe(switchMap(views => merge(...views.map(view => view.active$.pipe(map(() => view))))), takeUntilDestroyed())
13785
- .subscribe((view) => {
13786
- const commandTopic = _WorkbenchCommands.viewActiveTopic(view.id);
13787
- this._messageClient.publish(commandTopic, view.active, { retain: true }).then();
13788
- });
13789
- }
13790
14160
  /**
13791
14161
  * Handles commands to update the title of a view.
13792
14162
  */
@@ -13934,17 +14304,12 @@ class ContentProjectionDirective {
13934
14304
  this._contentViewRef.onDestroy(() => dispose$.next());
13935
14305
  // Position projected content out of the document flow relative to the page viewport.
13936
14306
  this.styleContent({ position: 'fixed' });
13937
- // Align content each time the bounding box element changes its size.
13938
- fromDimension$(this._host.nativeElement)
13939
- .pipe(takeUntil(dispose$))
13940
- .subscribe(dimension => {
13941
- if (isNullDimension(dimension)) {
13942
- // When removing the bounding box element (this directive's host) from the DOM, its dimension drops to 0.
13943
- // We ignore this event to preserve the dimension of projected content, crucial, for example, if projected
13944
- // content implements virtual scrolling. Otherwise, its content would reload when adding the host to the DOM again.
13945
- // For example, inactive views are removed from the DOM.
13946
- return;
13947
- }
14307
+ // Align content each time the size of the host element changes, or when the content is attached to the DOM.
14308
+ // For example, moving a view to another part of the same size will not trigger a dimension change event.
14309
+ merge(fromDimension$(this._host.nativeElement), this._view?.portal.attached$.pipe(filter(Boolean)) ?? EMPTY)
14310
+ .pipe(observeOn(animationFrameScheduler), // Align to host boundaries right before the next repaint.
14311
+ takeUntil(dispose$))
14312
+ .subscribe(() => {
13948
14313
  this.alignContentToHostBoundaries();
13949
14314
  });
13950
14315
  // Hide content when contextual view is detached, e.g., if not active, or located in the peripheral area and the main area is maximized.
@@ -13959,6 +14324,13 @@ class ContentProjectionDirective {
13959
14324
  */
13960
14325
  alignContentToHostBoundaries() {
13961
14326
  const hostPosition = this._host.nativeElement.getBoundingClientRect();
14327
+ if (!hostPosition.width && !hostPosition.height) {
14328
+ // When removing the bounding box element (this directive's host) from the DOM, its dimension drops to 0.
14329
+ // We ignore this event to preserve the dimension of projected content, crucial, for example, if projected
14330
+ // content implements virtual scrolling. Otherwise, its content would reload when adding the host to the DOM again.
14331
+ // For example, inactive views are removed from the DOM.
14332
+ return;
14333
+ }
13962
14334
  this.styleContent({
13963
14335
  top: `${hostPosition.top}px`,
13964
14336
  left: `${hostPosition.left}px`,
@@ -13994,9 +14366,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
13994
14366
  type: Input,
13995
14367
  args: [{ alias: 'wbContentProjectionContent', required: true }]
13996
14368
  }] } });
13997
- function isNullDimension(dimension) {
13998
- return dimension.offsetWidth === 0 && dimension.offsetHeight === 0;
13999
- }
14000
14369
 
14001
14370
  /*
14002
14371
  * Copyright (c) 2018-2022 Swiss Federal Railways
@@ -14065,11 +14434,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
14065
14434
  * Embeds the microfrontend of a view capability.
14066
14435
  */
14067
14436
  class MicrofrontendViewComponent {
14068
- constructor(_host, _route, _outletRouter, _manifestService, _messageClient, _destroyRef, _logger, _viewContextMenuService, _workbenchLayoutService, _workbenchRouter, _injector, _cd, view, iframeHostRef) {
14437
+ constructor(_host, _route, _outletRouter, _manifestService, _manifestObjectCache, _messageClient, _destroyRef, _logger, _viewContextMenuService, _workbenchLayoutService, _workbenchRouter, _injector, _cd, view, iframeHostRef) {
14069
14438
  this._host = _host;
14070
14439
  this._route = _route;
14071
14440
  this._outletRouter = _outletRouter;
14072
14441
  this._manifestService = _manifestService;
14442
+ this._manifestObjectCache = _manifestObjectCache;
14073
14443
  this._messageClient = _messageClient;
14074
14444
  this._destroyRef = _destroyRef;
14075
14445
  this._logger = _logger;
@@ -14084,6 +14454,7 @@ class MicrofrontendViewComponent {
14084
14454
  this._universalKeystrokes = [
14085
14455
  'keydown.escape', // allows closing notifications
14086
14456
  ];
14457
+ this.capability = null;
14087
14458
  /**
14088
14459
  * Indicates whether a workbench drag operation is in progress, such as when dragging a view or moving a sash.
14089
14460
  */
@@ -14092,6 +14463,8 @@ class MicrofrontendViewComponent {
14092
14463
  this.keystrokesToBubble$ = combineLatest([this.viewContextMenuKeystrokes$(), of(this._universalKeystrokes)])
14093
14464
  .pipe(map(keystrokes => new Array().concat(...keystrokes)));
14094
14465
  this.installWorkbenchDragDetector();
14466
+ this.installViewActivePublisher();
14467
+ this.installPartIdPublisher();
14095
14468
  this.splash = inject(MicrofrontendPlatformConfig).splash ?? MicrofrontendSplashComponent;
14096
14469
  }
14097
14470
  ngOnInit() {
@@ -14108,7 +14481,7 @@ class MicrofrontendViewComponent {
14108
14481
  async onNavigate(prevCapability, capability, params) {
14109
14482
  if (!capability) {
14110
14483
  this._logger.warn(() => `[NullCapabilityError] No application found to provide a view capability of id '${params[_MicrofrontendRouteParams.ɵVIEW_CAPABILITY_ID]}'. Maybe, the requested view is not public API or the providing application not available.`, LoggerNames.MICROFRONTEND_ROUTING);
14111
- await this.view.close();
14484
+ this.unload();
14112
14485
  return;
14113
14486
  }
14114
14487
  this.capability = capability;
@@ -14132,7 +14505,7 @@ class MicrofrontendViewComponent {
14132
14505
  await this.waitForCapabilityParam(capability.metadata.id);
14133
14506
  }
14134
14507
  // Navigate to the microfrontend.
14135
- const application = this._manifestService.applications.find(app => app.symbolicName === capability.metadata.appSymbolicName);
14508
+ const application = this._manifestService.getApplication(capability.metadata.appSymbolicName);
14136
14509
  this._logger.debug(() => `Loading microfrontend into workbench view [viewId=${this.view.id}, app=${capability.metadata.appSymbolicName}, baseUrl=${application.baseUrl}, path=${(capability.properties.path)}].`, LoggerNames.MICROFRONTEND_ROUTING, params, capability);
14137
14510
  await this._outletRouter.navigate(capability.properties.path, {
14138
14511
  outlet: this.view.id,
@@ -14142,14 +14515,14 @@ class MicrofrontendViewComponent {
14142
14515
  showSplash: capability.properties.showSplash,
14143
14516
  ɵcapabilityId: capability.metadata.id,
14144
14517
  });
14145
- // Inactive views are detached from the Angular change detection tree.
14146
- // Therefore, manually detect this component for changes for updating attributes on the `sci-router-outlet`.
14518
+ // Inactive views are not checked for changes since detached from the Angular component tree.
14519
+ // So, we manually trigger change detection to update attributes on the `sci-router-outlet`.
14147
14520
  if (!this.view.active) {
14148
14521
  this._cd.detectChanges();
14149
14522
  }
14150
14523
  }
14151
14524
  fetchCapability$(capabilityId) {
14152
- return this._manifestService.lookupCapabilities$({ id: capabilityId }).pipe(map(capabilities => capabilities.at(0)));
14525
+ return this._manifestObjectCache.observeCapability$(capabilityId);
14153
14526
  }
14154
14527
  installMenuItemAccelerators$() {
14155
14528
  // Since the iframe is added at a top-level location in the DOM, that is, not as a child element of this component,
@@ -14204,6 +14577,22 @@ class MicrofrontendViewComponent {
14204
14577
  this.view.closable = viewCapability.properties.closable ?? true;
14205
14578
  this.view.dirty = false;
14206
14579
  }
14580
+ installViewActivePublisher() {
14581
+ this.view.active$
14582
+ .pipe(takeUntilDestroyed())
14583
+ .subscribe(active => {
14584
+ const commandTopic = _WorkbenchCommands.viewActiveTopic(this.view.id);
14585
+ this._messageClient.publish(commandTopic, active, { retain: true }).then();
14586
+ });
14587
+ }
14588
+ installPartIdPublisher() {
14589
+ this.view.partId$
14590
+ .pipe(takeUntilDestroyed())
14591
+ .subscribe(partId => {
14592
+ const commandTopic = _WorkbenchCommands.viewPartIdTopic(this.view.id);
14593
+ this._messageClient.publish(commandTopic, partId, { retain: true }).then();
14594
+ });
14595
+ }
14207
14596
  /**
14208
14597
  * Promise that resolves once params contain the given capability id.
14209
14598
  */
@@ -14264,14 +14653,18 @@ class MicrofrontendViewComponent {
14264
14653
  propagateWorkbenchTheme() {
14265
14654
  runInInjectionContext(this._injector, () => Microfrontends.propagateTheme(this.routerOutletElement.nativeElement));
14266
14655
  }
14267
- ngOnDestroy() {
14268
- // Instruct the message broker to delete retained messages to free resources.
14656
+ unload() {
14657
+ // Delete retained messages to free resources.
14269
14658
  this._messageClient.publish(_WorkbenchCommands.viewActiveTopic(this.view.id), undefined, { retain: true }).then();
14270
14659
  this._messageClient.publish(_WorkbenchCommands.viewParamsTopic(this.view.id), undefined, { retain: true }).then();
14660
+ this._messageClient.publish(_WorkbenchCommands.viewPartIdTopic(this.view.id), undefined, { retain: true }).then();
14271
14661
  this._outletRouter.navigate(null, { outlet: this.view.id }).then();
14272
14662
  this.view.unregisterAdapter(MicrofrontendWorkbenchView);
14273
14663
  }
14274
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendViewComponent, deps: [{ token: i0.ElementRef }, { token: i2.ActivatedRoute }, { token: i2$3.OutletRouter }, { token: i2$3.ManifestService }, { token: i2$3.MessageClient }, { token: i0.DestroyRef }, { token: Logger }, { token: ViewMenuService }, { token: WorkbenchLayoutService }, { token: WorkbenchRouter }, { token: i0.Injector }, { token: i0.ChangeDetectorRef }, { token: ɵWorkbenchView }, { token: IFRAME_HOST }], target: i0.ɵɵFactoryTarget.Component }); }
14664
+ ngOnDestroy() {
14665
+ this.unload();
14666
+ }
14667
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendViewComponent, deps: [{ token: i0.ElementRef }, { token: i2.ActivatedRoute }, { token: i2$3.OutletRouter }, { token: i2$3.ManifestService }, { token: ManifestObjectCache }, { token: i2$3.MessageClient }, { token: i0.DestroyRef }, { token: Logger }, { token: ViewMenuService }, { token: WorkbenchLayoutService }, { token: WorkbenchRouter }, { token: i0.Injector }, { token: i0.ChangeDetectorRef }, { token: ɵWorkbenchView }, { token: IFRAME_HOST }], target: i0.ɵɵFactoryTarget.Component }); }
14275
14668
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.2", type: MicrofrontendViewComponent, isStandalone: true, selector: "wb-microfrontend-view", viewQueries: [{ propertyName: "routerOutletElement", first: true, predicate: ["router_outlet"], descendants: true, static: true }], ngImport: i0, template: "<wb-content-as-overlay [overlayHost]=\"iframeHostRef.ref$ | async\">\n <div class=\"microfrontend-view\"\n [class.workbench-drag]=\"isWorkbenchDrag\"\n [attr.data-viewid]=\"view.id\"\n wbGlassPane>\n <sci-router-outlet #router_outlet\n [name]=\"view.id\"\n [attr.data-capabilityid]=\"capability?.metadata!.id\"\n [attr.data-app]=\"capability?.metadata!.appSymbolicName\"\n [ngClass]=\"view.classList.value\"\n (focuswithin)=\"onFocusWithin($event)\"\n [keystrokes]=\"keystrokesToBubble$ | async\">\n <ng-container *ngComponentOutlet=\"splash\"></ng-container>\n </sci-router-outlet>\n </div>\n</wb-content-as-overlay>\n", styles: [":host{display:grid}div.microfrontend-view{display:grid}div.microfrontend-view.workbench-drag{pointer-events:none}div.microfrontend-view>sci-router-outlet::part(splash){display:grid}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "component", type: ContentAsOverlayComponent, selector: "wb-content-as-overlay", inputs: ["overlayHost"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "directive", type: GlassPaneDirective, selector: "[wbGlassPane]" }], viewProviders: [
14276
14669
  configureMicrofrontendGlassPane(),
14277
14670
  ] }); }
@@ -14287,7 +14680,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
14287
14680
  ], viewProviders: [
14288
14681
  configureMicrofrontendGlassPane(),
14289
14682
  ], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "<wb-content-as-overlay [overlayHost]=\"iframeHostRef.ref$ | async\">\n <div class=\"microfrontend-view\"\n [class.workbench-drag]=\"isWorkbenchDrag\"\n [attr.data-viewid]=\"view.id\"\n wbGlassPane>\n <sci-router-outlet #router_outlet\n [name]=\"view.id\"\n [attr.data-capabilityid]=\"capability?.metadata!.id\"\n [attr.data-app]=\"capability?.metadata!.appSymbolicName\"\n [ngClass]=\"view.classList.value\"\n (focuswithin)=\"onFocusWithin($event)\"\n [keystrokes]=\"keystrokesToBubble$ | async\">\n <ng-container *ngComponentOutlet=\"splash\"></ng-container>\n </sci-router-outlet>\n </div>\n</wb-content-as-overlay>\n", styles: [":host{display:grid}div.microfrontend-view{display:grid}div.microfrontend-view.workbench-drag{pointer-events:none}div.microfrontend-view>sci-router-outlet::part(splash){display:grid}\n"] }]
14290
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i2.ActivatedRoute }, { type: i2$3.OutletRouter }, { type: i2$3.ManifestService }, { type: i2$3.MessageClient }, { type: i0.DestroyRef }, { type: Logger }, { type: ViewMenuService }, { type: WorkbenchLayoutService }, { type: WorkbenchRouter }, { type: i0.Injector }, { type: i0.ChangeDetectorRef }, { type: ɵWorkbenchView }, { type: ViewContainerReference, decorators: [{
14683
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i2.ActivatedRoute }, { type: i2$3.OutletRouter }, { type: i2$3.ManifestService }, { type: ManifestObjectCache }, { type: i2$3.MessageClient }, { type: i0.DestroyRef }, { type: Logger }, { type: ViewMenuService }, { type: WorkbenchLayoutService }, { type: WorkbenchRouter }, { type: i0.Injector }, { type: i0.ChangeDetectorRef }, { type: ɵWorkbenchView }, { type: ViewContainerReference, decorators: [{
14291
14684
  type: Inject,
14292
14685
  args: [IFRAME_HOST]
14293
14686
  }] }], propDecorators: { routerOutletElement: [{
@@ -14310,6 +14703,132 @@ function configureMicrofrontendGlassPane() {
14310
14703
  ];
14311
14704
  }
14312
14705
 
14706
+ /*
14707
+ * Copyright (c) 2018-2024 Swiss Federal Railways
14708
+ *
14709
+ * This program and the accompanying materials are made
14710
+ * available under the terms of the Eclipse Public License 2.0
14711
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
14712
+ *
14713
+ * SPDX-License-Identifier: EPL-2.0
14714
+ */
14715
+ /**
14716
+ * Keys for associating workbench-specific data with a perspective.
14717
+ */
14718
+ const WorkbenchPerspectiveData = {
14719
+ /**
14720
+ * Property to get the capability providing the perspective.
14721
+ */
14722
+ capability: 'ɵcapability',
14723
+ };
14724
+
14725
+ /*
14726
+ * Copyright (c) 2018-2024 Swiss Federal Railways
14727
+ *
14728
+ * This program and the accompanying materials are made
14729
+ * available under the terms of the Eclipse Public License 2.0
14730
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
14731
+ *
14732
+ * SPDX-License-Identifier: EPL-2.0
14733
+ */
14734
+ /**
14735
+ * Registers perspectives for workbench perspective capabilities.
14736
+ */
14737
+ class MicrofrontendPerspectiveInstaller {
14738
+ constructor(_manifestService, _workbenchService, _logger) {
14739
+ this._manifestService = _manifestService;
14740
+ this._workbenchService = _workbenchService;
14741
+ this._logger = _logger;
14742
+ this.installPerspectiveCapabilityListener();
14743
+ }
14744
+ installPerspectiveCapabilityListener() {
14745
+ const differ = inject(IterableDiffers).find([]).create((_index, perspectiveCapability) => perspectiveCapability.metadata.id);
14746
+ this._manifestService.lookupCapabilities$({ type: WorkbenchCapabilities.Perspective })
14747
+ .pipe(takeUntilDestroyed())
14748
+ .subscribe(perspectiveCapabilities => {
14749
+ const changes = differ.diff(perspectiveCapabilities);
14750
+ changes?.forEachAddedItem(({ item: perspectiveCapability }) => this.registerPerspective(perspectiveCapability));
14751
+ });
14752
+ }
14753
+ async registerPerspective(perspectiveCapability) {
14754
+ return this._workbenchService.registerPerspective({
14755
+ id: perspectiveCapability.metadata.id,
14756
+ layout: this.createLayout(perspectiveCapability),
14757
+ data: {
14758
+ ...perspectiveCapability.properties?.data,
14759
+ [WorkbenchPerspectiveData.capability]: perspectiveCapability,
14760
+ },
14761
+ });
14762
+ }
14763
+ createLayout(perspectiveCapability) {
14764
+ return async (factory) => {
14765
+ const [initialPart, ...parts] = perspectiveCapability.properties.layout;
14766
+ // Add parts to the layout.
14767
+ let layout = factory.addPart(initialPart.id);
14768
+ for (const part of parts) {
14769
+ layout = layout.addPart(part.id, {
14770
+ relativeTo: part.relativeTo,
14771
+ align: part.align,
14772
+ ratio: part.ratio,
14773
+ });
14774
+ }
14775
+ // Add views to the layout.
14776
+ for (const part of [initialPart, ...parts]) {
14777
+ if (!part.views?.length) {
14778
+ continue;
14779
+ }
14780
+ for (const [viewIndex, view] of part.views.entries()) {
14781
+ const viewCapabilities = await this.lookupViewCapabilities(view.qualifier, {
14782
+ perspectiveProvider: perspectiveCapability.metadata.appSymbolicName,
14783
+ perspectiveQualifier: perspectiveCapability.qualifier,
14784
+ });
14785
+ viewCapabilities
14786
+ .map(viewCapability => viewCapability.metadata.id)
14787
+ .sort() // Ensure stable view order in case multiple capabilities match the qualifier.
14788
+ .forEach(viewCapabilityId => {
14789
+ const commands = MicrofrontendViewRoutes.createMicrofrontendNavigateCommands(viewCapabilityId, view.params ?? {});
14790
+ const alternativeViewId = `${part.id}-${viewCapabilityId}-${viewIndex}`;
14791
+ layout = layout
14792
+ .addView(alternativeViewId, { partId: part.id, activateView: view.active })
14793
+ .navigateView(alternativeViewId, commands, { cssClass: view.cssClass });
14794
+ });
14795
+ // Navigate to the "~" route if not finding a view capability, e.g., because the perspective provider references a private view capability
14796
+ // or has not declared an intention. Since there is no route registered under /~ the "Not Found" page will be displayed.
14797
+ if (!viewCapabilities.length) {
14798
+ this._logger.warn(() => `[NullCapabilityError] The perspective '${Objects.toMatrixNotation(perspectiveCapability.qualifier)}' cannot find the view '${Objects.toMatrixNotation(view.qualifier)}'. Verify the application is available and the view exists.`, LoggerNames.MICROFRONTEND);
14799
+ const alternativeViewId = `${part.id}-${viewIndex}`;
14800
+ layout = layout
14801
+ .addView(alternativeViewId, { partId: part.id, activateView: view.active })
14802
+ .navigateView(alternativeViewId, ['~', view.qualifier], { cssClass: view.cssClass });
14803
+ }
14804
+ }
14805
+ }
14806
+ return layout;
14807
+ };
14808
+ }
14809
+ /**
14810
+ * Searches for views that match the specified qualifier and are accessible to the perspective provider,
14811
+ * i.e., its own view capabilities or public view capabilities of other applications the perspective provider
14812
+ * has an intention for.
14813
+ */
14814
+ async lookupViewCapabilities(qualifier, perspective) {
14815
+ const viewCapabilities$ = this._manifestService.lookupCapabilities$({ type: WorkbenchCapabilities.View, qualifier })
14816
+ .pipe(filterArray(async (viewCapability) => {
14817
+ const allowed = await firstValueFrom(this._manifestService.isApplicationQualified$(perspective.perspectiveProvider, { capabilityId: viewCapability.metadata.id }));
14818
+ if (!allowed) {
14819
+ this._logger.warn(`[NotQualifiedError] Application '${(perspective.perspectiveProvider)}' is not allowed to reference the view '${Objects.toMatrixNotation(viewCapability.qualifier)}' in the perspective '${Objects.toMatrixNotation(perspective.perspectiveQualifier)}'. Ensure you have specified an intention and that the view is public.`, LoggerNames.MICROFRONTEND);
14820
+ }
14821
+ return allowed;
14822
+ }));
14823
+ return firstValueFrom(viewCapabilities$);
14824
+ }
14825
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPerspectiveInstaller, deps: [{ token: i2$3.ManifestService }, { token: WorkbenchService }, { token: Logger }], target: i0.ɵɵFactoryTarget.Injectable }); }
14826
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPerspectiveInstaller }); }
14827
+ }
14828
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImport: i0, type: MicrofrontendPerspectiveInstaller, decorators: [{
14829
+ type: Injectable
14830
+ }], ctorParameters: () => [{ type: i2$3.ManifestService }, { type: WorkbenchService }, { type: Logger }] });
14831
+
14313
14832
  /*
14314
14833
  * Copyright (c) 2018-2024 Swiss Federal Railways
14315
14834
  *
@@ -14346,20 +14865,34 @@ function provideWorkbenchMicrofrontendSupport(workbenchConfig) {
14346
14865
  useClass: MicrofrontendNotificationIntentHandler,
14347
14866
  multi: true,
14348
14867
  },
14868
+ {
14869
+ provide: MICROFRONTEND_PLATFORM_POST_STARTUP,
14870
+ useClass: MicrofrontendPerspectiveInstaller,
14871
+ multi: true,
14872
+ },
14873
+ {
14874
+ provide: MICROFRONTEND_PLATFORM_POST_STARTUP,
14875
+ useExisting: ManifestObjectCache,
14876
+ multi: true,
14877
+ },
14349
14878
  {
14350
14879
  provide: MicrofrontendPlatformConfig,
14351
14880
  useFactory: () => Defined.orElseThrow(inject(MicrofrontendPlatformInitializer).config, () => Error('[MicrofrontendPlatformError] Illegal state: Microfrontend platform configuration not loaded.')),
14352
14881
  },
14882
+ MicrofrontendPlatformInitializer,
14883
+ MicrofrontendPerspectiveIntentHandler,
14353
14884
  MicrofrontendViewIntentHandler,
14354
14885
  MicrofrontendPopupIntentHandler,
14355
14886
  MicrofrontendDialogIntentHandler,
14356
14887
  MicrofrontendMessageBoxLegacyIntentTranslator,
14357
14888
  MicrofrontendMessageBoxIntentHandler,
14889
+ MicrofrontendPerspectiveCapabilityValidator,
14358
14890
  MicrofrontendViewCapabilityValidator,
14359
- MicrofrontendViewCapabilityIdAssigner,
14360
14891
  MicrofrontendPopupCapabilityValidator,
14361
14892
  MicrofrontendDialogCapabilityValidator,
14362
14893
  MicrofrontendMessageBoxCapabilityValidator,
14894
+ StableCapabilityIdAssigner,
14895
+ ManifestObjectCache,
14363
14896
  NgZoneObservableDecorator,
14364
14897
  WorkbenchHostManifestInterceptor,
14365
14898
  provideBuiltInTextMessageBoxCapabilityRoute(),
@@ -14420,7 +14953,7 @@ function provideMicrofrontendViewRoute() {
14420
14953
  useFactory: () => ({
14421
14954
  matcher: MicrofrontendViewRoutes.provideMicrofrontendRouteMatcher(),
14422
14955
  component: MicrofrontendViewComponent,
14423
- canMatch: [canMatchWorkbenchView(true)],
14956
+ canMatch: [canMatchWorkbenchView(true), MicrofrontendViewRoutes.canMatchViewCapability],
14424
14957
  }),
14425
14958
  },
14426
14959
  ]);
@@ -14571,17 +15104,13 @@ function provideWorkbench(config) {
14571
15104
  provide: WorkbenchStorage,
14572
15105
  useClass: config.storage ?? DefaultWorkbenchStorage,
14573
15106
  },
14574
- {
14575
- provide: WORKBENCH_LAYOUT_CONFIG,
14576
- useFactory: () => config.layout ?? ((factory) => factory.addPart(MAIN_AREA)),
14577
- },
14578
15107
  {
14579
15108
  provide: WORKBENCH_PRE_STARTUP,
14580
15109
  useExisting: WorkbenchThemeSwitcher,
14581
15110
  multi: true,
14582
15111
  },
14583
15112
  {
14584
- provide: WORKBENCH_STARTUP,
15113
+ provide: WORKBENCH_POST_STARTUP,
14585
15114
  multi: true,
14586
15115
  useExisting: WorkbenchPerspectiveService,
14587
15116
  },
@@ -15054,5 +15583,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.2", ngImpor
15054
15583
  * Generated bundle index. Do not edit.
15055
15584
  */
15056
15585
 
15057
- export { LogAppender, LogLevel, Logger, LoggerName, MAIN_AREA, MICROFRONTEND_PLATFORM_POST_STARTUP, MICROFRONTEND_PLATFORM_PRE_STARTUP, MicrofrontendPlatformConfigLoader, Notification, NotificationService, Popup, PopupConfig, PopupService, VIEW_TAB_RENDERING_CONTEXT, WORKBENCH_ID, WORKBENCH_POST_STARTUP, WORKBENCH_PRE_STARTUP, WORKBENCH_STARTUP, WorkbenchAuxiliaryRoutesRegistrator, WorkbenchComponent, WorkbenchConfig, WorkbenchDialog, WorkbenchDialogActionDirective, WorkbenchDialogFooterDirective, WorkbenchDialogHeaderDirective, WorkbenchDialogService, WorkbenchLauncher, WorkbenchLayoutFactory, WorkbenchMessageBoxService, WorkbenchModule, WorkbenchPart, WorkbenchPartActionDirective, WorkbenchRouteData, WorkbenchRouter, WorkbenchRouterLinkDirective, WorkbenchService, WorkbenchStartup, WorkbenchStorage, WorkbenchTestingModule, WorkbenchView, WorkbenchViewMenuItemDirective, canMatchWorkbenchView, provideWorkbench, provideWorkbenchForTest };
15586
+ export { LogAppender, LogLevel, Logger, LoggerName, MAIN_AREA, MICROFRONTEND_PLATFORM_POST_STARTUP, MICROFRONTEND_PLATFORM_PRE_STARTUP, MicrofrontendPlatformConfigLoader, Notification, NotificationService, Popup, PopupConfig, PopupService, VIEW_TAB_RENDERING_CONTEXT, WORKBENCH_ID, WORKBENCH_POST_STARTUP, WORKBENCH_PRE_STARTUP, WORKBENCH_STARTUP, WorkbenchComponent, WorkbenchConfig, WorkbenchDialog, WorkbenchDialogActionDirective, WorkbenchDialogFooterDirective, WorkbenchDialogHeaderDirective, WorkbenchDialogService, WorkbenchLauncher, WorkbenchLayoutFactory, WorkbenchMessageBoxService, WorkbenchModule, WorkbenchPart, WorkbenchPartActionDirective, WorkbenchPerspectiveData, WorkbenchRouteData, WorkbenchRouter, WorkbenchRouterLinkDirective, WorkbenchService, WorkbenchStartup, WorkbenchStorage, WorkbenchTestingModule, WorkbenchView, WorkbenchViewMenuItemDirective, canMatchWorkbenchView, provideWorkbench, provideWorkbenchForTest };
15058
15587
  //# sourceMappingURL=scion-workbench.mjs.map