From 333c51104bf2832c66431d1a3234e8412b934b6d Mon Sep 17 00:00:00 2001 From: mofogasy Date: Fri, 30 Apr 2021 15:46:41 +0200 Subject: [PATCH 1/2] chore(workbench): compile with TypeScript strict checks enabled closes #246 --- package-lock.json | 18 +++---- package.json | 6 +-- .../activity-part/activity-part.component.ts | 7 +-- .../src/lib/activity-part/activity.ts | 40 +++++++------- .../wb-activity-action.directive.ts | 8 ++- .../activity-part/wb-activity.directive.ts | 8 +-- .../workbench-activity-part.service.ts | 4 +- .../src/lib/broadcast-channel.service.ts | 2 +- .../content-as-overlay.component.ts | 2 +- .../content-projection.directive.ts | 6 +-- .../view-container.reference.ts | 2 +- projects/scion/workbench/src/lib/dom.util.ts | 5 +- .../src/lib/layout/parts-layout.component.ts | 27 ++++++---- .../src/lib/layout/parts-layout.model.ts | 16 +++--- .../workbench/src/lib/layout/parts-layout.ts | 32 ++++++++---- .../src/lib/layout/tree-node.component.ts | 10 ++-- .../lib/layout/workbench-layout.service.ts | 14 ++--- .../scion/workbench/src/lib/logging/logger.ts | 10 ++-- .../message-box-css-classes.pipe.ts | 2 +- .../lib/message-box/message-box.component.ts | 14 ++--- .../lib/message-box/message-box.service.ts | 8 +-- .../src/lib/message-box/move.directive.ts | 6 +-- .../lib/message-box/text-message.component.ts | 2 +- .../lib/message-box/\311\265message-box.ts" | 6 +-- ...rofrontend-platform-initializer.service.ts | 22 ++++---- ...rofrontend-message-box-provider.service.ts | 2 +- ...ofrontend-notification-provider.service.ts | 2 +- ...ofrontend-popup-command-handler.service.ts | 10 ++-- .../microfrontend-popup.component.ts | 14 ++--- ...rofrontend-view-command-handler.service.ts | 14 ++--- .../microfrontend-view.component.ts | 44 +++++++++------- ...ontend-navigate-command-handler.service.ts | 8 +-- .../workbench-microfrontend-support.ts | 2 +- .../notification-css-classes.pipe.ts | 2 +- .../notification/notification.component.ts | 4 +- .../lib/notification/notification.service.ts | 4 +- .../lib/notification/\311\265notification.ts" | 4 +- .../src/lib/popup/popup.component.ts | 12 ++--- .../workbench/src/lib/popup/popup.config.ts | 12 ++--- .../workbench/src/lib/popup/popup.service.ts | 2 +- .../src/lib/portal/wb-component-portal.ts | 33 ++++++------ .../lib/portal/wb-portal-outlet.component.ts | 4 +- .../src/lib/routing/add-view-to-part.guard.ts | 4 +- ...b-activity-route-reuse-provider.service.ts | 2 +- .../lib/routing/wb-router-link.directive.ts | 2 +- ...ch-auxiliary-routes-registrator.service.ts | 4 +- .../lib/routing/workbench-layout-differ.ts | 2 +- .../lib/routing/workbench-router.service.ts | 30 +++++------ .../routing/workbench-url-observer.service.ts | 15 +++--- .../scion/workbench/src/lib/sash.directive.ts | 14 ++--- .../src/lib/spec/workbench-testing.module.ts | 2 +- .../lib/startup/workbench-launcher.service.ts | 2 +- .../src/lib/view-dnd/view-drag.service.ts | 9 ++-- .../lib/view-dnd/view-drop-zone.directive.ts | 18 +++---- .../view-tab-drag-image-renderer.service.ts | 18 +++---- .../view-context-menu/view-menu.component.ts | 2 +- .../view-context-menu/view-menu.directive.ts | 12 ++--- .../view-context-menu/view-menu.service.ts | 52 +++++++++---------- .../view-list-button.component.ts | 2 +- .../view-part-action-bar.component.ts | 4 +- .../view-part-action.directive.ts | 2 +- .../view-part-bar.component.html | 2 +- .../view-part-bar/view-part-bar.component.ts | 36 ++++++------- .../src/lib/view-part/view-part.component.ts | 6 +-- .../view-part/view-tab/view-tab.component.ts | 16 +++--- .../view-part/workbench-view-part.model.ts | 13 +++-- .../view-part/workbench-view-part.registry.ts | 9 +++- .../\311\265workbench-view-part.model.ts" | 14 ++--- .../src/lib/view/view-move-handler.service.ts | 13 ++--- .../src/lib/view/view-portal.pipe.ts | 7 ++- .../workbench/src/lib/view/view.component.ts | 2 +- .../src/lib/view/workbench-view.model.ts | 44 ++++++++-------- .../src/lib/view/workbench-view.registry.ts | 4 +- .../lib/view/\311\265workbench-view.model.ts" | 13 ++--- .../workbench/src/lib/workbench.service.ts | 4 +- .../src/lib/\311\265workbench.service.ts" | 11 ++-- projects/scion/workbench/tsconfig.lib.json | 11 +++- projects/scion/workbench/tslint.json | 1 + 78 files changed, 454 insertions(+), 398 deletions(-) diff --git a/package-lock.json b/package-lock.json index 840c32251..fe6134d98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1800,26 +1800,26 @@ } }, "@scion/microfrontend-platform": { - "version": "1.0.0-beta.13", - "resolved": "https://registry.npmjs.org/@scion/microfrontend-platform/-/microfrontend-platform-1.0.0-beta.13.tgz", - "integrity": "sha512-VAoZ3AVO56qXGzx/YYx05XDCznojb6tLtTuXle0FD0U8oJWxBZvxZd4sFlf17UVzCdoYimYGuPwPWLGUrUnrSQ==", + "version": "1.0.0-beta.15", + "resolved": "https://registry.npmjs.org/@scion/microfrontend-platform/-/microfrontend-platform-1.0.0-beta.15.tgz", + "integrity": "sha512-u/A/iH7O2OmxQZmvqZLztFlcXrRfxQVwFkU3xkadoMEJzeWIcprIBkm2E9oI69uoj9jOdE7wKGrpd+SiQTtHEA==", "requires": { "js-sha256": "0.9.0", "tslib": "^2.0.0" } }, "@scion/toolkit": { - "version": "11.0.0-beta.10", - "resolved": "https://registry.npmjs.org/@scion/toolkit/-/toolkit-11.0.0-beta.10.tgz", - "integrity": "sha512-fB9V+2htKjsyKgcs4hr7v0o/sPt//gs4MwIIug00pJEbpyBnSqF57hn92Sa3ukL9gT3Yn+NvUlWsSRLvywG3Fg==", + "version": "11.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@scion/toolkit/-/toolkit-11.0.0-beta.11.tgz", + "integrity": "sha512-DGkLF3BjKs/MzJcvKGwLL3otRGdj3HabchCBV6+1nVXfLFyLpto2qBs9+lEfh/13qRufVv9dRv1+RrZDkUh3ZQ==", "requires": { "tslib": "^2.0.0" } }, "@scion/toolkit.internal": { - "version": "11.0.0-beta.10", - "resolved": "https://registry.npmjs.org/@scion/toolkit.internal/-/toolkit.internal-11.0.0-beta.10.tgz", - "integrity": "sha512-pNJb75Olp109zhNtaZjosRoGsaf7mnwL97fZTgjLSSJpiOBKNAC5wS6WSHbrVTonOvizot4fJEjmYzhRI4wQLA==", + "version": "11.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@scion/toolkit.internal/-/toolkit.internal-11.0.0-beta.11.tgz", + "integrity": "sha512-qdKYj1lQbp09SW3759DtPD+JQRwaoqgeodD6dUpI4FVm+mLYR4/zEPiTprne1R0ZgKlyzLyxeOsxQzrvSao1WQ==", "requires": { "tslib": "^2.0.0" } diff --git a/package.json b/package.json index e0f7980c9..3fb735ea7 100644 --- a/package.json +++ b/package.json @@ -68,9 +68,9 @@ "@angular/platform-browser": "11.0.0", "@angular/platform-browser-dynamic": "11.0.0", "@angular/router": "11.0.0", - "@scion/microfrontend-platform": "1.0.0-beta.13", - "@scion/toolkit": "11.0.0-beta.10", - "@scion/toolkit.internal": "11.0.0-beta.10", + "@scion/microfrontend-platform": "1.0.0-beta.15", + "@scion/toolkit": "11.0.0-beta.11", + "@scion/toolkit.internal": "11.0.0-beta.11", "rxjs": "6.6.0", "tslib": "2.0.0", "zone.js": "0.10.3" 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 104a450b6..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,11 +52,8 @@ export class ActivityPartComponent { private _panelWidth = PANEL_INITIAL_WIDTH; private _panelWidth$ = new Subject(); - @ViewChild('viewport') - public viewport: ElementRef; - @ViewChild('panel', {read: ElementRef}) - private _panelElementRef: ElementRef; + private _panelElementRef!: ElementRef; constructor(public host: ElementRef, public activityPartService: WorkbenchActivityPartService, @@ -69,7 +66,7 @@ export class ActivityPartComponent { return this.activityPartService.activities.filter(it => it.visible); } - public get activeActivity(): Activity { + public get activeActivity(): Activity | null { const activeActivity = this.activityPartService.activeActivity; return activeActivity && activeActivity.visible ? activeActivity : null; } diff --git a/projects/scion/workbench/src/lib/activity-part/activity.ts b/projects/scion/workbench/src/lib/activity-part/activity.ts index 44eb7d7ee..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. @@ -61,27 +61,27 @@ export abstract class Activity { /** * Returns the routing commands of this activity. */ - public abstract get commands(): any[]; + public abstract readonly commands: any[]; /** * Returns the routing path of this activity. */ - public abstract get path(): string; + public abstract readonly path: string | undefined; /** * Emits upon activation change of this activity. */ - public abstract get active$(): Observable; + public abstract readonly active$: Observable; /** * Indicates if this activity is currently active. */ - public abstract get active(): boolean; + public abstract readonly active: boolean; /** * Returns the actions associated with this activity. */ - public abstract get actions(): ActivityAction[]; + public abstract readonly actions: ActivityAction[]; /** * Associates an action with this activity. When this activity is active, it is displayed in the activity panel header. @@ -95,19 +95,19 @@ export class InternalActivity implements Activity { private _commands: any[] = []; private _actions: ActivityAction[] = []; - private _path: string; + private _path: string | undefined; 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) { } @@ -125,7 +125,7 @@ export class InternalActivity implements Activity { return this._commands; } - public get path(): string { + public get path(): string | undefined { return this._path; } 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 578a3c165..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; 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/activity-part/workbench-activity-part.service.ts b/projects/scion/workbench/src/lib/activity-part/workbench-activity-part.service.ts index e33b5b137..c7dedbe7f 100644 --- a/projects/scion/workbench/src/lib/activity-part/workbench-activity-part.service.ts +++ b/projects/scion/workbench/src/lib/activity-part/workbench-activity-part.service.ts @@ -89,8 +89,8 @@ export class WorkbenchActivityPartService { /** * Returns the activity of the current routing context, or `null` if not in the routing context of an activity. */ - public getActivityFromRoutingContext(route: ActivatedRouteSnapshot): Activity { - for (let testee = route; testee !== null; testee = testee.parent) { + public getActivityFromRoutingContext(route: ActivatedRouteSnapshot): Activity | null { + for (let testee: ActivatedRouteSnapshot | null = route; testee !== null; testee = testee.parent) { const activity = testee.data[ACTIVITY_DATA_KEY]; if (activity) { return activity; diff --git a/projects/scion/workbench/src/lib/broadcast-channel.service.ts b/projects/scion/workbench/src/lib/broadcast-channel.service.ts index 1342038f6..40e78cb30 100644 --- a/projects/scion/workbench/src/lib/broadcast-channel.service.ts +++ b/projects/scion/workbench/src/lib/broadcast-channel.service.ts @@ -33,7 +33,7 @@ export class BroadcastChannelService { filter(event => event.storageArea === localStorage), filter(event => event.key === BROADCAST_CHANNEL_ITEM_KEY), filter(event => event.newValue !== null), // skip item remove events - map(event => JSON.parse(event.newValue)), + map(event => JSON.parse(event.newValue!)), ); } diff --git a/projects/scion/workbench/src/lib/content-projection/content-as-overlay.component.ts b/projects/scion/workbench/src/lib/content-projection/content-as-overlay.component.ts index 074a5f83c..a485150fc 100644 --- a/projects/scion/workbench/src/lib/content-projection/content-as-overlay.component.ts +++ b/projects/scion/workbench/src/lib/content-projection/content-as-overlay.component.ts @@ -54,5 +54,5 @@ export class ContentAsOverlayComponent { * Reference to the view container where to insert the overlay. */ @Input() - public overlayHost: ViewContainerRef | Promise; + public overlayHost!: ViewContainerRef | Promise; } diff --git a/projects/scion/workbench/src/lib/content-projection/content-projection.directive.ts b/projects/scion/workbench/src/lib/content-projection/content-projection.directive.ts index 8cf4740f7..47d64d9cf 100644 --- a/projects/scion/workbench/src/lib/content-projection/content-projection.directive.ts +++ b/projects/scion/workbench/src/lib/content-projection/content-projection.directive.ts @@ -24,19 +24,19 @@ export class ContentProjectionDirective implements OnInit, OnDestroy { private _destroy$ = new Subject(); private _boundingBoxElement: HTMLElement; - private _contentViewRef: EmbeddedViewRef; + private _contentViewRef!: EmbeddedViewRef; /** * Reference to the view container where to insert the overlay. */ @Input('wbContentProjectionOverlayHost') // tslint:disable-line:no-input-rename - public overlayHost: ViewContainerRef | Promise; + public overlayHost!: ViewContainerRef | Promise; /** * Template which to render as overlay. The template will stick to the bounding box of the host element of this directive. */ @Input('wbContentProjectionContent') // tslint:disable-line:no-input-rename - public contentTemplateRef: TemplateRef; + public contentTemplateRef!: TemplateRef; constructor(host: ElementRef, @Optional() private _view: WorkbenchView) { this._boundingBoxElement = host.nativeElement; diff --git a/projects/scion/workbench/src/lib/content-projection/view-container.reference.ts b/projects/scion/workbench/src/lib/content-projection/view-container.reference.ts index c0b02378a..741b45fd9 100644 --- a/projects/scion/workbench/src/lib/content-projection/view-container.reference.ts +++ b/projects/scion/workbench/src/lib/content-projection/view-container.reference.ts @@ -16,7 +16,7 @@ import { Injectable, InjectionToken, ViewContainerRef } from '@angular/core'; @Injectable() export class ViewContainerReference { - private _resolve: (host: ViewContainerRef) => void; + private _resolve: ((host: ViewContainerRef) => void) | null = null; private _promise = new Promise(resolve => this._resolve = resolve); /** diff --git a/projects/scion/workbench/src/lib/dom.util.ts b/projects/scion/workbench/src/lib/dom.util.ts index 1612e5c19..8dabc8ae6 100644 --- a/projects/scion/workbench/src/lib/dom.util.ts +++ b/projects/scion/workbench/src/lib/dom.util.ts @@ -17,11 +17,12 @@ 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); - Object.keys(style).forEach(key => target.style[key] = style[key]); + Object.keys(style).forEach(key => target.style.setProperty(key, style[key])); } /** 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 5fc35cfe8..50003a7d1 100644 --- a/projects/scion/workbench/src/lib/layout/parts-layout.component.ts +++ b/projects/scion/workbench/src/lib/layout/parts-layout.component.ts @@ -50,17 +50,17 @@ export class PartsLayoutComponent implements OnInit, OnDestroy { private _destroy$ = new Subject(); - public root: MTreeNode | MPart; + public root!: MTreeNode | MPart; /** * Reference to the root part of the layout. Is only set if the layout root is of the type {@link MPart}. */ - public rootPart: WbComponentPortal; + 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; + public rootNode: MTreeNode | null = null; constructor(private _viewPartRegistry: WorkbenchViewPartRegistry, private _layoutService: WorkbenchLayoutService, @@ -70,11 +70,11 @@ export class PartsLayoutComponent implements OnInit, OnDestroy { public ngOnInit(): void { this._layoutService.layout$ .pipe( - startWith(null as PartsLayout), // start with a null layout to initialize the 'pairwise' operator, so it emits once the layout is set. + startWith(null! as PartsLayout), // start with a null layout to initialize the 'pairwise' operator, so it emits once the layout is set. 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.model.ts b/projects/scion/workbench/src/lib/layout/parts-layout.model.ts index 290f51ac2..b04bd6df1 100644 --- a/projects/scion/workbench/src/lib/layout/parts-layout.model.ts +++ b/projects/scion/workbench/src/lib/layout/parts-layout.model.ts @@ -30,11 +30,11 @@ export interface MPartsLayout { */ export class MTreeNode { - public nodeId: string; - public child1: MTreeNode | MPart; - public child2: MTreeNode | MPart; - public ratio: number; - public direction: 'column' | 'row'; + public nodeId!: string; + public child1!: MTreeNode | MPart; + public child2!: MTreeNode | MPart; + public ratio!: number; + public direction!: 'column' | 'row'; public parent?: MTreeNode; constructor(treeNode: Partial) { @@ -61,10 +61,10 @@ export class MTreeNode { */ export class MPart { - public partId: string; + public partId!: string; public parent?: MTreeNode; public viewIds: string[] = []; - public activeViewId: string; + public activeViewId?: string; constructor(part: Partial) { part.parent && assertType(part.parent, {toBeOneOf: MTreeNode}); // check the type to ensure that it is not an object literal @@ -76,7 +76,7 @@ export class MPart { */ public getPath(): string[] { const path: string[] = []; - let parent: MTreeNode = this.parent; + let parent: MTreeNode | undefined = this.parent; while (parent) { path.push(parent.nodeId); parent = parent.parent; diff --git a/projects/scion/workbench/src/lib/layout/parts-layout.ts b/projects/scion/workbench/src/lib/layout/parts-layout.ts index 188b5496f..105f576c3 100644 --- a/projects/scion/workbench/src/lib/layout/parts-layout.ts +++ b/projects/scion/workbench/src/lib/layout/parts-layout.ts @@ -180,6 +180,8 @@ export class PartsLayout { * * By providing an options object, you can control if to throw an error if not found. */ + public findPart(partId: string, options: { orElseThrow: true }): Readonly>; + public findPart(partId: string, options: { orElseThrow: false } | {}): Readonly; public findPart(partId: string, options: { orElseThrow: boolean }): Readonly { return this._findPart(partId, options); } @@ -189,6 +191,8 @@ export class PartsLayout { * * By providing an object object, you can control if to throw an error if not found. */ + public findPartByViewId(viewId: string, options: { orElseThrow: true }): Readonly>; + public findPartByViewId(viewId: string, options: { orElseThrow: false } | {}): Readonly; public findPartByViewId(viewId: string, options: { orElseThrow: boolean }): Readonly { assertNotNullish(viewId, {orElseThrow: () => Error(`[PartsLayoutError] ViewId must not be 'null' or 'undefined'.`)}); return this._findPartByViewId(viewId, options); @@ -202,6 +206,8 @@ export class PartsLayout { * @param options - Controls the serialization into a URL-safe base64 string. * - `nullIfEmpty`: if `true` (or if not specified), returns `null` if containing a single root part with no views added to it. */ + public serialize(options: { nullIfEmpty: false }): string; + public serialize(options?: { nullIfEmpty?: true } | {}): string | null; public serialize(options?: { nullIfEmpty?: boolean }): string | null { if ((options?.nullIfEmpty ?? true) && this._root instanceof MPart && this._root.isEmpty()) { return null; @@ -258,23 +264,22 @@ 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) { + const siblingElement = (part.parent!.child1 === part ? part.parent!.child2 : part.parent!.child1); + if (!part.parent!.parent) { this._root = siblingElement; } - else if (part.parent.parent.child1 === part.parent) { - part.parent.parent.child1 = siblingElement; + else if (part.parent!.parent.child1 === part.parent) { + part.parent!.parent.child1 = siblingElement; } else { - part.parent.parent.child2 = siblingElement; + part.parent!.parent.child2 = siblingElement; } // If the removed part was the active part, make it's adjacent part the active part. @@ -398,6 +403,8 @@ export class PartsLayout { return this; } + private _findPart(partId: string, options: { orElseThrow: true }): NonNullable; + private _findPart(partId: string, options: { orElseThrow: false } | {}): MPart | null; private _findPart(partId: string, options: { orElseThrow: boolean }): MPart | null { assertNotNullish(partId, {orElseThrow: () => Error(`[PartsLayoutError] PartId must not be 'null' or 'undefined'.`)}); const part = this._findTreeElements(element => element instanceof MPart && element.partId === partId, {findFirst: true})[0] as MPart; @@ -407,6 +414,8 @@ export class PartsLayout { return part || null; } + private _findPartByViewId(viewId: string, options: { orElseThrow: true }): NonNullable; + private _findPartByViewId(viewId: string, options: { orElseThrow: false } | {}): MPart | null; private _findPartByViewId(viewId: string, options: { orElseThrow: boolean }): MPart | null { assertNotNullish(viewId, {orElseThrow: () => Error(`[PartsLayoutError] ViewId must not be 'null' or 'undefined'.`)}); const part = this._findTreeElements(element => element instanceof MPart && element.viewIds.includes(viewId), {findFirst: true})[0] as MPart; @@ -416,6 +425,8 @@ export class PartsLayout { return part || null; } + private _findTreeNode(nodeId: string, options: { orElseThrow: true }): NonNullable; + private _findTreeNode(nodeId: string, options: { orElseThrow: false } | {}): MTreeNode | null; private _findTreeNode(nodeId: string, options: { orElseThrow: boolean }): MTreeNode | null { assertNotNullish(nodeId, {orElseThrow: () => Error(`[PartsLayoutError] NodeId must not be 'null' or 'undefined'.`)}); @@ -468,9 +479,8 @@ function createRootLayout(workbenchAccessor: PartsLayoutWorkbenchAccessor): MPar /** * Serializes this layout into a URL-safe base64 string. - * Returns `null` if the layout is empty. */ -function serializeLayout(layout: MPartsLayout): string | null { +function serializeLayout(layout: MPartsLayout): string { return btoa(JSON.stringify(layout, (key, value) => { return (key === 'parent') ? undefined : value; // do not serialize node parents. })); @@ -491,14 +501,14 @@ function deserializeLayout(serializedLayout: string): MPartsLayout { }); // Link parent tree nodes - (function linkParentNodes(node: MTreeNode | MPart, parent: MTreeNode): void { + (function linkParentNodes(node: MTreeNode | MPart, parent: MTreeNode | undefined): void { node.parent = parent; if (node instanceof MTreeNode) { linkParentNodes(node.child1, node); linkParentNodes(node.child2, node); } - })(layout.root, null); + })(layout.root, undefined); return layout; } diff --git a/projects/scion/workbench/src/lib/layout/tree-node.component.ts b/projects/scion/workbench/src/lib/layout/tree-node.component.ts index ed33455a2..2a706a91c 100644 --- a/projects/scion/workbench/src/lib/layout/tree-node.component.ts +++ b/projects/scion/workbench/src/lib/layout/tree-node.component.ts @@ -26,10 +26,10 @@ import { WorkbenchRouter } from '../routing/workbench-router.service'; }) export class TreeNodeComponent { - private _treeNode: MTreeNode; + private _treeNode!: MTreeNode; - public sash1: Sash; - public sash2: Sash; + public sash1!: Sash; + public sash2!: Sash; @Input() public set treeNode(treeNode: MTreeNode) { @@ -65,8 +65,8 @@ export class TreeNodeComponent { private createSash(content: MTreeNode | MPart, proportion: string): Sash { return { - portal: content instanceof MPart ? this._viewPartRegistry.getElseThrow(content.partId).portal : null, - treeNode: content instanceof MTreeNode ? content : null, + portal: content instanceof MPart ? this._viewPartRegistry.getElseThrow(content.partId).portal : undefined, + treeNode: content instanceof MTreeNode ? content : undefined, proportion: proportion, }; } 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 fcd27974c..f4f436fcd 100644 --- a/projects/scion/workbench/src/lib/layout/workbench-layout.service.ts +++ b/projects/scion/workbench/src/lib/layout/workbench-layout.service.ts @@ -20,7 +20,7 @@ import { PartsLayout } from './parts-layout'; @Injectable() export class WorkbenchLayoutService { - private _layout: PartsLayout; + private _layout: PartsLayout | null = null; private _layoutChange$ = new Subject(); private _maximized$ = new BehaviorSubject(false); private _dragStart$ = new Subject(); @@ -48,14 +48,14 @@ export class WorkbenchLayoutService { public readonly layout$: Observable = this._layoutChange$ .pipe( startWith(undefined as void), - map(() => this.layout), - filter(Boolean), + map(() => this.layout), + filter((layout: PartsLayout | null): layout is PartsLayout => layout !== null), ); constructor(viewDragService: ViewDragService, private _zone: NgZone) { - this.dragging$ = merge<'start' | 'end'>( - merge(this._dragStart$, viewDragService.viewDragStart$).pipe(mapTo('start')), - merge(this._dragEnd$, viewDragService.viewDragEnd$).pipe(mapTo('end')), + this.dragging$ = merge( + merge(this._dragStart$, viewDragService.viewDragStart$).pipe(mapTo('start')), + merge(this._dragEnd$, viewDragService.viewDragEnd$).pipe(mapTo('end')), ); } @@ -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 b56ec7ba8..0916bcdb3 100644 --- a/projects/scion/workbench/src/lib/logging/logger.ts +++ b/projects/scion/workbench/src/lib/logging/logger.ts @@ -14,6 +14,8 @@ import { filter, map, startWith, takeUntil } from 'rxjs/operators'; import { Observable, Subject } from 'rxjs'; import { LogAppender, LogEvent, LoggerName, LogLevel } from './logging.model'; +type LogLevelStrings = keyof typeof LogLevel; + /** * Logger used by the workbench to log messages. * @@ -84,7 +86,7 @@ export abstract class Logger { export class ɵLogger implements Logger, OnDestroy { // tslint:disable-line:class-name private _destroy$ = new Subject(); - private _logLevel: LogLevel; + private _logLevel!: LogLevel; constructor(@Inject(LogAppender) @Optional() private _logAppenders: LogAppender[], router: Router, @@ -128,10 +130,10 @@ export class ɵLogger implements Logger, OnDestroy { // tslint:disable-line:cla return router.events .pipe( startWith(router.url), - filter(event => event instanceof NavigationStart), + filter((event): event is NavigationStart => event instanceof NavigationStart), map(routerEvent => router.parseUrl(routerEvent.url).queryParamMap), - map(queryParamMap => queryParamMap.get('loglevel')?.toUpperCase()), - map(logLevelRaw => LogLevel[logLevelRaw]), + map(queryParamMap => queryParamMap.get('loglevel')?.toUpperCase() as LogLevelStrings | undefined), + map(logLevelString => logLevelString ? LogLevel[logLevelString] : undefined), ); } diff --git a/projects/scion/workbench/src/lib/message-box/message-box-css-classes.pipe.ts b/projects/scion/workbench/src/lib/message-box/message-box-css-classes.pipe.ts index b74e64db4..a453548b9 100644 --- a/projects/scion/workbench/src/lib/message-box/message-box-css-classes.pipe.ts +++ b/projects/scion/workbench/src/lib/message-box/message-box-css-classes.pipe.ts @@ -13,7 +13,7 @@ export class MessageBoxCssClassesPipe implements PipeTransform { public transform(messageBox: ɵMessageBox): Observable { return combineLatest([messageBox.severity$, messageBox.cssClass$]) .pipe( - map(([severity, cssClasses]) => [] + map(([severity, cssClasses]) => new Array() .concat(cssClasses) .concat(severity) .concat(`e2e-severity-${severity}`)), 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 6811818cf..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,22 +36,22 @@ 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; @Input() - public messageBox: ɵMessageBox; + public messageBox!: ɵMessageBox; @Input() public set positionDelta(delta: number) { @@ -59,7 +59,7 @@ export class MessageBoxComponent implements OnInit, OnDestroy { } @ViewChildren('action_button') - public actionButtons: QueryList>; + public actionButtons!: QueryList>; constructor(private _injector: Injector, private _cd: ChangeDetectorRef, @@ -73,7 +73,7 @@ export class MessageBoxComponent implements OnInit, OnDestroy { this.portal = this.createPortal(this.messageBox); this._cd.detectChanges(); }); - this.textSelectable = this.messageBox.config.contentSelectable; + this.textSelectable = this.messageBox.config.contentSelectable ?? false; this.installBlinkRequestHandler(); this.installFocusRequestHandler(); } 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 17248555d..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, @@ -88,7 +88,7 @@ export class MessageBoxService implements OnDestroy { } if (config.modality === 'application') { - return Arrays.last(this._messageBoxServiceHierarchy).addMessageBox(config); + return Arrays.last(this._messageBoxServiceHierarchy)!.addMessageBox(config); } else if (config.context?.viewId) { const view = this._viewRegistry.getElseThrow(config.context.viewId); @@ -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 dbfd8b938..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(); @@ -53,7 +53,7 @@ export class MoveDirective implements OnDestroy { this._document.body.style.cursor = this.cursor; // Listen for 'mousemove' events - const mousemoveListener = fromEvent(this._document, 'mousemove') + const mousemoveListener = fromEvent(this._document, 'mousemove') .pipe(takeUntil(this._destroy$)) .subscribe((mousemoveEvent: MouseEvent) => { mousemoveEvent.preventDefault(); // prevent drag and drop diff --git a/projects/scion/workbench/src/lib/message-box/text-message.component.ts b/projects/scion/workbench/src/lib/message-box/text-message.component.ts index e33b854ec..99c5ef9d4 100644 --- a/projects/scion/workbench/src/lib/message-box/text-message.component.ts +++ b/projects/scion/workbench/src/lib/message-box/text-message.component.ts @@ -11,7 +11,7 @@ import { MessageBox } from './message-box'; }) export class TextMessageComponent { - public text: string; + public text?: string; constructor(messageBox: MessageBox) { this.text = messageBox.input; diff --git "a/projects/scion/workbench/src/lib/message-box/\311\265message-box.ts" "b/projects/scion/workbench/src/lib/message-box/\311\265message-box.ts" index 84fa994f4..020d0ba7a 100644 --- "a/projects/scion/workbench/src/lib/message-box/\311\265message-box.ts" +++ "b/projects/scion/workbench/src/lib/message-box/\311\265message-box.ts" @@ -17,10 +17,10 @@ import { TextMessageComponent } from './text-message.component'; export class ɵMessageBox implements MessageBox { // tslint:disable-line:class-name - private _closeResolveFn: (action: any) => void; + private _closeResolveFn!: (action: any) => void; public readonly input: T; - public readonly title$: BehaviorSubject>; + public readonly title$: BehaviorSubject>; public readonly actions$: BehaviorSubject; public readonly severity$: BehaviorSubject<'info' | 'warn' | 'error'>; public readonly cssClass$: BehaviorSubject; @@ -48,7 +48,7 @@ export class ɵMessageBox implements MessageBox { // tslint:disable- this.actions$.next(actions || []); } - public setTitle(title: string | undefined | Observable): void { + public setTitle(title: string | undefined | Observable): void { this.title$.next(title); } 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 62ffa896c..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; + 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,27 +93,27 @@ 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'})), }; } private enableIntentionRegisterApi(microfrontendPlatformConfig: PlatformConfig, appSymbolicName: string): void { - microfrontendPlatformConfig.apps.find(app => app.symbolicName === appSymbolicName).intentionRegisterApiDisabled = false; + microfrontendPlatformConfig.apps.find(app => app.symbolicName === appSymbolicName)!.intentionRegisterApiDisabled = false; } private disableScopeCheck(microfrontendPlatformConfig: PlatformConfig, appSymbolicName: string): void { - microfrontendPlatformConfig.apps.find(app => app.symbolicName === appSymbolicName).scopeCheckDisabled = true; + microfrontendPlatformConfig.apps.find(app => app.symbolicName === appSymbolicName)!.scopeCheckDisabled = true; } private async registerWildcardIntention(type: WorkbenchCapabilities): Promise { @@ -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-message-box/microfrontend-message-box-provider.service.ts b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-message-box/microfrontend-message-box-provider.service.ts index 3fd516a67..9921eda52 100644 --- a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-message-box/microfrontend-message-box-provider.service.ts +++ b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-message-box/microfrontend-message-box-provider.service.ts @@ -31,7 +31,7 @@ export class MicrofrontendMessageBoxProvider implements WorkbenchInitializer { logger.debug(() => 'Opening message box', LoggerNames.MICROFRONTEND, config); return messageBoxService.open({ ...config, - content: config.content ?? '', + content: config!.content ?? '', }); }); } diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-notification/microfrontend-notification-provider.service.ts b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-notification/microfrontend-notification-provider.service.ts index 389adce66..2bfa514f5 100644 --- a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-notification/microfrontend-notification-provider.service.ts +++ b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-notification/microfrontend-notification-provider.service.ts @@ -31,7 +31,7 @@ export class MicrofrontendNotificationProvider implements WorkbenchInitializer { logger.debug(() => 'Showing notification', LoggerNames.MICROFRONTEND, config); notificationService.notify({ ...config, - content: config.content ?? '', + content: config!.content ?? '', }); }); } 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 fda03b7ce..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,10 +41,11 @@ export class MicrofrontendPopupCommandHandler { private _router: Router, private _zone: NgZone, {symbolicName: hostAppSymbolicName}: MicroApplicationConfig) { - this._messageClient.onMessage<ɵWorkbenchPopupCommand>(ɵWorkbenchCommands.popup, async ({body: command}) => { + this._messageClient.onMessage<ɵWorkbenchPopupCommand>(ɵWorkbenchCommands.popup, async message => { + const command = message.body!; this._logger.debug(() => 'Handling microfrontend popup command', LoggerNames.MICROFRONTEND, command); - if (command.capability.metadata.appSymbolicName === hostAppSymbolicName) { + if (command.capability.metadata!.appSymbolicName === hostAppSymbolicName) { return this.openHostComponentPopup(command); } else { @@ -164,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: any[] = (path !== null ? RouterUtils.segmentsToCommands(RouterUtils.parsePath(this._router, path)) : null); - const commands: any[] = [{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-popup/microfrontend-popup.component.ts b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-popup/microfrontend-popup.component.ts index d969c7a5a..07178321a 100644 --- a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-popup/microfrontend-popup.component.ts +++ b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-popup/microfrontend-popup.component.ts @@ -31,17 +31,17 @@ export class MicrofrontendPopupComponent implements OnInit, OnDestroy { private _focusWithin$ = new Subject(); private _popupContext: ɵPopupContext; - public microfrontendCssClasses: string[]; + public microfrontendCssClasses!: string[]; @ViewChild('router_outlet', {static: true}) - public routerOutletElement: ElementRef; + public routerOutletElement!: ElementRef; constructor(private _popup: Popup<ɵPopupContext>, private _outletRouter: OutletRouter, private _manifestService: ManifestService, private _messageClient: MessageClient, private _logger: Logger) { - this._popupContext = this._popup.input; + this._popupContext = this._popup.input!; this._logger.debug(() => 'Constructing MicrofrontendPopupComponent.', LoggerNames.MICROFRONTEND); } @@ -53,9 +53,9 @@ export class MicrofrontendPopupComponent implements OnInit, OnDestroy { const popupCapability = this._popupContext.capability; // Obtain the capability provider. - const application = this.lookupApplication(popupCapability.metadata.appSymbolicName); + const application = this.lookupApplication(popupCapability.metadata!.appSymbolicName); if (!application) { - this._popup.closeWithError(`[NullApplicationError] Unexpected. Cannot resolve application '${popupCapability.metadata.appSymbolicName}'.`); + this._popup.closeWithError(`[NullApplicationError] Unexpected. Cannot resolve application '${popupCapability.metadata!.appSymbolicName}'.`); return; } @@ -92,10 +92,10 @@ export class MicrofrontendPopupComponent implements OnInit, OnDestroy { // Make the popup context available to embedded content. this.routerOutletElement.nativeElement.setContextValue(ɵPOPUP_CONTEXT, this._popupContext); - this.microfrontendCssClasses = ['e2e-popup', `e2e-${popupCapability.metadata.appSymbolicName}`, ...Arrays.coerce(popupCapability.properties.cssClass)]; + this.microfrontendCssClasses = ['e2e-popup', `e2e-${popupCapability.metadata!.appSymbolicName}`, ...Arrays.coerce(popupCapability.properties.cssClass)]; // Navigate to the microfrontend. - this._logger.debug(() => `Loading microfrontend into workbench popup [app=${popupCapability.metadata.appSymbolicName}, baseUrl=${application.baseUrl}, path=${microfrontendPath}].`, LoggerNames.MICROFRONTEND, this._popupContext.params, popupCapability); + this._logger.debug(() => `Loading microfrontend into workbench popup [app=${popupCapability.metadata!.appSymbolicName}, baseUrl=${application.baseUrl}, path=${microfrontendPath}].`, LoggerNames.MICROFRONTEND, this._popupContext.params, popupCapability); await this._outletRouter.navigate(microfrontendPath, { outlet: this.popupId, relativeTo: application.baseUrl, diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-view/microfrontend-view-command-handler.service.ts b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-view/microfrontend-view-command-handler.service.ts index ae31aa49f..b06a24daf 100644 --- a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-view/microfrontend-view-command-handler.service.ts +++ b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-view/microfrontend-view-command-handler.service.ts @@ -64,7 +64,7 @@ export class MicrofrontendViewCommandHandler implements OnDestroy { */ private installViewTitleCommandHandler(): void { this._messageClient.onMessage(ɵWorkbenchCommands.viewTitleTopic(':viewId'), message => { - const viewId = message.params.get('viewId'); + const viewId = message.params!.get('viewId')!; this.runIfPrivileged(viewId, message, view => { view.title = message.body; }); @@ -76,7 +76,7 @@ export class MicrofrontendViewCommandHandler implements OnDestroy { */ private installViewHeadingCommandHandler(): void { this._messageClient.onMessage(ɵWorkbenchCommands.viewHeadingTopic(':viewId'), message => { - const viewId = message.params.get('viewId'); + const viewId = message.params!.get('viewId')!; this.runIfPrivileged(viewId, message, view => { view.heading = message.body; }); @@ -88,7 +88,7 @@ export class MicrofrontendViewCommandHandler implements OnDestroy { */ private installViewDirtyCommandHandler(): void { this._messageClient.onMessage(ɵWorkbenchCommands.viewDirtyTopic(':viewId'), message => { - const viewId = message.params.get('viewId'); + const viewId = message.params!.get('viewId')!; this.runIfPrivileged(viewId, message, view => { view.dirty = message.body; }); @@ -100,7 +100,7 @@ export class MicrofrontendViewCommandHandler implements OnDestroy { */ private installViewClosableCommandHandler(): void { this._messageClient.onMessage(ɵWorkbenchCommands.viewClosableTopic(':viewId'), message => { - const viewId = message.params.get('viewId'); + const viewId = message.params!.get('viewId')!; this.runIfPrivileged(viewId, message, view => { view.closable = message.body; }); @@ -112,7 +112,7 @@ export class MicrofrontendViewCommandHandler implements OnDestroy { */ private installViewCloseCommandHandler(): void { this._messageClient.onMessage(ɵWorkbenchCommands.viewCloseTopic(':viewId'), message => { - const viewId = message.params.get('viewId'); + const viewId = message.params!.get('viewId')!; this.runIfPrivileged(viewId, message, view => { view.close().then(); }); @@ -123,7 +123,7 @@ export class MicrofrontendViewCommandHandler implements OnDestroy { this._manifestService.lookupCapabilities$({type: WorkbenchCapabilities.View}) .pipe(takeUntil(this._destroy$)) .subscribe(viewCapabilities => { - this._viewCapabilities = viewCapabilities.reduce((acc, capability) => acc.set(capability.metadata.id, capability), new Map()); + this._viewCapabilities = viewCapabilities.reduce((acc, capability) => acc.set(capability.metadata!.id, capability), new Map()); }); } @@ -153,7 +153,7 @@ export class MicrofrontendViewCommandHandler implements OnDestroy { return false; } - return viewCapability.metadata.appSymbolicName === sender; + return viewCapability.metadata!.appSymbolicName === sender; } public ngOnDestroy(): void { 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 3b5ac411a..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 @@ -38,11 +38,11 @@ export class MicrofrontendViewComponent implements OnInit, OnDestroy, WbBeforeDe 'keydown.escape', // allows closing notifications ]; - public microfrontendCssClasses: string[]; + public microfrontendCssClasses!: string[]; public iframeHost: Promise; @ViewChild('router_outlet', {static: true}) - public routerOutletElement: ElementRef; + public routerOutletElement!: ElementRef; /** * Keystrokes which to bubble across iframe boundaries of embedded content. @@ -60,7 +60,7 @@ export class MicrofrontendViewComponent implements OnInit, OnDestroy, WbBeforeDe this._logger.debug(() => `Constructing MicrofrontendViewComponent. [viewId=${this._view.viewId}]`, LoggerNames.MICROFRONTEND_ROUTING); this.iframeHost = iframeHost.get(); this.keystrokesToBubble$ = combineLatest([this.viewContextMenuKeystrokes$(), of(this._universalKeystrokes)]) - .pipe(map(keystrokes => [].concat(...keystrokes))); + .pipe(map(keystrokes => new Array().concat(...keystrokes))); } public ngOnInit(): void { @@ -76,8 +76,9 @@ export class MicrofrontendViewComponent implements OnInit, OnDestroy, WbBeforeDe this._route.params .pipe( - switchMap(params => this.observeViewCapability$(params[ɵMicrofrontendRouteParams.ɵVIEW_CAPABILITY_ID]).pipe(map(capability => ({capability, params})))), - startWith(undefined as { capability: WorkbenchViewCapability, params: Params }), // initialize 'pairwise' operator + switchMap(params => this.observeViewCapability$(params[ɵMicrofrontendRouteParams.ɵVIEW_CAPABILITY_ID]) + .pipe(map(capability => ({capability, params})))), + startWith(undefined! as WorkbenchViewCapabilityWithParams), // initialize 'pairwise' operator pairwise(), serializeExecution(([prev, curr]) => this.onNavigate(curr.params, prev?.capability, curr.capability)), catchError((error, caught) => { @@ -103,20 +104,20 @@ export class MicrofrontendViewComponent implements OnInit, OnDestroy, WbBeforeDe return; } - const application = this.lookupApplication(viewCapability.metadata.appSymbolicName); + const application = this.lookupApplication(viewCapability.metadata!.appSymbolicName); if (!application) { - this._logger.error(() => `[NullApplicationError] Unexpected. Cannot resolve application '${viewCapability.metadata.appSymbolicName}'.`, LoggerNames.MICROFRONTEND_ROUTING, viewCapability); + this._logger.error(() => `[NullApplicationError] Unexpected. Cannot resolve application '${viewCapability.metadata!.appSymbolicName}'.`, LoggerNames.MICROFRONTEND_ROUTING, viewCapability); await this._view.close(); return; } // Set this view's properties, but only when displaying its initial microfrontend, or when navigating to another microfrontend. - if (!prevViewCapability || prevViewCapability.metadata.id !== viewCapability.metadata.id) { + if (!prevViewCapability || prevViewCapability.metadata!.id !== viewCapability.metadata!.id) { this.setViewProperties(viewCapability); } // Signal that the currently loaded microfrontend, if any, is about to be replaced by a microfrontend of another application. - if (prevViewCapability && prevViewCapability.metadata.appSymbolicName !== viewCapability.metadata.appSymbolicName) { + if (prevViewCapability && prevViewCapability.metadata!.appSymbolicName !== viewCapability.metadata!.appSymbolicName) { await this._messageClient.publish(ɵWorkbenchCommands.viewUnloadingTopic(this.viewId)); } @@ -127,13 +128,13 @@ export class MicrofrontendViewComponent implements OnInit, OnDestroy, WbBeforeDe // new microfrontend into the iframe, allowing the currently loaded microfrontend to cleanup subscriptions. Params include the // capability id. if (prevViewCapability - && prevViewCapability.metadata.appSymbolicName === viewCapability.metadata.appSymbolicName - && prevViewCapability.metadata.id !== viewCapability.metadata.id) { - await this.waitForCapabilityParam(viewCapability.metadata.id); + && prevViewCapability.metadata!.appSymbolicName === viewCapability.metadata!.appSymbolicName + && prevViewCapability.metadata!.id !== viewCapability.metadata!.id) { + await this.waitForCapabilityParam(viewCapability.metadata!.id); } // Navigate to the microfrontend. - this._logger.debug(() => `Loading microfrontend into workbench view [viewId=${this._view.viewId}, app=${viewCapability.metadata.appSymbolicName}, baseUrl=${application.baseUrl}, path=${microfrontendPath}].`, LoggerNames.MICROFRONTEND_ROUTING, params, viewCapability); + this._logger.debug(() => `Loading microfrontend into workbench view [viewId=${this._view.viewId}, app=${viewCapability.metadata!.appSymbolicName}, baseUrl=${application.baseUrl}, path=${microfrontendPath}].`, LoggerNames.MICROFRONTEND_ROUTING, params, viewCapability); await this._outletRouter.navigate(microfrontendPath, { outlet: this.viewId, relativeTo: application.baseUrl, @@ -146,12 +147,12 @@ export class MicrofrontendViewComponent implements OnInit, OnDestroy, WbBeforeDe * Updates the properties of this view, such as the view title, as defined by the capability. */ private setViewProperties(viewCapability: WorkbenchViewCapability): void { - this._view.title = viewCapability.properties?.title ?? this._view.title; // to support setting the view's title via 'wb.title' param - this._view.heading = viewCapability.properties?.heading ?? this._view.heading; // to support setting the view's heading via 'wb.view-heading' param - this._view.cssClass = viewCapability.properties?.cssClass; - this._view.closable = viewCapability.properties?.closable ?? true; + this._view.title = viewCapability.properties.title ?? this._view.title; // to support setting the view's title via 'wb.title' param + this._view.heading = viewCapability.properties.heading ?? this._view.heading; // to support setting the view's heading via 'wb.view-heading' param + this._view.cssClass = viewCapability.properties.cssClass ?? []; + this._view.closable = viewCapability.properties.closable ?? true; this._view.dirty = false; - this.microfrontendCssClasses = ['e2e-view', `e2e-${viewCapability.metadata.appSymbolicName}`, ...this._view.cssClasses]; + this.microfrontendCssClasses = ['e2e-view', `e2e-${viewCapability.metadata!.appSymbolicName}`, ...this._view.cssClasses]; } /** @@ -229,7 +230,7 @@ export class MicrofrontendViewComponent implements OnInit, OnDestroy, WbBeforeDe return this._view.menuItems$ .pipe( filterArray(menuItem => !!menuItem.accelerator), - mapArray(menuItem => menuItem.accelerator.map(accelerator => { + mapArray(menuItem => menuItem.accelerator!.map(accelerator => { // Normalize keystrokes according to `SciRouterOutletElement#keystrokes` switch (accelerator) { case 'ctrl': @@ -253,3 +254,8 @@ export class MicrofrontendViewComponent implements OnInit, OnDestroy, WbBeforeDe this._destroy$.next(); } } + +interface WorkbenchViewCapabilityWithParams { + capability?: WorkbenchViewCapability; + params: Params; +} diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/routing/microfrontend-navigate-command-handler.service.ts b/projects/scion/workbench/src/lib/microfrontend-platform/routing/microfrontend-navigate-command-handler.service.ts index fa33fa0de..3995f0fdb 100644 --- a/projects/scion/workbench/src/lib/microfrontend-platform/routing/microfrontend-navigate-command-handler.service.ts +++ b/projects/scion/workbench/src/lib/microfrontend-platform/routing/microfrontend-navigate-command-handler.service.ts @@ -47,7 +47,7 @@ export class MicrofrontendNavigateCommandHandler implements OnDestroy { } private async handleNavigateCommand(message: TopicMessage<ɵWorkbenchRouterNavigateCommand>): Promise { - const navigateCommand = message.body; + const navigateCommand = message.body!; const replyTo = message.headers.get(MessageHeaders.ReplyTo); // For multiple capabilities, navigate sequentially to avoid resolving to the same view for target 'blank'. @@ -69,7 +69,7 @@ export class MicrofrontendNavigateCommandHandler implements OnDestroy { private navigate(viewCapability: WorkbenchViewCapability, qualifier: Qualifier, extras: WorkbenchNavigationExtras): Promise { const matrixParams = this.computeMatrixParams(qualifier, extras); - const routerNavigateCommand = this.buildRouterNavigateCommand(viewCapability.metadata.id, matrixParams); + const routerNavigateCommand = this.buildRouterNavigateCommand(viewCapability.metadata!.id, matrixParams); this._logger.debug(() => `Navigating to: ${viewCapability.properties.path}`, LoggerNames.MICROFRONTEND_ROUTING, routerNavigateCommand, viewCapability); @@ -94,7 +94,7 @@ export class MicrofrontendNavigateCommandHandler implements OnDestroy { if (extras.paramsHandling === 'merge' && extras.target === 'self' && extras.selfViewId) { const currentViewUrlSegments = this._router.parseUrl(this._router.url).root.children[extras.selfViewId].segments; - const currentMatrixParams = Arrays.last(currentViewUrlSegments).parameters; + const currentMatrixParams = Arrays.last(currentViewUrlSegments)!.parameters; const mergedMatrixParams = { ...currentMatrixParams, ...params, // new params have precedence over params contained in the URL @@ -132,7 +132,7 @@ function stringifyError(error: any): string { * Returns a new dictionary with `undefined` values removed. */ function withoutUndefinedEntries(object: Dictionary): Dictionary { - return Object.entries(object).reduce((dictionary, [key, value]) => { + return Object.entries(object).reduce((dictionary, [key, value]) => { if (value !== undefined) { dictionary[key] = value; } 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 47acf656f..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-css-classes.pipe.ts b/projects/scion/workbench/src/lib/notification/notification-css-classes.pipe.ts index 2e944e23c..a8744a5ae 100644 --- a/projects/scion/workbench/src/lib/notification/notification-css-classes.pipe.ts +++ b/projects/scion/workbench/src/lib/notification/notification-css-classes.pipe.ts @@ -13,7 +13,7 @@ export class NotificationCssClassesPipe implements PipeTransform { public transform(notification: ɵNotification): Observable { return combineLatest([notification.severity$, notification.duration$, notification.cssClass$]) .pipe( - map(([severity, duration, cssClasses]) => [] + map(([severity, duration, cssClasses]) => new Array() .concat(cssClasses) .concat(severity) .concat(`e2e-severity-${severity}`) diff --git a/projects/scion/workbench/src/lib/notification/notification.component.ts b/projects/scion/workbench/src/lib/notification/notification.component.ts index b698cd7cd..739d37e52 100644 --- a/projects/scion/workbench/src/lib/notification/notification.component.ts +++ b/projects/scion/workbench/src/lib/notification/notification.component.ts @@ -31,10 +31,10 @@ 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; + public notification!: ɵNotification; @Output() public closeNotification = new EventEmitter(); diff --git a/projects/scion/workbench/src/lib/notification/notification.service.ts b/projects/scion/workbench/src/lib/notification/notification.service.ts index f6177a9a4..345ce0f10 100644 --- a/projects/scion/workbench/src/lib/notification/notification.service.ts +++ b/projects/scion/workbench/src/lib/notification/notification.service.ts @@ -131,11 +131,11 @@ export class NotificationService implements OnDestroy { * Installs a keystroke listener to close the last notification when the user presses the escape keystroke. */ private installEscapeHandler(): void { - fromEvent(this._document, 'keydown') + fromEvent(this._document, 'keydown') .pipe( filter((event: KeyboardEvent) => event.key === 'Escape'), map(() => Arrays.last(this.notifications)), - filter<ɵNotification>(Boolean), + filter((notification): notification is ɵNotification => !!notification), subscribeInside(continueFn => this._zone.runOutsideAngular(continueFn)), observeInside(continueFn => this._zone.run(continueFn)), takeUntil(this._destroy$), diff --git "a/projects/scion/workbench/src/lib/notification/\311\265notification.ts" "b/projects/scion/workbench/src/lib/notification/\311\265notification.ts" index ec854bc88..c91cf8857 100644 --- "a/projects/scion/workbench/src/lib/notification/\311\265notification.ts" +++ "b/projects/scion/workbench/src/lib/notification/\311\265notification.ts" @@ -18,7 +18,7 @@ import { TextNotificationComponent } from './text-notification.component'; export class ɵNotification implements Notification { // tslint:disable-line:class-name public readonly input: T; - public readonly title$: BehaviorSubject>; + public readonly title$: BehaviorSubject>; public readonly severity$: BehaviorSubject<'info' | 'warn' | 'error'>; public readonly duration$: BehaviorSubject<'short' | 'medium' | 'long' | 'infinite' | number>; public readonly cssClass$: BehaviorSubject; @@ -39,7 +39,7 @@ export class ɵNotification implements Notification { // tslint:disa } } - public setTitle(title: string | undefined | Observable): void { + public setTitle(title: string | undefined | Observable): void { this.title$.next(title); } diff --git a/projects/scion/workbench/src/lib/popup/popup.component.ts b/projects/scion/workbench/src/lib/popup/popup.component.ts index c2be19800..15406ddfb 100644 --- a/projects/scion/workbench/src/lib/popup/popup.component.ts +++ b/projects/scion/workbench/src/lib/popup/popup.component.ts @@ -28,32 +28,32 @@ export class PopupComponent { public portal: ComponentPortal; @HostBinding('style.width') - public get popupWidth(): string { + public get popupWidth(): string | undefined { return this._popupConfig.size?.width; } @HostBinding('style.min-width') - public get popupMinWidth(): string { + public get popupMinWidth(): string | undefined { return this._popupConfig.size?.minWidth; } @HostBinding('style.max-width') - public get popupMaxWidth(): string { + public get popupMaxWidth(): string | undefined { return this._popupConfig.size?.maxWidth; } @HostBinding('style.height') - public get popupHeight(): string { + public get popupHeight(): string | undefined { return this._popupConfig.size?.height; } @HostBinding('style.min-height') - public get popupMinHeight(): string { + public get popupMinHeight(): string | undefined { return this._popupConfig.size?.minHeight; } @HostBinding('style.max-height') - public get popupMaxHeight(): string { + public get popupMaxHeight(): string | undefined { return this._popupConfig.size?.maxHeight; } diff --git a/projects/scion/workbench/src/lib/popup/popup.config.ts b/projects/scion/workbench/src/lib/popup/popup.config.ts index ec8e9985b..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; + 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. @@ -202,11 +202,11 @@ export abstract class Popup { */ export class ɵPopup implements Popup { // tslint:disable-line:class-name - private _closeResolveFn: (result: any | undefined) => void; + private _closeResolveFn!: (result: any | undefined) => void; public readonly whenClose = new Promise(resolve => this._closeResolveFn = resolve); - constructor(public readonly input: any | undefined, public readonly size: PopupSize) { + constructor(public readonly input: any | undefined, public readonly size: PopupSize | undefined) { } /** diff --git a/projects/scion/workbench/src/lib/popup/popup.service.ts b/projects/scion/workbench/src/lib/popup/popup.service.ts index ab3d42ca7..cf9b2d03a 100644 --- a/projects/scion/workbench/src/lib/popup/popup.service.ts +++ b/projects/scion/workbench/src/lib/popup/popup.service.ts @@ -187,7 +187,7 @@ export class PopupService { private installPopupCloser(config: PopupConfig, popupElement: HTMLElement, overlayRef: OverlayRef, popupHandle: Popup, takeUntilClose: () => MonoTypeOperatorFunction): void { // Close the popup on escape keystroke. if (config.closeStrategy?.onEscape ?? true) { - fromEvent(popupElement, 'keydown') + fromEvent(popupElement, 'keydown') .pipe( filter((event: KeyboardEvent) => event.key === 'Escape'), subscribeInside(continueFn => this._zone.runOutsideAngular(continueFn)), 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 afd485a6e..460c6843f 100644 --- a/projects/scion/workbench/src/lib/portal/wb-component-portal.ts +++ b/projects/scion/workbench/src/lib/portal/wb-component-portal.ts @@ -18,13 +18,13 @@ import { ComponentFactory, ComponentFactoryResolver, ComponentRef, InjectFlags, */ export class WbComponentPortal { - private _config: PortalConfig; + private _config!: PortalConfig; private _componentFactory: ComponentFactory; - private _viewContainerRef: ViewContainerRef | null; - private _reattachFn: () => void; - private _componentRef: ComponentRef | null; - private _portalInjector: WbPortalInjector; + private _viewContainerRef: ViewContainerRef | null = null; + private _reattachFn: (() => void) | null = null; + private _componentRef: ComponentRef | null = null; + private _portalInjector!: WbPortalInjector; constructor(componentFactoryResolver: ComponentFactoryResolver, componentType: ComponentType) { this._componentFactory = componentFactoryResolver.resolveComponentFactory(componentType); @@ -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(); } @@ -96,8 +96,8 @@ export class WbComponentPortal { } // Attach this portlet - this._viewContainerRef.insert(this._componentRef.hostView, 0); - this._componentRef.changeDetectorRef.reattach(); + this._viewContainerRef!.insert(this._componentRef!.hostView, 0); + this._componentRef!.changeDetectorRef.reattach(); // Invoke 'onAttach' lifecycle hook this._config.onAttach && this._config.onAttach(); @@ -120,28 +120,31 @@ export class WbComponentPortal { this._config.onDetach && this._config.onDetach(); // Detach this portlet - const index = this._viewContainerRef.indexOf(this._componentRef.hostView); - this._viewContainerRef.detach(index); - this._componentRef.changeDetectorRef.detach(); + const index = this._viewContainerRef!.indexOf(this._componentRef!.hostView); + this._viewContainerRef!.detach(index); + this._componentRef!.changeDetectorRef.detach(); } /** * 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 { + if (!this._componentRef) { + throw Error('[PortalError] Illegal state: Portal already destroyed.'); + } return this._componentRef; } - public get viewContainerRef(): 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 { @@ -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 7c2936b79..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,10 +18,10 @@ 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; + private _viewContainerRef!: ViewContainerRef; @Input('wbPortal') // tslint:disable-line:no-input-rename public set portal(portal: WbComponentPortal) { diff --git a/projects/scion/workbench/src/lib/routing/add-view-to-part.guard.ts b/projects/scion/workbench/src/lib/routing/add-view-to-part.guard.ts index 4fc944048..49b6d7f87 100644 --- a/projects/scion/workbench/src/lib/routing/add-view-to-part.guard.ts +++ b/projects/scion/workbench/src/lib/routing/add-view-to-part.guard.ts @@ -34,7 +34,7 @@ export class WbAddViewToPartGuard implements CanActivate { public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { const viewId: string = route.outlet; - const viewTarget: ViewTarget = ((this._router.getCurrentNavigation().extras.state || {})[VIEW_TARGET] || {})[viewId] || {}; + const viewTarget: ViewTarget = ((this._router.getCurrentNavigation()!.extras.state || {})[VIEW_TARGET] || {})[viewId] || {}; // Read the layout from the URL. const partsLayout = this._workbenchRouter.getCurrentNavigationContext().partsLayout; @@ -84,7 +84,7 @@ export class WbAddViewToPartGuard implements CanActivate { switch (insertionIndex) { case undefined: { // index after the active view, if any, or after the last view otherwise const part = partsLayout.findPart(partId, {orElseThrow: true}); - const index = part.viewIds.indexOf(part.activeViewId); + const index = part.viewIds.indexOf(part.activeViewId!); return (index > -1 ? index + 1 : part.viewIds.length); } case 'start': { diff --git a/projects/scion/workbench/src/lib/routing/wb-activity-route-reuse-provider.service.ts b/projects/scion/workbench/src/lib/routing/wb-activity-route-reuse-provider.service.ts index 7a0e593a2..4bb1b595a 100644 --- a/projects/scion/workbench/src/lib/routing/wb-activity-route-reuse-provider.service.ts +++ b/projects/scion/workbench/src/lib/routing/wb-activity-route-reuse-provider.service.ts @@ -47,7 +47,7 @@ export class WbActivityRouteReuseProvider implements WbRouteReuseProvider { } return hierarchy - .reduce((path, routeSnapshot) => [...path, ...routeSnapshot.url.map(it => it.path)], []) + .reduce((path, routeSnapshot) => [...path, ...routeSnapshot.url.map(it => it.path)], new Array()) .join('/'); } } 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 8468c20a4..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 472312a41..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 => !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 => !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-layout-differ.ts b/projects/scion/workbench/src/lib/routing/workbench-layout-differ.ts index 1653cf233..a8a2d519c 100644 --- a/projects/scion/workbench/src/lib/routing/workbench-layout-differ.ts +++ b/projects/scion/workbench/src/lib/routing/workbench-layout-differ.ts @@ -76,7 +76,7 @@ export class WorkbenchLayoutDiff { } public toString(): string { - return `${[] + return `${new Array() .concat(this.addedParts.length ? `addedParts=[${this.addedParts}]` : []) .concat(this.removedParts.length ? `removedParts=[${this.removedParts}]` : []) .concat(this.addedViews.length ? `addedViews=[${this.addedViews}]` : []) 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 8504c7183..7d2c8bdd0 100644 --- a/projects/scion/workbench/src/lib/routing/workbench-router.service.ts +++ b/projects/scion/workbench/src/lib/routing/workbench-router.service.ts @@ -25,7 +25,7 @@ import { WorkbenchLayoutDiff } from './workbench-layout-differ'; @Injectable() export class WorkbenchRouter { - private _currentNavigationContext$ = new BehaviorSubject(null); + private _currentNavigationContext$ = new BehaviorSubject(null); constructor(private _router: Router, private _viewRegistry: WorkbenchViewRegistry, @@ -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) { @@ -101,7 +101,7 @@ export class WorkbenchRouter { throw Error(`[WorkbenchRouterError] Target view outlet not found: ${extras.selfViewId}'`); } - return this.ɵnavigate(layout => ({layout, viewOutlets: {[extras.selfViewId]: commands}}), extras); + return this.ɵnavigate(layout => ({layout, viewOutlets: {[extras.selfViewId!]: commands}}), extras); } default: { throw Error(`[WorkbenchRouterError] Invalid routing target. Expected 'self' or 'blank', but received ${extras.target}'.`); @@ -133,7 +133,7 @@ export class WorkbenchRouter { await this.waitForNavigationToComplete(); // Let the caller modify the layout. - const result = computeNavigationFn(this._layoutService.layout); + const result = computeNavigationFn(this._layoutService.layout!); // Coerce the result to a {WorkbenchNavigation} object. const navigation: WorkbenchNavigation = result instanceof PartsLayout ? ({layout: result}) : result; @@ -156,7 +156,7 @@ export class WorkbenchRouter { /** * @see normalizeCommands */ - private normalizeOutletCommands(outlets?: { [outlet: string]: any[]; }, relativeTo?: ActivatedRoute | null): { [outlet: string]: any[]; } | null { + private normalizeOutletCommands(outlets?: { [outlet: string]: Commands | null }, relativeTo?: ActivatedRoute | null): { [outlet: string]: Commands | null } | null { if (!outlets || !Object.keys(outlets).length) { return null; } @@ -184,11 +184,11 @@ export class WorkbenchRouter { * * @internal */ - public normalizeCommands(commands: any[], relativeTo?: ActivatedRoute | null): any[] { - const normalizeFn = (outlet: string, extras?: NavigationExtras): any[] => { + public normalizeCommands(commands: Commands, relativeTo?: ActivatedRoute | null): Commands { + const normalizeFn = (outlet: string, extras?: NavigationExtras): Commands => { return this._router.createUrlTree(commands, extras) .root.children[outlet].segments - .reduce((acc, p) => [...acc, p.path, ...(Object.keys(p.parameters).length ? [p.parameters] : [])], []); + .reduce((acc, p) => [...acc, p.path, ...(Object.keys(p.parameters).length ? [p.parameters] : [])], []); }; if (!relativeTo) { @@ -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; @@ -236,7 +236,7 @@ export class WorkbenchRouter { * * @internal */ - public setCurrentNavigationContext(context: WorkbenchNavigationContext): void { + public setCurrentNavigationContext(context: WorkbenchNavigationContext | null): void { this._currentNavigationContext$.next(context); } @@ -271,13 +271,13 @@ export class WorkbenchRouter { /** * Serializes given commands into valid URL segments. */ - private serializeCommands(commands: any[]): string[] { + private serializeCommands(commands: Commands): string[] { const serializedCommands: string[] = []; commands.forEach(cmd => { // if matrix param, append it to the last segment if (typeof cmd === 'object') { - serializedCommands.push(new UrlSegment(serializedCommands.pop(), cmd).toString()); + serializedCommands.push(new UrlSegment(serializedCommands.pop()!, cmd).toString()); } else { serializedCommands.push(encodeURIComponent(cmd)); @@ -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[]; @@ -408,5 +406,5 @@ export interface WorkbenchNavigation { * add a property to this dictionary and set the commands to construct the outlet URL. * To remove an outlet from the URL, set its commands to `null`. */ - viewOutlets?: { [outlet: string]: Commands; }; + viewOutlets?: { [outlet: string]: Commands | null }; } 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 ed8bb14a4..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 @@ -9,7 +9,7 @@ */ import { ChildrenOutletContexts, GuardsCheckEnd, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router, RouterEvent } from '@angular/router'; -import { takeUntil } from 'rxjs/operators'; +import { filter, takeUntil } from 'rxjs/operators'; import { ComponentFactoryResolver, Injectable, Injector, OnDestroy } from '@angular/core'; import { Subject } from 'rxjs'; import { WorkbenchAuxiliaryRoutesRegistrator } from './workbench-auxiliary-routes-registrator.service'; @@ -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); } /** @@ -275,7 +275,10 @@ export class WorkbenchUrlObserver implements OnDestroy { private installRouterEventListeners(): void { this._router.events - .pipe(takeUntil(this._destroy$)) + .pipe( + filter((event): event is RouterEvent => event instanceof RouterEvent), + takeUntil(this._destroy$), + ) .subscribe((event: RouterEvent) => { if (event instanceof NavigationStart) { this.onNavigationStart(event); diff --git a/projects/scion/workbench/src/lib/sash.directive.ts b/projects/scion/workbench/src/lib/sash.directive.ts index f92cb95f7..d19125764 100644 --- a/projects/scion/workbench/src/lib/sash.directive.ts +++ b/projects/scion/workbench/src/lib/sash.directive.ts @@ -22,10 +22,10 @@ import { first, takeUntil } from 'rxjs/operators'; export class SashDirective implements OnDestroy, OnChanges { private _destroy$ = new Subject(); - private _mousePosition: number; + private _mousePosition: number | undefined; @Input('wbSash') // tslint:disable-line:no-input-rename - public wbDirection: 'vertical' | 'horizontal'; + public wbDirection: 'vertical' | 'horizontal' = 'horizontal'; @Output() public wbSashStart = new EventEmitter(false); @@ -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) { } @@ -51,7 +51,7 @@ export class SashDirective implements OnDestroy, OnChanges { @HostListener('dblclick') public onDoubleClick(): void { - this.wbSashReset.emit(null); + this.wbSashReset.emit(); } @HostListener('mousedown', ['$event']) @@ -68,12 +68,12 @@ export class SashDirective implements OnDestroy, OnChanges { this._document.body.style.cursor = this.cursor; // Listen for 'mousemove' events - const mousemoveListener = fromEvent(this._document, 'mousemove') + const mousemoveListener = fromEvent(this._document, 'mousemove') .pipe(takeUntil(this._destroy$)) .subscribe((mousemoveEvent: MouseEvent) => { mousemoveEvent.preventDefault(); const mousePosition = this.extractMousePosition(mousemoveEvent); - const delta = mousePosition - this._mousePosition; + const delta = mousePosition - this._mousePosition!; this._mousePosition = mousePosition; this.wbSashChange.emit(delta); }); @@ -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/spec/workbench-testing.module.ts b/projects/scion/workbench/src/lib/spec/workbench-testing.module.ts index 600376964..cc6f6e163 100644 --- a/projects/scion/workbench/src/lib/spec/workbench-testing.module.ts +++ b/projects/scion/workbench/src/lib/spec/workbench-testing.module.ts @@ -28,7 +28,7 @@ export class WorkbenchTestingModule { return { ngModule: WorkbenchTestingModule, providers: [ - WorkbenchModule.forRoot(workbenchModuleConfig).providers, + WorkbenchModule.forRoot(workbenchModuleConfig).providers ?? [], {provide: ViewActivationInstantProvider, useClass: ViewActivationTestingInstantProvider}, {provide: PARTS_LAYOUT_ROOT_PART_IDENTITY, useValue: 'main'}, ], diff --git a/projects/scion/workbench/src/lib/startup/workbench-launcher.service.ts b/projects/scion/workbench/src/lib/startup/workbench-launcher.service.ts index 88244476d..09add97ea 100644 --- a/projects/scion/workbench/src/lib/startup/workbench-launcher.service.ts +++ b/projects/scion/workbench/src/lib/startup/workbench-launcher.service.ts @@ -137,7 +137,7 @@ export class WorkbenchStartup { private _started = false; /* @internal */ - public notifyStarted: () => void; + public notifyStarted!: () => void; /** * Promise that resolves when the workbench has completed the startup. 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 2913273c9..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') @@ -61,7 +61,7 @@ export class ViewDragService { * Checks if the given event is a view drag event with the same origin. */ public isViewDragEvent(event: DragEvent): boolean { - return event.dataTransfer.types.includes(VIEW_DRAG_TRANSFER_TYPE) && this.getViewDragData() !== null; + return !!event.dataTransfer && event.dataTransfer.types.includes(VIEW_DRAG_TRANSFER_TYPE) && this.getViewDragData() !== null; } /** @@ -84,7 +84,7 @@ export class ViewDragService { const fromEvent$ = (eventName: ViewDragEventType): Observable => { if (!options || !options.eventType || Arrays.coerce(options.eventType).includes(eventName)) { - return fromEvent(target, eventName, options); + return fromEvent(target, eventName, options ?? {}); } return EMPTY; }; @@ -234,8 +234,9 @@ 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; + partId: string | null; /** * Identity of the new part to be created, if the region is either 'north', 'east', 'south', or 'west'. * If not set, a UUID is generated. diff --git a/projects/scion/workbench/src/lib/view-dnd/view-drop-zone.directive.ts b/projects/scion/workbench/src/lib/view-dnd/view-drop-zone.directive.ts index 75e29756d..78ef2752e 100644 --- a/projects/scion/workbench/src/lib/view-dnd/view-drop-zone.directive.ts +++ b/projects/scion/workbench/src/lib/view-dnd/view-drop-zone.directive.ts @@ -17,7 +17,7 @@ import { ViewDragData, ViewDragService } from './view-drag.service'; const DROP_REGION_MAX_SIZE = 150; const DROP_REGION_GAP = 20; const DROP_REGION_BGCOLOR = 'rgba(0, 0, 0, .1)'; -const NULL_BOUNDS: Bounds = null; +const NULL_BOUNDS: Bounds = null!; /** * Adds a view drop zone to the host element allowing the view to be dropped either in the north, @@ -34,17 +34,17 @@ export class ViewDropZoneDirective implements OnInit, OnDestroy { private _destroy$ = new Subject(); private _host: HTMLElement; - private _dropZoneOverlay: HTMLElement; - private _dropRegionElement1: HTMLElement; - private _dropRegionElement2: HTMLElement; + private _dropZoneOverlay!: HTMLElement; + private _dropRegionElement1!: HTMLElement; + private _dropRegionElement2!: HTMLElement; - private _dropRegion: Region; + private _dropRegion: Region | null = null; /** * Specifies which drop regions to allow. If not specified, all regions are allowed. */ @Input() - public wbViewDropZoneRegions: Region[]; + public wbViewDropZoneRegions?: Region[]; /** * Emits upon a view drop action. @@ -146,8 +146,8 @@ export class ViewDropZoneDirective implements OnInit, OnDestroy { private onDrop(event: DragEvent): void { const dropEvent: WbViewDropEvent = { - dropRegion: this._dropRegion, - dragData: this._viewDragService.getViewDragData(), + dropRegion: this._dropRegion!, + dragData: this._viewDragService.getViewDragData()!, sourceEvent: event, }; @@ -296,7 +296,7 @@ interface Bounds { right: string; bottom: string; left: string; - background: string; + background: string | null; } export interface WbViewDropEvent { 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 324bde859..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; - private _constrainDragImageRectFn: (rect: ViewDragImageRect) => ViewDragImageRect; - private _dragData: ViewDragData; + private _viewDragImagePortalOutlet: DomPortalOutlet | null = null; + private _constrainDragImageRectFn: ((rect: ViewDragImageRect) => ViewDragImageRect) | null = null; + private _dragData: ViewDragData | null = null; constructor(private _viewDragService: ViewDragService, private _workbenchModuleConfig: WorkbenchModuleConfig, @@ -59,7 +59,7 @@ export class ViewTabDragImageRenderer implements OnDestroy { */ public unsetConstrainDragImageRectFn(fn: (rect: ViewDragImageRect) => ViewDragImageRect): void { if (fn === this._constrainDragImageRectFn) { - this._constrainDragImageRectFn = undefined; + this._constrainDragImageRectFn = null; } } @@ -70,7 +70,7 @@ export class ViewTabDragImageRenderer implements OnDestroy { * or when dragging the view tab into this window if started the drag operation in another window. */ private onWindowDragEnter(event: DragEvent): void { - this._dragData = this._viewDragService.getViewDragData(); + this._dragData = this._viewDragService.getViewDragData()!; const dragPosition = this.computeDragImageRect(this._dragData, event); // create the drag image @@ -93,10 +93,10 @@ export class ViewTabDragImageRenderer implements OnDestroy { * Method invoked while dragging a view over the current window. It is invoked ouside of the Angular zone. */ private onWindowDragOver(event: DragEvent): void { - const dragPosition = this.computeDragImageRect(this._dragData, event); + const dragPosition = this.computeDragImageRect(this._dragData!, event); // update the drag image position - setStyle(this._viewDragImagePortalOutlet.outletElement as HTMLElement, { + setStyle(this._viewDragImagePortalOutlet!.outletElement as HTMLElement, { left: `${dragPosition.x}px`, top: `${dragPosition.y}px`, }); @@ -124,7 +124,7 @@ export class ViewTabDragImageRenderer implements OnDestroy { } private disposeDragImage(): void { - this._viewDragImagePortalOutlet.dispose(); + this._viewDragImagePortalOutlet!.dispose(); this._viewDragImagePortalOutlet = null; this._dragData = null; } @@ -167,7 +167,7 @@ export class ViewTabDragImageRenderer implements OnDestroy { const injector = Injector.create({ parent: this._injector, providers: [ - {provide: WorkbenchView, useValue: new DragImageWorkbenchView(this._dragData)}, + {provide: WorkbenchView, useValue: new DragImageWorkbenchView(this._dragData!)}, {provide: VIEW_TAB_CONTEXT, useValue: 'drag-image'}, ], }); diff --git a/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.component.ts b/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.component.ts index 0f1e333f1..4ebbb84c0 100644 --- a/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.component.ts +++ b/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.component.ts @@ -35,7 +35,7 @@ export class ViewMenuComponent implements OnInit, OnDestroy { } public ngOnInit(): void { - fromEvent(this._overlayRef.backdropElement, 'mousedown') + fromEvent(this._overlayRef.backdropElement!, 'mousedown') .pipe(takeUntil(this._destroy$)) .subscribe(() => this.closeContextMenu()); } diff --git a/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.directive.ts b/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.directive.ts index ca2a991a5..3d89733de 100644 --- a/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.directive.ts +++ b/projects/scion/workbench/src/lib/view-part/view-context-menu/view-menu.directive.ts @@ -1,6 +1,6 @@ import { Directive, EventEmitter, Input, OnDestroy, Optional, Output, TemplateRef } from '@angular/core'; -import { Disposable } from './../../disposable'; -import { WorkbenchMenuItem} from '../../workbench.model'; +import { Disposable } from '../../disposable'; +import { WorkbenchMenuItem } from '../../workbench.model'; import { WorkbenchService } from '../../workbench.service'; import { TemplatePortal } from '@angular/cdk/portal'; import { WorkbenchView } from '../../view/workbench-view.model'; @@ -34,19 +34,19 @@ export class ViewMenuItemDirective implements OnDestroy { * Supported modifiers are 'ctrl', 'shift', 'alt' and 'meta'. */ @Input() - public accelerator: string[]; + public accelerator?: string[]; /** * Allows grouping menu items of the same group. */ @Input() - public group: string; + public group?: string; /** * Allows disabling the menu item based on a condition. */ @Input() - public disabled: boolean; + public disabled = false; /** * Emits when the user performs the menu action, either by clicking the menu or via keyboard accelerator, if any. @@ -67,7 +67,7 @@ export class ViewMenuItemDirective implements OnDestroy { private createMenuItem(view: WorkbenchView): WorkbenchMenuItem { return ({ - portal: new TemplatePortal(this._template, null, {$implicit: view}), + portal: new TemplatePortal(this._template, null!, {$implicit: view}), accelerator: this.accelerator, group: this.group, isDisabled: (): boolean => this.disabled, 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 942faac34..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'; @@ -72,7 +72,7 @@ export class ViewMenuService { const config = new OverlayConfig({ scrollStrategy: this._overlay.scrollStrategies.noop(), hasBackdrop: true, - backdropClass: null, + backdropClass: undefined, disposeOnNavigation: true, positionStrategy: this._overlay.position() .flexibleConnectedTo(location) @@ -103,16 +103,16 @@ export class ViewMenuService { view.menuItems$ .pipe( // skip menu items which have no accelerator configured - filterArray((menuItem: WorkbenchMenuItem) => menuItem.accelerator && menuItem.accelerator.length > 0), + filterArray((menuItem: WorkbenchMenuItem) => !!menuItem.accelerator && menuItem.accelerator.length > 0), // register the menu item keyboard accelerators switchMap((menuItems: WorkbenchMenuItem[]): Observable => { const accelerators$ = menuItems.map(menuItem => { - const key = Arrays.last(menuItem.accelerator); - const modifierKeys = menuItem.accelerator.slice(0, -1); + const key = Arrays.last(menuItem.accelerator!); + const modifierKeys = menuItem.accelerator!.slice(0, -1); return fromEvent(coerceElement(target), 'keydown') .pipe( - filter(event => event.key?.toLowerCase() === key.toLowerCase()), // ignore the shift modifier when comparing the pressed key + filter(event => event.key?.toLowerCase() === key!.toLowerCase()), // ignore the shift modifier when comparing the pressed key filter(event => Arrays.isEqual(modifierKeys, getModifierState(event), {exactOrder: false})), // check the modifier state of the pressed key tap(event => { event.preventDefault(); @@ -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'}; - 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'}; - 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'}; - 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'}; - 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'}; - 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'}; - 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-list-button/view-list-button.component.ts b/projects/scion/workbench/src/lib/view-part/view-list-button/view-list-button.component.ts index 6722ed93e..4209793c8 100644 --- a/projects/scion/workbench/src/lib/view-part/view-list-button/view-list-button.component.ts +++ b/projects/scion/workbench/src/lib/view-part/view-list-button/view-list-button.component.ts @@ -44,7 +44,7 @@ export class ViewListButtonComponent { const config = new OverlayConfig({ scrollStrategy: this._overlay.scrollStrategies.noop(), hasBackdrop: true, - backdropClass: null, + backdropClass: undefined, disposeOnNavigation: true, positionStrategy: this._overlay.position() .flexibleConnectedTo(this._host) diff --git a/projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action-bar.component.ts b/projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action-bar.component.ts index b72af4378..3fbddc1e7 100644 --- a/projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action-bar.component.ts +++ b/projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action-bar.component.ts @@ -43,8 +43,8 @@ export class ViewPartActionBarComponent { } } -function combineAndFilterViewPartActions(align: 'start' | 'end'): OperatorFunction<[WorkbenchViewPartAction[], WorkbenchViewPartAction[], string], WorkbenchViewPartAction[]> { - return map(([localActions, globalActions, activeViewId]: [WorkbenchViewPartAction[], WorkbenchViewPartAction[], string]): WorkbenchViewPartAction[] => { +function combineAndFilterViewPartActions(align: 'start' | 'end'): OperatorFunction<[WorkbenchViewPartAction[], WorkbenchViewPartAction[], string | null], WorkbenchViewPartAction[]> { + return map(([localActions, globalActions, activeViewId]: [WorkbenchViewPartAction[], WorkbenchViewPartAction[], string | null]): WorkbenchViewPartAction[] => { return [...localActions, ...globalActions] .filter(action => (action.align || 'start') === align) .filter(action => !action.viewId || action.viewId === activeViewId); diff --git a/projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action.directive.ts b/projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action.directive.ts index 758b3977f..9a61340cd 100644 --- a/projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action.directive.ts +++ b/projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action.directive.ts @@ -38,7 +38,7 @@ import { asapScheduler } from 'rxjs'; @Directive({selector: 'ng-template[wbViewPartAction]'}) export class ViewPartActionDirective implements OnInit, OnDestroy { - private _action: Disposable; + private _action!: Disposable; @Input() public align: 'start' | 'end' = 'start'; diff --git a/projects/scion/workbench/src/lib/view-part/view-part-bar/view-part-bar.component.html b/projects/scion/workbench/src/lib/view-part/view-part-bar/view-part-bar.component.html index 28e7eefda..9b539aae1 100644 --- a/projects/scion/workbench/src/lib/view-part/view-part-bar/view-part-bar.component.html +++ b/projects/scion/workbench/src/lib/view-part/view-part-bar/view-part-bar.component.html @@ -15,7 +15,7 @@
+ [style.width.px]="dragData!.viewTabWidth">
diff --git a/projects/scion/workbench/src/lib/view-part/view-part-bar/view-part-bar.component.ts b/projects/scion/workbench/src/lib/view-part/view-part-bar/view-part-bar.component.ts index 4df31c18f..b267e6489 100644 --- a/projects/scion/workbench/src/lib/view-part/view-part-bar/view-part-bar.component.ts +++ b/projects/scion/workbench/src/lib/view-part/view-part-bar/view-part-bar.component.ts @@ -37,29 +37,29 @@ export class ViewPartBarComponent implements OnInit, OnDestroy { private _host: HTMLElement; @ViewChildren(ViewTabComponent) - private _viewTabs: QueryList; + private _viewTabs!: QueryList; @ViewChild(SciViewportComponent, {static: true}) - private _viewport: SciViewportComponent; + private _viewport!: SciViewportComponent; @ViewChild(ViewListButtonComponent, {static: true, read: ElementRef}) - private _viewListButtonElement: ElementRef; + private _viewListButtonElement!: ElementRef; /** * Transfer data of the view being dragged over this tabbar. */ - public dragData: ViewDragData; + public dragData: ViewDragData | null = null; /** * Reference to the viewtab over which the user is dragging a viewtab. */ - public dropTargetViewTab: ViewTabComponent; + public dropTargetViewTab: ViewTabComponent | null = null; /** * Reference to the viewtab on which the drag operation started. * This reference is only set if the drag operation started on a viewtab of this tabbar. */ - public dragSourceViewTab: ViewTabComponent; + public dragSourceViewTab: ViewTabComponent | null = null; /** * Indicates if the user is dragging a viewtab over this component. @@ -72,19 +72,19 @@ export class ViewPartBarComponent implements OnInit, OnDestroy { * Indicates if the user is dragging over the viewtab where the drag operation started. */ public get isDragSourceDragOver(): boolean { - return this.dragSourceViewTab && this.dropTargetViewTab === this.dragSourceViewTab; + return !!this.dragSourceViewTab && this.dropTargetViewTab === this.dragSourceViewTab; } /** * Indicates if the user is dragging a viewtab over a valid viewtab drop slot, * which is either when dragging over a viewtab or the tabbar tail. */ - public isTabDropSlotDragOver: boolean; + public isTabDropSlotDragOver = false; /** * Indicates if the user is auto scrolling the viewtabs. */ - public isAutoScroll: boolean; + public isAutoScroll = false; /** * Locks the y-axis of the viewtab drag image to snap it to the tabbar while dragging over. @@ -156,7 +156,7 @@ export class ViewPartBarComponent implements OnInit, OnDestroy { @HostListener('dragstart') public onViewDragStart(): void { this.dragData = this._viewDragService.getViewDragData(); - this.dragSourceViewTab = this.dropTargetViewTab = this._viewTabs.find(viewTab => viewTab.viewId === this.dragData.viewId); + this.dragSourceViewTab = this.dropTargetViewTab = this._viewTabs.find(viewTab => viewTab.viewId === this.dragData!.viewId)!; } /** @@ -172,7 +172,7 @@ export class ViewPartBarComponent implements OnInit, OnDestroy { */ private onTabbarDragEnter(): void { this.dragData = this._viewDragService.getViewDragData(); - setCssVariable(this._host, '--drag-source-width', `${this.dragData.viewTabWidth}px`); + setCssVariable(this._host, '--drag-source-width', `${this.dragData!.viewTabWidth}px`); // Lock the y-axis to snap the view drag image to the view tabbar. this._viewTabDragImageRenderer.setConstrainDragImageRectFn(this.constrainFn); @@ -190,13 +190,13 @@ export class ViewPartBarComponent implements OnInit, OnDestroy { // Determine over which viewtab the user is dragging. this.dropTargetViewTab = this._viewTabs.find(viewTab => { return pointerOffsetX >= viewTab.host.offsetLeft && pointerOffsetX <= viewTab.host.offsetLeft + viewTab.host.offsetWidth; - }); + })!; // Compute if the user is dragging over a viewtab drop slot. A drop slot is some valid drop target within the tabbar, // which is either when dragging over a viewtab or the tabbar tail. const lastViewTab = Arrays.last(this._viewTabs.toArray(), viewTab => viewTab !== this.dragSourceViewTab); const viewTabsWidth = lastViewTab ? (lastViewTab.host.offsetLeft + lastViewTab.host.offsetWidth) : 0; - this.isTabDropSlotDragOver = pointerOffsetX <= viewTabsWidth + this.dragData.viewTabWidth; + this.isTabDropSlotDragOver = pointerOffsetX <= viewTabsWidth + this.dragData!.viewTabWidth; } /** @@ -221,13 +221,13 @@ export class ViewPartBarComponent implements OnInit, OnDestroy { return; } - const dropIndex = this._viewTabs.toArray().indexOf(this.dropTargetViewTab); + const dropIndex = this._viewTabs.toArray().indexOf(this.dropTargetViewTab!); this._viewDragService.dispatchViewMoveEvent({ source: { - appInstanceId: this.dragData.appInstanceId, - partId: this.dragData.partId, - viewId: this.dragData.viewId, - viewUrlSegments: this.dragData.viewUrlSegments, + appInstanceId: this.dragData!.appInstanceId, + partId: this.dragData!.partId, + viewId: this.dragData!.viewId, + viewUrlSegments: this.dragData!.viewUrlSegments, }, target: { appInstanceId: this._workbench.appInstanceId, diff --git a/projects/scion/workbench/src/lib/view-part/view-part.component.ts b/projects/scion/workbench/src/lib/view-part/view-part.component.ts index 92fc94452..2f6f69dee 100644 --- a/projects/scion/workbench/src/lib/view-part/view-part.component.ts +++ b/projects/scion/workbench/src/lib/view-part/view-part.component.ts @@ -26,8 +26,8 @@ export class ViewPartComponent implements OnDestroy { private _destroy$ = new Subject(); - public hasViews: boolean; - public hasActions: boolean; + public hasViews = false; + public hasActions = false; @HostBinding('attr.tabindex') public tabIndex = -1; @@ -79,7 +79,7 @@ export class ViewPartComponent implements OnDestroy { }); } - public get activeViewId$(): Observable { + public get activeViewId$(): Observable { return this._part.activeViewId$; } 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 bc975bd45..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 @@ -43,8 +43,8 @@ export class ViewTabComponent implements OnDestroy { private _context: 'tabbar' | 'tabbar-dropdown'; public host: HTMLElement; - public view: ɵWorkbenchView; - public viewTabContentPortal: ComponentPortal; + public view!: ɵWorkbenchView; + public viewTabContentPortal!: ComponentPortal; @Input() @HostBinding('attr.data-viewid') @@ -125,6 +125,10 @@ export class ViewTabComponent implements OnDestroy { @HostListener('dragstart', ['$event']) public onDragStart(event: DragEvent): void { this._viewPart.activateView(this.viewId).then(() => { + 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}. @@ -132,8 +136,8 @@ export class ViewTabComponent implements OnDestroy { this._viewDragService.setViewDragData({ viewId: this.view.viewId, - viewTitle: this.view.title, - viewHeading: this.view.heading, + viewTitle: this.view.title ?? '', + viewHeading: this.view.heading ?? '', viewClosable: this.view.closable, viewDirty: this.view.dirty, viewUrlSegments: this.view.urlSegments, @@ -150,7 +154,7 @@ export class ViewTabComponent implements OnDestroy { @HostListener('dragend', ['$event']) public onDragEnd(event: DragEvent): void { // Ensure this view stays activated if the user cancels the drag operation. - if (event.dataTransfer.dropEffect === 'none') { + if (event.dataTransfer?.dropEffect === 'none') { this._viewPart.activateView(this.viewId).then(); } this._viewDragService.unsetViewDragData(); @@ -211,7 +215,7 @@ export class ViewTabComponent implements OnDestroy { .pipe( switchMap(() => this.view.cssClasses$), map(cssClasses => differ.diff(cssClasses)), - filter(Boolean), + filter((diff): diff is IterableChanges => diff !== null), takeUntil(this._destroy$), ) .subscribe((diff: IterableChanges) => { diff --git a/projects/scion/workbench/src/lib/view-part/workbench-view-part.model.ts b/projects/scion/workbench/src/lib/view-part/workbench-view-part.model.ts index fbf2a4cd0..c6042fe5e 100644 --- a/projects/scion/workbench/src/lib/view-part/workbench-view-part.model.ts +++ b/projects/scion/workbench/src/lib/view-part/workbench-view-part.model.ts @@ -15,12 +15,12 @@ export abstract class WorkbenchViewPart { /** * Emits the currently active view in this viewpart. */ - public abstract get activeViewId$(): Observable; + public abstract readonly activeViewId$: Observable; /** * The currently active view, if any. */ - public abstract get activeViewId(): string | null; + public abstract readonly activeViewId: string | null; /** * Emits the views opened in this viewpart. @@ -28,9 +28,12 @@ export abstract class WorkbenchViewPart { * 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 abstract get viewIds$(): Observable; + public abstract readonly viewIds$: Observable; - public abstract get viewIds(): string[]; + /** + * The currently opened views in this viewpart. + */ + public abstract readonly viewIds: string[]; /** * Emits the actions of this viewpart. @@ -38,7 +41,7 @@ export abstract class WorkbenchViewPart { * Upon subscription, the actions are emitted, and then emits continuously * when new actions are added or removed. It never completes. */ - public abstract get actions$(): Observable; + public abstract readonly actions$: Observable; /** * Registers an action with this viewpart. 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 e25032b48..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 86c8a5173..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" @@ -21,7 +21,7 @@ import { WorkbenchRouter } from '../routing/workbench-router.service'; export class ɵWorkbenchViewPart implements WorkbenchViewPart { // tslint:disable-line:class-name - private _part: MPart; + private _part!: MPart; private _hiddenViewTabs = new Set(); private _hiddenViewTabs$ = new BehaviorSubject([]); private _layoutService: WorkbenchLayoutService; @@ -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,15 +109,15 @@ 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 { - return (this._layoutService.layout.activePart.partId === this.partId); + return (this._layoutService.layout?.activePart.partId === this.partId); } public setHiddenViewTabs(viewIds: string[]): void { 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 6ad445582..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(); @@ -88,7 +89,7 @@ export class ViewMoveHandler implements OnDestroy { else { const newViewId = this._viewRegistry.computeNextViewOutletIdentity(); this._wbRouter.ɵnavigate(layout => ({ - layout: layout.addView(event.target.partId, newViewId, event.target.insertionIndex), + layout: layout.addView(event.target.partId!, newViewId, event.target.insertionIndex), viewOutlets: {[newViewId]: commands}, })).then(); } @@ -98,7 +99,7 @@ export class ViewMoveHandler implements OnDestroy { const urlTree = await this._wbRouter.createUrlTree(layout => ({ layout: layout.clear(), viewOutlets: layout.parts - .reduce((viewIds, part) => viewIds.concat(part.viewIds), []) + .reduce((viewIds, part) => viewIds.concat(part.viewIds), new Array()) .filter(viewId => viewId !== event.source.viewId) .reduce((acc, viewId) => ({...acc, [viewId]: null}), {}), })); @@ -116,12 +117,12 @@ export class ViewMoveHandler implements OnDestroy { if (addToNewPart) { const newPartId = event.target.newPartId || UUID.randomUUID(); this._wbRouter.ɵnavigate(layout => layout - .addPart(newPartId, {relativeTo: event.target.partId, align: coerceLayoutAlignment(event.target.region)}) + .addPart(newPartId, {relativeTo: event.target.partId!, align: coerceLayoutAlignment(event.target.region!)}) .moveView(event.source.viewId, newPartId), ).then(); } else { - this._wbRouter.ɵnavigate(layout => layout.moveView(event.source.viewId, event.target.partId, event.target.insertionIndex)).then(); + this._wbRouter.ɵnavigate(layout => layout.moveView(event.source.viewId, event.target.partId!, event.target.insertionIndex)).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 ef3c28b78..d04525510 100644 --- a/projects/scion/workbench/src/lib/view/view-portal.pipe.ts +++ b/projects/scion/workbench/src/lib/view/view-portal.pipe.ts @@ -22,7 +22,10 @@ export class ViewPortalPipe implements PipeTransform { constructor(private _viewRegistry: WorkbenchViewRegistry) { } - public transform(viewId: string): WbComponentPortal | null { - return this._viewRegistry.getElseNull(viewId)?.portal; + public transform(viewId: string | null): WbComponentPortal | null { + if (!viewId) { + return null; + } + return this._viewRegistry.getElseNull(viewId)?.portal ?? null; } } diff --git a/projects/scion/workbench/src/lib/view/view.component.ts b/projects/scion/workbench/src/lib/view/view.component.ts index 18d1f7b81..0d91847f5 100644 --- a/projects/scion/workbench/src/lib/view/view.component.ts +++ b/projects/scion/workbench/src/lib/view/view.component.ts @@ -53,7 +53,7 @@ export class ViewComponent implements OnDestroy { } @ViewChild('router_outlet') - public routerOutlet: WbRouterOutletComponent; // specs + public routerOutlet!: WbRouterOutletComponent; // specs @HostBinding('attr.data-viewid') public get viewId(): string { 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 df0d556d4..34d065d64 100644 --- a/projects/scion/workbench/src/lib/view/workbench-view.model.ts +++ b/projects/scion/workbench/src/lib/view/workbench-view.model.ts @@ -1,7 +1,7 @@ import { Observable } from 'rxjs'; import { UrlSegment } from '@angular/router'; import { Disposable } from '../disposable'; -import { WorkbenchMenuItem} from '../workbench.model'; +import { WorkbenchMenuItem } from '../workbench.model'; import { WorkbenchViewPart } from '../view-part/workbench-view-part.model'; /** @@ -13,24 +13,24 @@ export abstract class WorkbenchView { /** * View outlet identity which is unique in this application. */ - public readonly viewId: string; + public abstract readonly viewId: string; /** * The viewpart which contains this view. * * Note: the viewpart of a view can change, e.g. when the view is moved to another viewpart. */ - public readonly part: WorkbenchViewPart; + public abstract readonly part: WorkbenchViewPart | null; /** * Specifies the title to be displayed in the view tab. */ - public title: string; + public abstract title: string | null; /** * Specifies the sub title to be displayed in the view tab. */ - public heading: string; + public abstract heading: string | null; /** * Specifies CSS class(es) added to the and elements, e.g. used for e2e testing. @@ -40,54 +40,61 @@ export abstract class WorkbenchView { /** * Returns CSS classes specified, if any. */ - public abstract get cssClasses(): string[]; + public abstract readonly cssClasses: string[]; /** * Specifies if the content of the current view is dirty. * If dirty, a dirty marker is displayed in the view tab. */ - public dirty: boolean; + public abstract dirty: boolean; /** * Specifies if the view is blocked, e.g., not interactable because of showing a view-modal message box. */ - public blocked: boolean; + public abstract blocked: boolean; /** * Specifies if a close button should be displayed in the view tab. */ - public closable: boolean; + public abstract closable: boolean; /** * Indicates whether this view is the active viewpart view. */ - public abstract get active(): boolean; + public abstract readonly active: boolean; /** * Indicates whether this view is the active viewpart view. * Emits the current state upon subscription. */ - public abstract get active$(): Observable; + public abstract readonly active$: Observable; /** * The position of this view in the tabbar. */ - public readonly position: number; + public abstract readonly position: number; /** * `True` when this view is the first view in the tabbar. */ - public readonly first: boolean; + public abstract readonly first: boolean; /** * `True` when this view is the last view in the tabbar. */ - public readonly last: boolean; + public abstract readonly last: boolean; /** * Indicates whether this view is destroyed. */ - public abstract get destroyed(): boolean; + public abstract readonly destroyed: boolean; + + /** + * Returns the URL segments of this view. + * + * A {@link UrlSegment} is a part of a URL between the two slashes. It contains a path and the matrix parameters associated with the segment. + */ + public abstract readonly urlSegments: UrlSegment[]; /** * Destroys this view (or sibling views) and the associated routed component. @@ -111,13 +118,6 @@ export abstract class WorkbenchView { */ public abstract move(region: 'north' | 'south' | 'west' | 'east' | 'blank-window'): Promise; - /** - * Returns the URL segments of this view. - * - * A {@link UrlSegment} is a part of a URL between the two slashes. It contains a path and the matrix parameters associated with the segment. - */ - public abstract get urlSegments(): UrlSegment[]; - /** * Registers a menu item which is added to the context menu of 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 c5431b4ec..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(); } @@ -68,7 +68,7 @@ export class WorkbenchViewRegistry implements OnDestroy { * Returns the {@link WorkbenchView} of the given identity, or returns `null` if not found. */ public getElseNull(viewId: string): ɵWorkbenchView | null { - return this._viewRegistry.get(viewId) || null; + return this._viewRegistry.get(viewId) ?? null; } public get viewIds(): string[] { diff --git "a/projects/scion/workbench/src/lib/view/\311\265workbench-view.model.ts" "b/projects/scion/workbench/src/lib/view/\311\265workbench-view.model.ts" index 20503ee0b..07adf8ea4 100644 --- "a/projects/scion/workbench/src/lib/view/\311\265workbench-view.model.ts" +++ "b/projects/scion/workbench/src/lib/view/\311\265workbench-view.model.ts" @@ -36,14 +36,14 @@ export class ɵWorkbenchView implements WorkbenchView { // tslint:disable-line:c private readonly _router: Router; private readonly _viewActivationInstantProvider: ViewActivationInstantProvider; - public title: string; - public heading: string; + public title: string | null = null; + public heading: string | null = null; public dirty: boolean; public closable: boolean; - public scrollTop: number | null; - public scrollLeft: number | null; - public activationInstant: number; + public scrollTop = 0; + public scrollLeft = 0; + public activationInstant!: number; public readonly active$: BehaviorSubject; public readonly cssClasses$: BehaviorSubject; @@ -63,6 +63,7 @@ export class ɵWorkbenchView implements WorkbenchView { // tslint:disable-line:c this.active$ = new BehaviorSubject(active); this.cssClasses$ = new BehaviorSubject([]); + this.dirty = false; this.closable = true; this.menuItems$ = combineLatest([this._menuItemProviders$, this._workbench.viewMenuItemProviders$]) @@ -115,7 +116,7 @@ export class ɵWorkbenchView implements WorkbenchView { // tslint:disable-line:c return viewPart; } - const part = this._layoutService.layout.findPartByViewId(this.viewId, {orElseThrow: true}); + const part = this._layoutService.layout!.findPartByViewId(this.viewId, {orElseThrow: true}); return this._viewPartRegistry.getElseThrow(part.partId); } diff --git a/projects/scion/workbench/src/lib/workbench.service.ts b/projects/scion/workbench/src/lib/workbench.service.ts index b8245ed4e..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 f1aedd957..f354ef35c 100644 --- "a/projects/scion/workbench/src/lib/\311\265workbench.service.ts" +++ "b/projects/scion/workbench/src/lib/\311\265workbench.service.ts" @@ -25,11 +25,16 @@ export class ɵWorkbenchService implements WorkbenchService { // tslint:disable- public readonly viewPartActions$ = new BehaviorSubject([]); public readonly viewMenuItemProviders$ = new BehaviorSubject([]); + public readonly views$: Observable; public readonly appInstanceId = UUID.randomUUID(); constructor(private _wbRouter: WorkbenchRouter, private _viewRegistry: WorkbenchViewRegistry, private _layoutService: WorkbenchLayoutService) { + this.views$ = this._layoutService.layout$.pipe(map(layout => layout.parts.reduce( + (viewIds, part) => viewIds.concat(part.viewIds), + new Array())), + ); } public destroyView(...viewIds: string[]): Promise { @@ -41,11 +46,7 @@ export class ɵWorkbenchService implements WorkbenchService { // tslint:disable- } public resolveViewPart(viewId: string): string { - 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), []))); + return this._layoutService.layout!.findPartByViewId(viewId, {orElseThrow: true}).partId; } public getView(viewId: string): WorkbenchView | null { diff --git a/projects/scion/workbench/tsconfig.lib.json b/projects/scion/workbench/tsconfig.lib.json index bbd5213d1..1ba5a2847 100644 --- a/projects/scion/workbench/tsconfig.lib.json +++ b/projects/scion/workbench/tsconfig.lib.json @@ -6,12 +6,19 @@ "declaration": true, "stripInternal": true, "inlineSources": true, - "types": [] + "types": [], + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true }, "angularCompilerOptions": { "skipTemplateCodegen": true, "strictMetadataEmit": true, - "enableResourceInlining": true + "enableResourceInlining": true, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true }, "exclude": [ "src/test.ts", diff --git a/projects/scion/workbench/tslint.json b/projects/scion/workbench/tslint.json index 04677ca61..0af6d3ddf 100644 --- a/projects/scion/workbench/tslint.json +++ b/projects/scion/workbench/tslint.json @@ -1,6 +1,7 @@ { "extends": "../../../tslint.json", "rules": { + "no-non-null-assertion": false, "directive-selector": [ true, "attribute", From 97eab3cbddb2e5f89fb84659a70fd50b3ba8c45f Mon Sep 17 00:00:00 2001 From: mofogasy Date: Fri, 30 Apr 2021 15:48:37 +0200 Subject: [PATCH 2/2] chore(workbench-client): compile with TypeScript strict checks enabled --- .../message-box-opener-page.component.ts | 2 +- .../src/lib/popup/workbench-popup-service.ts | 4 ++-- .../src/lib/popup/workbench-popup.ts | 4 ++-- .../src/lib/routing/workbench-router.ts | 6 +++--- .../src/lib/view/workbench-view.ts | 14 +++++++------- projects/scion/workbench-client/tsconfig.lib.json | 11 +++++++++-- projects/scion/workbench-client/tslint.json | 5 ++++- 7 files changed, 28 insertions(+), 18 deletions(-) diff --git a/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts b/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts index 3ae1aacf3..25a8907f5 100644 --- a/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts +++ b/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts @@ -74,7 +74,7 @@ export class MessageBoxOpenerPageComponent { const currentView = Beans.get(WorkbenchView); const unsetViewContext = (this.form.get(VIEW_CONTEXT).value === false); - unsetViewContext && Beans.register(WorkbenchView, {useValue: undefined}); + unsetViewContext && Beans.register(WorkbenchView, {useValue: null}); try { this._messageBoxService.open({ title: this.form.get(TITLE).value.replace(/\\n/g, '\n') || undefined, // restore line breaks as sanitized by the user agent diff --git a/projects/scion/workbench-client/src/lib/popup/workbench-popup-service.ts b/projects/scion/workbench-client/src/lib/popup/workbench-popup-service.ts index 2faa30e14..31f9f9934 100644 --- a/projects/scion/workbench-client/src/lib/popup/workbench-popup-service.ts +++ b/projects/scion/workbench-client/src/lib/popup/workbench-popup-service.ts @@ -71,7 +71,7 @@ export class WorkbenchPopupService { // To be able to integrate popups from apps without workbench integration, we do not delegate the opening of the popup to // the app that provides the requested popup, but interact with the workbench directly. Nevertheless, we issue an intent // so that the platform throws an error in case of unqualified interaction. - await Beans.get(IntentClient).publish({type: WorkbenchCapabilities.Popup, qualifier, params: Maps.coerce(config.params)}, {...config, anchor: undefined}); + await Beans.get(IntentClient).publish({type: WorkbenchCapabilities.Popup, qualifier, params: Maps.coerce(config.params)}, {...config, anchor: undefined!}); const view = Beans.opt(WorkbenchView); const popupCommand: ɵWorkbenchPopupCommand = { @@ -85,7 +85,7 @@ export class WorkbenchPopupService { ]), context: { viewId: view?.viewId, - capabilityId: await view?.capability$.pipe(map(capability => capability.metadata.id), take(1)).toPromise(), + capabilityId: await view?.capability$.pipe(map(capability => capability.metadata!.id), take(1)).toPromise(), }, }; const popupOriginPublisher = this.observePopupOrigin$(config).subscribe(origin => { diff --git a/projects/scion/workbench-client/src/lib/popup/workbench-popup.ts b/projects/scion/workbench-client/src/lib/popup/workbench-popup.ts index 75088bd18..8fdd1813c 100644 --- a/projects/scion/workbench-client/src/lib/popup/workbench-popup.ts +++ b/projects/scion/workbench-client/src/lib/popup/workbench-popup.ts @@ -45,12 +45,12 @@ export abstract class WorkbenchPopup { /** * Capability that represents the microfrontend loaded into this workbench popup. */ - public readonly capability: WorkbenchPopupCapability; + public abstract readonly capability: WorkbenchPopupCapability; /** * Parameters including qualifier entries as passed for navigation by the popup opener. */ - public readonly params: Map; + public abstract readonly params: Map; /** * Closes the popup. Optionally, pass a result to the popup opener. diff --git a/projects/scion/workbench-client/src/lib/routing/workbench-router.ts b/projects/scion/workbench-client/src/lib/routing/workbench-router.ts index 096ac5e69..19b8d4845 100644 --- a/projects/scion/workbench-client/src/lib/routing/workbench-router.ts +++ b/projects/scion/workbench-client/src/lib/routing/workbench-router.ts @@ -81,7 +81,7 @@ export class WorkbenchRouter { const {intent, capability} = await this.currentNavigation(); return { capabilities: [capability], - qualifier: intent.qualifier, + qualifier: intent.qualifier!, // a view must always be qualified extras: { ...extras, target: 'self', @@ -133,7 +133,7 @@ export class WorkbenchRouter { return { capability: currentCapability, - intent: this.deriveViewIntent(currentCapability.qualifier, currentParams), + intent: this.deriveViewIntent(currentCapability.qualifier!, currentParams), // a view must always be qualified }; } @@ -154,7 +154,7 @@ export class WorkbenchRouter { * Derives the intent that was issued to open the view of the passed capability. */ private deriveViewIntent(capabilityQualifier: Qualifier, params: Map): Intent { - const intentQualifier = Object.entries(capabilityQualifier).reduce((acc, [key, value]) => { + const intentQualifier = Object.entries(capabilityQualifier).reduce((acc, [key, value]) => { if (!params.has(key) && value !== '?') { throw Error(`[ViewContextError] Missing required qualifier param '${key}'.`); } diff --git a/projects/scion/workbench-client/src/lib/view/workbench-view.ts b/projects/scion/workbench-client/src/lib/view/workbench-view.ts index ea23fddb1..8d8bde505 100644 --- a/projects/scion/workbench-client/src/lib/view/workbench-view.ts +++ b/projects/scion/workbench-client/src/lib/view/workbench-view.ts @@ -36,7 +36,7 @@ export abstract class WorkbenchView { /** * Represents the identity of this workbench view. */ - public readonly viewId: string; + public abstract readonly viewId: string; /** * Observable containing the view capability that represents the microfrontend loaded into this workbench view. @@ -46,7 +46,7 @@ export abstract class WorkbenchView { * or navigating to a microfrontend of another app. Consequently, do not forget to unsubscribe from this Observables before * displaying another microfrontend. */ - public readonly capability$: Observable; + public abstract readonly capability$: Observable; /** * Observable containing the parameters including the qualifier as passed for navigation in {@link WorkbenchNavigationExtras.params}. @@ -56,7 +56,7 @@ export abstract class WorkbenchView { * navigating to a microfrontend of another app. Consequently, do not forget to unsubscribe from this Observables before displaying * another microfrontend. */ - public readonly params$: Observable>; + public abstract readonly params$: Observable>; /** * Indicates whether this is the active view in its view part. @@ -66,7 +66,7 @@ export abstract class WorkbenchView { * the view or navigating to a microfrontend of another app. Consequently, do not forget to unsubscribe from this Observables before displaying * another microfrontend. */ - public readonly active$: Observable; + public abstract readonly active$: Observable; /** * Sets the title to be displayed in the view tab. @@ -139,7 +139,7 @@ export class ɵWorkbenchView implements WorkbenchView, PreDestroy { // tslint:di */ private _beforeInAppNavigation$ = new Subject(); private _closingListeners = new Set(); - private _closingSubscription: Subscription; + private _closingSubscription: Subscription | undefined; public active$: Observable; public capability$: Observable; @@ -264,7 +264,7 @@ export class ɵWorkbenchView implements WorkbenchView, PreDestroy { // tslint:di public removeClosingListener(listener: ViewClosingListener): void { if (this._closingListeners.delete(listener) && this._closingListeners.size === 0) { this._closingSubscription?.unsubscribe(); - this._closingSubscription = null; + this._closingSubscription = undefined; } } @@ -405,7 +405,7 @@ function lookupViewCapabilityAndShareReplay(): OperatorFunction latestViewCapabilityId === viewCapability.metadata.id), + filter(viewCapability => latestViewCapabilityId === viewCapability.metadata!.id), ); } diff --git a/projects/scion/workbench-client/tsconfig.lib.json b/projects/scion/workbench-client/tsconfig.lib.json index 857611a86..91776c59d 100644 --- a/projects/scion/workbench-client/tsconfig.lib.json +++ b/projects/scion/workbench-client/tsconfig.lib.json @@ -6,12 +6,19 @@ "declarationMap": true, "stripInternal": true, "inlineSources": true, - "types": [] + "types": [], + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true }, "angularCompilerOptions": { "skipTemplateCodegen": true, "strictMetadataEmit": true, - "enableResourceInlining": true + "enableResourceInlining": true, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true }, "exclude": [ "src/test.ts", diff --git a/projects/scion/workbench-client/tslint.json b/projects/scion/workbench-client/tslint.json index 1f63906f0..91a05652e 100644 --- a/projects/scion/workbench-client/tslint.json +++ b/projects/scion/workbench-client/tslint.json @@ -1,3 +1,6 @@ { - "extends": "../../../tslint.json" + "extends": "../../../tslint.json", + "rules": { + "no-non-null-assertion": false + } }