Skip to content

Commit 1e4300e

Browse files
committed
feat(workbench): support navigation of views in the initial layout (or perspective)
Previously, views in the initial layout (or perspective) were limited to only supporting empty-path routes and could not be navigated or opened multiple times. closes #445 BREAKING CHANGE: Views in the initial layout (or perspective) must now be navigated. Previously, no explicit navigation was required because view and route were coupled via route outlet and view id. **Migrate the layout as follows:** Explicitly navigate views, passing an empty array of commands and the view id as navigation hint. ```ts // Before Migration provideWorkbench({ layout: (factory: WorkbenchLayoutFactory) => factory .addPart(MAIN_AREA) .addPart('left', {relativeTo: MAIN_AREA, align: 'left'}) .addView('navigator', {partId: 'left', activateView: true}), }); // After Migration provideWorkbench({ layout: (factory: WorkbenchLayoutFactory) => factory .addPart(MAIN_AREA) .addPart('left', {relativeTo: MAIN_AREA, align: 'left'}) .addView('navigator', {partId: 'left', activateView: true}) // Navigate view, passing hint to match route. .navigateView('navigator', [], {hint: 'navigator'}), }); ``` **Migrate the routes as follows:** - Remove the outlet property; - Add `canMatchWorkbenchView` guard and initialize it with the hint passed to the navigation; ```ts // Before Migration provideRouter([ { path: '', outlet: 'navigator', loadComponent: () => ..., }, ]); // After Migration provideRouter([ { path: '', // Match route only if navigated with specified hint. canMatch: [canMatchWorkbenchView('navigator')], loadComponent: () => ..., }, ]); ``` BREAKING CHANGE: Changed type of view id from `string` to `ViewId` If storing the view id in a variable, change its type from `string` to `ViewId`.
1 parent 249c8f1 commit 1e4300e

File tree

312 files changed

+9662
-5487
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

312 files changed

+9662
-5487
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ SCION Workbench enables the creation of Angular web applications that require a
1616
- [**Getting Started**][link-getting-started]\
1717
Follow these steps to install the SCION Workbench in your project and start with a basic introduction to the SCION Workbench.
1818

19-
#### Workbench Demo Applications
19+
#### Workbench Sample Applications
2020

21-
- [**SCION Workbench Testing App**][link-testing-app]\
22-
Visit our technical testing application to explore the workbench and experiment with its features.
21+
- [**Playground Application**][link-playground-app]\
22+
Visit our playground application to explore the workbench and experiment with its features.
2323

24-
- [**SCION Workbench Getting Started App**][link-getting-started-app]\
24+
- [**Getting Started Application**][link-getting-started-app]\
2525
Open the application developed in the [Getting Started][link-getting-started] guide.
2626

2727
#### Documentation
@@ -72,7 +72,7 @@ SCION Workbench enables the creation of Angular web applications that require a
7272
[link-getting-started]: /docs/site/getting-started.md
7373
[link-howto]: /docs/site/howto/how-to.md
7474
[link-demo-app]: https://schweizerischebundesbahnen.github.io/scion-workbench-demo/#/(view.24:person/64//view.22:person/32//view.5:person/79//view.3:person/15//view.2:person/38//view.1:person/66//activity:person-list)?viewgrid=eyJpZCI6MSwic2FzaDEiOlsidmlld3BhcnQuMSIsInZpZXcuMSIsInZpZXcuMiIsInZpZXcuMSJdLCJzYXNoMiI6eyJpZCI6Miwic2FzaDEiOlsidmlld3BhcnQuMiIsInZpZXcuMyIsInZpZXcuMyJdLCJzYXNoMiI6eyJpZCI6Mywic2FzaDEiOlsidmlld3BhcnQuNCIsInZpZXcuMjQiLCJ2aWV3LjI0Il0sInNhc2gyIjpbInZpZXdwYXJ0LjMiLCJ2aWV3LjIyIiwidmlldy41Iiwidmlldy4yMiJdLCJzcGxpdHRlciI6MC41MTk0Mzg0NDQ5MjQ0MDY2LCJoc3BsaXQiOmZhbHNlfSwic3BsaXR0ZXIiOjAuNTU5NDI0MzI2ODMzNzk3NSwiaHNwbGl0Ijp0cnVlfSwic3BsaXR0ZXIiOjAuMzIyNjI3NzM3MjI2Mjc3MywiaHNwbGl0IjpmYWxzZX0%3D
75-
[link-testing-app]: https://scion-workbench-testing-app.vercel.app
75+
[link-playground-app]: https://scion-workbench-testing-app.vercel.app
7676
[link-getting-started-app]: https://scion-workbench-getting-started.vercel.app
7777
[link-features]: /docs/site/features.md
7878
[link-announcements]: /docs/site/announcements.md

apps/workbench-client-testing-app/src/app/app.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<ng-container *ngIf="workbenchContextActive | async; else workbench_context_note">
22
<sci-viewport cdkTrapFocus>
3-
<router-outlet></router-outlet>
3+
<router-outlet/>
44
</sci-viewport>
55
<section class="metadata">
66
<span class="chip has-focus e2e-has-focus" *ngIf="focusMonitor!.focus$ | async">has-focus</span>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<input [formControl]="formControl" class="e2e-class" placeholder="class-1 class-2">
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@use '@scion/components.internal/design' as sci-design;
2+
3+
:host {
4+
display: inline-grid;
5+
6+
> input {
7+
@include sci-design.style-input-field();
8+
min-width: 0;
9+
}
10+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2018-2024 Swiss Federal Railways
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*/
10+
11+
import {Component, forwardRef} from '@angular/core';
12+
import {ControlValueAccessor, NG_VALUE_ACCESSOR, NonNullableFormBuilder, ReactiveFormsModule} from '@angular/forms';
13+
import {noop} from 'rxjs';
14+
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
15+
import {Arrays} from '@scion/toolkit/util';
16+
17+
@Component({
18+
selector: 'app-css-class',
19+
templateUrl: './css-class.component.html',
20+
styleUrls: ['./css-class.component.scss'],
21+
standalone: true,
22+
imports: [
23+
ReactiveFormsModule,
24+
],
25+
providers: [
26+
{provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => CssClassComponent)},
27+
],
28+
})
29+
export class CssClassComponent implements ControlValueAccessor {
30+
31+
private _cvaChangeFn: (cssClasses: string | string[] | undefined) => void = noop;
32+
private _cvaTouchedFn: () => void = noop;
33+
34+
protected formControl = this._formBuilder.control<string>('');
35+
36+
constructor(private _formBuilder: NonNullableFormBuilder) {
37+
this.formControl.valueChanges
38+
.pipe(takeUntilDestroyed())
39+
.subscribe(() => {
40+
this._cvaChangeFn(this.parse(this.formControl.value));
41+
this._cvaTouchedFn();
42+
});
43+
}
44+
45+
private parse(stringified: string): string[] | string | undefined {
46+
const cssClasses = stringified.split(/\s+/).filter(Boolean);
47+
switch (cssClasses.length) {
48+
case 0:
49+
return undefined;
50+
case 1:
51+
return cssClasses[0];
52+
default:
53+
return cssClasses;
54+
}
55+
}
56+
57+
private stringify(cssClasses: string | string[] | undefined | null): string {
58+
return Arrays.coerce(cssClasses).join(' ');
59+
}
60+
61+
/**
62+
* Method implemented as part of `ControlValueAccessor` to work with Angular forms API
63+
* @docs-private
64+
*/
65+
public writeValue(cssClasses: string | string[] | undefined | null): void {
66+
this.formControl.setValue(this.stringify(cssClasses), {emitEvent: false});
67+
}
68+
69+
/**
70+
* Method implemented as part of `ControlValueAccessor` to work with Angular forms API
71+
* @docs-private
72+
*/
73+
public registerOnChange(fn: any): void {
74+
this._cvaChangeFn = fn;
75+
}
76+
77+
/**
78+
* Method implemented as part of `ControlValueAccessor` to work with Angular forms API
79+
* @docs-private
80+
*/
81+
public registerOnTouched(fn: any): void {
82+
this._cvaTouchedFn = fn;
83+
}
84+
}

apps/workbench-client-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
</sci-form-field>
2828

2929
<sci-form-field label="CSS Class(es)">
30-
<input [formControl]="form.controls.options.controls.cssClass" class="e2e-class" placeholder="Separate multiple CSS classes by space">
30+
<app-css-class [formControl]="form.controls.options.controls.cssClass"/>
3131
</sci-form-field>
3232
</section>
3333
</form>

apps/workbench-client-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010

1111
import {Component} from '@angular/core';
1212
import {FormGroup, NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
13-
import {WorkbenchDialogService, WorkbenchView} from '@scion/workbench-client';
13+
import {WorkbenchDialogService, WorkbenchView, ViewId} from '@scion/workbench-client';
1414
import {stringifyError} from '../common/stringify-error.util';
1515
import {SciFormFieldComponent} from '@scion/components.internal/form-field';
1616
import {KeyValueEntry, SciKeyValueFieldComponent} from '@scion/components.internal/key-value-field';
1717
import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
1818
import {startWith} from 'rxjs/operators';
1919
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
20+
import {CssClassComponent} from '../css-class/css-class.component';
2021

2122
@Component({
2223
selector: 'app-dialog-opener-page',
@@ -28,6 +29,7 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
2829
SciFormFieldComponent,
2930
SciKeyValueFieldComponent,
3031
SciCheckboxComponent,
32+
CssClassComponent,
3133
],
3234
})
3335
export default class DialogOpenerPageComponent {
@@ -46,9 +48,9 @@ export default class DialogOpenerPageComponent {
4648
options: this._formBuilder.group({
4749
params: this._formBuilder.array<FormGroup<KeyValueEntry>>([]),
4850
modality: this._formBuilder.control<'application' | 'view' | ''>(''),
49-
contextualViewId: this._formBuilder.control(''),
51+
contextualViewId: this._formBuilder.control<ViewId | ''>(''),
5052
animate: this._formBuilder.control(undefined),
51-
cssClass: this._formBuilder.control(''),
53+
cssClass: this._formBuilder.control<string | string[] | undefined>(undefined),
5254
}),
5355
});
5456

@@ -76,7 +78,7 @@ export default class DialogOpenerPageComponent {
7678
context: {
7779
viewId: this.form.controls.options.controls.contextualViewId.value || undefined,
7880
},
79-
cssClass: this.form.controls.options.controls.cssClass.value.split(/\s+/).filter(Boolean),
81+
cssClass: this.form.controls.options.controls.cssClass.value,
8082
})
8183
.then(result => this.returnValue = result)
8284
.catch(error => this.dialogError = stringifyError(error) || 'Dialog was closed with an error');

apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
</sci-form-field>
4545

4646
<sci-form-field label="CSS Class(es)">
47-
<input [formControl]="form.controls.cssClass" class="e2e-class" placeholder="Separate multiple CSS classes by space">
47+
<app-css-class [formControl]="form.controls.cssClass"/>
4848
</sci-form-field>
4949
</section>
5050

apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {SciFormFieldComponent} from '@scion/components.internal/form-field';
1717
import {NgIf} from '@angular/common';
1818
import {stringifyError} from '../common/stringify-error.util';
1919
import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
20+
import {CssClassComponent} from '../css-class/css-class.component';
2021

2122
@Component({
2223
selector: 'app-message-box-opener-page',
@@ -29,6 +30,7 @@ import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
2930
SciFormFieldComponent,
3031
SciKeyValueFieldComponent,
3132
SciCheckboxComponent,
33+
CssClassComponent,
3234
],
3335
})
3436
export default class MessageBoxOpenerPageComponent {
@@ -42,7 +44,7 @@ export default class MessageBoxOpenerPageComponent {
4244
severity: this._formBuilder.control<'info' | 'warn' | 'error' | ''>(''),
4345
modality: this._formBuilder.control<'application' | 'view' | ''>(''),
4446
contentSelectable: this._formBuilder.control(true),
45-
cssClass: this._formBuilder.control(''),
47+
cssClass: this._formBuilder.control<string | string[] | undefined>(undefined),
4648
viewContext: this._formBuilder.control(true),
4749
});
4850

@@ -76,7 +78,7 @@ export default class MessageBoxOpenerPageComponent {
7678
severity: this.form.controls.severity.value || undefined,
7779
modality: this.form.controls.modality.value || undefined,
7880
contentSelectable: this.form.controls.contentSelectable.value || undefined,
79-
cssClass: this.form.controls.cssClass.value.split(/\s+/).filter(Boolean),
81+
cssClass: this.form.controls.cssClass.value,
8082
}, qualifier ?? undefined)
8183
.then(closeAction => this.closeAction = closeAction)
8284
.catch(error => this.openError = stringifyError(error));

apps/workbench-client-testing-app/src/app/notification-opener-page/notification-opener-page.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
</sci-form-field>
4444

4545
<sci-form-field label="CSS Class(es)">
46-
<input [formControl]="form.controls.cssClass" class="e2e-class" placeholder="Separate multiple CSS classes by space">
46+
<app-css-class [formControl]="form.controls.cssClass"/>
4747
</sci-form-field>
4848
</section>
4949
</form>

0 commit comments

Comments
 (0)