Skip to content

Commit

Permalink
feat(workbench): enable action contribution to specific parts or part…
Browse files Browse the repository at this point in the history
…s in a specific area

BREAKING CHANGE: Programmatic contribution of part actions has changed.

To migrate
- specify a Portal instead of a `ComponentRef` or `TemplateRef`
- replace `WorkbenchPart.registerPartAction` with `WorkbenchService.registerPartAction`

```ts
const workbenchService = inject(WorkbenchService);

workbenchService.registerPartAction({
  portal: new ComponentPortal(YourComponent),
  target: {
    partId: ['console', 'navigator'],
  },
});
```
  • Loading branch information
danielwiehl authored and Marcarrian committed Jun 6, 2023
1 parent a3afa6b commit 10b5f6a
Show file tree
Hide file tree
Showing 57 changed files with 1,222 additions and 610 deletions.
5 changes: 0 additions & 5 deletions apps/workbench-testing-app/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@ export const routes: Routes = [
path: 'test-host-popup',
loadComponent: () => import('./host-popup-page/host-popup-page.component'),
},
{
path: 'register-route',
loadComponent: () => import('./route-register-page/route-register-page.component'),
data: {[WorkbenchRouteData.title]: 'Route Registrator', [WorkbenchRouteData.heading]: 'Workbench E2E Testpage', [WorkbenchRouteData.cssClass]: 'e2e-register-route', pinToStartPage: true},
},
{
path: 'test-pages',
loadChildren: () => import('./test-pages/routes'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
<sci-tabbar>
<ng-template sciTab label="Add Part" cssClass="e2e-add-part">
<app-add-part-page></app-add-part-page>
<app-add-part-page/>
</ng-template>
<ng-template sciTab label="Add View" cssClass="e2e-add-view">
<app-add-view-page></app-add-view-page>
<app-add-view-page/>
</ng-template>
<ng-template sciTab label="Activate View" cssClass="e2e-activate-view">
<app-activate-view-page></app-activate-view-page>
<app-activate-view-page/>
</ng-template>
<ng-template sciTab label="Register Part Action" cssClass="e2e-register-part-action">
<app-register-part-action-page/>
</ng-template>
<ng-template sciTab label="Register Angular Route" cssClass="e2e-register-route">
<app-register-route-page/>
</ng-template>
</sci-tabbar>
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {SciTabbarModule} from '@scion/components.internal/tabbar';
import AddPartPageComponent from './add-part-page/add-part-page.component';
import AddViewPageComponent from './add-view-page/add-view-page.component';
import ActivateViewPageComponent from './activate-view-page/activate-view-page.component';
import RegisterPartActionPageComponent from './register-part-action-page/register-part-action-page.component';
import RegisterRoutePageComponent from './register-route-page/register-route-page.component';

@Component({
selector: 'app-layout-page',
Expand All @@ -24,6 +26,8 @@ import ActivateViewPageComponent from './activate-view-page/activate-view-page.c
AddPartPageComponent,
AddViewPageComponent,
ActivateViewPageComponent,
RegisterPartActionPageComponent,
RegisterRoutePageComponent,
],
})
export default class LayoutPageComponent {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<form [formGroup]="form" autocomplete="off">
<section>
<sci-form-field label="Content">
<input [formControlName]="CONTENT" class="e2e-content">
</sci-form-field>

<sci-form-field label="Align">
<select [formControlName]="ALIGN" class="e2e-align">
<option value="">--</option>
<option value="start">start</option>
<option value="end">end</option>
</select>
</sci-form-field>

<sci-form-field label="CSS Class(es)">
<input [formControlName]="CSS_CLASS" class="e2e-class" placeholder="Separate multiple CSS classes by space">
</sci-form-field>
</section>

<section [formGroupName]="TARGET" class="e2e-target">
<header>Target</header>

<sci-form-field label="View(s)">
<input [formControlName]="VIEW" class="e2e-view-id" list="views" placeholder="Separate multiple views by space">
<datalist id="views">
<option *ngFor="let view of workbenchService.views$ | async" [value]="view.id">{{view.id}}</option>
</datalist>
</sci-form-field>

<sci-form-field label="Part(s)">
<input [formControlName]="PART" class="e2e-part-id" list="parts" placeholder="Separate multiple parts by space">
<datalist id="parts">
<option *ngFor="let part of workbenchService.parts$ | async" [value]="part.id">{{part.id}}</option>
</datalist>
</sci-form-field>

<sci-form-field label="Area">
<input [formControlName]="AREA" class="e2e-area" list="areas">
<datalist id="areas">
<option value="">--</option>
<option value="main">Main Area</option>
<option value="peripheral">Peripheral Area</option>
</datalist>
</sci-form-field>
</section>

<button (click)="onRegister()" [disabled]="form.invalid" class="e2e-register">
Register
</button>
</form>

<output class="register-success e2e-register-success" *ngIf="registerError === false">
Success
</output>

<output class="register-error e2e-register-error" *ngIf="registerError">
{{registerError}}
</output>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
:host {
display: flex;
flex-direction: column;
gap: 1em;

> form {
flex: none;
display: flex;
flex-direction: column;
gap: 1em;

> section {
flex: none;
display: flex;
flex-direction: column;
gap: .5em;
border: 1px solid var(--sci-color-P400);
border-radius: 5px;
padding: 1em;

> header {
margin-top: 0;
margin-bottom: 2em;
font-weight: bold;
}
}
}

> output.register-success {
flex: none;
display: none;
}

> output.register-error {
flex: none;
border: 1px solid var(--sci-color-warn);
background-color: var(--sci-color-W100);
border-radius: 3px;
padding: 1em;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright (c) 2018-2023 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 {Component, inject, InjectionToken, Injector} from '@angular/core';
import {FormGroup, NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
import {WorkbenchService} from '@scion/workbench';
import {SciFormFieldModule} from '@scion/components.internal/form-field';
import {AsyncPipe, NgFor, NgIf} from '@angular/common';
import {ComponentPortal} from '@angular/cdk/portal';
import {undefinedIfEmpty} from '../../common/undefined-if-empty.util';

const CONTENT = 'content';
const ALIGN = 'align';
const CSS_CLASS = 'cssClass';
const TARGET = 'target';
const VIEW = 'view';
const PART = 'part';
const AREA = 'area';

@Component({
selector: 'app-register-part-action-page',
templateUrl: './register-part-action-page.component.html',
styleUrls: ['./register-part-action-page.component.scss'],
standalone: true,
imports: [
NgIf,
NgFor,
AsyncPipe,
ReactiveFormsModule,
SciFormFieldModule,
],
})
export default class RegisterPartActionPageComponent {

public readonly CONTENT = CONTENT;
public readonly ALIGN = ALIGN;
public readonly CSS_CLASS = CSS_CLASS;
public readonly TARGET = TARGET;
public readonly VIEW = VIEW;
public readonly PART = PART;
public readonly AREA = AREA;

public form: FormGroup;
public registerError: string | false | undefined;

constructor(formBuilder: NonNullableFormBuilder, public workbenchService: WorkbenchService) {
this.form = formBuilder.group({
[CONTENT]: formBuilder.control('', {validators: Validators.required}),
[ALIGN]: formBuilder.control(''),
[CSS_CLASS]: formBuilder.control(''),
[TARGET]: formBuilder.group({
[VIEW]: formBuilder.control(''),
[PART]: formBuilder.control(''),
[AREA]: formBuilder.control(''),
}),
});
}

public onRegister(): void {
this.registerError = undefined;
try {
this.workbenchService.registerPartAction({
portal: new ComponentPortal(TextComponent, undefined, Injector.create({
providers: [
{provide: TextComponent.TEXT, useValue: this.form.get(CONTENT).value},
],
})),
align: this.form.get(ALIGN).value || undefined,
target: {
viewId: undefinedIfEmpty(this.form.get([TARGET, VIEW]).value.split(/\s+/).filter(Boolean)),
partId: undefinedIfEmpty(this.form.get([TARGET, PART]).value.split(/\s+/).filter(Boolean)),
area: this.form.get([TARGET, AREA]).value || undefined,
},
cssClass: undefinedIfEmpty(this.form.get(CSS_CLASS).value.split(/\s+/).filter(Boolean)),
});
this.registerError = false;
this.form.reset();
}
catch (error) {
this.registerError = error;
}
}
}

@Component({
selector: 'app-text',
template: '{{text}}',
standalone: true,
})
class TextComponent {

public static readonly TEXT = new InjectionToken<string>('TEXT');

public readonly text = inject(TextComponent.TEXT);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ const TITLE = 'title';
const CSS_CLASS = 'cssClass';

@Component({
selector: 'app-route-register-page',
templateUrl: './route-register-page.component.html',
styleUrls: ['./route-register-page.component.scss'],
selector: 'app-register-route-page',
templateUrl: './register-route-page.component.html',
styleUrls: ['./register-route-page.component.scss'],
standalone: true,
imports: [
NgIf,
Expand All @@ -35,7 +35,7 @@ const CSS_CLASS = 'cssClass';
SciFormFieldModule,
],
})
export default class RouteRegisterPageComponent {
export default class RegisterRoutePageComponent {

public readonly PATH = PATH;
public readonly COMPONENT = COMPONENT;
Expand All @@ -45,8 +45,8 @@ export default class RouteRegisterPageComponent {
public readonly CSS_CLASS = CSS_CLASS;

public readonly componentRefs = new Map<string, () => Promise<DefaultExport<Type<unknown>>>>()
.set('view-page', () => import('../view-page/view-page.component'))
.set('router-page', () => import('../router-page/router-page.component'));
.set('view-page', () => import('../../view-page/view-page.component'))
.set('router-page', () => import('../../router-page/router-page.component'));

public form: FormGroup;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
<ng-template #panel_part_actions>
<input class="e2e-part-actions"
[formControl]="partActionsFormControl"
placeholder='Enter action(s) as JSON array: [{"icon":"open_in_new","align":"end","cssClass":"e2e-open-in-new"}]'>
placeholder='Enter action(s) as JSON array: [{"content":"...","align":"end","cssClass":"e2e-open-in-new"}]'>
</ng-template>
</sci-accordion>

Expand All @@ -97,8 +97,7 @@

<ng-template wbPartAction
*ngFor="let action of partActions$ | async"
[align]="action.align">
<button class="material-icons" [ngClass]="action.cssClass">
{{action.icon}}
</button>
[align]="action.align"
[cssClass]="action.cssClass">
{{action.content}}
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default class ViewPageComponent implements OnDestroy {
private _destroy$ = new Subject<void>();

public uuid = UUID.randomUUID();
public partActions$: Observable<PartAction[]>;
public partActions$: Observable<WorkbenchPartActionDescriptor[]>;
public partActionsFormControl = new UntypedFormControl('');

public WorkbenchRouteData = WorkbenchRouteData;
Expand All @@ -78,7 +78,7 @@ export default class ViewPageComponent implements OnDestroy {
view.cssClass = view.cssClasses.concat(route.snapshot.paramMap.get('cssClass') ?? []);
}

private parsePartActions(): PartAction[] {
private parsePartActions(): WorkbenchPartActionDescriptor[] {
if (!this.partActionsFormControl.value) {
return [];
}
Expand Down Expand Up @@ -117,8 +117,8 @@ export default class ViewPageComponent implements OnDestroy {
}
}

export interface PartAction {
icon: string;
export interface WorkbenchPartActionDescriptor {
content: string;
align: 'start' | 'end';
cssClass: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
</button>
</header>
<wb-workbench>
<ng-template wbPartAction *ngIf="showNewTabAction$ | async">
<button [wbRouterLink]="'/start-page'" class="material-icons e2e-open-new-tab" [wbRouterLinkExtras]="{target: 'blank', blankInsertionIndex: 'end'}">
<ng-template wbPartAction *ngIf="showNewTabAction$ | async" area="main" cssClass="e2e-open-new-tab">
<button [wbRouterLink]="'/start-page'" class="material-icons" [wbRouterLinkExtras]="{target: 'blank', blankInsertionIndex: 'end'}">
add
</button>
</ng-template>
Expand Down
2 changes: 1 addition & 1 deletion docs/site/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This page gives you an overview of existing and planned workbench features. Deve
|Perspective|layout|[![][done]](#)|Arrangement of views around the main area. Multiple perspectives are supported. Different perspectives provide a different perspective on the application while sharing the main area. Only one perspective can be active at a time. [#305](https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/305).
|View|layout|[![][done]](#)|Visual component for displaying content stacked or side-by-side in the workbench layout.
|Multi-Window|layout|[![][done]](#)|Views can be opened in new browser windows.
|Part Actions|layout|[![][done]](#)|Actions that are displayed in the tabbar of a part. Actions can stick to a view, so they are only visible when the view is active.
|Part Actions|layout|[![][done]](#)|Actions that are displayed in the tabbar of a part. Actions can stick to a view, so they are only visible if the view is active.
|View Context Menu|layout|[![][done]](#)|A viewtab has a context menu. By default, the workbench adds some workbench-specific menu items to the context menu, such as for closing other views. Custom menu items can be added to the context menu as well.
|Persistent Navigation|navigation|[![][done]](#)|The arrangement of the views is added to the browser URL or local storage, enabling persistent navigation.
|Default Page|layout|[![][done]](#)|The workbench adds a primary router outlet to the main area when no view is open, enabling the display of a start page.
Expand Down
2 changes: 1 addition & 1 deletion docs/site/howto/how-to-open-popup.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
## [SCION Workbench][menu-home] > [How To Guides][menu-how-to] > Miscellaneous

A popup is a visual workbench component for displaying content above other content. It is positioned relative to an anchor,
which can be either a coordinate or an HTML element. The popup moves when the anchor moves. By default, the popup closes on focus loss, or when the user hits the escape key. When opening a popup in the context of a view, the popup is bound to the lifecycle of the view, that is, the popup is displayed only when the view is active and is closed when the view is closed.
which can be either a coordinate or an HTML element. The popup moves when the anchor moves. By default, the popup closes on focus loss, or when the user hits the escape key. If opening a popup in the context of a view, the popup is bound to the lifecycle of the view, that is, the popup is displayed only if the view is active and is closed when the view is closed.

### How to open a popup
To open a popup, inject `PopupService` and invoke the `open` method, passing a `PopupConfig` options object to control the appearance of the popup.
Expand Down
Loading

0 comments on commit 10b5f6a

Please sign in to comment.