From ce5089e57ba48f53f17fede4ffe4fa72cf74a01b Mon Sep 17 00:00:00 2001 From: k-genov Date: Tue, 10 Sep 2024 16:29:56 +0200 Subject: [PATCH] feat(workbench/popup): support returning result on focus loss A result can be set on the popup handle that will be returned to the popup opener on focus loss. ```ts import {inject} from '@angular/core'; import {Popup} from '@scion/workbench'; inject(Popup).setResult('result'); ``` BREAKING CHANGE: The method `closeWithError` has been removed from the `Popup` handle. Instead, pass an `Error` object to the `close` method. **Before migration:** ```ts import {inject} from '@angular/core'; import {Popup} from '@scion/workbench'; inject(Popup).closeWithError('some error'); ``` **After migration:** ```ts import {inject} from '@angular/core'; import {Popup} from '@scion/workbench'; inject(Popup).close(new Error('some error')); ``` --- .../app/popup-page/popup-page.component.html | 6 +- .../app/popup-page/popup-page.component.scss | 15 +- .../app/popup-page/popup-page.component.ts | 12 +- .../host-popup-page.component.html | 8 +- .../host-popup-page.component.scss | 15 +- .../host-popup-page.component.ts | 12 +- .../app/popup-page/popup-page.component.html | 6 +- .../app/popup-page/popup-page.component.scss | 15 +- .../app/popup-page/popup-page.component.ts | 12 +- docs/site/howto/how-to-open-popup.md | 2 +- .../workbench-client/host-popup.e2e-spec.ts | 126 +++++++++++---- .../page-object/host-popup-page.po.ts | 16 +- .../page-object/popup-page.po.ts | 14 +- .../src/workbench-client/popup.e2e-spec.ts | 148 +++++++++++++----- .../workbench/page-object/popup-page.po.ts | 14 +- .../src/workbench/popup.e2e-spec.ts | 94 ++++++++--- .../src/lib/popup/workbench-popup.config.ts | 9 +- .../src/lib/popup/workbench-popup.ts | 40 ++--- .../src/lib/\311\265workbench-commands.ts" | 5 + .../microfrontend-host-popup.component.ts | 12 +- .../microfrontend-popup.component.ts | 16 +- .../workbench/src/lib/popup/popup.config.ts | 39 ++--- .../workbench/src/lib/popup/popup.service.ts | 15 +- 23 files changed, 443 insertions(+), 208 deletions(-) diff --git a/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.html b/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.html index 4ec618681..ffba4528c 100644 --- a/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.html +++ b/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.html @@ -126,12 +126,16 @@ +
+ -
diff --git a/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.scss b/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.scss index 7d699350e..99480109b 100644 --- a/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.scss +++ b/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.scss @@ -81,9 +81,16 @@ > div.buttons { flex: none; - display: grid; - grid-template-columns: 1fr 1fr; - column-gap: .25em; - margin-top: 1em; + display: flex; + gap: 2em; + padding-top: 1em; + align-items: center; + justify-content: space-between; + + > label.close-with-error { + display: flex; + align-items: center; + gap: .5em; + } } } diff --git a/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.ts b/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.ts index 09060ce15..1d57a1401 100644 --- a/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.ts +++ b/apps/workbench-client-testing-app/src/app/popup-page/popup-page.component.ts @@ -22,6 +22,7 @@ import {A11yModule} from '@angular/cdk/a11y'; import {SciKeyValueComponent} from '@scion/components.internal/key-value'; import {SciFormFieldComponent} from '@scion/components.internal/form-field'; import {SciAccordionComponent, SciAccordionItemDirective} from '@scion/components.internal/accordion'; +import {SciCheckboxComponent} from '@scion/components.internal/checkbox'; /** * Popup test component which can grow and shrink. @@ -43,6 +44,7 @@ import {SciAccordionComponent, SciAccordionItemDirective} from '@scion/component SciAccordionItemDirective, SciKeyValueComponent, ReactiveFormsModule, + SciCheckboxComponent, ], }) export default class PopupPageComponent { @@ -91,6 +93,7 @@ export default class PopupPageComponent { minWidth: this._formBuilder.control('100vw'), width: this._formBuilder.control(''), maxWidth: this._formBuilder.control(''), + closeWithError: this._formBuilder.control(false), result: this._formBuilder.control(''), }); @@ -107,11 +110,12 @@ export default class PopupPageComponent { popup.signalReady(); } - public onClose(): void { - this.popup.close(this.form.controls.result.value); + protected onApplyReturnValue(): void { + this.popup.setResult(this.form.controls.result.value); } - public onCloseWithError(): void { - this.popup.closeWithError(this.form.controls.result.value); + protected onClose(): void { + const result = this.form.controls.closeWithError.value ? new Error(this.form.controls.result.value) : this.form.controls.result.value; + this.popup.close(result); } } diff --git a/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.html b/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.html index 49dbe37aa..7ce38fc4a 100644 --- a/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.html +++ b/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.html @@ -104,12 +104,16 @@ +
- - + +
diff --git a/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.scss b/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.scss index 79ef8a094..871016287 100644 --- a/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.scss +++ b/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.scss @@ -77,9 +77,16 @@ > div.buttons { flex: none; - display: grid; - grid-template-columns: 1fr 1fr; - column-gap: .25em; - margin-top: 1em; + display: flex; + gap: 2em; + padding-top: 1em; + align-items: center; + justify-content: space-between; + + > label.close-with-error { + display: flex; + align-items: center; + gap: .5em; + } } } diff --git a/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.ts b/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.ts index 12758c83e..54798f94b 100644 --- a/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.ts +++ b/apps/workbench-testing-app/src/app/host-popup-page/host-popup-page.component.ts @@ -20,6 +20,7 @@ import {NullIfEmptyPipe} from '../common/null-if-empty.pipe'; import {SciKeyValueComponent} from '@scion/components.internal/key-value'; import {SciFormFieldComponent} from '@scion/components.internal/form-field'; import {SciAccordionComponent, SciAccordionItemDirective} from '@scion/components.internal/accordion'; +import {SciCheckboxComponent} from '@scion/components.internal/checkbox'; /** * Popup component provided by the host app via a popup capability. @@ -41,6 +42,7 @@ import {SciAccordionComponent, SciAccordionItemDirective} from '@scion/component SciAccordionItemDirective, SciKeyValueComponent, ReactiveFormsModule, + SciCheckboxComponent, ], }) export default class HostPopupPageComponent { @@ -84,6 +86,7 @@ export default class HostPopupPageComponent { minWidth: this._formBuilder.control(''), width: this._formBuilder.control(''), maxWidth: this._formBuilder.control(''), + closeWithError: this._formBuilder.control(false), result: this._formBuilder.control(''), }); @@ -92,11 +95,12 @@ export default class HostPopupPageComponent { private _formBuilder: NonNullableFormBuilder) { } - public onClose(): void { - this.popup.close(this.form.controls.result.value); + protected onApplyReturnValue(): void { + this.popup.setResult(this.form.controls.result.value); } - public onCloseWithError(): void { - this.popup.closeWithError(this.form.controls.result.value); + protected onClose(): void { + const result = this.form.controls.closeWithError.value ? new Error(this.form.controls.result.value) : this.form.controls.result.value; + this.popup.close(result); } } diff --git a/apps/workbench-testing-app/src/app/popup-page/popup-page.component.html b/apps/workbench-testing-app/src/app/popup-page/popup-page.component.html index 8b27c4088..b183f3524 100644 --- a/apps/workbench-testing-app/src/app/popup-page/popup-page.component.html +++ b/apps/workbench-testing-app/src/app/popup-page/popup-page.component.html @@ -60,12 +60,16 @@ +
+ -
diff --git a/apps/workbench-testing-app/src/app/popup-page/popup-page.component.scss b/apps/workbench-testing-app/src/app/popup-page/popup-page.component.scss index f6f1978d2..b358f688c 100644 --- a/apps/workbench-testing-app/src/app/popup-page/popup-page.component.scss +++ b/apps/workbench-testing-app/src/app/popup-page/popup-page.component.scss @@ -41,9 +41,16 @@ > div.buttons { flex: none; - display: grid; - grid-template-columns: 1fr 1fr; - column-gap: .25em; - margin-top: 1em; + display: flex; + gap: 1em; + padding-top: 1em; + align-items: center; + justify-content: space-between; + + > label.close-with-error { + display: flex; + align-items: center; + gap: .5em; + } } } diff --git a/apps/workbench-testing-app/src/app/popup-page/popup-page.component.ts b/apps/workbench-testing-app/src/app/popup-page/popup-page.component.ts index 65a0a1323..96bdb15ec 100644 --- a/apps/workbench-testing-app/src/app/popup-page/popup-page.component.ts +++ b/apps/workbench-testing-app/src/app/popup-page/popup-page.component.ts @@ -17,6 +17,7 @@ import {NullIfEmptyPipe} from '../common/null-if-empty.pipe'; import {JsonPipe} from '@angular/common'; import {SciFormFieldComponent} from '@scion/components.internal/form-field'; import {SciAccordionComponent, SciAccordionItemDirective} from '@scion/components.internal/accordion'; +import {SciCheckboxComponent} from '@scion/components.internal/checkbox'; @Component({ selector: 'app-popup-page', @@ -32,6 +33,7 @@ import {SciAccordionComponent, SciAccordionItemDirective} from '@scion/component SciAccordionComponent, SciAccordionItemDirective, ReactiveFormsModule, + SciCheckboxComponent, ], }) export class PopupPageComponent { @@ -75,17 +77,19 @@ export class PopupPageComponent { minWidth: this._formBuilder.control(''), width: this._formBuilder.control(''), maxWidth: this._formBuilder.control(''), + closeWithError: this._formBuilder.control(false), result: this._formBuilder.control(''), }); constructor(public popup: Popup, private _formBuilder: NonNullableFormBuilder) { } - public onClose(): void { - this.popup.close(this.form.controls.result.value); + protected onApplyReturnValue(): void { + this.popup.setResult(this.form.controls.result.value); } - public onCloseWithError(): void { - this.popup.closeWithError(this.form.controls.result.value); + protected onClose(): void { + const result = this.form.controls.closeWithError.value ? new Error(this.form.controls.result.value) : this.form.controls.result.value; + this.popup.close(result); } } diff --git a/docs/site/howto/how-to-open-popup.md b/docs/site/howto/how-to-open-popup.md index 6f0840811..0c9ebc55c 100644 --- a/docs/site/howto/how-to-open-popup.md +++ b/docs/site/howto/how-to-open-popup.md @@ -24,7 +24,7 @@ const result = await popupService.open({ }); ``` -To interact with the popup in the popup component, inject the popup handle `Popup`, e.g., to close the popup or read input passed to the popup by the popup opener. +To interact with the popup in the popup component, inject the popup handle `Popup`, e.g., to read input passed to the popup or to close the popup, optionally passing a result to the popup opener. ```typescript diff --git a/projects/scion/e2e-testing/src/workbench-client/host-popup.e2e-spec.ts b/projects/scion/e2e-testing/src/workbench-client/host-popup.e2e-spec.ts index 386337e85..447050d3c 100644 --- a/projects/scion/e2e-testing/src/workbench-client/host-popup.e2e-spec.ts +++ b/projects/scion/e2e-testing/src/workbench-client/host-popup.e2e-spec.ts @@ -39,46 +39,114 @@ test.describe('Workbench Host Popup', () => { await expectPopup(popupPage).toBeVisible(); }); - test('should allow closing the popup and returning a value to the popup opener', async ({appPO, microfrontendNavigator}) => { - await appPO.navigateTo({microfrontendSupport: true}); + test.describe('popup result', () => { + test('should allow closing the popup and returning a value to the popup opener', async ({appPO, microfrontendNavigator}) => { + await appPO.navigateTo({microfrontendSupport: true}); - // TODO [#271]: Register popup capability in the host app via RegisterWorkbenchCapabilityPagePO when implemented the issue #271 - // https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/271 + // TODO [#271]: Register popup capability in the host app via RegisterWorkbenchCapabilityPagePO when implemented the issue #271 + // https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/271 - await microfrontendNavigator.registerIntention('app1', {type: 'popup', qualifier: {component: 'host-popup'}}); + await microfrontendNavigator.registerIntention('app1', {type: 'popup', qualifier: {component: 'host-popup'}}); - // open the popup - const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); - await popupOpenerPage.enterQualifier({component: 'host-popup'}); - await popupOpenerPage.enterCssClass('testee'); - await popupOpenerPage.open(); + // open the popup + const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); + await popupOpenerPage.enterQualifier({component: 'host-popup'}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); - const popup = appPO.popup({cssClass: 'testee'}); - const popupPage = new HostPopupPagePO(popup); + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new HostPopupPagePO(popup); - await popupPage.close({returnValue: 'RETURN VALUE'}); - await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE'); - }); + await popupPage.close({returnValue: 'RETURN VALUE'}); + await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE'); + }); - test('should allow closing the popup with an error', async ({appPO, microfrontendNavigator}) => { - await appPO.navigateTo({microfrontendSupport: true}); + test('should allow closing the popup with an error', async ({appPO, microfrontendNavigator}) => { + await appPO.navigateTo({microfrontendSupport: true}); - // TODO [#271]: Register popup capability in the host app via RegisterWorkbenchCapabilityPagePO when implemented the issue #271 - // https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/271 + // TODO [#271]: Register popup capability in the host app via RegisterWorkbenchCapabilityPagePO when implemented the issue #271 + // https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/271 - await microfrontendNavigator.registerIntention('app1', {type: 'popup', qualifier: {component: 'host-popup'}}); + await microfrontendNavigator.registerIntention('app1', {type: 'popup', qualifier: {component: 'host-popup'}}); - // open the popup - const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); - await popupOpenerPage.enterQualifier({component: 'host-popup'}); - await popupOpenerPage.enterCssClass('testee'); - await popupOpenerPage.open(); + // open the popup + const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); + await popupOpenerPage.enterQualifier({component: 'host-popup'}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); - const popup = appPO.popup({cssClass: 'testee'}); - const popupPage = new HostPopupPagePO(popup); + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new HostPopupPagePO(popup); - await popupPage.close({returnValue: 'ERROR', closeWithError: true}); - await expect(popupOpenerPage.error).toHaveText('ERROR'); + await popupPage.close({returnValue: 'ERROR', closeWithError: true}); + await expect(popupOpenerPage.error).toHaveText('ERROR'); + }); + + test('should allow returning value on focus loss', async ({appPO, microfrontendNavigator}) => { + await appPO.navigateTo({microfrontendSupport: true}); + + // TODO [#271]: Register popup capability in the host app via RegisterWorkbenchCapabilityPagePO when implemented the issue #271 + // https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/271 + + await microfrontendNavigator.registerIntention('app1', {type: 'popup', qualifier: {component: 'host-popup'}}); + + // open the popup + const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); + await popupOpenerPage.enterQualifier({component: 'host-popup'}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); + + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new HostPopupPagePO(popup); + await popupPage.enterReturnValue('RETURN VALUE', {apply: true}); + + await popupOpenerPage.view.tab.click(); + await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE'); + }); + + test('should return only the latest result value on close', async ({appPO, microfrontendNavigator}) => { + await appPO.navigateTo({microfrontendSupport: true}); + + // TODO [#271]: Register popup capability in the host app via RegisterWorkbenchCapabilityPagePO when implemented the issue #271 + // https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/271 + + await microfrontendNavigator.registerIntention('app1', {type: 'popup', qualifier: {component: 'host-popup'}}); + + // open the popup + const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); + await popupOpenerPage.enterQualifier({component: 'host-popup'}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); + + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new HostPopupPagePO(popup); + await popupPage.enterReturnValue('RETURN VALUE 1', {apply: true}); + + await popupPage.close({returnValue: 'RETURN VALUE 2'}); + await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE 2'); + }); + + test('should not return value on escape keystroke', async ({appPO, microfrontendNavigator, page}) => { + await appPO.navigateTo({microfrontendSupport: true}); + + // TODO [#271]: Register popup capability in the host app via RegisterWorkbenchCapabilityPagePO when implemented the issue #271 + // https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/271 + + await microfrontendNavigator.registerIntention('app1', {type: 'popup', qualifier: {component: 'host-popup'}}); + + // open the popup + const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); + await popupOpenerPage.enterQualifier({component: 'host-popup'}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); + + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new HostPopupPagePO(popup); + await popupPage.enterReturnValue('RETURN VALUE', {apply: true}); + + await page.keyboard.press('Escape'); + await expect(popupOpenerPage.returnValue).not.toBeAttached(); + }); }); test('should stick to the popup anchor', async ({appPO, microfrontendNavigator}) => { diff --git a/projects/scion/e2e-testing/src/workbench-client/page-object/host-popup-page.po.ts b/projects/scion/e2e-testing/src/workbench-client/page-object/host-popup-page.po.ts index bef396b4e..4c86a1a29 100644 --- a/projects/scion/e2e-testing/src/workbench-client/page-object/host-popup-page.po.ts +++ b/projects/scion/e2e-testing/src/workbench-client/page-object/host-popup-page.po.ts @@ -17,6 +17,7 @@ import {SciAccordionPO} from '../../@scion/components.internal/accordion.po'; import {SciKeyValuePO} from '../../@scion/components.internal/key-value.po'; import {Locator} from '@playwright/test'; import {WorkbenchPopupPagePO} from '../../workbench/page-object/workbench-popup-page.po'; +import {SciCheckboxPO} from '../../@scion/components.internal/checkbox.po'; /** * Page object to interact with {@link HostPopupPageComponent}. @@ -92,11 +93,15 @@ export class HostPopupPagePO implements WorkbenchPopupPagePO { await this.locator.locator('input.e2e-max-height').fill(size.maxHeight ?? ''); } - public async enterReturnValue(returnValue: string): Promise { + public async enterReturnValue(returnValue: string, options?: {apply?: boolean}): Promise { const accordion = new SciAccordionPO(this.locator.locator('sci-accordion.e2e-return-value')); await accordion.expand(); try { await accordion.itemLocator().locator('input.e2e-return-value').fill(returnValue); + + if (options?.apply) { + await this.locator.locator('button.e2e-apply-return-value').click(); + } } finally { await accordion.collapse(); @@ -108,11 +113,10 @@ export class HostPopupPagePO implements WorkbenchPopupPagePO { await this.enterReturnValue(options.returnValue); } - if (options?.closeWithError === true) { - await this.locator.locator('button.e2e-close-with-error').click(); - } - else { - await this.locator.locator('button.e2e-close').click(); + if (options?.closeWithError) { + await new SciCheckboxPO(this.locator.locator('sci-checkbox.e2e-close-with-error')).toggle(true); } + + await this.locator.locator('button.e2e-close').click(); } } diff --git a/projects/scion/e2e-testing/src/workbench-client/page-object/popup-page.po.ts b/projects/scion/e2e-testing/src/workbench-client/page-object/popup-page.po.ts index ceb05cd6d..b393b55f8 100644 --- a/projects/scion/e2e-testing/src/workbench-client/page-object/popup-page.po.ts +++ b/projects/scion/e2e-testing/src/workbench-client/page-object/popup-page.po.ts @@ -19,6 +19,7 @@ import {SciKeyValuePO} from '../../@scion/components.internal/key-value.po'; import {SciRouterOutletPO} from './sci-router-outlet.po'; import {MicrofrontendPopupPagePO} from '../../workbench/page-object/workbench-popup-page.po'; import {AppPO} from '../../app.po'; +import {SciCheckboxPO} from '../../@scion/components.internal/checkbox.po'; /** * Page object to interact with {@link PopupPageComponent}. @@ -117,11 +118,15 @@ export class PopupPagePO implements MicrofrontendPopupPagePO { await this.locator.locator('input.e2e-max-height').fill(size.maxHeight ?? ''); } - public async enterReturnValue(returnValue: string): Promise { + public async enterReturnValue(returnValue: string, options?: {apply?: boolean}): Promise { const accordion = new SciAccordionPO(this.locator.locator('sci-accordion.e2e-return-value')); await accordion.expand(); try { await accordion.itemLocator().locator('input.e2e-return-value').fill(returnValue); + + if (options?.apply) { + await this.locator.locator('button.e2e-apply-return-value').click(); + } } finally { await accordion.collapse(); @@ -134,11 +139,10 @@ export class PopupPagePO implements MicrofrontendPopupPagePO { } if (options?.closeWithError) { - await this.locator.locator('button.e2e-close-with-error').click(); - } - else { - await this.locator.locator('button.e2e-close').click(); + await new SciCheckboxPO(this.locator.locator('sci-checkbox.e2e-close-with-error')).toggle(true); } + + await this.locator.locator('button.e2e-close').click(); } /** diff --git a/projects/scion/e2e-testing/src/workbench-client/popup.e2e-spec.ts b/projects/scion/e2e-testing/src/workbench-client/popup.e2e-spec.ts index 51051f0ee..9c5821f09 100644 --- a/projects/scion/e2e-testing/src/workbench-client/popup.e2e-spec.ts +++ b/projects/scion/e2e-testing/src/workbench-client/popup.e2e-spec.ts @@ -151,52 +151,130 @@ test.describe('Workbench Popup', () => { await expect.poll(() => popup.getAlign()).toEqual('west'); }); - test('should allow closing the popup with a return value', async ({appPO, microfrontendNavigator}) => { - await appPO.navigateTo({microfrontendSupport: true}); + test.describe('popup result', () => { + test('should allow closing the popup with a return value', async ({appPO, microfrontendNavigator}) => { + await appPO.navigateTo({microfrontendSupport: true}); - await microfrontendNavigator.registerCapability('app1', { - type: 'popup', - qualifier: {component: 'testee'}, - properties: { - path: 'test-popup', - }, + await microfrontendNavigator.registerCapability('app1', { + type: 'popup', + qualifier: {component: 'testee'}, + properties: { + path: 'test-popup', + }, + }); + + // open the popup + const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); + await popupOpenerPage.enterQualifier({component: 'testee'}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); + + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new PopupPagePO(popup); + + await popupPage.close({returnValue: 'RETURN VALUE'}); + await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE'); }); - // open the popup - const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); - await popupOpenerPage.enterQualifier({component: 'testee'}); - await popupOpenerPage.enterCssClass('testee'); - await popupOpenerPage.open(); + test('should allow closing the popup with an error', async ({appPO, microfrontendNavigator}) => { + await appPO.navigateTo({microfrontendSupport: true}); - const popup = appPO.popup({cssClass: 'testee'}); - const popupPage = new PopupPagePO(popup); + await microfrontendNavigator.registerCapability('app1', { + type: 'popup', + qualifier: {component: 'testee'}, + properties: { + path: 'test-popup', + }, + }); - await popupPage.close({returnValue: 'RETURN VALUE'}); - await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE'); - }); + // open the popup + const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); + await popupOpenerPage.enterQualifier({component: 'testee'}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); - test('should allow closing the popup with an error', async ({appPO, microfrontendNavigator}) => { - await appPO.navigateTo({microfrontendSupport: true}); + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new PopupPagePO(popup); - await microfrontendNavigator.registerCapability('app1', { - type: 'popup', - qualifier: {component: 'testee'}, - properties: { - path: 'test-popup', - }, + await popupPage.close({returnValue: 'ERROR', closeWithError: true}); + await expect(popupOpenerPage.error).toHaveText('ERROR'); }); - // open the popup - const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); - await popupOpenerPage.enterQualifier({component: 'testee'}); - await popupOpenerPage.enterCssClass('testee'); - await popupOpenerPage.open(); + test('should allow returning value on focus loss', async ({appPO, microfrontendNavigator}) => { + await appPO.navigateTo({microfrontendSupport: true}); - const popup = appPO.popup({cssClass: 'testee'}); - const popupPage = new PopupPagePO(popup); + await microfrontendNavigator.registerCapability('app1', { + type: 'popup', + qualifier: {component: 'testee'}, + properties: { + path: 'test-popup', + }, + }); + + // open the popup + const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); + await popupOpenerPage.enterQualifier({component: 'testee'}); + await popupOpenerPage.enterCloseStrategy({closeOnFocusLost: true}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); + + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new PopupPagePO(popup); + await popupPage.enterReturnValue('RETURN VALUE', {apply: true}); + + await popupOpenerPage.view.tab.click(); + await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE'); + }); + + test('should return only the latest result value on close', async ({appPO, microfrontendNavigator}) => { + await appPO.navigateTo({microfrontendSupport: true}); + + await microfrontendNavigator.registerCapability('app1', { + type: 'popup', + qualifier: {component: 'testee'}, + properties: { + path: 'test-popup', + }, + }); - await popupPage.close({returnValue: 'ERROR', closeWithError: true}); - await expect(popupOpenerPage.error).toHaveText('ERROR'); + // open the popup + const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); + await popupOpenerPage.enterQualifier({component: 'testee'}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); + + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new PopupPagePO(popup); + await popupPage.enterReturnValue('RETURN VALUE 1', {apply: true}); + + await popupPage.close({returnValue: 'RETURN VALUE 2'}); + await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE 2'); + }); + + test('should not return value on escape keystroke', async ({appPO, microfrontendNavigator, page}) => { + await appPO.navigateTo({microfrontendSupport: true}); + + await microfrontendNavigator.registerCapability('app1', { + type: 'popup', + qualifier: {component: 'testee'}, + properties: { + path: 'test-popup', + }, + }); + + // open the popup + const popupOpenerPage = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1'); + await popupOpenerPage.enterQualifier({component: 'testee'}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); + + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new PopupPagePO(popup); + await popupPage.enterReturnValue('RETURN VALUE', {apply: true}); + + await page.keyboard.press('Escape'); + await expect(popupOpenerPage.returnValue).not.toBeAttached(); + }); }); test('should stick to the popup anchor', async ({appPO, microfrontendNavigator}) => { diff --git a/projects/scion/e2e-testing/src/workbench/page-object/popup-page.po.ts b/projects/scion/e2e-testing/src/workbench/page-object/popup-page.po.ts index 6cd1bb674..7be0aef97 100644 --- a/projects/scion/e2e-testing/src/workbench/page-object/popup-page.po.ts +++ b/projects/scion/e2e-testing/src/workbench/page-object/popup-page.po.ts @@ -14,6 +14,7 @@ import {PopupReferrer, PopupSize} from '@scion/workbench'; import {SciAccordionPO} from '../../@scion/components.internal/accordion.po'; import {Locator} from '@playwright/test'; import {WorkbenchPopupPagePO} from './workbench-popup-page.po'; +import {SciCheckboxPO} from '../../@scion/components.internal/checkbox.po'; /** * Page object to interact with {@link PopupPageComponent}. @@ -47,18 +48,21 @@ export class PopupPagePO implements WorkbenchPopupPagePO { } if (options?.closeWithError) { - await this.locator.locator('button.e2e-close-with-error').click(); - } - else { - await this.locator.locator('button.e2e-close').click(); + await new SciCheckboxPO(this.locator.locator('sci-checkbox.e2e-close-with-error')).toggle(true); } + + await this.locator.locator('button.e2e-close').click(); } - public async enterReturnValue(returnValue: string): Promise { + public async enterReturnValue(returnValue: string, options?: {apply?: boolean}): Promise { const accordion = new SciAccordionPO(this.locator.locator('sci-accordion.e2e-return-value')); await accordion.expand(); try { await accordion.itemLocator().locator('input.e2e-return-value').fill(returnValue); + + if (options?.apply) { + await this.locator.locator('button.e2e-apply-return-value').click(); + } } finally { await accordion.collapse(); diff --git a/projects/scion/e2e-testing/src/workbench/popup.e2e-spec.ts b/projects/scion/e2e-testing/src/workbench/popup.e2e-spec.ts index a2a770cc7..c55cbb938 100644 --- a/projects/scion/e2e-testing/src/workbench/popup.e2e-spec.ts +++ b/projects/scion/e2e-testing/src/workbench/popup.e2e-spec.ts @@ -263,34 +263,86 @@ test.describe('Workbench Popup', () => { await expect(popupPage.input).toHaveText('TEST INPUT'); }); - test('should allow closing the popup and returning a value to the popup opener', async ({appPO, workbenchNavigator}) => { - await appPO.navigateTo({microfrontendSupport: false}); + test.describe('popup result', () => { + test('should allow closing the popup and returning a value to the popup opener', async ({appPO, workbenchNavigator}) => { + await appPO.navigateTo({microfrontendSupport: false}); - const popupOpenerPage = await workbenchNavigator.openInNewTab(PopupOpenerPagePO); - await popupOpenerPage.selectPopupComponent('popup-page'); - await popupOpenerPage.enterCssClass('testee'); - await popupOpenerPage.open(); + const popupOpenerPage = await workbenchNavigator.openInNewTab(PopupOpenerPagePO); + await popupOpenerPage.selectPopupComponent('popup-page'); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); - const popup = appPO.popup({cssClass: 'testee'}); - const popupPage = new PopupPagePO(popup); + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new PopupPagePO(popup); - await popupPage.close({returnValue: 'RETURN VALUE'}); - await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE'); - }); + await popupPage.close({returnValue: 'RETURN VALUE'}); + await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE'); + }); - test('should allow closing the popup with an error', async ({appPO, workbenchNavigator}) => { - await appPO.navigateTo({microfrontendSupport: false}); + test('should allow closing the popup with an error', async ({appPO, workbenchNavigator}) => { + await appPO.navigateTo({microfrontendSupport: false}); - const popupOpenerPage = await workbenchNavigator.openInNewTab(PopupOpenerPagePO); - await popupOpenerPage.selectPopupComponent('popup-page'); - await popupOpenerPage.enterCssClass('testee'); - await popupOpenerPage.open(); + const popupOpenerPage = await workbenchNavigator.openInNewTab(PopupOpenerPagePO); + await popupOpenerPage.selectPopupComponent('popup-page'); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); - const popup = appPO.popup({cssClass: 'testee'}); - const popupPage = new PopupPagePO(popup); + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new PopupPagePO(popup); + + await popupPage.close({returnValue: 'ERROR', closeWithError: true}); + await expect(popupOpenerPage.error).toHaveText('ERROR'); + }); - await popupPage.close({returnValue: 'ERROR', closeWithError: true}); - await expect(popupOpenerPage.error).toHaveText('ERROR'); + test('should allow returning value on focus loss', async ({appPO, workbenchNavigator}) => { + await appPO.navigateTo({microfrontendSupport: false}); + + const popupOpenerPage = await workbenchNavigator.openInNewTab(PopupOpenerPagePO); + await popupOpenerPage.selectPopupComponent('popup-page'); + await popupOpenerPage.enterCloseStrategy({closeOnFocusLost: true}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); + + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new PopupPagePO(popup); + await popupPage.enterReturnValue('RETURN VALUE', {apply: true}); + + await popupOpenerPage.view.tab.click(); + await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE'); + }); + + test('should return only the latest result value on close', async ({appPO, workbenchNavigator}) => { + await appPO.navigateTo({microfrontendSupport: false}); + + const popupOpenerPage = await workbenchNavigator.openInNewTab(PopupOpenerPagePO); + await popupOpenerPage.selectPopupComponent('popup-page'); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); + + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new PopupPagePO(popup); + await popupPage.enterReturnValue('RETURN VALUE 1', {apply: true}); + + await popupPage.close({returnValue: 'RETURN VALUE 2'}); + await expect(popupOpenerPage.returnValue).toHaveText('RETURN VALUE 2'); + }); + + test('should not return value on escape keystroke', async ({appPO, workbenchNavigator, page}) => { + await appPO.navigateTo({microfrontendSupport: false}); + + const popupOpenerPage = await workbenchNavigator.openInNewTab(PopupOpenerPagePO); + await popupOpenerPage.selectPopupComponent('popup-page'); + await popupOpenerPage.enterCloseStrategy({closeOnEscape: true}); + await popupOpenerPage.enterCssClass('testee'); + await popupOpenerPage.open(); + + const popup = appPO.popup({cssClass: 'testee'}); + const popupPage = new PopupPagePO(popup); + await popupPage.enterReturnValue('RETURN VALUE', {apply: true}); + + await page.keyboard.press('Escape'); + await expect(popupOpenerPage.returnValue).not.toBeAttached(); + }); }); test('should associate popup with specified CSS class(es) ', async ({appPO, workbenchNavigator}) => { diff --git a/projects/scion/workbench-client/src/lib/popup/workbench-popup.config.ts b/projects/scion/workbench-client/src/lib/popup/workbench-popup.config.ts index 4e42c6966..9de4068b2 100644 --- a/projects/scion/workbench-client/src/lib/popup/workbench-popup.config.ts +++ b/projects/scion/workbench-client/src/lib/popup/workbench-popup.config.ts @@ -83,14 +83,13 @@ export interface WorkbenchPopupConfig { */ export interface CloseStrategy { /** - * If `true`, which is by default, will close the popup on focus loss. - * No return value will be passed to the popup opener. + * Controls if to close the popup on focus loss, returning the result set via {@link Popup#setResult} to the popup opener. + * Defaults to `true`. */ onFocusLost?: boolean; /** - * If `true`, which is by default, will close the popup when the user - * hits the escape key. No return value will be passed to the popup - * opener. + * Controls if to close the popup when pressing escape. Defaults to `true`. + * No return value will be passed to the popup opener. */ onEscape?: boolean; } 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 f8a6b5057..403e305af 100644 --- a/projects/scion/workbench-client/src/lib/popup/workbench-popup.ts +++ b/projects/scion/workbench-client/src/lib/popup/workbench-popup.ts @@ -37,7 +37,7 @@ import {WorkbenchPopupReferrer} from './workbench-popup-referrer'; * * @category Popup */ -export abstract class WorkbenchPopup { +export abstract class WorkbenchPopup { /** * Capability that represents the microfrontend loaded into this workbench popup. @@ -64,20 +64,20 @@ export abstract class WorkbenchPopup { public abstract readonly params: Map; /** - * Closes the popup. Optionally, pass a result to the popup opener. + * Sets a result that will be passed to the popup opener when the popup is closed on focus loss {@link CloseStrategy#onFocusLost}. */ - public abstract close(result?: R | undefined): void; + public abstract setResult(result?: R): void; /** - * Closes the popup returning the given error to the popup opener. + * Closes the popup. Optionally, pass a result or an error to the popup opener. */ - public abstract closeWithError(error: Error | string): void; + public abstract close(result?: R | Error): void; } /** * @ignore */ -export class ɵWorkbenchPopup implements WorkbenchPopup { +export class ɵWorkbenchPopup implements WorkbenchPopup { public params: Map; public capability: WorkbenchPopupCapability; @@ -100,17 +100,21 @@ export class ɵWorkbenchPopup implements WorkbenchPopup { /** * @inheritDoc */ - public close(result?: R | undefined): void { - Beans.get(MessageClient).publish(ɵWorkbenchCommands.popupCloseTopic(this._context.popupId), result).then(); + public setResult(result?: R): void { + Beans.get(MessageClient).publish(ɵWorkbenchCommands.popupResultTopic(this._context.popupId), result).then(); } /** * @inheritDoc */ - public closeWithError(error: Error | string): void { - Beans.get(MessageClient).publish(ɵWorkbenchCommands.popupCloseTopic(this._context.popupId), readErrorMessage(error), { - headers: new Map().set(ɵWorkbenchPopupMessageHeaders.CLOSE_WITH_ERROR, true), - }).then(); + public async close(result?: R | Error): Promise { + if (result instanceof Error) { + const headers = new Map().set(ɵWorkbenchPopupMessageHeaders.CLOSE_WITH_ERROR, true); + Beans.get(MessageClient).publish(ɵWorkbenchCommands.popupCloseTopic(this._context.popupId), result.message, {headers}).then(); + } + else { + Beans.get(MessageClient).publish(ɵWorkbenchCommands.popupCloseTopic(this._context.popupId), result).then(); + } } /** @@ -151,15 +155,3 @@ export class ɵWorkbenchPopup implements WorkbenchPopup { export enum ɵWorkbenchPopupMessageHeaders { CLOSE_WITH_ERROR = 'ɵWORKBENCH-POPUP:CLOSE_WITH_ERROR', } - -/** - * Returns the error message if given an error object, or the `toString` representation otherwise. - * - * @internal - */ -function readErrorMessage(error: any): string { - if (error instanceof Error) { - return error.message; - } - return error?.toString(); -} diff --git "a/projects/scion/workbench-client/src/lib/\311\265workbench-commands.ts" "b/projects/scion/workbench-client/src/lib/\311\265workbench-commands.ts" index 8a4352818..8564ceebc 100644 --- "a/projects/scion/workbench-client/src/lib/\311\265workbench-commands.ts" +++ "b/projects/scion/workbench-client/src/lib/\311\265workbench-commands.ts" @@ -101,6 +101,11 @@ export const ɵWorkbenchCommands = { */ popupCloseTopic: (popupId: string) => `ɵworkbench/popups/${popupId}/close`, + /** + * Computes the topic via which to set a result + */ + popupResultTopic: (popupId: string) => `ɵworkbench/popups/${popupId}/result`, + /** * Computes the topic via which the title of a dialog can be set. */ diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-host-popup/microfrontend-host-popup.component.ts b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-host-popup/microfrontend-host-popup.component.ts index f3ffd7b29..38afe992b 100644 --- a/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-host-popup/microfrontend-host-popup.component.ts +++ b/projects/scion/workbench/src/lib/microfrontend-platform/microfrontend-host-popup/microfrontend-host-popup.component.ts @@ -61,7 +61,7 @@ export class MicrofrontendHostPopupComponent implements OnDestroy { // Perform navigation in the named router outlet. this.navigate(path, {outletName: this.outletName, params}).then(success => { if (!success) { - popup.closeWithError(Error('[PopupNavigateError] Navigation canceled, most likely by a route guard or a parallel navigation.')); + popup.close(Error('[PopupNavigateError] Navigation canceled, most likely by a route guard or a parallel navigation.')); } }); } @@ -91,17 +91,17 @@ function provideWorkbenchPopupHandle(popupContext: ɵPopupContext): StaticProvid useFactory: (): WorkbenchPopup => { const popup = inject(Popup); - return new class implements WorkbenchPopup { + return new class implements WorkbenchPopup { public readonly capability = popupContext.capability; public readonly params = popupContext.params; public readonly referrer = popupContext.referrer; - public close(result?: R | undefined): void { - popup.close(result); + public setResult(result?: R): void { + popup.setResult(result); } - public closeWithError(error: Error | string): void { - popup.closeWithError(error); + public close(result?: R | Error): void { + popup.close(result); } public signalReady(): void { 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 b740ef3b9..f2b99aad4 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 @@ -71,17 +71,17 @@ export class MicrofrontendPopupComponent implements OnInit, OnDestroy { public ngOnInit(): void { // Listen to popup close requests. - this._messageClient.observe$(ɵWorkbenchCommands.popupCloseTopic(this.popup.id)) + this._messageClient.observe$(ɵWorkbenchCommands.popupCloseTopic(this.popup.id)) .pipe(takeUntilDestroyed(this._destroyRef)) .subscribe(closeRequest => { - if (closeRequest.headers.get(ɵWorkbenchPopupMessageHeaders.CLOSE_WITH_ERROR) === true) { - this.popup.closeWithError(closeRequest.body); - } - else { - this.popup.close(closeRequest.body); - } + this.popup.close(closeRequest.headers.get(ɵWorkbenchPopupMessageHeaders.CLOSE_WITH_ERROR) ? new Error(closeRequest.body as string) : closeRequest.body); }); + // Listen to popup result requests. + this._messageClient.observe$(ɵWorkbenchCommands.popupResultTopic(this.popup.id)) + .pipe(takeUntilDestroyed(this._destroyRef)) + .subscribe(result => this.popup.setResult(result.body)); + // Make the popup context available to embedded content. this.routerOutletElement.nativeElement.setContextValue(ɵPOPUP_CONTEXT, this._popupContext); @@ -105,7 +105,7 @@ export class MicrofrontendPopupComponent implements OnInit, OnDestroy { // Close the popup on focus loss. if (this._popupContext.closeOnFocusLost && !focusWithin) { - this.popup.close(); + this.popup.close(this.popup.result); } if (focusWithin) { diff --git a/projects/scion/workbench/src/lib/popup/popup.config.ts b/projects/scion/workbench/src/lib/popup/popup.config.ts index 52c4e0372..9936586c4 100644 --- a/projects/scion/workbench/src/lib/popup/popup.config.ts +++ b/projects/scion/workbench/src/lib/popup/popup.config.ts @@ -136,14 +136,13 @@ export abstract class PopupConfig { */ export interface CloseStrategy { /** - * If `true`, which is by default, will close the popup on focus loss. - * No return value will be passed to the popup opener. + * Controls if to close the popup on focus loss, returning the result set via {@link Popup#setResult} to the popup opener. + * Defaults to `true`. */ onFocusLost?: boolean; /** - * If `true`, which is by default, will close the popup when the user - * hits the escape key. No return value will be passed to the popup - * opener. + * Controls if to close the popup when pressing escape. Defaults to `true`. + * No return value will be passed to the popup opener. */ onEscape?: boolean; } @@ -182,7 +181,7 @@ export interface PopupSize { * Represents a handle that a popup component can inject to interact with the popup, for example, * to read input data or the configured size, or to close the popup. */ -export abstract class Popup { +export abstract class Popup { /** * Input data as passed by the popup opener when opened the popup, or `undefined` if not passed. @@ -207,20 +206,20 @@ export abstract class Popup { public abstract readonly cssClasses: string[]; /** - * Closes the popup. Optionally, pass a result to the popup opener. + * Sets a result that will be passed to the popup opener when the popup is closed on focus loss {@link CloseStrategy#onFocusLost}. */ - public abstract close(result?: R | undefined): void; + public abstract setResult(result?: R): void; /** - * Closes the popup returning the given error to the popup opener. + * Closes the popup. Optionally, pass a result or an error to the popup opener. */ - public abstract closeWithError(error: Error | string): void; + public abstract close(result?: R | Error): void; } /** * @internal */ -export class ɵPopup implements Popup, Blockable { +export class ɵPopup implements Popup, Blockable { private readonly _popupEnvironmentInjector = inject(EnvironmentInjector); private readonly _context = { @@ -236,7 +235,7 @@ export class ɵPopup implements Popup, Blockable { * Indicates whether this popup is blocked by dialog(s) that overlay it. */ public readonly blockedBy$ = new BehaviorSubject<ɵWorkbenchDialog | null>(null); - public result: unknown | ɵPopupErrorResult | undefined; + public result: R | Error | undefined; constructor(public id: string, private _config: PopupConfig) { this.cssClasses = Arrays.coerce(this._config.cssClass); @@ -259,14 +258,13 @@ export class ɵPopup implements Popup, Blockable { } /** @inheritDoc */ - public close(result?: R | undefined): void { + public setResult(result?: R): void { this.result = result; - this.destroy(); } /** @inheritDoc */ - public closeWithError(error: Error | string): void { - this.result = new ɵPopupErrorResult(error); + public close(result?: R | Error): void { + this.result = result; this.destroy(); } @@ -303,15 +301,6 @@ export class ɵPopup implements Popup, Blockable { } } -/** - * @internal - */ -export class ɵPopupErrorResult { - - constructor(public error: string | Error) { - } -} - /** * Information about the context in which a popup was opened. */ diff --git a/projects/scion/workbench/src/lib/popup/popup.service.ts b/projects/scion/workbench/src/lib/popup/popup.service.ts index 22986ab89..bcb9ebff0 100644 --- a/projects/scion/workbench/src/lib/popup/popup.service.ts +++ b/projects/scion/workbench/src/lib/popup/popup.service.ts @@ -13,7 +13,7 @@ import {ConnectedOverlayPositionChange, ConnectedPosition, FlexibleConnectedPosi import {combineLatestWith, firstValueFrom, fromEvent, identity, MonoTypeOperatorFunction, Observable} from 'rxjs'; import {distinctUntilChanged, filter, map, shareReplay, startWith} from 'rxjs/operators'; import {ComponentPortal} from '@angular/cdk/portal'; -import {Popup, PopupConfig, ɵPopup, ɵPopupErrorResult} from './popup.config'; +import {Popup, PopupConfig, ɵPopup} from './popup.config'; import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y'; import {Objects, Observables} from '@scion/toolkit/util'; import {WORKBENCH_VIEW_REGISTRY} from '../view/workbench-view.registry'; @@ -77,7 +77,7 @@ export class PopupService { * - resolves to the result if closed with a result * - resolves to `undefined` if closed without a result */ - public async open(config: PopupConfig): Promise { + public async open(config: PopupConfig): Promise { // Ensure to run in Angular zone to display the popup even when called from outside of the Angular zone. if (!NgZone.isInAngularZone()) { return this._zone.run(() => this.open(config)); @@ -170,14 +170,9 @@ export class PopupService { overlayRef.dispose(); }); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { popupDestroyRef.onDestroy(() => { - if (popup.result instanceof ɵPopupErrorResult) { - reject(popup.result.error); - } - else { - resolve(popup.result as R); - } + popup.result instanceof Error ? reject(popup.result) : resolve(popup.result as R); }); }); } @@ -235,7 +230,7 @@ export class PopupService { filter((focusOrigin: FocusOrigin) => !focusOrigin), takeUntilDestroyed(popupDestroyRef), ) - .subscribe(() => popup.close()); + .subscribe(() => popup.close(popup.result)); } // Close the popup when closing the view.