From 0b31ca36378826c89940566d446509540aa0e4ee Mon Sep 17 00:00:00 2001 From: Daniel Wiehl Date: Tue, 12 Mar 2019 19:52:48 +0100 Subject: [PATCH] feat: allow adding actions to the viewpart action bar Viewpart actions are added to the viewpart action bar located to the right of the view tabs. If added in the context of a view, the action is local to the containing viewpart and only shown if the view is active. If not in the context of a viewpart, the action is added to every viewpart instead. closes #104 --- README.md | 11 +-- .../src/app/app-routing.module.ts | 2 + .../workbench-app/src/app/app.component.html | 6 ++ .../workbench-app/src/app/app.component.ts | 13 +++- .../workbench-app/src/app/app.module.ts | 4 ++ .../view-4a3a8932.component.html | 5 ++ .../view-4a3a8932/view-4a3a8932.component.ts | 14 ++++ .../welcome-page/welcome-page.component.html | 1 + .../src/view-part-action.e2e-spec.ts | 62 ++++++++++++++++ .../workbench/src/view-tab-bar.e2e-spec.ts | 51 ++++++++++++++ .../e2e/workbench/src/workbench.e2e-spec.ts | 36 ++++++++++ .../view-list-button.component.scss | 2 +- .../view-list/view-list.component.scss | 3 + .../view-part-action-bar.component.html | 19 +++++ .../view-part-action-bar.component.scss | 19 +++++ .../view-part-action-bar.component.ts | 53 ++++++++++++++ .../view-part-action.directive.ts | 66 ++++++++++++++++++ .../view-part-bar.component.html | 5 +- .../view-part-bar.component.scss | 10 ++- .../view-part-bar/view-part-bar.component.ts | 11 +-- .../lib/view-part/view-part.component.html | 2 +- .../lib/view-part/view-part.component.scss | 2 +- .../src/lib/view-part/view-part.component.ts | 8 ++- .../view-part/view-tab/view-tab.component.ts | 1 + .../view-part/workbench-view-part.service.ts | 4 ++ .../workbench/src/lib/workbench.model.ts | 57 +++++++++++++++ .../workbench/src/lib/workbench.module.ts | 5 ++ .../workbench/src/lib/workbench.service.ts | 17 +++++ .../how-to-provide-a-viewpart-action.md | 35 ++++++++++ resources/site/how-to/workbench/how-to.md | 1 + .../site/pics/workbench-sketch-large.png | Bin 107926 -> 90143 bytes .../site/pics/workbench-sketch-small.png | Bin 24829 -> 53384 bytes 32 files changed, 505 insertions(+), 20 deletions(-) create mode 100644 projects/app/workbench/workbench-app/src/app/view-4a3a8932/view-4a3a8932.component.html create mode 100644 projects/app/workbench/workbench-app/src/app/view-4a3a8932/view-4a3a8932.component.ts create mode 100644 projects/e2e/workbench/src/view-part-action.e2e-spec.ts create mode 100644 projects/e2e/workbench/src/view-tab-bar.e2e-spec.ts create mode 100644 projects/e2e/workbench/src/workbench.e2e-spec.ts create mode 100644 projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action-bar.component.html create mode 100644 projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action-bar.component.scss create mode 100644 projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action-bar.component.ts create mode 100644 projects/scion/workbench/src/lib/view-part/view-part-action-bar/view-part-action.directive.ts create mode 100644 resources/site/how-to/workbench/how-to-provide-a-viewpart-action.md diff --git a/README.md b/README.md index cf49b1cf9..2ad3ce1fd 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,12 @@ SCION Workbench helps to build multi-view web applications and integrates separa ![SCION Workbench](/resources/site/pics/workbench-small.png) The Workbench provides core features of a modern rich web application. -- Tabbed, movable and stackable views -- Activity panel as application entry point -- Popups -- Global notifications -- Global or view-local message boxes +- tabbed, movable and stackable views +- activity panel as application entry point +- popups +- global notifications +- global or view-local message boxes +- global or view-local viewpart actions - URL encoded navigational state ![SCION Workbench Features](/resources/site/pics/workbench-sketch-small.png) diff --git a/projects/app/workbench/workbench-app/src/app/app-routing.module.ts b/projects/app/workbench/workbench-app/src/app/app-routing.module.ts index b2ebd6833..7820b90e8 100644 --- a/projects/app/workbench/workbench-app/src/app/app-routing.module.ts +++ b/projects/app/workbench/workbench-app/src/app/app-routing.module.ts @@ -4,6 +4,7 @@ import { Activity1a90c8d31Component } from './activity-1a90c8d3/activity-1a90c8d import { Activity1a90c8d32Component } from './activity-1a90c8d3/activity-1a90c8d3-2.component'; import { WelcomePageComponent } from './welcome-page/welcome-page.component'; import { ViewComponent } from './view/view.component'; +import { View4a3a8932Component } from './view-4a3a8932/view-4a3a8932.component'; const routes: Routes = [ {path: '', component: WelcomePageComponent}, @@ -11,6 +12,7 @@ const routes: Routes = [ {path: 'activity-1a90c8d31-1', component: Activity1a90c8d31Component}, {path: 'activity-1a90c8d31-2', component: Activity1a90c8d32Component}, {path: 'view', component: ViewComponent}, + {path: 'view-4a3a8932', component: View4a3a8932Component}, ]; @NgModule({ diff --git a/projects/app/workbench/workbench-app/src/app/app.component.html b/projects/app/workbench/workbench-app/src/app/app.component.html index f4c31c5be..3bcccbe90 100644 --- a/projects/app/workbench/workbench-app/src/app/app.component.html +++ b/projects/app/workbench/workbench-app/src/app/app.component.html @@ -11,4 +11,10 @@ itemCssClass="material-icons" routerLink="activity-1a90c8d31-2"> + + + + diff --git a/projects/app/workbench/workbench-app/src/app/app.component.ts b/projects/app/workbench/workbench-app/src/app/app.component.ts index 9c6313c28..32d8da665 100644 --- a/projects/app/workbench/workbench-app/src/app/app.component.ts +++ b/projects/app/workbench/workbench-app/src/app/app.component.ts @@ -3,6 +3,7 @@ import { ActivatedRoute } from '@angular/router'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { WorkbenchRouter, WorkbenchService } from '@scion/workbench'; @Component({ selector: 'app-root', @@ -14,15 +15,25 @@ export class AppComponent implements OnDestroy { private _destroy$ = new Subject(); public showActivities = true; + public showOpenNewViewTabAction = true; public ensureWelcomeView = false; - constructor(route: ActivatedRoute) { + constructor(route: ActivatedRoute, workbench: WorkbenchService, wbRouter: WorkbenchRouter) { route.queryParamMap .pipe(takeUntil(this._destroy$)) .subscribe(queryParams => { this.showActivities = coerceBooleanProperty(queryParams.get('show-activities') || true); + this.showOpenNewViewTabAction = coerceBooleanProperty(queryParams.get('show-open-new-view-tab-action') || true); this.ensureWelcomeView = coerceBooleanProperty(queryParams.get('ensure-welcome-view') || false); }); + + workbench.views$ + .pipe(takeUntil(this._destroy$)) + .subscribe(views => { + if (this.ensureWelcomeView && views.length === 0) { + wbRouter.navigate(['/welcome']).then(); + } + }); } public ngOnDestroy(): void { diff --git a/projects/app/workbench/workbench-app/src/app/app.module.ts b/projects/app/workbench/workbench-app/src/app/app.module.ts index 30e0b8493..44e5b48e7 100644 --- a/projects/app/workbench/workbench-app/src/app/app.module.ts +++ b/projects/app/workbench/workbench-app/src/app/app.module.ts @@ -7,6 +7,9 @@ import { WorkbenchModule } from '@scion/workbench'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { Activity1a90c8d31Component } from './activity-1a90c8d3/activity-1a90c8d3-1.component'; import { Activity1a90c8d32Component } from './activity-1a90c8d3/activity-1a90c8d3-2.component'; +import { WelcomePageComponent } from './welcome-page/welcome-page.component'; +import { ViewComponent } from './view/view.component'; +import { View4a3a8932Component } from './view-4a3a8932/view-4a3a8932.component'; @NgModule({ declarations: [ @@ -15,6 +18,7 @@ import { Activity1a90c8d32Component } from './activity-1a90c8d3/activity-1a90c8d Activity1a90c8d32Component, WelcomePageComponent, ViewComponent, + View4a3a8932Component, ], imports: [ BrowserModule, diff --git a/projects/app/workbench/workbench-app/src/app/view-4a3a8932/view-4a3a8932.component.html b/projects/app/workbench/workbench-app/src/app/view-4a3a8932/view-4a3a8932.component.html new file mode 100644 index 000000000..7ce7e72ee --- /dev/null +++ b/projects/app/workbench/workbench-app/src/app/view-4a3a8932/view-4a3a8932.component.html @@ -0,0 +1,5 @@ + + + diff --git a/projects/app/workbench/workbench-app/src/app/view-4a3a8932/view-4a3a8932.component.ts b/projects/app/workbench/workbench-app/src/app/view-4a3a8932/view-4a3a8932.component.ts new file mode 100644 index 000000000..c4ecba955 --- /dev/null +++ b/projects/app/workbench/workbench-app/src/app/view-4a3a8932/view-4a3a8932.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { WorkbenchView } from '@scion/workbench'; + +@Component({ + selector: 'app-view-4a3a8932', + templateUrl: './view-4a3a8932.component.html', +}) +export class View4a3a8932Component { + + constructor(view: WorkbenchView) { + view.title = 'Testcase 4a3a8932'; + view.cssClass = 'e2e-view-4a3a8932'; + } +} diff --git a/projects/app/workbench/workbench-app/src/app/welcome-page/welcome-page.component.html b/projects/app/workbench/workbench-app/src/app/welcome-page/welcome-page.component.html index a9cf17505..2824c0945 100644 --- a/projects/app/workbench/workbench-app/src/app/welcome-page/welcome-page.component.html +++ b/projects/app/workbench/workbench-app/src/app/welcome-page/welcome-page.component.html @@ -6,4 +6,5 @@

Welcome to SCION Workbench

Open view 2 Open view 3 Open view 4 + Testcase '4a3a8932' diff --git a/projects/e2e/workbench/src/view-part-action.e2e-spec.ts b/projects/e2e/workbench/src/view-part-action.e2e-spec.ts new file mode 100644 index 000000000..c2f2b4789 --- /dev/null +++ b/projects/e2e/workbench/src/view-part-action.e2e-spec.ts @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { AppPO } from './page-object/app.po'; +import { browser } from 'protractor'; +import { WelcomePagePO } from './page-object/welcome-page.po'; + +describe('ViewPartAction', () => { + + const appPO = new AppPO(); + + beforeEach(async () => { + await browser.get('/'); + }); + + it('should be added to all viewparts', async () => { + const openNewTabActionButtonPO = appPO.findViewPartAction('e2e-open-new-tab'); + + await expect(appPO.isViewTabBarShowing()).toBeTruthy(); + await expect(openNewTabActionButtonPO.isPresent()).toBeTruthy(); + + await browser.get('/#/?show-open-new-view-tab-action=false'); + await expect(appPO.isViewTabBarShowing()).toBeFalsy(); + await expect(openNewTabActionButtonPO.isPresent()).toBeFalsy(); + + await browser.get('/#/?show-open-new-view-tab-action=true'); + await expect(appPO.isViewTabBarShowing()).toBeTruthy(); + await expect(openNewTabActionButtonPO.isPresent()).toBeTruthy(); + }); + + it('should stick to a view if registered in the context of a view [testcase: 4a3a8932]', async () => { + const welcomePagePO = new WelcomePagePO(); + const viewTabPO = appPO.findViewTab('e2e-view-4a3a8932'); + const viewLocalActionButtonPO = appPO.findViewPartAction('e2e-button-4a3a8932'); + + await welcomePagePO.clickTile('e2e-tile-4a3a8932'); + + // Open a view which contributes a view-local action + await expect(viewTabPO.isActive()).toBeTruthy(); + await expect(viewLocalActionButtonPO.isPresent()).toBeTruthy(); + + // Open a new view tab + await appPO.openNewViewTab(); + await expect(viewLocalActionButtonPO.isPresent()).toBeFalsy(); + + // Activate previous view + await viewTabPO.click(); + await expect(viewLocalActionButtonPO.isPresent()).toBeTruthy(); + + // Close the view + await viewTabPO.close(); + await expect(viewTabPO.isPresent()).toBeFalsy(); + await expect(viewLocalActionButtonPO.isPresent()).toBeFalsy(); + }); +}); diff --git a/projects/e2e/workbench/src/view-tab-bar.e2e-spec.ts b/projects/e2e/workbench/src/view-tab-bar.e2e-spec.ts new file mode 100644 index 000000000..02ee3d19a --- /dev/null +++ b/projects/e2e/workbench/src/view-tab-bar.e2e-spec.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { AppPO } from './page-object/app.po'; +import { browser } from 'protractor'; +import { WelcomePagePO } from './page-object/welcome-page.po'; + +describe('ViewTabBar', () => { + + const appPO = new AppPO(); + const welcomePagePO = new WelcomePagePO(); + + beforeEach(async () => { + await browser.get('/'); + }); + + it('should not show if no views are open and no viewpart actions present', async () => { + await browser.get('/#/?show-open-new-view-tab-action=false'); + + await expect(appPO.getViewTabCount()).toEqual(0); + await expect(appPO.isViewTabBarShowing()).toBeFalsy(); + + await welcomePagePO.clickTile('e2e-tile-view-1'); + await expect(appPO.getViewTabCount()).toEqual(1); + await expect(appPO.isViewTabBarShowing()).toBeTruthy(); + + await appPO.findViewTab('e2e-tile-view-1').close(); + await expect(appPO.getViewTabCount()).toEqual(0); + await expect(appPO.isViewTabBarShowing()).toBeFalsy(); + + await browser.get('/#/?show-open-new-view-tab-action=true'); + + await expect(appPO.getViewTabCount()).toEqual(0); + await expect(appPO.isViewTabBarShowing()).toBeTruthy(); + + await welcomePagePO.clickTile('e2e-tile-view-1'); + await expect(appPO.getViewTabCount()).toEqual(1); + await expect(appPO.isViewTabBarShowing()).toBeTruthy(); + + await appPO.findViewTab('e2e-tile-view-1').close(); + await expect(appPO.getViewTabCount()).toEqual(0); + await expect(appPO.isViewTabBarShowing()).toBeTruthy(); + }); +}); diff --git a/projects/e2e/workbench/src/workbench.e2e-spec.ts b/projects/e2e/workbench/src/workbench.e2e-spec.ts new file mode 100644 index 000000000..e1373d94b --- /dev/null +++ b/projects/e2e/workbench/src/workbench.e2e-spec.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { AppPO } from './page-object/app.po'; +import { browser } from 'protractor'; +import { expectViewToShow } from './util/testing.util'; + +describe('Workbench', () => { + + const appPO = new AppPO(); + + beforeEach(async () => { + await browser.get('/'); + }); + + it('should allow to always have an entry view open', async () => { + await browser.get('/#/?ensure-welcome-view=true'); + + await expect(appPO.getViewTabCount()).toEqual(1); + await expect(appPO.isViewTabBarShowing()).toBeTruthy(); + await expectViewToShow({viewCssClass: 'e2e-welcome-page', componentSelector: 'app-welcome-page'}); + + // close the view + await appPO.findViewTab('e2e-welcome-page').close(); + await expect(appPO.getViewTabCount()).toEqual(1); + await expect(appPO.isViewTabBarShowing()).toBeTruthy(); + await expectViewToShow({viewCssClass: 'e2e-welcome-page', componentSelector: 'app-welcome-page'}); + }); +}); diff --git a/projects/scion/workbench/src/lib/view-part/view-list-button/view-list-button.component.scss b/projects/scion/workbench/src/lib/view-part/view-list-button/view-list-button.component.scss index 7b97f0461..9699cbea6 100644 --- a/projects/scion/workbench/src/lib/view-part/view-list-button/view-list-button.component.scss +++ b/projects/scion/workbench/src/lib/view-part/view-list-button/view-list-button.component.scss @@ -59,5 +59,5 @@ $size: 14px; } :host-context:not(.visible) { - visibility: hidden; + display: none; } diff --git a/projects/scion/workbench/src/lib/view-part/view-list/view-list.component.scss b/projects/scion/workbench/src/lib/view-part/view-list/view-list.component.scss index d2616a091..1580f422d 100644 --- a/projects/scion/workbench/src/lib/view-part/view-list/view-list.component.scss +++ b/projects/scion/workbench/src/lib/view-part/view-list/view-list.component.scss @@ -10,6 +10,9 @@ width: 100%; height: 100%; + // Sets the viewport client (