diff --git a/projects/scion/workbench/src/lib/activity-part/activity-part.component.ts b/projects/scion/workbench/src/lib/activity-part/activity-part.component.ts index 8613988af..7faccad24 100644 --- a/projects/scion/workbench/src/lib/activity-part/activity-part.component.ts +++ b/projects/scion/workbench/src/lib/activity-part/activity-part.component.ts @@ -52,9 +52,6 @@ export class ActivityPartComponent { private _panelWidth = PANEL_INITIAL_WIDTH; private _panelWidth$ = new Subject(); - @ViewChild('viewport') - public viewport!: ElementRef; - @ViewChild('panel', {read: ElementRef}) private _panelElementRef!: ElementRef; diff --git a/projects/scion/workbench/src/lib/activity-part/activity.ts b/projects/scion/workbench/src/lib/activity-part/activity.ts index 68daf8a6b..c62121fd7 100644 --- a/projects/scion/workbench/src/lib/activity-part/activity.ts +++ b/projects/scion/workbench/src/lib/activity-part/activity.ts @@ -14,43 +14,43 @@ export abstract class Activity { /** * Specifies the title of the activity. */ - public title?: string; + public abstract title: string | null; /** * Specifies CSS class(es) added to the activity item and activity panel, e.g. used for e2e testing. */ - public cssClass?: string | string[]; + public abstract cssClass: string | string[] | undefined; /** * Specifies the text for the activity item. * * You can use it in combination with `itemCssClass`, e.g. to render an icon glyph by using its textual name. */ - public itemText?: string; + public abstract itemText: string | null; /** * Specifies CSS class(es) added to the activity item, e.g. used for e2e testing or to set an icon font class. */ - public itemCssClass?: string | string[]; + public abstract itemCssClass: string | string[] | undefined; /** * Controls whether to open this activity in the activity panel or to open it in a separate view. */ - public target?: 'activity-panel' | 'view'; + public abstract target: 'activity-panel' | 'view'; /** * Controls whether to show or hide this activity. By default, this activity is showing. */ - public visible?: boolean; + public abstract visible: boolean; /** * Specifies where to insert this activity in the list of activities. */ - public position?: number; + public abstract position: number | undefined; /** * Specifies the number of pixels added to the activity panel width if this is the active activity. */ - public panelWidthDelta?: number; + public abstract panelWidthDelta: number; /** * Specifies the routing commands used by Angular router to navigate when this activity is activated. @@ -99,15 +99,15 @@ export class InternalActivity implements Activity { private _active$ = new BehaviorSubject(false); public panelWidthDelta = 0; - public title?: string; - public cssClass?: string | string[]; + public title: string | null = null; + public cssClass: string | string[] | undefined; - public itemText?: string; - public itemCssClass?: string | string[]; + public itemText: string | null = null; + public itemCssClass: string | string[] | undefined; public target: 'activity-panel' | 'view' = 'activity-panel'; public visible = true; - public position?: number; + public position: number | undefined; constructor(private _wbRouter: WorkbenchRouter, private _injector: Injector) { } diff --git a/projects/scion/workbench/src/lib/activity-part/wb-activity-action.directive.ts b/projects/scion/workbench/src/lib/activity-part/wb-activity-action.directive.ts index 6696dbe9c..803b1a737 100644 --- a/projects/scion/workbench/src/lib/activity-part/wb-activity-action.directive.ts +++ b/projects/scion/workbench/src/lib/activity-part/wb-activity-action.directive.ts @@ -10,7 +10,6 @@ import { Directive, OnDestroy, TemplateRef } from '@angular/core'; import { WorkbenchActivityPartService } from './workbench-activity-part.service'; -import { Activity } from './activity'; import { ActivatedRoute } from '@angular/router'; import { Disposable } from '../disposable'; @@ -34,18 +33,17 @@ import { Disposable } from '../disposable'; }) export class WbActivityActionDirective implements OnDestroy { - private readonly _activity: Activity | null; private readonly _action: Disposable; constructor(private _template: TemplateRef, activityService: WorkbenchActivityPartService, route: ActivatedRoute) { - this._activity = activityService.getActivityFromRoutingContext(route.snapshot); - if (!this._activity) { + const activity = activityService.getActivityFromRoutingContext(route.snapshot); + if (!activity) { throw Error('[RoutingContextError] Route not in the context of an activity'); } - this._action = this._activity.registerAction(this._template); + this._action = activity.registerAction(this._template); } public ngOnDestroy(): void { diff --git a/projects/scion/workbench/src/lib/activity-part/wb-activity.directive.ts b/projects/scion/workbench/src/lib/activity-part/wb-activity.directive.ts index a80327954..5d0f6a6cb 100644 --- a/projects/scion/workbench/src/lib/activity-part/wb-activity.directive.ts +++ b/projects/scion/workbench/src/lib/activity-part/wb-activity.directive.ts @@ -37,7 +37,7 @@ export class WbActivityDirective implements OnInit, OnDestroy { * Specifies the title of the activity. */ @Input() - public set title(title: string) { + public set title(title: string | null) { this.activity.title = title; } @@ -47,7 +47,7 @@ export class WbActivityDirective implements OnInit, OnDestroy { * You can use it in combination with `itemCssClass`, e.g. to render an icon glyph by using its textual name. */ @Input() - public set itemText(itemText: string) { + public set itemText(itemText: string | null) { this.activity.itemText = itemText; } @@ -55,7 +55,7 @@ export class WbActivityDirective implements OnInit, OnDestroy { * Specifies CSS class(es) added to the activity item, e.g. used for e2e testing or to set an icon font class. */ @Input() - public set itemCssClass(itemCssClass: string | string[]) { + public set itemCssClass(itemCssClass: string | string[] | undefined) { this.activity.itemCssClass = itemCssClass; } @@ -63,7 +63,7 @@ export class WbActivityDirective implements OnInit, OnDestroy { * Specifies CSS class(es) added to the activity item and activity panel, e.g. used for e2e testing. */ @Input() - public set cssClass(cssClass: string | string[]) { + public set cssClass(cssClass: string | string[] | undefined) { this.activity.cssClass = cssClass; } diff --git a/projects/scion/workbench/src/lib/dom.util.ts b/projects/scion/workbench/src/lib/dom.util.ts index 01123be5a..8dabc8ae6 100644 --- a/projects/scion/workbench/src/lib/dom.util.ts +++ b/projects/scion/workbench/src/lib/dom.util.ts @@ -17,7 +17,8 @@ export function createElement(tag: string, options: ElementCreateOptions): HTMLE /** * Applies the given style(s) to the given element. * - * To unset a style property provide `null` as its value. + * Specify styles to be modified by passing a dictionary containing CSS property names (hyphen case). + * To remove a style, set its value to `null`. */ export function setStyle(element: HTMLElement | ElementRef, style: { [style: string]: any | null }): void { const target = coerceElement(element); diff --git a/projects/scion/workbench/src/lib/layout/parts-layout.component.ts b/projects/scion/workbench/src/lib/layout/parts-layout.component.ts index c798551ac..50003a7d1 100644 --- a/projects/scion/workbench/src/lib/layout/parts-layout.component.ts +++ b/projects/scion/workbench/src/lib/layout/parts-layout.component.ts @@ -55,12 +55,12 @@ export class PartsLayoutComponent implements OnInit, OnDestroy { /** * Reference to the root part of the layout. Is only set if the layout root is of the type {@link MPart}. */ - public rootPart!: WbComponentPortal | null; + public rootPart: WbComponentPortal | null = null; /** * Reference to the root node of the layout. Is only set if the layout root is of the type {@link MTreeNode}. */ - public rootNode!: MTreeNode | null; + public rootNode: MTreeNode | null = null; constructor(private _viewPartRegistry: WorkbenchViewPartRegistry, private _layoutService: WorkbenchLayoutService, @@ -74,7 +74,7 @@ export class PartsLayoutComponent implements OnInit, OnDestroy { pairwise(), takeUntil(this._destroy$), ) - .subscribe(([prevLayout, curLayout]: [PartsLayout, PartsLayout]) => { + .subscribe(([prevLayout, curLayout]: [PartsLayout | null, PartsLayout]) => { // Determine the parts which are about to be re-parented in the DOM, and detach them temporarily from the Angular component tree, // so that they are not destroyed when being re-parented. const reattachFn = this.detachPortalsToBeMovedFromComponentTree(prevLayout, curLayout); @@ -98,22 +98,29 @@ export class PartsLayoutComponent implements OnInit, OnDestroy { * * Returns a function to attach the detached portals to the Angular component tree anew. */ - private detachPortalsToBeMovedFromComponentTree(prevLayout: PartsLayout, newLayout: PartsLayout): () => void { + private detachPortalsToBeMovedFromComponentTree(prevLayout: PartsLayout | null, currLayout: PartsLayout): () => void { if (!prevLayout) { return noop; // no current layout in place, so no portal is moved } // For each part, compute its path in the layout tree. The path is the list of all parent node ids. - const newPartPaths: Map = newLayout.parts.reduce((acc, part) => { + const currPartPathsByPartId: Map = currLayout.parts.reduce((acc, part) => { return acc.set(part.partId, part.getPath()); }, new Map()); // Determine parts to be moved in the layout tree. const partPortalsToBeMoved: WbComponentPortal[] = prevLayout.parts.reduce((acc, prevPart) => { - const prevPartPath = prevPart.getPath(); - const newPartPath = newPartPaths.get(prevPart.partId)!; + const prevPartPath: string[] = prevPart.getPath(); + const currPartPath: string[] | undefined = currPartPathsByPartId.get(prevPart.partId); - if (!Arrays.isEqual(newPartPath, prevPartPath)) { + // If the part is no longer contained in the layout but is still present in the parts registry, detach it so that its views + // will not get destroyed when applying the new layout, i.e., when moving all its views to another part. + if (!currPartPath) { + return acc.concat(this._viewPartRegistry.getElseNull(prevPart.partId)?.portal || []); + } + + // If the part is moved in the layout, detach it to not destroy its views. + if (!Arrays.isEqual(currPartPath, prevPartPath)) { return acc.concat(this._viewPartRegistry.getElseThrow(prevPart.partId).portal); } return acc; diff --git a/projects/scion/workbench/src/lib/layout/parts-layout.ts b/projects/scion/workbench/src/lib/layout/parts-layout.ts index a85fec9e9..105f576c3 100644 --- a/projects/scion/workbench/src/lib/layout/parts-layout.ts +++ b/projects/scion/workbench/src/lib/layout/parts-layout.ts @@ -208,7 +208,7 @@ export class PartsLayout { */ public serialize(options: { nullIfEmpty: false }): string; public serialize(options?: { nullIfEmpty?: true } | {}): string | null; - public serialize(options?: { nullIfEmpty?: boolean }): string | null{ + public serialize(options?: { nullIfEmpty?: boolean }): string | null { if ((options?.nullIfEmpty ?? true) && this._root instanceof MPart && this._root.isEmpty()) { return null; } @@ -264,13 +264,12 @@ export class PartsLayout { private _removePart(partId: string): this { assertNotNullish(partId, {orElseThrow: () => Error(`[PartsLayoutError] PartId must not be 'null' or 'undefined'.`)}); - const part = this._findPart(partId, {orElseThrow: true}); - // The last part is never removed. if (this.parts.length === 1) { return this; } + const part = this._findPart(partId, {orElseThrow: true}); const partIndex = this.parts.indexOf(part); const siblingElement = (part.parent!.child1 === part ? part.parent!.child2 : part.parent!.child1); if (!part.parent!.parent) { diff --git a/projects/scion/workbench/src/lib/layout/workbench-layout.service.ts b/projects/scion/workbench/src/lib/layout/workbench-layout.service.ts index e797a1cff..f4f436fcd 100644 --- a/projects/scion/workbench/src/lib/layout/workbench-layout.service.ts +++ b/projects/scion/workbench/src/lib/layout/workbench-layout.service.ts @@ -47,9 +47,9 @@ export class WorkbenchLayoutService { */ public readonly layout$: Observable = this._layoutChange$ .pipe( - startWith(null! as PartsLayout), - map(() => this.layout), - filter((layout): layout is PartsLayout => Boolean(layout)), + startWith(undefined as void), + map(() => this.layout), + filter((layout: PartsLayout | null): layout is PartsLayout => layout !== null), ); constructor(viewDragService: ViewDragService, private _zone: NgZone) { @@ -95,7 +95,7 @@ export class WorkbenchLayoutService { * Returns a reference to current {@link PartsLayout}, if any. Is `null` until the initial navigation is performed. */ public get layout(): PartsLayout | null { - return this._layout || null; + return this._layout; } /** diff --git a/projects/scion/workbench/src/lib/logging/logger.ts b/projects/scion/workbench/src/lib/logging/logger.ts index 2eb7eb464..0916bcdb3 100644 --- a/projects/scion/workbench/src/lib/logging/logger.ts +++ b/projects/scion/workbench/src/lib/logging/logger.ts @@ -12,7 +12,9 @@ import { Inject, Injectable, OnDestroy, Optional } from '@angular/core'; import { NavigationStart, ParamMap, Router, RouterEvent } from '@angular/router'; import { filter, map, startWith, takeUntil } from 'rxjs/operators'; import { Observable, Subject } from 'rxjs'; -import { LogAppender, LogEvent, LoggerName, LogLevel, LogLevelStrings } from './logging.model'; +import { LogAppender, LogEvent, LoggerName, LogLevel } from './logging.model'; + +type LogLevelStrings = keyof typeof LogLevel; /** * Logger used by the workbench to log messages. @@ -130,8 +132,8 @@ export class ɵLogger implements Logger, OnDestroy { // tslint:disable-line:cla startWith(router.url), filter((event): event is NavigationStart => event instanceof NavigationStart), map(routerEvent => router.parseUrl(routerEvent.url).queryParamMap), - map(queryParamMap => queryParamMap.get('loglevel')?.toUpperCase() as LogLevelStrings), - map(logLevelRaw => logLevelRaw ? LogLevel[logLevelRaw] : undefined), + map(queryParamMap => queryParamMap.get('loglevel')?.toUpperCase() as LogLevelStrings | undefined), + map(logLevelString => logLevelString ? LogLevel[logLevelString] : undefined), ); } diff --git a/projects/scion/workbench/src/lib/logging/logging.model.ts b/projects/scion/workbench/src/lib/logging/logging.model.ts index 7e6561e98..5544fc671 100644 --- a/projects/scion/workbench/src/lib/logging/logging.model.ts +++ b/projects/scion/workbench/src/lib/logging/logging.model.ts @@ -16,7 +16,6 @@ import { Injectable } from '@angular/core'; export enum LogLevel { DEBUG, INFO, WARN, ERROR } -export type LogLevelStrings = keyof typeof LogLevel; /** * Provides information about a message to be logged. diff --git a/projects/scion/workbench/src/lib/message-box/message-box.component.ts b/projects/scion/workbench/src/lib/message-box/message-box.component.ts index 0d2bdf04d..9bdbd958e 100644 --- a/projects/scion/workbench/src/lib/message-box/message-box.component.ts +++ b/projects/scion/workbench/src/lib/message-box/message-box.component.ts @@ -36,16 +36,16 @@ export class MessageBoxComponent implements OnInit, OnDestroy { private _cancelBlinkTimer$ = new Subject(); private _activeActionButton: HTMLElement | undefined; - public portal!: ComponentPortal; + public portal: ComponentPortal | undefined; @HostBinding('style.transform') - public transform!: string; + public transform: string | undefined; @HostBinding('class.blinking') - public blinking!: boolean; + public blinking = false; @HostBinding('class.text-selectable') - public textSelectable!: boolean; + public textSelectable = false; @HostBinding('attr.tabindex') public tabindex = -1; diff --git a/projects/scion/workbench/src/lib/message-box/message-box.service.ts b/projects/scion/workbench/src/lib/message-box/message-box.service.ts index 7abcf3913..8642a2e70 100644 --- a/projects/scion/workbench/src/lib/message-box/message-box.service.ts +++ b/projects/scion/workbench/src/lib/message-box/message-box.service.ts @@ -46,7 +46,7 @@ export class MessageBoxService implements OnDestroy { private _destroy$ = new Subject(); private _messageBoxes$ = new BehaviorSubject<ɵMessageBox[]>([]); - private _messageBoxServiceHierarchy: MessageBoxService[]; + private _messageBoxServiceHierarchy: [MessageBoxService, ...MessageBoxService[]]; // minItems: 1 constructor(@Optional() @SkipSelf() private _parentMessageBoxService: MessageBoxService, @Optional() private _view: WorkbenchView, @@ -133,13 +133,13 @@ export class MessageBoxService implements OnDestroy { /** * Returns the message box service hierarchy. */ - private computeMessageBoxServiceHierarchy(): MessageBoxService[] { + private computeMessageBoxServiceHierarchy(): [MessageBoxService, ...MessageBoxService[]] { const hierarchy: MessageBoxService[] = []; let current: MessageBoxService = this; do { hierarchy.push(current); } while ((current = current._parentMessageBoxService)); // tslint:disable-line:no-conditional-assignment - return hierarchy; + return hierarchy as [MessageBoxService, ...MessageBoxService[]]; } /** diff --git a/projects/scion/workbench/src/lib/message-box/move.directive.ts b/projects/scion/workbench/src/lib/message-box/move.directive.ts index e32f1d48e..c2cf5275a 100644 --- a/projects/scion/workbench/src/lib/message-box/move.directive.ts +++ b/projects/scion/workbench/src/lib/message-box/move.directive.ts @@ -20,8 +20,8 @@ import { first, takeUntil } from 'rxjs/operators'; export class MoveDirective implements OnDestroy { private _destroy$ = new Subject(); - private _x!: number; - private _y!: number; + private _x = 0; + private _y = 0; @Output() public wbMoveStart = new EventEmitter(); diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/initialization/microfrontend-platform-initializer.service.ts b/projects/scion/workbench/src/lib/microfrontend-platform/initialization/microfrontend-platform-initializer.service.ts index 37f087289..22a46377f 100644 --- a/projects/scion/workbench/src/lib/microfrontend-platform/initialization/microfrontend-platform-initializer.service.ts +++ b/projects/scion/workbench/src/lib/microfrontend-platform/initialization/microfrontend-platform-initializer.service.ts @@ -25,7 +25,7 @@ import { LogDelegate } from './log-delegate.service'; @Injectable() export class MicrofrontendPlatformInitializer implements WorkbenchInitializer, OnDestroy { - private _hostAppConfigIfAbsent!: ApplicationConfig | null; + private _syntheticHostAppConfig: ApplicationConfig | null = null; constructor(private _workbenchModuleConfig: WorkbenchModuleConfig, private _microfrontendPlatformConfigLoader: MicrofrontendPlatformConfigLoader, @@ -44,13 +44,13 @@ export class MicrofrontendPlatformInitializer implements WorkbenchInitializer, O const microfrontendPlatformConfig = await this._microfrontendPlatformConfigLoader.load(); // Create a synthetic app config for the workbench host if not passed in the app list. - this._hostAppConfigIfAbsent = this.createHostAppConfigIfAbsent(microfrontendPlatformConfig); + this._syntheticHostAppConfig = this.createSyntheticHostAppConfigIfAbsent(microfrontendPlatformConfig); // Assemble the effective microfrontend platform config. - const effectiveHostSymbolicName = this._hostAppConfigIfAbsent?.symbolicName || this._workbenchModuleConfig.microfrontends?.platformHost?.symbolicName!; + const effectiveHostSymbolicName = this._syntheticHostAppConfig?.symbolicName || this._workbenchModuleConfig.microfrontends!.platformHost!.symbolicName!; const effectiveMicrofrontendPlatformConfig: PlatformConfig = { ...microfrontendPlatformConfig, - apps: microfrontendPlatformConfig.apps.concat(this._hostAppConfigIfAbsent || []), + apps: microfrontendPlatformConfig.apps.concat(this._syntheticHostAppConfig || []), }; // Enable the API to register intentions at runtime, required for microfrontend routing to register the wildcard `view` intention. @@ -93,17 +93,17 @@ export class MicrofrontendPlatformInitializer implements WorkbenchInitializer, O * Regarding the manifest URL, we create an object URL for the manifest object as passed to the workbench module config, * or use an empty manifest if absent. */ - private createHostAppConfigIfAbsent(microfrontendPlatformConfig: PlatformConfig): ApplicationConfig | null { + private createSyntheticHostAppConfigIfAbsent(microfrontendPlatformConfig: PlatformConfig): ApplicationConfig | null { // Do nothing if the host config is present in the app list. - const hostSymbolicName = this._workbenchModuleConfig.microfrontends?.platformHost?.symbolicName; + const hostSymbolicName = this._workbenchModuleConfig.microfrontends!.platformHost?.symbolicName; if (microfrontendPlatformConfig.apps.some(app => app.symbolicName === hostSymbolicName)) { return null; } // Get the manifest object passed to the workbench module config, or create an empty manifest if absent. - const hostManifest: ApplicationManifest = this._workbenchModuleConfig.microfrontends?.platformHost?.manifest || {name: 'Workbench Host'}; + const hostManifest: ApplicationManifest = this._workbenchModuleConfig.microfrontends!.platformHost?.manifest || {name: 'Workbench Host'}; return { - symbolicName: this._workbenchModuleConfig.microfrontends?.platformHost?.symbolicName || 'workbench-host', + symbolicName: this._workbenchModuleConfig.microfrontends!.platformHost?.symbolicName || 'workbench-host', manifestUrl: URL.createObjectURL(new Blob([JSON.stringify(hostManifest)], {type: 'application/json'})), }; } @@ -121,6 +121,6 @@ export class MicrofrontendPlatformInitializer implements WorkbenchInitializer, O } public ngOnDestroy(): void { - this._hostAppConfigIfAbsent && URL.revokeObjectURL(this._hostAppConfigIfAbsent.manifestUrl); + this._syntheticHostAppConfig && URL.revokeObjectURL(this._syntheticHostAppConfig.manifestUrl); } } diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-popup/microfrontend-popup-command-handler.service.ts b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-popup/microfrontend-popup-command-handler.service.ts index d832bf341..73f3bed5d 100644 --- a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-popup/microfrontend-popup-command-handler.service.ts +++ b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-popup/microfrontend-popup-command-handler.service.ts @@ -24,6 +24,7 @@ import { ROUTER_OUTLET_NAME } from '../../workbench.constants'; import { Router } from '@angular/router'; import { WbRouterOutletComponent } from '../../routing/wb-router-outlet.component'; import { RouterUtils } from '../../routing/router.util'; +import { Commands } from '../../routing/workbench-router.service'; /** * Handles microfrontend popup commands, instructing the Workbench {@link PopupService} to navigate to the microfrontend of a given popup capability. @@ -40,7 +41,7 @@ export class MicrofrontendPopupCommandHandler { private _router: Router, private _zone: NgZone, {symbolicName: hostAppSymbolicName}: MicroApplicationConfig) { - this._messageClient.onMessage<ɵWorkbenchPopupCommand>(ɵWorkbenchCommands.popup, async (message) => { + this._messageClient.onMessage<ɵWorkbenchPopupCommand>(ɵWorkbenchCommands.popup, async message => { const command = message.body!; this._logger.debug(() => 'Handling microfrontend popup command', LoggerNames.MICROFRONTEND, command); @@ -165,8 +166,8 @@ export class MicrofrontendPopupCommandHandler { // Replace placeholders with the values of the qualifier and params, if any. path = RouterUtils.substituteNamedParameters(path, extras.params); - const outletCommands = path !== null ? RouterUtils.segmentsToCommands(RouterUtils.parsePath(this._router, path)) : null; - const commands = [{outlets: {[extras.outletName]: outletCommands}}]; + const outletCommands: Commands | null = (path !== null ? RouterUtils.segmentsToCommands(RouterUtils.parsePath(this._router, path)) : null); + const commands: Commands = [{outlets: {[extras.outletName]: outletCommands}}]; return this._router.navigate(commands, {skipLocationChange: true, queryParamsHandling: 'merge'}); } } diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-view/microfrontend-view.component.ts b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-view/microfrontend-view.component.ts index b100dd9e2..0aa1725a0 100644 --- a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-view/microfrontend-view.component.ts +++ b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-view/microfrontend-view.component.ts @@ -255,9 +255,6 @@ export class MicrofrontendViewComponent implements OnInit, OnDestroy, WbBeforeDe } } -/** - * @internal - */ interface WorkbenchViewCapabilityWithParams { capability?: WorkbenchViewCapability; params: Params; diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/workbench-microfrontend-support.ts b/projects/scion/workbench/src/lib/microfrontend-platform/workbench-microfrontend-support.ts index fc87e80ab..77f5b5674 100644 --- a/projects/scion/workbench/src/lib/microfrontend-platform/workbench-microfrontend-support.ts +++ b/projects/scion/workbench/src/lib/microfrontend-platform/workbench-microfrontend-support.ts @@ -79,7 +79,7 @@ class MicrofrontendPlatformModuleConfigLoader implements MicrofrontendPlatformCo } public load(): Promise { - return Promise.resolve(this._workbenchModuleConfig.microfrontends?.platform as PlatformConfig); + return Promise.resolve(this._workbenchModuleConfig.microfrontends!.platform as PlatformConfig); } } diff --git a/projects/scion/workbench/src/lib/notification/notification.component.ts b/projects/scion/workbench/src/lib/notification/notification.component.ts index 447af44de..739d37e52 100644 --- a/projects/scion/workbench/src/lib/notification/notification.component.ts +++ b/projects/scion/workbench/src/lib/notification/notification.component.ts @@ -31,7 +31,7 @@ export class NotificationComponent implements OnChanges, OnDestroy { private _destroy$ = new Subject(); private _closeTimerChange$ = new Subject(); - public portal!: ComponentPortal; + public portal: ComponentPortal | undefined; @Input() public notification!: ɵNotification; diff --git a/projects/scion/workbench/src/lib/popup/popup.component.ts b/projects/scion/workbench/src/lib/popup/popup.component.ts index 1123eed2f..15406ddfb 100644 --- a/projects/scion/workbench/src/lib/popup/popup.component.ts +++ b/projects/scion/workbench/src/lib/popup/popup.component.ts @@ -28,33 +28,33 @@ export class PopupComponent { public portal: ComponentPortal; @HostBinding('style.width') - public get popupWidth(): string | null { - return this._popupConfig.size?.width ?? null; + public get popupWidth(): string | undefined { + return this._popupConfig.size?.width; } @HostBinding('style.min-width') - public get popupMinWidth(): string | null { - return this._popupConfig.size?.minWidth ?? null; + public get popupMinWidth(): string | undefined { + return this._popupConfig.size?.minWidth; } @HostBinding('style.max-width') - public get popupMaxWidth(): string | null { - return this._popupConfig.size?.maxWidth ?? null; + public get popupMaxWidth(): string | undefined { + return this._popupConfig.size?.maxWidth; } @HostBinding('style.height') - public get popupHeight(): string | null { - return this._popupConfig.size?.height ?? null; + public get popupHeight(): string | undefined { + return this._popupConfig.size?.height; } @HostBinding('style.min-height') - public get popupMinHeight(): string | null { - return this._popupConfig.size?.minHeight ?? null; + public get popupMinHeight(): string | undefined { + return this._popupConfig.size?.minHeight; } @HostBinding('style.max-height') - public get popupMaxHeight(): string | null { - return this._popupConfig.size?.maxHeight ?? null; + public get popupMaxHeight(): string | undefined { + return this._popupConfig.size?.maxHeight; } constructor(private _popupConfig: PopupConfig, injector: Injector) { diff --git a/projects/scion/workbench/src/lib/popup/popup.config.ts b/projects/scion/workbench/src/lib/popup/popup.config.ts index 46efed0fa..b1c742d3b 100644 --- a/projects/scion/workbench/src/lib/popup/popup.config.ts +++ b/projects/scion/workbench/src/lib/popup/popup.config.ts @@ -29,14 +29,14 @@ export abstract class PopupConfig { * * The align setting can be used to further control where the popup opens relative to its anchor. */ - public readonly anchor!: ElementRef | Element | PopupOrigin | Observable; + public abstract readonly anchor: ElementRef | Element | PopupOrigin | Observable; /** * Specifies the component to display in the popup. * * In the component, you can inject the popup handle {@link Popup} to interact with the popup, such as to close it * or obtain its input data. */ - public readonly component!: Type; + public abstract readonly component: Type; /** * Instructs Angular how to construct the component. In most cases, construct options need not to be set. */ @@ -179,12 +179,12 @@ export abstract class Popup { /** * Input data as passed by the popup opener when opened the popup, or `undefined` if not passed. */ - public readonly input: T | undefined; + public abstract readonly input: T | undefined; /** * Preferred popup size as specified by the popup opener, or `undefined` if not set. */ - public readonly size: PopupSize | undefined; + public abstract readonly size: PopupSize | undefined; /** * Closes the popup. Optionally, pass a result to the popup opener. diff --git a/projects/scion/workbench/src/lib/portal/wb-component-portal.ts b/projects/scion/workbench/src/lib/portal/wb-component-portal.ts index 4d1e2b55f..460c6843f 100644 --- a/projects/scion/workbench/src/lib/portal/wb-component-portal.ts +++ b/projects/scion/workbench/src/lib/portal/wb-component-portal.ts @@ -81,7 +81,7 @@ export class WbComponentPortal { this.detachFromComponentTree(); this._viewContainerRef = viewContainerRef; - this._portalInjector.elementInjector = this.viewContainerRef ? this.viewContainerRef.injector : null; + this._portalInjector.elementInjector = this.viewContainerRef ? this.viewContainerRef.injector : Injector.NULL; this.attachToComponentTree(); } @@ -129,19 +129,22 @@ export class WbComponentPortal { * Destroys the component instance and all of the data structures associated with it. */ public destroy(): void { - this._componentRef!.destroy(); + this.componentRef.destroy(); } public get componentRef(): ComponentRef { - return this._componentRef!; + if (!this._componentRef) { + throw Error('[PortalError] Illegal state: Portal already destroyed.'); + } + return this._componentRef; } - public get viewContainerRef(): ViewContainerRef { - return this._viewContainerRef!; + public get viewContainerRef(): ViewContainerRef | null { + return this._viewContainerRef; } public get isAttached(): boolean { - return this._viewContainerRef && this._viewContainerRef.indexOf(this._componentRef!.hostView) > -1 || false; + return this._componentRef && this._viewContainerRef && this._viewContainerRef.indexOf(this._componentRef.hostView) > -1 || false; } public get isDestroyed(): boolean { @@ -182,7 +185,7 @@ export interface PortalConfig { */ class WbPortalInjector implements Injector { - constructor(private _customTokens: WeakMap, public elementInjector: Injector | null) { + constructor(private _customTokens: WeakMap, public elementInjector: Injector) { } public get(token: Type | InjectionToken, notFoundValue?: T, flags?: InjectFlags): T { @@ -208,6 +211,6 @@ class WbPortalInjector implements Injector { * - el1.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) -> do not check the module * - mod2.injector.get(token, default) */ - return (this.elementInjector || Injector.NULL).get(token, notFoundValue, flags); + return this.elementInjector.get(token, notFoundValue, flags); } } diff --git a/projects/scion/workbench/src/lib/portal/wb-portal-outlet.component.ts b/projects/scion/workbench/src/lib/portal/wb-portal-outlet.component.ts index 152766b0d..a15253ce8 100644 --- a/projects/scion/workbench/src/lib/portal/wb-portal-outlet.component.ts +++ b/projects/scion/workbench/src/lib/portal/wb-portal-outlet.component.ts @@ -18,7 +18,7 @@ import { WbComponentPortal } from './wb-component-portal'; }) export class WbPortalOutletComponent implements OnDestroy { - private _portal!: WbComponentPortal; + private _portal: WbComponentPortal | undefined; @ViewChild(TemplateRef, {read: ViewContainerRef, static: true}) private _viewContainerRef!: ViewContainerRef; diff --git a/projects/scion/workbench/src/lib/routing/wb-router-link.directive.ts b/projects/scion/workbench/src/lib/routing/wb-router-link.directive.ts index 628647bb5..03b3734b8 100644 --- a/projects/scion/workbench/src/lib/routing/wb-router-link.directive.ts +++ b/projects/scion/workbench/src/lib/routing/wb-router-link.directive.ts @@ -78,7 +78,7 @@ export class WbRouterLinkDirective { export class WbRouterLinkWithHrefDirective extends WbRouterLinkDirective implements OnChanges { @HostBinding('href') - public href!: string; + public href: string | undefined; constructor(private _router: Router, private _locationStrategy: LocationStrategy, diff --git a/projects/scion/workbench/src/lib/routing/workbench-auxiliary-routes-registrator.service.ts b/projects/scion/workbench/src/lib/routing/workbench-auxiliary-routes-registrator.service.ts index 25346aaef..6e140ad61 100644 --- a/projects/scion/workbench/src/lib/routing/workbench-auxiliary-routes-registrator.service.ts +++ b/projects/scion/workbench/src/lib/routing/workbench-auxiliary-routes-registrator.service.ts @@ -42,7 +42,7 @@ export class WorkbenchAuxiliaryRoutesRegistrator { })); this.replaceRouterConfig([ - ...this._router.config.filter(route => route.outlet === undefined || !outletNames.has(route.outlet)), // all registered routes, except auxiliary routes for any of the given outlets + ...this._router.config.filter(route => !route.outlet || !outletNames.has(route.outlet)), // all registered routes, except auxiliary routes for any of the given outlets ...outletAuxRoutes, ]); @@ -58,7 +58,7 @@ export class WorkbenchAuxiliaryRoutesRegistrator { return; } - this.replaceRouterConfig(this._router.config.filter(route => route.outlet === undefined || !outletNames.has(route.outlet))); + this.replaceRouterConfig(this._router.config.filter(route => !route.outlet || !outletNames.has(route.outlet))); } /** diff --git a/projects/scion/workbench/src/lib/routing/workbench-router.service.ts b/projects/scion/workbench/src/lib/routing/workbench-router.service.ts index 4574fdbf4..7d2c8bdd0 100644 --- a/projects/scion/workbench/src/lib/routing/workbench-router.service.ts +++ b/projects/scion/workbench/src/lib/routing/workbench-router.service.ts @@ -55,7 +55,7 @@ export class WorkbenchRouter { * * @see WbRouterLinkDirective */ - public async navigate(commandList: any[], extras: WbNavigationExtras = {}): Promise { + public async navigate(commandList: Commands, extras: WbNavigationExtras = {}): Promise { const commands = this.normalizeCommands(commandList, extras.relativeTo); if (extras.closeIfPresent) { @@ -208,7 +208,7 @@ export class WorkbenchRouter { * * @internal */ - public resolvePresentViewIds(commands: any[]): string[] { + public resolvePresentViewIds(commands: Commands): string[] { const serializeCommands = this.serializeCommands(commands); const urlTree = this._router.parseUrl(this._router.url); const urlSegmentGroups = urlTree.root.children; @@ -378,7 +378,7 @@ export interface WorkbenchNavigationContext { } /** - * An array of URL fragments with which to construct the target URL. + * An array of URL fragments with which to construct a view's URL. * * If the path is static, can be the literal URL string. For a dynamic path, pass an array of path segments, * followed by the parameters for each segment. @@ -388,8 +388,6 @@ export interface WorkbenchNavigationContext { * available in {@link ActivatedRoute#params}. * * Example command array with 'details' as matrix parameter: `['user', userName, {details: true}]`, - * - * @internal */ export type Commands = any[]; diff --git a/projects/scion/workbench/src/lib/routing/workbench-url-observer.service.ts b/projects/scion/workbench/src/lib/routing/workbench-url-observer.service.ts index 08c627f87..565b05cbf 100644 --- a/projects/scion/workbench/src/lib/routing/workbench-url-observer.service.ts +++ b/projects/scion/workbench/src/lib/routing/workbench-url-observer.service.ts @@ -104,7 +104,7 @@ export class WorkbenchUrlObserver implements OnDestroy { */ private computeWorkbenchLayoutDiff(url: string): WorkbenchNavigationContext { const urlTree = this._router.parseUrl(url); - const serializedPartsLayout = urlTree.queryParamMap.get(PARTS_LAYOUT_QUERY_PARAM)!; + const serializedPartsLayout = urlTree.queryParamMap.get(PARTS_LAYOUT_QUERY_PARAM) ?? undefined; const partsLayout = this._partsLayoutFactory.create(serializedPartsLayout); return { partsLayout, @@ -144,9 +144,9 @@ export class WorkbenchUrlObserver implements OnDestroy { * Invoke this method after navigation failure or cancellation. The navigation is cancelled when guards perform a redirect or reject navigation. */ private undoWorkbenchLayoutDiffer(): void { - const preNavigateUrl = this._router.url; // Browser URL is only updated after successful navigation - const preNavigateLayout = this._layoutService.layout!; // Layout in `LayoutService` is only updated after successful navigation - this._workbenchLayoutDiffer.diff(this._router.parseUrl(preNavigateUrl), preNavigateLayout); + const prevNavigateUrl = this._router.url; // Browser URL is only updated after successful navigation + const prevNavigateLayout = this._layoutService.layout; // Layout in `LayoutService` is only updated after successful navigation + this._workbenchLayoutDiffer.diff(this._router.parseUrl(prevNavigateUrl), prevNavigateLayout ?? undefined); } /** @@ -277,7 +277,7 @@ export class WorkbenchUrlObserver implements OnDestroy { this._router.events .pipe( filter((event): event is RouterEvent => event instanceof RouterEvent), - takeUntil(this._destroy$) + takeUntil(this._destroy$), ) .subscribe((event: RouterEvent) => { if (event instanceof NavigationStart) { diff --git a/projects/scion/workbench/src/lib/sash.directive.ts b/projects/scion/workbench/src/lib/sash.directive.ts index 7ddded90b..d19125764 100644 --- a/projects/scion/workbench/src/lib/sash.directive.ts +++ b/projects/scion/workbench/src/lib/sash.directive.ts @@ -22,7 +22,7 @@ import { first, takeUntil } from 'rxjs/operators'; export class SashDirective implements OnDestroy, OnChanges { private _destroy$ = new Subject(); - private _mousePosition: number | null = null; + private _mousePosition: number | undefined; @Input('wbSash') // tslint:disable-line:no-input-rename public wbDirection: 'vertical' | 'horizontal' = 'horizontal'; @@ -40,7 +40,7 @@ export class SashDirective implements OnDestroy, OnChanges { public wbSashReset = new EventEmitter(false); @HostBinding('style.cursor') - public cursor!: string; + public cursor: string | undefined; constructor(@Inject(DOCUMENT) private _document: any) { } @@ -86,7 +86,7 @@ export class SashDirective implements OnDestroy, OnChanges { ) .subscribe(() => { mousemoveListener.unsubscribe(); - this._mousePosition = null; + this._mousePosition = undefined; this._document.body.style.cursor = oldCursor; this.wbSashEnd.next(); }); diff --git a/projects/scion/workbench/src/lib/view-dnd/view-drag.service.ts b/projects/scion/workbench/src/lib/view-dnd/view-drag.service.ts index b13bc28e7..2bec83d4e 100644 --- a/projects/scion/workbench/src/lib/view-dnd/view-drag.service.ts +++ b/projects/scion/workbench/src/lib/view-dnd/view-drag.service.ts @@ -49,7 +49,7 @@ export class ViewDragService { /** * Indicates if this app is the drag source for the ongoing drag operation (if any). */ - private _isDragSource!: boolean; + private _isDragSource = false; constructor(private _broadcastChannel: BroadcastChannelService, private _zone: NgZone) { fromEvent(window, 'unload') @@ -234,6 +234,7 @@ export interface ViewMoveEvent { target: { /** * Part to which to add the view. If using a {@link region} other than 'center', that part is used as a reference for creating a new part. + * Set the part to `null` if moving the view to a blank window. */ partId: string | null; /** diff --git a/projects/scion/workbench/src/lib/view-dnd/view-tab-drag-image-renderer.service.ts b/projects/scion/workbench/src/lib/view-dnd/view-tab-drag-image-renderer.service.ts index 205bd5c8c..17984c12f 100644 --- a/projects/scion/workbench/src/lib/view-dnd/view-tab-drag-image-renderer.service.ts +++ b/projects/scion/workbench/src/lib/view-dnd/view-tab-drag-image-renderer.service.ts @@ -34,9 +34,9 @@ export type ConstrainFn = (rect: ViewDragImageRect) => ViewDragImageRect; export class ViewTabDragImageRenderer implements OnDestroy { private _destroy$ = new Subject(); - private _viewDragImagePortalOutlet!: DomPortalOutlet | null; - private _constrainDragImageRectFn!: ((rect: ViewDragImageRect) => ViewDragImageRect) | null; - private _dragData!: ViewDragData | null; + private _viewDragImagePortalOutlet: DomPortalOutlet | null = null; + private _constrainDragImageRectFn: ((rect: ViewDragImageRect) => ViewDragImageRect) | null = null; + private _dragData: ViewDragData | null = null; constructor(private _viewDragService: ViewDragService, private _workbenchModuleConfig: WorkbenchModuleConfig, diff --git a/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.service.ts b/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.service.ts index 37aff916a..347a2af83 100644 --- a/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.service.ts +++ b/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.service.ts @@ -18,7 +18,7 @@ import { filter, mapTo, switchMap, take, takeUntil, tap } from 'rxjs/operators'; import { fromEvent, merge, Observable, Subject, TeardownLogic } from 'rxjs'; import { coerceElement } from '@angular/cdk/coercion'; import { TEXT, TextComponent } from '../view-context-menu/text.component'; -import { WorkbenchModuleConfig } from '../../workbench-module-config'; +import { MenuItemConfig, WorkbenchModuleConfig } from '../../workbench-module-config'; import { WorkbenchService } from '../../workbench.service'; import { Arrays } from '@scion/toolkit/util'; import { filterArray } from '@scion/toolkit/operators'; @@ -135,8 +135,8 @@ export class ViewMenuService { } private registerCloseViewMenuItem(): void { - const defaults = {visible: true, text: 'Close tab', group: 'close', accelerator: ['ctrl', 'k']}; - const appConfig = this._workbenchModuleConfig.viewMenuItems?.close; + const defaults: MenuItemConfig = {visible: true, text: 'Close tab', group: 'close', accelerator: ['ctrl', 'k']}; + const appConfig: MenuItemConfig | undefined = this._workbenchModuleConfig.viewMenuItems?.close; const config = {...defaults, ...appConfig}; config.visible && this._workbench.registerViewMenuItem((view: WorkbenchView): WorkbenchMenuItem => { @@ -154,8 +154,8 @@ export class ViewMenuService { } private registerCloseOtherViewsMenuItem(): void { - const defaults = {visible: true, text: 'Close other tabs', group: 'close', accelerator: ['ctrl', 'shift', 'k']}; - const appConfig = this._workbenchModuleConfig.viewMenuItems?.closeOthers; + const defaults: MenuItemConfig = {visible: true, text: 'Close other tabs', group: 'close', accelerator: ['ctrl', 'shift', 'k']}; + const appConfig: MenuItemConfig | undefined = this._workbenchModuleConfig.viewMenuItems?.closeOthers; const config = {...defaults, ...appConfig}; config.visible && this._workbench.registerViewMenuItem((view: WorkbenchView): WorkbenchMenuItem => { @@ -174,8 +174,8 @@ export class ViewMenuService { } private registerCloseAllViewsMenuItem(): void { - const defaults = {visible: true, text: 'Close all tabs', group: 'close', accelerator: ['ctrl', 'shift', 'alt', 'k']}; - const appConfig = this._workbenchModuleConfig.viewMenuItems?.closeAll; + const defaults: MenuItemConfig = {visible: true, text: 'Close all tabs', group: 'close', accelerator: ['ctrl', 'shift', 'alt', 'k']}; + const appConfig: MenuItemConfig | undefined = this._workbenchModuleConfig.viewMenuItems?.closeAll; const config = {...defaults, ...appConfig}; config.visible && this._workbench.registerViewMenuItem((view: WorkbenchView): WorkbenchMenuItem => { @@ -193,8 +193,8 @@ export class ViewMenuService { } private registerCloseViewsToTheRightMenuItem(): void { - const defaults = {visible: true, text: 'Close tabs to the right', group: 'close', accelerator: undefined}; - const appConfig = this._workbenchModuleConfig.viewMenuItems?.closeToTheRight; + const defaults: MenuItemConfig = {visible: true, text: 'Close tabs to the right', group: 'close'}; + const appConfig: MenuItemConfig | undefined = this._workbenchModuleConfig.viewMenuItems?.closeToTheRight; const config = {...defaults, ...appConfig}; config.visible && this._workbench.registerViewMenuItem((view: WorkbenchView): WorkbenchMenuItem => { @@ -213,8 +213,8 @@ export class ViewMenuService { } private registerCloseViewsToTheLeftMenuItem(): void { - const defaults = {visible: true, text: 'Close tabs to the left', group: 'close', accelerator: undefined}; - const appConfig = this._workbenchModuleConfig.viewMenuItems?.closeToTheLeft; + const defaults: MenuItemConfig = {visible: true, text: 'Close tabs to the left', group: 'close'}; + const appConfig: MenuItemConfig | undefined = this._workbenchModuleConfig.viewMenuItems?.closeToTheLeft; const config = {...defaults, ...appConfig}; config.visible && this._workbench.registerViewMenuItem((view: WorkbenchView): WorkbenchMenuItem => { @@ -233,8 +233,8 @@ export class ViewMenuService { } private registerMoveRightMenuItem(): void { - const defaults = {visible: true, text: 'Move right', group: 'move', accelerator: ['ctrl', 'alt', 'end']}; - const appConfig = this._workbenchModuleConfig.viewMenuItems?.moveRight; + const defaults: MenuItemConfig = {visible: true, text: 'Move right', group: 'move', accelerator: ['ctrl', 'alt', 'end']}; + const appConfig: MenuItemConfig | undefined = this._workbenchModuleConfig.viewMenuItems?.moveRight; const config = {...defaults, ...appConfig}; config.visible && this._workbench.registerViewMenuItem((view: WorkbenchView): WorkbenchMenuItem => { @@ -253,8 +253,8 @@ export class ViewMenuService { } private registerMoveLeftMenuItem(): void { - const defaults = {visible: true, text: 'Move left', group: 'move', accelerator: undefined}; - const appConfig = this._workbenchModuleConfig.viewMenuItems?.moveLeft; + const defaults: MenuItemConfig = {visible: true, text: 'Move left', group: 'move'}; + const appConfig: MenuItemConfig | undefined = this._workbenchModuleConfig.viewMenuItems?.moveLeft; const config = {...defaults, ...appConfig}; config.visible && this._workbench.registerViewMenuItem((view: WorkbenchView): WorkbenchMenuItem => { @@ -273,8 +273,8 @@ export class ViewMenuService { } private registerMoveUpMenuItem(): void { - const defaults = {visible: true, text: 'Move up', group: 'move', accelerator: undefined}; - const appConfig = this._workbenchModuleConfig.viewMenuItems?.moveUp; + const defaults: MenuItemConfig = {visible: true, text: 'Move up', group: 'move'}; + const appConfig: MenuItemConfig | undefined = this._workbenchModuleConfig.viewMenuItems?.moveUp; const config = {...defaults, ...appConfig}; config.visible && this._workbench.registerViewMenuItem((view: WorkbenchView): WorkbenchMenuItem => { @@ -293,8 +293,8 @@ export class ViewMenuService { } private registerMoveDownMenuItem(): void { - const defaults = {visible: true, text: 'Move down', group: 'move', accelerator: undefined}; - const appConfig = this._workbenchModuleConfig.viewMenuItems?.moveDown; + const defaults: MenuItemConfig = {visible: true, text: 'Move down', group: 'move'}; + const appConfig: MenuItemConfig | undefined = this._workbenchModuleConfig.viewMenuItems?.moveDown; const config = {...defaults, ...appConfig}; config.visible && this._workbench.registerViewMenuItem((view: WorkbenchView): WorkbenchMenuItem => { @@ -313,8 +313,8 @@ export class ViewMenuService { } private registerMoveToNewWindowMenuItem(): void { - const defaults = {visible: true, text: 'Move to new window', group: 'open', accelerator: undefined}; - const appConfig = this._workbenchModuleConfig.viewMenuItems?.moveBlank; + const defaults: MenuItemConfig = {visible: true, text: 'Move to new window', group: 'open'}; + const appConfig: MenuItemConfig | undefined = this._workbenchModuleConfig.viewMenuItems?.moveBlank; const config = {...defaults, ...appConfig}; config.visible && this._workbench.registerViewMenuItem((view: WorkbenchView): WorkbenchMenuItem => { diff --git a/projects/scion/workbench/src/lib/view-part/view-tab-content/view-tab-content.component.ts b/projects/scion/workbench/src/lib/view-part/view-tab-content/view-tab-content.component.ts index 2bbb9025a..64cf7740d 100644 --- a/projects/scion/workbench/src/lib/view-part/view-tab-content/view-tab-content.component.ts +++ b/projects/scion/workbench/src/lib/view-part/view-tab-content/view-tab-content.component.ts @@ -32,8 +32,8 @@ export class ViewTabContentComponent { } @HostBinding('class.blocked') - public get blocked(): boolean | null { - return this.view.blocked ?? null; + public get blocked(): boolean { + return this.view.blocked; } public onClose(event: Event): void { diff --git a/projects/scion/workbench/src/lib/view-part/view-tab/view-tab.component.ts b/projects/scion/workbench/src/lib/view-part/view-tab/view-tab.component.ts index 78864105a..74e6734d2 100644 --- a/projects/scion/workbench/src/lib/view-part/view-tab/view-tab.component.ts +++ b/projects/scion/workbench/src/lib/view-part/view-tab/view-tab.component.ts @@ -125,13 +125,15 @@ export class ViewTabComponent implements OnDestroy { @HostListener('dragstart', ['$event']) public onDragStart(event: DragEvent): void { this._viewPart.activateView(this.viewId).then(() => { - if (event.dataTransfer) { - event.dataTransfer.effectAllowed = 'move'; - event.dataTransfer.setData(VIEW_DRAG_TRANSFER_TYPE, this.view.viewId); - // Use an invisible
as the native drag image because the effective drag image is rendered by {ViewTabDragImageRenderer}. - event.dataTransfer.setDragImage(createElement('div', {style: {display: 'none'}}), 0, 0); + if (!event.dataTransfer) { + return; } + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.setData(VIEW_DRAG_TRANSFER_TYPE, this.view.viewId); + // Use an invisible
as the native drag image because the effective drag image is rendered by {ViewTabDragImageRenderer}. + event.dataTransfer.setDragImage(createElement('div', {style: {display: 'none'}}), 0, 0); + this._viewDragService.setViewDragData({ viewId: this.view.viewId, viewTitle: this.view.title ?? '', diff --git a/projects/scion/workbench/src/lib/view-part/workbench-view-part.registry.ts b/projects/scion/workbench/src/lib/view-part/workbench-view-part.registry.ts index 2ac86cb91..191b8b9e3 100644 --- a/projects/scion/workbench/src/lib/view-part/workbench-view-part.registry.ts +++ b/projects/scion/workbench/src/lib/view-part/workbench-view-part.registry.ts @@ -27,10 +27,17 @@ export class WorkbenchViewPartRegistry implements OnDestroy { * Destroys the viewpart of the given id and removes it from this registry. */ public remove(partId: string): void { - this._viewPartRegistry.get(partId)!.destroy(); + this.getElseThrow(partId).destroy(); this._viewPartRegistry.delete(partId); } + /** + * Returns the {@link WorkbenchViewPart} of the given identity, or `null` if not found. + */ + public getElseNull(partId: string): ɵWorkbenchViewPart | null { + return this._viewPartRegistry.get(partId) || null; + } + /** * Returns the {@link WorkbenchViewPart} of the given identity, or throws an Error if not found. */ diff --git "a/projects/scion/workbench/src/lib/view-part/\311\265workbench-view-part.model.ts" "b/projects/scion/workbench/src/lib/view-part/\311\265workbench-view-part.model.ts" index c52d5d8e8..8e94e8939 100644 --- "a/projects/scion/workbench/src/lib/view-part/\311\265workbench-view-part.model.ts" +++ "b/projects/scion/workbench/src/lib/view-part/\311\265workbench-view-part.model.ts" @@ -45,15 +45,15 @@ export class ɵWorkbenchViewPart implements WorkbenchViewPart { // tslint:disabl this._part = part; viewIdsChange && this.viewIds$.next(part.viewIds); - activeViewChange && this.activeViewId$.next(part.activeViewId!); + activeViewChange && this.activeViewId$.next(part.activeViewId ?? null); } public get viewIds(): string[] { return this._part.viewIds; } - public get activeViewId(): string { - return this._part.activeViewId!; + public get activeViewId(): string | null { + return this._part.activeViewId ?? null; } public registerViewPartAction(action: WorkbenchViewPartAction): Disposable { @@ -109,11 +109,11 @@ export class ɵWorkbenchViewPart implements WorkbenchViewPart { // tslint:disabl * Note: This instruction runs asynchronously via URL routing. */ public async activateSiblingView(): Promise { - if (this._markedForDestruction) { + if (this._markedForDestruction || !this.activeViewId) { return false; } - return this._wbRouter.ɵnavigate(layout => layout.activateAdjacentView(this.activeViewId)); + return this._wbRouter.ɵnavigate(layout => layout.activateAdjacentView(this.activeViewId!)); } public isActive(): boolean { diff --git a/projects/scion/workbench/src/lib/view/view-move-handler.service.ts b/projects/scion/workbench/src/lib/view/view-move-handler.service.ts index 2188cf42f..4edcbf16e 100644 --- a/projects/scion/workbench/src/lib/view/view-move-handler.service.ts +++ b/projects/scion/workbench/src/lib/view/view-move-handler.service.ts @@ -72,7 +72,8 @@ export class ViewMoveHandler implements OnDestroy { } private addView(event: ViewMoveEvent): void { - const addToNewViewPart = (event.target.region || 'center') !== 'center'; + const region = event.target.region || 'center'; + const addToNewViewPart = region !== 'center'; const commands = RouterUtils.segmentsToCommands(event.source.viewUrlSegments); if (addToNewViewPart) { @@ -80,7 +81,7 @@ export class ViewMoveHandler implements OnDestroy { const newPartId = event.target.newPartId || UUID.randomUUID(); this._wbRouter.ɵnavigate(layout => ({ layout: layout - .addPart(newPartId, {relativeTo: event.target.partId!, align: coerceLayoutAlignment(event.target.region!)}) + .addPart(newPartId, {relativeTo: event.target.partId ?? undefined, align: coerceLayoutAlignment(region)}) .addView(newPartId, newViewId), viewOutlets: {[newViewId]: commands}, })).then(); diff --git a/projects/scion/workbench/src/lib/view/view-portal.pipe.ts b/projects/scion/workbench/src/lib/view/view-portal.pipe.ts index 75329f51e..d04525510 100644 --- a/projects/scion/workbench/src/lib/view/view-portal.pipe.ts +++ b/projects/scion/workbench/src/lib/view/view-portal.pipe.ts @@ -23,6 +23,9 @@ export class ViewPortalPipe implements PipeTransform { } public transform(viewId: string | null): WbComponentPortal | null { - return viewId ? this._viewRegistry.get(viewId)?.portal ?? null : null; + if (!viewId) { + return null; + } + return this._viewRegistry.getElseNull(viewId)?.portal ?? null; } } diff --git a/projects/scion/workbench/src/lib/view/workbench-view.model.ts b/projects/scion/workbench/src/lib/view/workbench-view.model.ts index a3e00e57c..34d065d64 100644 --- a/projects/scion/workbench/src/lib/view/workbench-view.model.ts +++ b/projects/scion/workbench/src/lib/view/workbench-view.model.ts @@ -51,7 +51,7 @@ export abstract class WorkbenchView { /** * Specifies if the view is blocked, e.g., not interactable because of showing a view-modal message box. */ - public abstract blocked?: boolean; + public abstract blocked: boolean; /** * Specifies if a close button should be displayed in the view tab. diff --git a/projects/scion/workbench/src/lib/view/workbench-view.registry.ts b/projects/scion/workbench/src/lib/view/workbench-view.registry.ts index 95d7bf21d..b0dafe23d 100644 --- a/projects/scion/workbench/src/lib/view/workbench-view.registry.ts +++ b/projects/scion/workbench/src/lib/view/workbench-view.registry.ts @@ -32,7 +32,7 @@ export class WorkbenchViewRegistry implements OnDestroy { * Destroys the view of the given id and removes it from this registry. */ public remove(viewId: string): void { - this._viewRegistry.get(viewId)!.destroy(); + this.getElseThrow(viewId).destroy(); this._viewRegistry.delete(viewId); this._viewRegistryChange$.next(); } @@ -65,10 +65,10 @@ export class WorkbenchViewRegistry implements OnDestroy { } /** - * Returns the {@link WorkbenchView} of the given identity, or returns `undefined` if not found. + * Returns the {@link WorkbenchView} of the given identity, or returns `null` if not found. */ - public get(viewId: string): ɵWorkbenchView | undefined { - return this._viewRegistry.get(viewId); + public getElseNull(viewId: string): ɵWorkbenchView | null { + return this._viewRegistry.get(viewId) ?? null; } public get viewIds(): string[] { diff --git a/projects/scion/workbench/src/lib/workbench.service.ts b/projects/scion/workbench/src/lib/workbench.service.ts index 6d81ef32a..35bc9f591 100644 --- a/projects/scion/workbench/src/lib/workbench.service.ts +++ b/projects/scion/workbench/src/lib/workbench.service.ts @@ -37,7 +37,7 @@ export abstract class WorkbenchService { /** * A unique ID per instance of the app. If opened in a different browser tab, it has a different instance ID. */ - public readonly appInstanceId!: string; + public abstract readonly appInstanceId: string; /** * Emits the views opened in the workbench. @@ -45,7 +45,7 @@ export abstract class WorkbenchService { * Upon subscription, the currently opened views are emitted, and then emits continuously * when new views are opened or existing views closed. It never completes. */ - public readonly views$!: Observable; + public abstract readonly views$: Observable; /** * Returns a reference to the specified {@link WorkbenchView}, or `null` if not found. diff --git "a/projects/scion/workbench/src/lib/\311\265workbench.service.ts" "b/projects/scion/workbench/src/lib/\311\265workbench.service.ts" index 4adf7b88e..7490a8c30 100644 --- "a/projects/scion/workbench/src/lib/\311\265workbench.service.ts" +++ "b/projects/scion/workbench/src/lib/\311\265workbench.service.ts" @@ -9,7 +9,7 @@ */ import { Injectable } from '@angular/core'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { WorkbenchMenuItemFactoryFn, WorkbenchViewPartAction } from './workbench.model'; import { UUID } from '@scion/toolkit/uuid'; import { WorkbenchLayoutService } from './layout/workbench-layout.service'; @@ -25,6 +25,7 @@ export class ɵWorkbenchService implements WorkbenchService { // tslint:disable- public readonly viewPartActions$ = new BehaviorSubject([]); public readonly viewMenuItemProviders$ = new BehaviorSubject([]); + public readonly views$ = this._layoutService.layout$.pipe(map(layout => layout.parts.reduce((viewIds, part) => viewIds.concat(part.viewIds), new Array()))); public readonly appInstanceId = UUID.randomUUID(); constructor(private _wbRouter: WorkbenchRouter, @@ -44,12 +45,8 @@ export class ɵWorkbenchService implements WorkbenchService { // tslint:disable- return this._layoutService.layout!.findPartByViewId(viewId, {orElseThrow: true}).partId; } - public get views$(): Observable { - return this._layoutService.layout$.pipe(map(layout => layout.parts.reduce((viewIds, part) => viewIds.concat(part.viewIds), new Array()))); - } - public getView(viewId: string): WorkbenchView | null { - return this._viewRegistry.get(viewId) ?? null; + return this._viewRegistry.getElseNull(viewId); } public registerViewPartAction(action: WorkbenchViewPartAction): Disposable {