diff --git a/README.md b/README.md
index 4cfe4ac56..52fbc74fa 100644
--- a/README.md
+++ b/README.md
@@ -16,12 +16,12 @@ SCION Workbench enables the creation of Angular web applications that require a
- [**Getting Started**][link-getting-started]\
Follow these steps to install the SCION Workbench in your project and start with a basic introduction to the SCION Workbench.
-#### Workbench Demo Applications
+#### Workbench Sample Applications
-- [**SCION Workbench Testing App**][link-testing-app]\
- Visit our technical testing application to explore the workbench and experiment with its features.
+- [**Playground Application**][link-playground-app]\
+ Visit our playground application to explore the workbench and experiment with its features.
-- [**SCION Workbench Getting Started App**][link-getting-started-app]\
+- [**Getting Started Application**][link-getting-started-app]\
Open the application developed in the [Getting Started][link-getting-started] guide.
#### Documentation
@@ -72,7 +72,7 @@ SCION Workbench enables the creation of Angular web applications that require a
[link-getting-started]: /docs/site/getting-started.md
[link-howto]: /docs/site/howto/how-to.md
[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
-[link-testing-app]: https://scion-workbench-testing-app.vercel.app
+[link-playground-app]: https://scion-workbench-testing-app.vercel.app
[link-getting-started-app]: https://scion-workbench-getting-started.vercel.app
[link-features]: /docs/site/features.md
[link-announcements]: /docs/site/announcements.md
diff --git a/apps/workbench-client-testing-app/src/app/app.component.html b/apps/workbench-client-testing-app/src/app/app.component.html
index 687ba960f..1fb07fe1d 100644
--- a/apps/workbench-client-testing-app/src/app/app.component.html
+++ b/apps/workbench-client-testing-app/src/app/app.component.html
@@ -1,6 +1,6 @@
-
+
diff --git a/apps/workbench-client-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.ts b/apps/workbench-client-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.ts
index c07f99fae..b8d310759 100644
--- a/apps/workbench-client-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.ts
+++ b/apps/workbench-client-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.ts
@@ -10,13 +10,14 @@
import {Component} from '@angular/core';
import {FormGroup, NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
-import {WorkbenchDialogService, WorkbenchView} from '@scion/workbench-client';
+import {WorkbenchDialogService, WorkbenchView, ViewId} from '@scion/workbench-client';
import {stringifyError} from '../common/stringify-error.util';
import {SciFormFieldComponent} from '@scion/components.internal/form-field';
import {KeyValueEntry, SciKeyValueFieldComponent} from '@scion/components.internal/key-value-field';
import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
import {startWith} from 'rxjs/operators';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
+import {CssClassComponent} from '../css-class/css-class.component';
@Component({
selector: 'app-dialog-opener-page',
@@ -28,6 +29,7 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
SciFormFieldComponent,
SciKeyValueFieldComponent,
SciCheckboxComponent,
+ CssClassComponent,
],
})
export default class DialogOpenerPageComponent {
@@ -46,9 +48,9 @@ export default class DialogOpenerPageComponent {
options: this._formBuilder.group({
params: this._formBuilder.array>([]),
modality: this._formBuilder.control<'application' | 'view' | ''>(''),
- contextualViewId: this._formBuilder.control(''),
+ contextualViewId: this._formBuilder.control(''),
animate: this._formBuilder.control(undefined),
- cssClass: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
}),
});
@@ -76,7 +78,7 @@ export default class DialogOpenerPageComponent {
context: {
viewId: this.form.controls.options.controls.contextualViewId.value || undefined,
},
- cssClass: this.form.controls.options.controls.cssClass.value.split(/\s+/).filter(Boolean),
+ cssClass: this.form.controls.options.controls.cssClass.value,
})
.then(result => this.returnValue = result)
.catch(error => this.dialogError = stringifyError(error) || 'Dialog was closed with an error');
diff --git a/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.html b/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.html
index 1eda144db..7c72c3ca4 100644
--- a/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.html
+++ b/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.html
@@ -44,7 +44,7 @@
-
+
diff --git a/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts b/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts
index 36dbd6f54..cfb84019c 100644
--- a/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts
+++ b/apps/workbench-client-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts
@@ -17,6 +17,7 @@ import {SciFormFieldComponent} from '@scion/components.internal/form-field';
import {NgIf} from '@angular/common';
import {stringifyError} from '../common/stringify-error.util';
import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
+import {CssClassComponent} from '../css-class/css-class.component';
@Component({
selector: 'app-message-box-opener-page',
@@ -29,6 +30,7 @@ import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
SciFormFieldComponent,
SciKeyValueFieldComponent,
SciCheckboxComponent,
+ CssClassComponent,
],
})
export default class MessageBoxOpenerPageComponent {
@@ -42,7 +44,7 @@ export default class MessageBoxOpenerPageComponent {
severity: this._formBuilder.control<'info' | 'warn' | 'error' | ''>(''),
modality: this._formBuilder.control<'application' | 'view' | ''>(''),
contentSelectable: this._formBuilder.control(true),
- cssClass: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
viewContext: this._formBuilder.control(true),
});
@@ -76,7 +78,7 @@ export default class MessageBoxOpenerPageComponent {
severity: this.form.controls.severity.value || undefined,
modality: this.form.controls.modality.value || undefined,
contentSelectable: this.form.controls.contentSelectable.value || undefined,
- cssClass: this.form.controls.cssClass.value.split(/\s+/).filter(Boolean),
+ cssClass: this.form.controls.cssClass.value,
}, qualifier ?? undefined)
.then(closeAction => this.closeAction = closeAction)
.catch(error => this.openError = stringifyError(error));
diff --git a/apps/workbench-client-testing-app/src/app/notification-opener-page/notification-opener-page.component.html b/apps/workbench-client-testing-app/src/app/notification-opener-page/notification-opener-page.component.html
index bddf49792..c423c3df6 100644
--- a/apps/workbench-client-testing-app/src/app/notification-opener-page/notification-opener-page.component.html
+++ b/apps/workbench-client-testing-app/src/app/notification-opener-page/notification-opener-page.component.html
@@ -43,7 +43,7 @@
-
+
diff --git a/apps/workbench-client-testing-app/src/app/notification-opener-page/notification-opener-page.component.ts b/apps/workbench-client-testing-app/src/app/notification-opener-page/notification-opener-page.component.ts
index e451c5bbd..8fede1d1d 100644
--- a/apps/workbench-client-testing-app/src/app/notification-opener-page/notification-opener-page.component.ts
+++ b/apps/workbench-client-testing-app/src/app/notification-opener-page/notification-opener-page.component.ts
@@ -15,6 +15,7 @@ import {NgIf} from '@angular/common';
import {stringifyError} from '../common/stringify-error.util';
import {KeyValueEntry, SciKeyValueFieldComponent} from '@scion/components.internal/key-value-field';
import {SciFormFieldComponent} from '@scion/components.internal/form-field';
+import {CssClassComponent} from '../css-class/css-class.component';
@Component({
selector: 'app-notification-opener-page',
@@ -26,6 +27,7 @@ import {SciFormFieldComponent} from '@scion/components.internal/form-field';
ReactiveFormsModule,
SciFormFieldComponent,
SciKeyValueFieldComponent,
+ CssClassComponent,
],
})
export default class NotificationOpenerPageComponent {
@@ -38,7 +40,7 @@ export default class NotificationOpenerPageComponent {
severity: this._formBuilder.control<'info' | 'warn' | 'error' | ''>(''),
duration: this._formBuilder.control<'short' | 'medium' | 'long' | 'infinite' | number | ''>(''),
group: this._formBuilder.control(''),
- cssClass: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
});
public notificationOpenError: string | undefined;
@@ -61,7 +63,7 @@ export default class NotificationOpenerPageComponent {
severity: this.form.controls.severity.value || undefined,
duration: this.parseDurationFromUI(),
group: this.form.controls.group.value || undefined,
- cssClass: this.form.controls.cssClass.value.split(/\s+/).filter(Boolean),
+ cssClass: this.form.controls.cssClass.value,
}, qualifier ?? undefined)
.catch(error => this.notificationOpenError = stringifyError(error) || 'Workbench Notification could not be opened');
}
diff --git a/apps/workbench-client-testing-app/src/app/popup-opener-page/popup-opener-page.component.html b/apps/workbench-client-testing-app/src/app/popup-opener-page/popup-opener-page.component.html
index afbab410c..a59132970 100644
--- a/apps/workbench-client-testing-app/src/app/popup-opener-page/popup-opener-page.component.html
+++ b/apps/workbench-client-testing-app/src/app/popup-opener-page/popup-opener-page.component.html
@@ -23,7 +23,7 @@
-
+
diff --git a/apps/workbench-client-testing-app/src/app/popup-opener-page/popup-opener-page.component.ts b/apps/workbench-client-testing-app/src/app/popup-opener-page/popup-opener-page.component.ts
index 331a95818..1d4a57639 100644
--- a/apps/workbench-client-testing-app/src/app/popup-opener-page/popup-opener-page.component.ts
+++ b/apps/workbench-client-testing-app/src/app/popup-opener-page/popup-opener-page.component.ts
@@ -10,7 +10,7 @@
import {Component, ElementRef, ViewChild} from '@angular/core';
import {FormGroup, NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
-import {CloseStrategy, PopupOrigin, WorkbenchPopupService, WorkbenchView} from '@scion/workbench-client';
+import {CloseStrategy, PopupOrigin, ViewId, WorkbenchPopupService, WorkbenchView} from '@scion/workbench-client';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
import {undefinedIfEmpty} from '../common/undefined-if-empty.util';
@@ -22,6 +22,7 @@ import {KeyValueEntry, SciKeyValueFieldComponent} from '@scion/components.intern
import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
import {SciAccordionComponent, SciAccordionItemDirective} from '@scion/components.internal/accordion';
import {parseTypedString} from '../common/parse-typed-value.util';
+import {CssClassComponent} from '../css-class/css-class.component';
@Component({
selector: 'app-popup-opener-page',
@@ -37,6 +38,7 @@ import {parseTypedString} from '../common/parse-typed-value.util';
SciAccordionItemDirective,
SciCheckboxComponent,
PopupPositionLabelPipe,
+ CssClassComponent,
],
})
export default class PopupOpenerPageComponent {
@@ -62,9 +64,9 @@ export default class PopupOpenerPageComponent {
width: this._formBuilder.control(undefined),
height: this._formBuilder.control(undefined),
}),
- contextualViewId: this._formBuilder.control(''),
+ contextualViewId: this._formBuilder.control(''),
align: this._formBuilder.control<'east' | 'west' | 'north' | 'south' | ''>(''),
- cssClass: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
closeStrategy: this._formBuilder.group({
onFocusLost: this._formBuilder.control(true),
onEscape: this._formBuilder.control(true),
@@ -99,7 +101,7 @@ export default class PopupOpenerPageComponent {
onFocusLost: this.form.controls.closeStrategy.controls.onFocusLost.value ?? undefined,
onEscape: this.form.controls.closeStrategy.controls.onEscape.value ?? undefined,
}),
- cssClass: this.form.controls.cssClass.value.split(/\s+/).filter(Boolean),
+ cssClass: this.form.controls.cssClass.value,
context: {
viewId: parseTypedString(this.form.controls.contextualViewId.value || undefined),
},
diff --git a/apps/workbench-client-testing-app/src/app/register-workbench-capability-page/register-workbench-capability-page.component.html b/apps/workbench-client-testing-app/src/app/register-workbench-capability-page/register-workbench-capability-page.component.html
index c46d17b03..cf1d28cf0 100644
--- a/apps/workbench-client-testing-app/src/app/register-workbench-capability-page/register-workbench-capability-page.component.html
+++ b/apps/workbench-client-testing-app/src/app/register-workbench-capability-page/register-workbench-capability-page.component.html
@@ -33,131 +33,131 @@
@if (form.controls.type.value === WorkbenchCapabilities.View) {
-
+
-
+
-
+
-
+
-
+
-
+
-
+
}
@if (form.controls.type.value === WorkbenchCapabilities.Popup) {
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
}
@if (form.controls.type.value === WorkbenchCapabilities.Dialog) {
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
}
diff --git a/apps/workbench-client-testing-app/src/app/register-workbench-capability-page/register-workbench-capability-page.component.ts b/apps/workbench-client-testing-app/src/app/register-workbench-capability-page/register-workbench-capability-page.component.ts
index 19ee03407..64d578b9a 100644
--- a/apps/workbench-client-testing-app/src/app/register-workbench-capability-page/register-workbench-capability-page.component.ts
+++ b/apps/workbench-client-testing-app/src/app/register-workbench-capability-page/register-workbench-capability-page.component.ts
@@ -21,6 +21,7 @@ import {stringifyError} from '../common/stringify-error.util';
import {SciFormFieldComponent} from '@scion/components.internal/form-field';
import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
import {parseTypedString} from '../common/parse-typed-value.util';
+import {CssClassComponent} from '../css-class/css-class.component';
/**
* Allows registering workbench capabilities.
@@ -37,6 +38,7 @@ import {parseTypedString} from '../common/parse-typed-value.util';
SciKeyValueFieldComponent,
SciCheckboxComponent,
SciViewportComponent,
+ CssClassComponent,
],
})
export default class RegisterWorkbenchCapabilityPageComponent {
@@ -54,7 +56,7 @@ export default class RegisterWorkbenchCapabilityPageComponent {
heading: this._formBuilder.control(''),
closable: this._formBuilder.control(null),
showSplash: this._formBuilder.control(null),
- cssClass: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
pinToStartPage: this._formBuilder.control(false),
}),
popupProperties: this._formBuilder.group({
@@ -69,7 +71,7 @@ export default class RegisterWorkbenchCapabilityPageComponent {
}),
showSplash: this._formBuilder.control(null),
pinToStartPage: this._formBuilder.control(false),
- cssClass: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
}),
dialogProperties: this._formBuilder.group({
path: this._formBuilder.control(''),
@@ -87,7 +89,7 @@ export default class RegisterWorkbenchCapabilityPageComponent {
padding: this._formBuilder.control(null),
showSplash: this._formBuilder.control(null),
pinToStartPage: this._formBuilder.control(false),
- cssClass: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
}),
});
@@ -111,7 +113,7 @@ export default class RegisterWorkbenchCapabilityPageComponent {
case WorkbenchCapabilities.Dialog:
return this.readDialogCapabilityFromUI();
default:
- throw Error('[IllegalArgumentError] Capability expected to be a workbench capability, but was not.');
+ throw Error('Capability expected to be a workbench capability, but was not.');
}
})();
@@ -144,7 +146,7 @@ export default class RegisterWorkbenchCapabilityPageComponent {
path: parseTypedString(this.form.controls.viewProperties.controls.path.value), // allow `undefined` to test capability validation
title: this.form.controls.viewProperties.controls.title.value || undefined,
heading: this.form.controls.viewProperties.controls.heading.value || undefined,
- cssClass: this.form.controls.viewProperties.controls.cssClass.value.split(/\s+/).filter(Boolean),
+ cssClass: this.form.controls.viewProperties.controls.cssClass.value,
closable: this.form.controls.viewProperties.controls.closable.value ?? undefined,
showSplash: this.form.controls.viewProperties.controls.showSplash.value ?? undefined,
pinToStartPage: this.form.controls.viewProperties.controls.pinToStartPage.value,
@@ -175,7 +177,7 @@ export default class RegisterWorkbenchCapabilityPageComponent {
}),
showSplash: this.form.controls.popupProperties.controls.showSplash.value ?? undefined,
pinToStartPage: this.form.controls.popupProperties.controls.pinToStartPage.value,
- cssClass: this.form.controls.popupProperties.controls.cssClass.value.split(/\s+/).filter(Boolean),
+ cssClass: this.form.controls.popupProperties.controls.cssClass.value,
},
};
}
@@ -207,7 +209,7 @@ export default class RegisterWorkbenchCapabilityPageComponent {
padding: this.form.controls.dialogProperties.controls.padding.value ?? undefined,
showSplash: this.form.controls.dialogProperties.controls.showSplash.value ?? undefined,
pinToStartPage: this.form.controls.dialogProperties.controls.pinToStartPage.value,
- cssClass: this.form.controls.dialogProperties.controls.cssClass.value.split(/\s+/).filter(Boolean),
+ cssClass: this.form.controls.dialogProperties.controls.cssClass.value,
},
};
}
diff --git a/apps/workbench-client-testing-app/src/app/router-page/router-page.component.html b/apps/workbench-client-testing-app/src/app/router-page/router-page.component.html
index e9562eb6d..529ad4fa1 100644
--- a/apps/workbench-client-testing-app/src/app/router-page/router-page.component.html
+++ b/apps/workbench-client-testing-app/src/app/router-page/router-page.component.html
@@ -39,7 +39,7 @@
-
+
diff --git a/apps/workbench-client-testing-app/src/app/router-page/router-page.component.ts b/apps/workbench-client-testing-app/src/app/router-page/router-page.component.ts
index 33d5b7c0f..d4af729f8 100644
--- a/apps/workbench-client-testing-app/src/app/router-page/router-page.component.ts
+++ b/apps/workbench-client-testing-app/src/app/router-page/router-page.component.ts
@@ -17,6 +17,7 @@ import {NgIf} from '@angular/common';
import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
import {SciFormFieldComponent} from '@scion/components.internal/form-field';
import {parseTypedObject} from '../common/parse-typed-value.util';
+import {CssClassComponent} from '../css-class/css-class.component';
@Component({
selector: 'app-router-page',
@@ -29,6 +30,7 @@ import {parseTypedObject} from '../common/parse-typed-value.util';
SciFormFieldComponent,
SciKeyValueFieldComponent,
SciCheckboxComponent,
+ CssClassComponent,
],
})
export default class RouterPageComponent {
@@ -40,7 +42,7 @@ export default class RouterPageComponent {
insertionIndex: this._formBuilder.control(''),
activate: this._formBuilder.control(undefined),
close: this._formBuilder.control(undefined),
- cssClass: this._formBuilder.control(undefined),
+ cssClass: this._formBuilder.control(undefined),
});
public navigateError: string | undefined;
@@ -62,7 +64,7 @@ export default class RouterPageComponent {
target: this.form.controls.target.value || undefined,
blankInsertionIndex: coerceInsertionIndex(this.form.controls.insertionIndex.value),
params: params || undefined,
- cssClass: this.form.controls.cssClass.value?.split(/\s+/).filter(Boolean),
+ cssClass: this.form.controls.cssClass.value,
};
await this._router.navigate(qualifier, extras)
.then(success => success ? Promise.resolve() : Promise.reject('Navigation failed'))
diff --git a/apps/workbench-client-testing-app/src/app/test-pages/angular-zone-test-page/angular-zone-test-page.component.ts b/apps/workbench-client-testing-app/src/app/test-pages/angular-zone-test-page/angular-zone-test-page.component.ts
index a5336a784..0ab3bae84 100644
--- a/apps/workbench-client-testing-app/src/app/test-pages/angular-zone-test-page/angular-zone-test-page.component.ts
+++ b/apps/workbench-client-testing-app/src/app/test-pages/angular-zone-test-page/angular-zone-test-page.component.ts
@@ -45,7 +45,7 @@ export default class AngularZoneTestPageComponent {
}
private async testWorkbenchViewCapability(model: TestCaseModel): Promise {
- const workbenchViewTestee = this._zone.runOutsideAngular(() => new ɵWorkbenchView('VIEW_ID'));
+ const workbenchViewTestee = this._zone.runOutsideAngular(() => new ɵWorkbenchView('view.999'));
// Register two view capabilities
const viewCapabilityId1 = await Beans.get(ManifestService).registerCapability({
@@ -79,7 +79,7 @@ export default class AngularZoneTestPageComponent {
}
private async testWorkbenchViewParams(model: TestCaseModel): Promise {
- const workbenchViewTestee = this._zone.runOutsideAngular(() => new ɵWorkbenchView('VIEW_ID'));
+ const workbenchViewTestee = this._zone.runOutsideAngular(() => new ɵWorkbenchView('view.999'));
// Subscribe to params
workbenchViewTestee.params$
@@ -97,7 +97,7 @@ export default class AngularZoneTestPageComponent {
}
private async testWorkbenchViewActive(model: TestCaseModel): Promise {
- const workbenchViewTestee = this._zone.runOutsideAngular(() => new ɵWorkbenchView('VIEW_ID'));
+ const workbenchViewTestee = this._zone.runOutsideAngular(() => new ɵWorkbenchView('view.999'));
// Subscribe to active state
workbenchViewTestee.active$
diff --git a/apps/workbench-client-testing-app/src/app/test-pages/bulk-navigation-test-page/bulk-navigation-test-page.component.html b/apps/workbench-client-testing-app/src/app/test-pages/bulk-navigation-test-page/bulk-navigation-test-page.component.html
index 2958909b1..dac957f23 100644
--- a/apps/workbench-client-testing-app/src/app/test-pages/bulk-navigation-test-page/bulk-navigation-test-page.component.html
+++ b/apps/workbench-client-testing-app/src/app/test-pages/bulk-navigation-test-page/bulk-navigation-test-page.component.html
@@ -3,8 +3,8 @@
-
-
+
+
diff --git a/apps/workbench-client-testing-app/src/app/test-pages/bulk-navigation-test-page/bulk-navigation-test-page.component.ts b/apps/workbench-client-testing-app/src/app/test-pages/bulk-navigation-test-page/bulk-navigation-test-page.component.ts
index 9f66f70ea..7a9e3c375 100644
--- a/apps/workbench-client-testing-app/src/app/test-pages/bulk-navigation-test-page/bulk-navigation-test-page.component.ts
+++ b/apps/workbench-client-testing-app/src/app/test-pages/bulk-navigation-test-page/bulk-navigation-test-page.component.ts
@@ -13,6 +13,7 @@ import {WorkbenchRouter} from '@scion/workbench-client';
import {NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
import {APP_IDENTITY} from '@scion/microfrontend-platform';
import {SciFormFieldComponent} from '@scion/components.internal/form-field';
+import {CssClassComponent} from '../../css-class/css-class.component';
@Component({
selector: 'app-bulk-navigation-test-page',
@@ -22,13 +23,14 @@ import {SciFormFieldComponent} from '@scion/components.internal/form-field';
imports: [
SciFormFieldComponent,
ReactiveFormsModule,
+ CssClassComponent,
],
})
export default class BulkNavigationTestPageComponent {
public form = this._formBuilder.group({
viewCount: this._formBuilder.control(1, Validators.required),
- cssClass: this._formBuilder.control('', Validators.required),
+ cssClass: this._formBuilder.control(undefined, Validators.required),
});
constructor(private _formBuilder: NonNullableFormBuilder,
diff --git a/apps/workbench-getting-started-app/src/app/app.config.ts b/apps/workbench-getting-started-app/src/app/app.config.ts
index 649785ebe..9701d1b37 100644
--- a/apps/workbench-getting-started-app/src/app/app.config.ts
+++ b/apps/workbench-getting-started-app/src/app/app.config.ts
@@ -23,11 +23,12 @@ export const appConfig: ApplicationConfig = {
layout: (factory: WorkbenchLayoutFactory) => factory
.addPart(MAIN_AREA)
.addPart('left', {relativeTo: MAIN_AREA, align: 'left', ratio: .25})
- .addView('todos', {partId: 'left', activateView: true}),
+ .addView('todos', {partId: 'left', activateView: true})
+ .navigateView('todos', ['todos']),
}),
provideRouter([
{path: '', loadComponent: () => import('./welcome/welcome.component')},
- {path: '', outlet: 'todos', loadComponent: () => import('./todos/todos.component')},
+ {path: 'todos', loadComponent: () => import('./todos/todos.component')},
{path: 'todos/:id', loadComponent: () => import('./todo/todo.component')},
], withHashLocation()),
provideAnimations(),
diff --git a/apps/workbench-getting-started-app/src/app/todo.service.ts b/apps/workbench-getting-started-app/src/app/todo.service.ts
index aac5b15f9..ec2a50ba1 100644
--- a/apps/workbench-getting-started-app/src/app/todo.service.ts
+++ b/apps/workbench-getting-started-app/src/app/todo.service.ts
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2023 Swiss Federal Railways
+ * Copyright (c) 2018-2024 Swiss Federal Railways
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
diff --git a/apps/workbench-getting-started-app/src/app/todos/todos.component.html b/apps/workbench-getting-started-app/src/app/todos/todos.component.html
index c03e41d73..41042bee4 100644
--- a/apps/workbench-getting-started-app/src/app/todos/todos.component.html
+++ b/apps/workbench-getting-started-app/src/app/todos/todos.component.html
@@ -1,5 +1,5 @@
-
- {{todo.task}}
+ {{ todo.task }}
diff --git a/apps/workbench-testing-app/src/app/app.component.html b/apps/workbench-testing-app/src/app/app.component.html
index 1cfc844c5..4367cd2ed 100644
--- a/apps/workbench-testing-app/src/app/app.component.html
+++ b/apps/workbench-testing-app/src/app/app.component.html
@@ -1,4 +1,4 @@
-
+
diff --git a/apps/workbench-testing-app/src/app/app.routes.ts b/apps/workbench-testing-app/src/app/app.routes.ts
index d79ad07db..773641cb7 100644
--- a/apps/workbench-testing-app/src/app/app.routes.ts
+++ b/apps/workbench-testing-app/src/app/app.routes.ts
@@ -10,16 +10,17 @@
import {Routes} from '@angular/router';
import {WorkbenchComponent} from './workbench/workbench.component';
-import {WorkbenchRouteData} from '@scion/workbench';
import {topLevelTestPageRoutes} from './test-pages/routes';
+import {canMatchWorkbenchView, WorkbenchRouteData} from '@scion/workbench';
export const routes: Routes = [
{
path: '',
component: WorkbenchComponent,
+ canMatch: [canMatchWorkbenchView(false)],
children: [
{
- path: '', // default workbench page
+ path: '',
loadComponent: () => import('./start-page/start-page.component'),
},
],
@@ -28,6 +29,30 @@ export const routes: Routes = [
path: 'workbench-page',
redirectTo: '',
},
+ {
+ path: '',
+ canMatch: [canMatchWorkbenchView('test-router')],
+ loadComponent: () => import('./router-page/router-page.component'),
+ data: {
+ [WorkbenchRouteData.title]: 'Workbench Router',
+ [WorkbenchRouteData.heading]: 'Workbench E2E Testpage',
+ [WorkbenchRouteData.cssClass]: 'e2e-test-router',
+ path: '',
+ navigationHint: 'test-router',
+ },
+ },
+ {
+ path: '',
+ canMatch: [canMatchWorkbenchView('test-view')],
+ loadComponent: () => import('./view-page/view-page.component'),
+ data: {
+ [WorkbenchRouteData.title]: 'Workbench View',
+ [WorkbenchRouteData.heading]: 'Workbench E2E Testpage',
+ [WorkbenchRouteData.cssClass]: 'e2e-test-view',
+ path: '',
+ navigationHint: 'test-view',
+ },
+ },
{
path: 'start-page',
loadComponent: () => import('./start-page/start-page.component'),
@@ -36,17 +61,38 @@ export const routes: Routes = [
{
path: 'test-router',
loadComponent: () => import('./router-page/router-page.component'),
- data: {[WorkbenchRouteData.title]: 'Workbench Router', [WorkbenchRouteData.heading]: 'Workbench E2E Testpage', [WorkbenchRouteData.cssClass]: 'e2e-test-router', pinToStartPage: true},
+ data: {
+ [WorkbenchRouteData.title]: 'Workbench Router',
+ [WorkbenchRouteData.heading]: 'Workbench E2E Testpage',
+ [WorkbenchRouteData.cssClass]: 'e2e-test-router',
+ pinToStartPage: true,
+ path: 'test-router',
+ navigationHint: '',
+ },
},
{
path: 'test-view',
+ canMatch: [canMatchWorkbenchView('test-view')],
loadComponent: () => import('./view-page/view-page.component'),
- data: {[WorkbenchRouteData.title]: 'Workbench View', [WorkbenchRouteData.heading]: 'Workbench E2E Testpage', [WorkbenchRouteData.cssClass]: 'e2e-test-view', pinToStartPage: true},
+ data: {
+ [WorkbenchRouteData.title]: 'Workbench View',
+ [WorkbenchRouteData.heading]: 'Workbench E2E Testpage',
+ [WorkbenchRouteData.cssClass]: 'e2e-test-view',
+ path: 'test-view',
+ navigationHint: 'test-view',
+ },
},
{
- path: 'test-perspective',
- loadComponent: () => import('./perspective-page/perspective-page.component'),
- data: {[WorkbenchRouteData.title]: 'Workbench Perspective', [WorkbenchRouteData.heading]: 'Workbench E2E Testpage', [WorkbenchRouteData.cssClass]: 'e2e-test-perspective', pinToStartPage: true},
+ path: 'test-view',
+ loadComponent: () => import('./view-page/view-page.component'),
+ data: {
+ [WorkbenchRouteData.title]: 'Workbench View',
+ [WorkbenchRouteData.heading]: 'Workbench E2E Testpage',
+ [WorkbenchRouteData.cssClass]: 'e2e-test-view',
+ pinToStartPage: true,
+ path: 'test-view',
+ navigationHint: '',
+ },
},
{
path: 'test-layout',
diff --git a/apps/workbench-testing-app/src/app/css-class/css-class.component.html b/apps/workbench-testing-app/src/app/css-class/css-class.component.html
new file mode 100644
index 000000000..1579b5c12
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/css-class/css-class.component.html
@@ -0,0 +1 @@
+
diff --git a/apps/workbench-testing-app/src/app/css-class/css-class.component.scss b/apps/workbench-testing-app/src/app/css-class/css-class.component.scss
new file mode 100644
index 000000000..85980ba98
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/css-class/css-class.component.scss
@@ -0,0 +1,10 @@
+@use '@scion/components.internal/design' as sci-design;
+
+:host {
+ display: inline-grid;
+
+ > input {
+ @include sci-design.style-input-field();
+ min-width: 0;
+ }
+}
diff --git a/apps/workbench-testing-app/src/app/css-class/css-class.component.ts b/apps/workbench-testing-app/src/app/css-class/css-class.component.ts
new file mode 100644
index 000000000..2156983d7
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/css-class/css-class.component.ts
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018-2024 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, forwardRef} from '@angular/core';
+import {ControlValueAccessor, NG_VALUE_ACCESSOR, NonNullableFormBuilder, ReactiveFormsModule} from '@angular/forms';
+import {noop} from 'rxjs';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
+import {Arrays} from '@scion/toolkit/util';
+
+@Component({
+ selector: 'app-css-class',
+ templateUrl: './css-class.component.html',
+ styleUrls: ['./css-class.component.scss'],
+ standalone: true,
+ imports: [
+ ReactiveFormsModule,
+ ],
+ providers: [
+ {provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => CssClassComponent)},
+ ],
+})
+export class CssClassComponent implements ControlValueAccessor {
+
+ private _cvaChangeFn: (cssClasses: string | string[] | undefined) => void = noop;
+ private _cvaTouchedFn: () => void = noop;
+
+ protected formControl = this._formBuilder.control('');
+
+ constructor(private _formBuilder: NonNullableFormBuilder) {
+ this.formControl.valueChanges
+ .pipe(takeUntilDestroyed())
+ .subscribe(() => {
+ this._cvaChangeFn(this.parse(this.formControl.value));
+ this._cvaTouchedFn();
+ });
+ }
+
+ private parse(stringified: string): string[] | string | undefined {
+ const cssClasses = stringified.split(/\s+/).filter(Boolean);
+ switch (cssClasses.length) {
+ case 0:
+ return undefined;
+ case 1:
+ return cssClasses[0];
+ default:
+ return cssClasses;
+ }
+ }
+
+ private stringify(cssClasses: string | string[] | undefined | null): string {
+ return Arrays.coerce(cssClasses).join(' ');
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public writeValue(cssClasses: string | string[] | undefined | null): void {
+ this.formControl.setValue(this.stringify(cssClasses), {emitEvent: false});
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public registerOnChange(fn: any): void {
+ this._cvaChangeFn = fn;
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public registerOnTouched(fn: any): void {
+ this._cvaTouchedFn = fn;
+ }
+}
diff --git a/apps/workbench-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.html b/apps/workbench-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.html
index 6d20acd0c..5a50bbf64 100644
--- a/apps/workbench-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.html
+++ b/apps/workbench-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.html
@@ -33,7 +33,7 @@
-
+
diff --git a/apps/workbench-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.ts b/apps/workbench-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.ts
index 2cde0d9b4..36c8b819a 100644
--- a/apps/workbench-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.ts
+++ b/apps/workbench-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.ts
@@ -10,7 +10,7 @@
import {ApplicationRef, Component, Type} from '@angular/core';
import {FormGroup, NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
-import {WorkbenchDialogService} from '@scion/workbench';
+import {ViewId, WorkbenchDialogService} from '@scion/workbench';
import {startWith} from 'rxjs/operators';
import {NgIf} from '@angular/common';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@@ -23,6 +23,7 @@ import BlankTestPageComponent from '../test-pages/blank-test-page/blank-test-pag
import FocusTestPageComponent from '../test-pages/focus-test-page/focus-test-page.component';
import PopupOpenerPageComponent from '../popup-opener-page/popup-opener-page.component';
import InputFieldTestPageComponent from '../test-pages/input-field-test-page/input-field-test-page.component';
+import {CssClassComponent} from '../css-class/css-class.component';
@Component({
selector: 'app-dialog-opener-page',
@@ -35,6 +36,7 @@ import InputFieldTestPageComponent from '../test-pages/input-field-test-page/inp
SciFormFieldComponent,
SciKeyValueFieldComponent,
SciCheckboxComponent,
+ CssClassComponent,
],
})
export default class DialogOpenerPageComponent {
@@ -44,8 +46,8 @@ export default class DialogOpenerPageComponent {
options: this._formBuilder.group({
inputs: this._formBuilder.array>([]),
modality: this._formBuilder.control<'application' | 'view' | ''>(''),
- contextualViewId: this._formBuilder.control(''),
- cssClass: this._formBuilder.control(''),
+ contextualViewId: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
animate: this._formBuilder.control(undefined),
}),
count: this._formBuilder.control(''),
@@ -79,7 +81,7 @@ export default class DialogOpenerPageComponent {
return dialogService.open(component, {
inputs: SciKeyValueFieldComponent.toDictionary(this.form.controls.options.controls.inputs) ?? undefined,
modality: this.form.controls.options.controls.modality.value || undefined,
- cssClass: [`index-${index}`].concat(this.form.controls.options.controls.cssClass.value.split(/\s+/).filter(Boolean) || []),
+ cssClass: [`index-${index}`].concat(this.form.controls.options.controls.cssClass.value ?? []),
animate: this.form.controls.options.controls.animate.value,
context: {
viewId: this.form.controls.options.controls.contextualViewId.value || undefined,
diff --git a/apps/workbench-testing-app/src/app/header/header.component.ts b/apps/workbench-testing-app/src/app/header/header.component.ts
index 4f957410c..72f66fb1a 100644
--- a/apps/workbench-testing-app/src/app/header/header.component.ts
+++ b/apps/workbench-testing-app/src/app/header/header.component.ts
@@ -189,6 +189,11 @@ export class HeaderComponent {
private contributeSettingsMenuItems(): MenuItem[] {
return [
+ new MenuItem({
+ text: 'Reset forms on submit',
+ checked: this._settingsService.isEnabled('resetFormsOnSubmit'),
+ onAction: () => this._settingsService.toggle('resetFormsOnSubmit'),
+ }),
new MenuItem({
text: 'Log Angular change detection cycles',
cssClass: 'e2e-log-angular-change-detection-cycles',
diff --git a/apps/workbench-testing-app/src/app/layout-page/activate-view-page/activate-view-page.component.html b/apps/workbench-testing-app/src/app/layout-page/activate-view-page/activate-view-page.component.html
deleted file mode 100644
index e608d70a1..000000000
--- a/apps/workbench-testing-app/src/app/layout-page/activate-view-page/activate-view-page.component.html
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
diff --git a/apps/workbench-testing-app/src/app/layout-page/activate-view-page/activate-view-page.component.ts b/apps/workbench-testing-app/src/app/layout-page/activate-view-page/activate-view-page.component.ts
deleted file mode 100644
index 8db2fee99..000000000
--- a/apps/workbench-testing-app/src/app/layout-page/activate-view-page/activate-view-page.component.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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} from '@angular/core';
-import {NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
-import {WorkbenchRouter, WorkbenchService} from '@scion/workbench';
-import {AsyncPipe, NgFor, NgIf} from '@angular/common';
-import {stringifyError} from '../../common/stringify-error.util';
-import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
-import {SciFormFieldComponent} from '@scion/components.internal/form-field';
-
-@Component({
- selector: 'app-activate-view-page',
- templateUrl: './activate-view-page.component.html',
- styleUrls: ['./activate-view-page.component.scss'],
- standalone: true,
- imports: [
- NgIf,
- NgFor,
- AsyncPipe,
- ReactiveFormsModule,
- SciFormFieldComponent,
- SciCheckboxComponent,
- ],
-})
-export default class ActivateViewPageComponent {
-
- public form = this._formBuilder.group({
- viewId: this._formBuilder.control('', Validators.required),
- options: this._formBuilder.group({
- activatePart: this._formBuilder.control(undefined),
- }),
- });
- public navigateError: string | false | undefined;
-
- constructor(private _formBuilder: NonNullableFormBuilder,
- private _wbRouter: WorkbenchRouter,
- public workbenchService: WorkbenchService) {
- }
-
- public onNavigate(): void {
- this.navigateError = undefined;
-
- this._wbRouter
- .ɵnavigate(layout => layout.activateView(this.form.controls.viewId.value, {
- activatePart: this.form.controls.options.controls.activatePart.value,
- }))
- .then(() => {
- this.navigateError = false;
- this.form.reset();
- })
- .catch(error => this.navigateError = stringifyError(error));
- }
-}
diff --git a/apps/workbench-testing-app/src/app/layout-page/add-part-page/add-part-page.component.html b/apps/workbench-testing-app/src/app/layout-page/add-part-page/add-part-page.component.html
deleted file mode 100644
index 3271d2bc3..000000000
--- a/apps/workbench-testing-app/src/app/layout-page/add-part-page/add-part-page.component.html
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
diff --git a/apps/workbench-testing-app/src/app/layout-page/add-part-page/add-part-page.component.scss b/apps/workbench-testing-app/src/app/layout-page/add-part-page/add-part-page.component.scss
deleted file mode 100644
index 05e8cc1f9..000000000
--- a/apps/workbench-testing-app/src/app/layout-page/add-part-page/add-part-page.component.scss
+++ /dev/null
@@ -1,42 +0,0 @@
-: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-border);
- border-radius: var(--sci-corner);
- padding: 1em;
-
- > header {
- margin-bottom: 1em;
- font-weight: bold;
- }
- }
- }
-
- > output.navigate-success {
- flex: none;
- display: none;
- }
-
- > output.navigate-error {
- flex: none;
- border: 1px solid var(--sci-color-negative);
- background-color: var(--sci-color-background-negative);
- color: var(--sci-color-negative);
- border-radius: var(--sci-corner);
- padding: 1em;
- font-family: monospace;
- }
-}
diff --git a/apps/workbench-testing-app/src/app/layout-page/add-part-page/add-part-page.component.ts b/apps/workbench-testing-app/src/app/layout-page/add-part-page/add-part-page.component.ts
deleted file mode 100644
index 1eda67ac9..000000000
--- a/apps/workbench-testing-app/src/app/layout-page/add-part-page/add-part-page.component.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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} from '@angular/core';
-import {NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
-import {WorkbenchRouter, WorkbenchService} from '@scion/workbench';
-import {AsyncPipe, NgFor, NgIf} from '@angular/common';
-import {stringifyError} from '../../common/stringify-error.util';
-import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
-import {SciFormFieldComponent} from '@scion/components.internal/form-field';
-
-@Component({
- selector: 'app-add-part-page',
- templateUrl: './add-part-page.component.html',
- styleUrls: ['./add-part-page.component.scss'],
- standalone: true,
- imports: [
- NgIf,
- NgFor,
- AsyncPipe,
- ReactiveFormsModule,
- SciFormFieldComponent,
- SciCheckboxComponent,
- ],
-})
-export default class AddPartPageComponent {
-
- public form = this._formBuilder.group({
- partId: this._formBuilder.control('', Validators.required),
- relativeTo: this._formBuilder.group({
- partId: this._formBuilder.control(undefined),
- align: this._formBuilder.control<'left' | 'right' | 'top' | 'bottom' | undefined>(undefined, Validators.required),
- ratio: this._formBuilder.control(undefined),
- }),
- activate: this._formBuilder.control(undefined),
- });
-
- public navigateError: string | false | undefined;
-
- constructor(private _formBuilder: NonNullableFormBuilder,
- private _wbRouter: WorkbenchRouter,
- public workbenchService: WorkbenchService) {
- }
-
- public onNavigate(): void {
- this.navigateError = undefined;
-
- this._wbRouter
- .ɵnavigate(layout => layout.addPart(this.form.controls.partId.value!, {
- relativeTo: this.form.controls.relativeTo.controls.partId.value || undefined,
- align: this.form.controls.relativeTo.controls.align.value!,
- ratio: this.form.controls.relativeTo.controls.ratio.value,
- },
- {activate: this.form.controls.activate.value},
- ))
- .then(() => {
- this.navigateError = false;
- this.form.reset();
- })
- .catch(error => this.navigateError = stringifyError(error));
- }
-}
diff --git a/apps/workbench-testing-app/src/app/layout-page/add-view-page/add-view-page.component.html b/apps/workbench-testing-app/src/app/layout-page/add-view-page/add-view-page.component.html
deleted file mode 100644
index 2136a6d36..000000000
--- a/apps/workbench-testing-app/src/app/layout-page/add-view-page/add-view-page.component.html
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
diff --git a/apps/workbench-testing-app/src/app/layout-page/add-view-page/add-view-page.component.scss b/apps/workbench-testing-app/src/app/layout-page/add-view-page/add-view-page.component.scss
deleted file mode 100644
index 05e8cc1f9..000000000
--- a/apps/workbench-testing-app/src/app/layout-page/add-view-page/add-view-page.component.scss
+++ /dev/null
@@ -1,42 +0,0 @@
-: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-border);
- border-radius: var(--sci-corner);
- padding: 1em;
-
- > header {
- margin-bottom: 1em;
- font-weight: bold;
- }
- }
- }
-
- > output.navigate-success {
- flex: none;
- display: none;
- }
-
- > output.navigate-error {
- flex: none;
- border: 1px solid var(--sci-color-negative);
- background-color: var(--sci-color-background-negative);
- color: var(--sci-color-negative);
- border-radius: var(--sci-corner);
- padding: 1em;
- font-family: monospace;
- }
-}
diff --git a/apps/workbench-testing-app/src/app/layout-page/add-view-page/add-view-page.component.ts b/apps/workbench-testing-app/src/app/layout-page/add-view-page/add-view-page.component.ts
deleted file mode 100644
index 64bc924bb..000000000
--- a/apps/workbench-testing-app/src/app/layout-page/add-view-page/add-view-page.component.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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} from '@angular/core';
-import {NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
-import {WorkbenchRouter, WorkbenchService} from '@scion/workbench';
-import {AsyncPipe, NgFor, NgIf} from '@angular/common';
-import {stringifyError} from '../../common/stringify-error.util';
-import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
-import {SciFormFieldComponent} from '@scion/components.internal/form-field';
-
-@Component({
- selector: 'app-add-view-page',
- templateUrl: './add-view-page.component.html',
- styleUrls: ['./add-view-page.component.scss'],
- standalone: true,
- imports: [
- NgIf,
- NgFor,
- AsyncPipe,
- ReactiveFormsModule,
- SciFormFieldComponent,
- SciCheckboxComponent,
- ],
-})
-export default class AddViewPageComponent {
-
- public form = this._formBuilder.group({
- viewId: this._formBuilder.control('', Validators.required),
- options: this._formBuilder.group({
- partId: this._formBuilder.control('', Validators.required),
- position: this._formBuilder.control(undefined),
- activateView: this._formBuilder.control(undefined),
- activatePart: this._formBuilder.control(undefined),
- }),
- });
- public navigateError: string | false | undefined;
-
- constructor(private _formBuilder: NonNullableFormBuilder,
- private _wbRouter: WorkbenchRouter,
- public workbenchService: WorkbenchService) {
- }
-
- public onNavigate(): void {
- this.navigateError = undefined;
-
- this._wbRouter
- .ɵnavigate(layout => layout.addView(this.form.controls.viewId.value, {
- partId: this.form.controls.options.controls.partId.value,
- position: this.form.controls.options.controls.position.value ?? undefined,
- activateView: this.form.controls.options.controls.activateView.value,
- activatePart: this.form.controls.options.controls.activatePart.value,
- }))
- .then(() => {
- this.navigateError = false;
- this.form.reset();
- })
- .catch(error => this.navigateError = stringifyError(error));
- }
-}
diff --git a/apps/workbench-testing-app/src/app/layout-page/create-perspective-page/create-perspective-page.component.html b/apps/workbench-testing-app/src/app/layout-page/create-perspective-page/create-perspective-page.component.html
new file mode 100644
index 000000000..b4e93f787
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/create-perspective-page/create-perspective-page.component.html
@@ -0,0 +1,38 @@
+
+
+@if (registerError === false) {
+
+}
+
+@if (registerError) {
+
+}
diff --git a/apps/workbench-testing-app/src/app/perspective-page/perspective-page.component.scss b/apps/workbench-testing-app/src/app/layout-page/create-perspective-page/create-perspective-page.component.scss
similarity index 98%
rename from apps/workbench-testing-app/src/app/perspective-page/perspective-page.component.scss
rename to apps/workbench-testing-app/src/app/layout-page/create-perspective-page/create-perspective-page.component.scss
index 46a42ce96..242c1f2f0 100644
--- a/apps/workbench-testing-app/src/app/perspective-page/perspective-page.component.scss
+++ b/apps/workbench-testing-app/src/app/layout-page/create-perspective-page/create-perspective-page.component.scss
@@ -2,7 +2,6 @@
display: flex;
flex-direction: column;
gap: 1em;
- padding: 1em;
> form {
flex: none;
diff --git a/apps/workbench-testing-app/src/app/layout-page/create-perspective-page/create-perspective-page.component.ts b/apps/workbench-testing-app/src/app/layout-page/create-perspective-page/create-perspective-page.component.ts
new file mode 100644
index 000000000..d005fa094
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/create-perspective-page/create-perspective-page.component.ts
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2018-2024 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} from '@angular/core';
+import {AddPartsComponent, PartDescriptor} from '../tables/add-parts/add-parts.component';
+import {AddViewsComponent, ViewDescriptor} from '../tables/add-views/add-views.component';
+import {NavigateViewsComponent, NavigationDescriptor} from '../tables/navigate-views/navigate-views.component';
+import {FormGroup, NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
+import {SettingsService} from '../../settings.service';
+import {WorkbenchLayout, WorkbenchLayoutFactory, WorkbenchLayoutFn, WorkbenchService} from '@scion/workbench';
+import {stringifyError} from '../../common/stringify-error.util';
+import {SciFormFieldComponent} from '@scion/components.internal/form-field';
+import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
+import {KeyValueEntry, SciKeyValueFieldComponent} from '@scion/components.internal/key-value-field';
+import {Observable} from 'rxjs';
+import {mapArray} from '@scion/toolkit/operators';
+import {AsyncPipe} from '@angular/common';
+
+@Component({
+ selector: 'app-create-perspective-page',
+ templateUrl: './create-perspective-page.component.html',
+ styleUrls: ['./create-perspective-page.component.scss'],
+ standalone: true,
+ imports: [
+ AddPartsComponent,
+ AddViewsComponent,
+ NavigateViewsComponent,
+ ReactiveFormsModule,
+ SciFormFieldComponent,
+ SciCheckboxComponent,
+ SciKeyValueFieldComponent,
+ AsyncPipe,
+ ],
+})
+export default class CreatePerspectivePageComponent {
+
+ protected form = this._formBuilder.group({
+ id: this._formBuilder.control('', Validators.required),
+ transient: this._formBuilder.control(undefined),
+ data: this._formBuilder.array>([]),
+ parts: this._formBuilder.control([], Validators.required),
+ views: this._formBuilder.control([]),
+ viewNavigations: this._formBuilder.control([]),
+ });
+
+ protected registerError: string | false | undefined;
+ protected partProposals$: Observable;
+ protected viewProposals$: Observable;
+
+ constructor(private _formBuilder: NonNullableFormBuilder,
+ private _settingsService: SettingsService,
+ private _workbenchService: WorkbenchService) {
+ this.partProposals$ = this.form.controls.parts.valueChanges
+ .pipe(mapArray(part => part.id));
+ this.viewProposals$ = this.form.controls.views.valueChanges
+ .pipe(mapArray(view => view.id));
+ }
+
+ protected async onRegister(): Promise {
+ this.registerError = undefined;
+ try {
+ await this._workbenchService.registerPerspective({
+ id: this.form.controls.id.value,
+ transient: this.form.controls.transient.value || undefined,
+ data: SciKeyValueFieldComponent.toDictionary(this.form.controls.data) ?? undefined,
+ layout: this.createLayout(),
+ });
+ this.registerError = false;
+ this.resetForm();
+ }
+ catch (error) {
+ this.registerError = stringifyError(error);
+ }
+ }
+
+ private createLayout(): WorkbenchLayoutFn {
+ // Capture form values, since the `layout` function is evaluated independently of the form life-cycle
+ const [initialPart, ...parts] = this.form.controls.parts.value;
+ const views = this.form.controls.views.value;
+ const viewNavigations = this.form.controls.viewNavigations.value;
+
+ return (factory: WorkbenchLayoutFactory): WorkbenchLayout => {
+ // Add initial part.
+ let layout = factory.addPart(initialPart.id, {
+ activate: initialPart.options?.activate,
+ });
+
+ // Add other parts.
+ for (const part of parts) {
+ layout = layout.addPart(part.id, {
+ relativeTo: part.relativeTo!.relativeTo,
+ align: part.relativeTo!.align!,
+ ratio: part.relativeTo!.ratio,
+ }, {activate: part.options?.activate});
+ }
+
+ // Add views.
+ for (const view of views) {
+ layout = layout.addView(view.id, {
+ partId: view.options.partId,
+ position: view.options.position,
+ activateView: view.options.activateView,
+ activatePart: view.options.activatePart,
+ cssClass: view.options.cssClass,
+ });
+ }
+
+ // Add navigations.
+ for (const viewNavigation of viewNavigations) {
+ layout = layout.navigateView(viewNavigation.id, viewNavigation.commands, {
+ hint: viewNavigation.extras?.hint,
+ state: viewNavigation.extras?.state,
+ cssClass: viewNavigation.extras?.cssClass,
+ });
+ }
+ return layout;
+ };
+ }
+
+ private resetForm(): void {
+ if (this._settingsService.isEnabled('resetFormsOnSubmit')) {
+ this.form.reset();
+ this.form.setControl('data', this._formBuilder.array>([]));
+ }
+ }
+}
diff --git a/apps/workbench-testing-app/src/app/layout-page/layout-page.component.html b/apps/workbench-testing-app/src/app/layout-page/layout-page.component.html
index 8a5ace030..f7e2e58ae 100644
--- a/apps/workbench-testing-app/src/app/layout-page/layout-page.component.html
+++ b/apps/workbench-testing-app/src/app/layout-page/layout-page.component.html
@@ -1,17 +1,11 @@
-
-
+
+
-
-
-
-
-
+
+
-
-
-
diff --git a/apps/workbench-testing-app/src/app/layout-page/layout-page.component.ts b/apps/workbench-testing-app/src/app/layout-page/layout-page.component.ts
index 8510cf8bf..2db924b6c 100644
--- a/apps/workbench-testing-app/src/app/layout-page/layout-page.component.ts
+++ b/apps/workbench-testing-app/src/app/layout-page/layout-page.component.ts
@@ -9,12 +9,10 @@
*/
import {Component} from '@angular/core';
-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';
import {SciTabbarComponent, SciTabDirective} from '@scion/components.internal/tabbar';
+import ModifyLayoutPageComponent from './modify-layout-page/modify-layout-page.component';
+import CreatePerspectivePageComponent from './create-perspective-page/create-perspective-page.component';
@Component({
selector: 'app-layout-page',
@@ -24,11 +22,9 @@ import {SciTabbarComponent, SciTabDirective} from '@scion/components.internal/ta
imports: [
SciTabbarComponent,
SciTabDirective,
- AddPartPageComponent,
- AddViewPageComponent,
- ActivateViewPageComponent,
+ ModifyLayoutPageComponent,
+ CreatePerspectivePageComponent,
RegisterPartActionPageComponent,
- RegisterRoutePageComponent,
],
})
export default class LayoutPageComponent {
diff --git a/apps/workbench-testing-app/src/app/layout-page/modify-layout-page/modify-layout-page.component.html b/apps/workbench-testing-app/src/app/layout-page/modify-layout-page/modify-layout-page.component.html
new file mode 100644
index 000000000..151eec0b5
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/modify-layout-page/modify-layout-page.component.html
@@ -0,0 +1,22 @@
+
+
+@if (modifyError) {
+
+}
diff --git a/apps/workbench-testing-app/src/app/layout-page/activate-view-page/activate-view-page.component.scss b/apps/workbench-testing-app/src/app/layout-page/modify-layout-page/modify-layout-page.component.scss
similarity index 87%
rename from apps/workbench-testing-app/src/app/layout-page/activate-view-page/activate-view-page.component.scss
rename to apps/workbench-testing-app/src/app/layout-page/modify-layout-page/modify-layout-page.component.scss
index 05e8cc1f9..6a312ca72 100644
--- a/apps/workbench-testing-app/src/app/layout-page/activate-view-page/activate-view-page.component.scss
+++ b/apps/workbench-testing-app/src/app/layout-page/modify-layout-page/modify-layout-page.component.scss
@@ -25,12 +25,7 @@
}
}
- > output.navigate-success {
- flex: none;
- display: none;
- }
-
- > output.navigate-error {
+ > output.modify-error {
flex: none;
border: 1px solid var(--sci-color-negative);
background-color: var(--sci-color-background-negative);
diff --git a/apps/workbench-testing-app/src/app/layout-page/modify-layout-page/modify-layout-page.component.ts b/apps/workbench-testing-app/src/app/layout-page/modify-layout-page/modify-layout-page.component.ts
new file mode 100644
index 000000000..c630bab97
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/modify-layout-page/modify-layout-page.component.ts
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2018-2024 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} from '@angular/core';
+import {AddPartsComponent, PartDescriptor} from '../tables/add-parts/add-parts.component';
+import {AddViewsComponent, ViewDescriptor} from '../tables/add-views/add-views.component';
+import {NavigateViewsComponent, NavigationDescriptor} from '../tables/navigate-views/navigate-views.component';
+import {NonNullableFormBuilder, ReactiveFormsModule} from '@angular/forms';
+import {SettingsService} from '../../settings.service';
+import {WorkbenchRouter, WorkbenchService} from '@scion/workbench';
+import {stringifyError} from '../../common/stringify-error.util';
+import {combineLatestWith, Observable} from 'rxjs';
+import {mapArray} from '@scion/toolkit/operators';
+import {map, startWith} from 'rxjs/operators';
+import {AsyncPipe} from '@angular/common';
+
+@Component({
+ selector: 'app-modify-layout-page',
+ templateUrl: './modify-layout-page.component.html',
+ styleUrls: ['./modify-layout-page.component.scss'],
+ standalone: true,
+ imports: [
+ AddPartsComponent,
+ AddViewsComponent,
+ NavigateViewsComponent,
+ ReactiveFormsModule,
+ AsyncPipe,
+ ],
+})
+export default class ModifyLayoutPageComponent {
+
+ protected form = this._formBuilder.group({
+ parts: this._formBuilder.control([]),
+ views: this._formBuilder.control([]),
+ viewNavigations: this._formBuilder.control([]),
+ });
+
+ protected modifyError: string | false | undefined;
+ protected partProposals$: Observable;
+ protected viewProposals$: Observable;
+
+ constructor(workbenchService: WorkbenchService,
+ private _formBuilder: NonNullableFormBuilder,
+ private _settingsService: SettingsService,
+ private _workbenchRouter: WorkbenchRouter) {
+ this.partProposals$ = workbenchService.parts$
+ .pipe(
+ combineLatestWith(this.form.controls.parts.valueChanges.pipe(startWith([]))),
+ map(([a, b]) => [...a, ...b]),
+ mapArray(part => part.id),
+ );
+ this.viewProposals$ = workbenchService.views$
+ .pipe(
+ combineLatestWith(this.form.controls.views.valueChanges.pipe(startWith([]))),
+ map(([a, b]) => [...a, ...b]),
+ mapArray(view => view.id),
+ );
+ }
+
+ protected async onModify(): Promise {
+ this.modifyError = undefined;
+ this.navigate()
+ .then(success => success ? Promise.resolve() : Promise.reject('Modification failed'))
+ .then(() => this.resetForm())
+ .catch(error => this.modifyError = stringifyError(error));
+ }
+
+ private navigate(): Promise {
+ return this._workbenchRouter.ɵnavigate(layout => {
+ // Add parts.
+ for (const part of this.form.controls.parts.value) {
+ layout = layout.addPart(part.id, {
+ relativeTo: part.relativeTo!.relativeTo,
+ align: part.relativeTo!.align!,
+ ratio: part.relativeTo!.ratio!,
+ }, {activate: part.options?.activate});
+ }
+
+ // Add views.
+ for (const view of this.form.controls.views.value) {
+ layout = layout.addView(view.id, {
+ partId: view.options.partId,
+ position: view.options.position,
+ activateView: view.options.activateView,
+ activatePart: view.options.activatePart,
+ cssClass: view.options.cssClass,
+ });
+ }
+
+ // Add navigations.
+ for (const viewNavigation of this.form.controls.viewNavigations.value) {
+ layout = layout.navigateView(viewNavigation.id, viewNavigation.commands, {
+ hint: viewNavigation.extras?.hint,
+ state: viewNavigation.extras?.state,
+ cssClass: viewNavigation.extras?.cssClass,
+ });
+ }
+
+ return layout;
+ });
+ }
+
+ private resetForm(): void {
+ if (this._settingsService.isEnabled('resetFormsOnSubmit')) {
+ this.form.reset();
+ }
+ }
+}
diff --git a/apps/workbench-testing-app/src/app/layout-page/register-part-action-page/register-part-action-page.component.html b/apps/workbench-testing-app/src/app/layout-page/register-part-action-page/register-part-action-page.component.html
index 7aaa11c47..1bd982d0e 100644
--- a/apps/workbench-testing-app/src/app/layout-page/register-part-action-page/register-part-action-page.component.html
+++ b/apps/workbench-testing-app/src/app/layout-page/register-part-action-page/register-part-action-page.component.html
@@ -13,11 +13,11 @@
-
+
-
+
diff --git a/apps/workbench-testing-app/src/app/layout-page/register-part-action-page/register-part-action-page.component.ts b/apps/workbench-testing-app/src/app/layout-page/register-part-action-page/register-part-action-page.component.ts
index 617d84f9f..79eae6dfb 100644
--- a/apps/workbench-testing-app/src/app/layout-page/register-part-action-page/register-part-action-page.component.ts
+++ b/apps/workbench-testing-app/src/app/layout-page/register-part-action-page/register-part-action-page.component.ts
@@ -17,6 +17,8 @@ import {undefinedIfEmpty} from '../../common/undefined-if-empty.util';
import {stringifyError} from '../../common/stringify-error.util';
import {SciFormFieldComponent} from '@scion/components.internal/form-field';
import {Arrays} from '@scion/toolkit/util';
+import {SettingsService} from '../../settings.service';
+import {CssClassComponent} from '../../css-class/css-class.component';
@Component({
selector: 'app-register-part-action-page',
@@ -29,6 +31,7 @@ import {Arrays} from '@scion/toolkit/util';
AsyncPipe,
ReactiveFormsModule,
SciFormFieldComponent,
+ CssClassComponent,
],
})
export default class RegisterPartActionPageComponent {
@@ -36,7 +39,7 @@ export default class RegisterPartActionPageComponent {
public form = this._formBuilder.group({
content: this._formBuilder.control('', {validators: Validators.required}),
align: this._formBuilder.control<'start' | 'end' | ''>(''),
- cssClass: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
canMatch: this._formBuilder.group({
view: this._formBuilder.control(''),
part: this._formBuilder.control(''),
@@ -45,7 +48,9 @@ export default class RegisterPartActionPageComponent {
});
public registerError: string | false | undefined;
- constructor(private _formBuilder: NonNullableFormBuilder, public workbenchService: WorkbenchService) {
+ constructor(private _formBuilder: NonNullableFormBuilder,
+ private _settingsService: SettingsService,
+ public workbenchService: WorkbenchService) {
}
public onRegister(): void {
@@ -74,15 +79,21 @@ export default class RegisterPartActionPageComponent {
}
return true;
}),
- cssClass: this.form.controls.cssClass.value.split(/\s+/).filter(Boolean),
+ cssClass: this.form.controls.cssClass.value,
});
this.registerError = false;
- this.form.reset();
+ this.resetForm();
}
catch (error: unknown) {
this.registerError = stringifyError(error);
}
}
+
+ private resetForm(): void {
+ if (this._settingsService.isEnabled('resetFormsOnSubmit')) {
+ this.form.reset();
+ }
+ }
}
@Component({
diff --git a/apps/workbench-testing-app/src/app/layout-page/register-route-page/register-route-page.component.html b/apps/workbench-testing-app/src/app/layout-page/register-route-page/register-route-page.component.html
deleted file mode 100644
index e8fe2fc01..000000000
--- a/apps/workbench-testing-app/src/app/layout-page/register-route-page/register-route-page.component.html
+++ /dev/null
@@ -1,34 +0,0 @@
-
diff --git a/apps/workbench-testing-app/src/app/layout-page/register-route-page/register-route-page.component.scss b/apps/workbench-testing-app/src/app/layout-page/register-route-page/register-route-page.component.scss
deleted file mode 100644
index 48a512dc4..000000000
--- a/apps/workbench-testing-app/src/app/layout-page/register-route-page/register-route-page.component.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-:host {
- display: grid;
- padding: 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-border);
- border-radius: var(--sci-corner);
- padding: 1em;
-
- > header {
- margin-bottom: 1em;
- font-weight: bold;
- }
- }
- }
-}
diff --git a/apps/workbench-testing-app/src/app/layout-page/register-route-page/register-route-page.component.ts b/apps/workbench-testing-app/src/app/layout-page/register-route-page/register-route-page.component.ts
deleted file mode 100644
index 51ff9dab5..000000000
--- a/apps/workbench-testing-app/src/app/layout-page/register-route-page/register-route-page.component.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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, Type} from '@angular/core';
-import {NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
-import {WorkbenchRouteData} from '@scion/workbench';
-import {KeyValuePipe, NgFor, NgIf} from '@angular/common';
-import {DefaultExport, Router, Routes} from '@angular/router';
-import {SciFormFieldComponent} from '@scion/components.internal/form-field';
-
-@Component({
- selector: 'app-register-route-page',
- templateUrl: './register-route-page.component.html',
- styleUrls: ['./register-route-page.component.scss'],
- standalone: true,
- imports: [
- NgIf,
- NgFor,
- ReactiveFormsModule,
- KeyValuePipe,
- SciFormFieldComponent,
- ],
-})
-export default class RegisterRoutePageComponent {
-
- public readonly componentRefs = new Map Promise>>>()
- .set('view-page', () => import('../../view-page/view-page.component'))
- .set('router-page', () => import('../../router-page/router-page.component'));
-
- public form = this._formBuilder.group({
- path: this._formBuilder.control(''),
- component: this._formBuilder.control<'view-page' | 'router-page' | ''>('', Validators.required),
- outlet: this._formBuilder.control(''),
- routeData: this._formBuilder.group({
- title: this._formBuilder.control(''),
- cssClass: this._formBuilder.control(''),
- }),
- });
-
- constructor(private _formBuilder: NonNullableFormBuilder, private _router: Router) {
- }
-
- public onRegister(): void {
- this.replaceRouterConfig([
- ...this._router.config,
- {
- path: this.form.controls.path.value,
- outlet: this.form.controls.outlet.value || undefined,
- loadComponent: this.componentRefs.get(this.form.controls.component.value),
- data: {
- [WorkbenchRouteData.title]: this.form.controls.routeData.controls.title.value || undefined,
- [WorkbenchRouteData.heading]: 'Workbench E2E Testpage',
- [WorkbenchRouteData.cssClass]: this.form.controls.routeData.controls.cssClass.value.split(/\s+/).filter(Boolean),
- },
- },
- ]);
-
- // Perform navigation to apply the route config change.
- this._router.navigate([], {skipLocationChange: true, onSameUrlNavigation: 'reload'}).then();
- this.form.reset();
- }
-
- /**
- * Replaces the router configuration to install or uninstall routes at runtime.
- *
- * Same implementation as in {@link WorkbenchAuxiliaryRoutesRegistrator}.
- */
- private replaceRouterConfig(config: Routes): void {
- // Note:
- // - Do not use Router.resetConfig(...) which would destroy any currently routed component because copying all routes
- // - Do not assign the router a new Routes object (Router.config = ...) to allow resolution of routes added during `NavigationStart` (since Angular 7.x)
- // (because Angular uses a reference to the Routes object during route navigation)
- const newRoutes: Routes = [...config];
- this._router.config.splice(0, this._router.config.length, ...newRoutes);
- }
-}
diff --git a/apps/workbench-testing-app/src/app/layout-page/tables/add-parts/add-parts.component.html b/apps/workbench-testing-app/src/app/layout-page/tables/add-parts/add-parts.component.html
new file mode 100644
index 000000000..c1b0f405c
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/tables/add-parts/add-parts.component.html
@@ -0,0 +1,38 @@
+
+
+
+
+
diff --git a/apps/workbench-testing-app/src/app/layout-page/tables/add-parts/add-parts.component.scss b/apps/workbench-testing-app/src/app/layout-page/tables/add-parts/add-parts.component.scss
new file mode 100644
index 000000000..bd37309e2
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/tables/add-parts/add-parts.component.scss
@@ -0,0 +1,24 @@
+@use '@scion/components.internal/design' as sci-design;
+
+:host {
+ display: grid;
+
+ > form {
+ display: grid;
+ grid-template-columns: 7.5em repeat(3, 1fr) min-content auto;
+ gap: .5em .75em;
+ align-items: center;
+
+ > span.checkbox {
+ text-align: center;
+ }
+
+ > sci-checkbox {
+ justify-self: center;
+ }
+
+ > input, select {
+ @include sci-design.style-input-field();
+ }
+ }
+}
diff --git a/apps/workbench-testing-app/src/app/layout-page/tables/add-parts/add-parts.component.ts b/apps/workbench-testing-app/src/app/layout-page/tables/add-parts/add-parts.component.ts
new file mode 100644
index 000000000..6fe68b96d
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/tables/add-parts/add-parts.component.ts
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2018-2024 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, forwardRef, Input} from '@angular/core';
+import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, NonNullableFormBuilder, ReactiveFormsModule, ValidationErrors, Validator, Validators} from '@angular/forms';
+import {SciFormFieldComponent} from '@scion/components.internal/form-field';
+import {noop} from 'rxjs';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
+import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
+import {MAIN_AREA} from '@scion/workbench';
+import {SciMaterialIconDirective} from '@scion/components.internal/material-icon';
+import {UUID} from '@scion/toolkit/uuid';
+
+@Component({
+ selector: 'app-add-parts',
+ templateUrl: './add-parts.component.html',
+ styleUrls: ['./add-parts.component.scss'],
+ standalone: true,
+ imports: [
+ ReactiveFormsModule,
+ SciFormFieldComponent,
+ SciCheckboxComponent,
+ SciMaterialIconDirective,
+ ],
+ providers: [
+ {provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => AddPartsComponent)},
+ {provide: NG_VALIDATORS, multi: true, useExisting: forwardRef(() => AddPartsComponent)},
+ ],
+})
+export class AddPartsComponent implements ControlValueAccessor, Validator {
+
+ private _cvaChangeFn: (value: PartDescriptor[]) => void = noop;
+ private _cvaTouchedFn: () => void = noop;
+
+ @Input()
+ public requiresInitialPart = false;
+
+ @Input({transform: arrayAttribute})
+ public partProposals: string[] = [];
+
+ protected form = this._formBuilder.group({
+ parts: this._formBuilder.array;
+ relativeTo: FormGroup<{
+ relativeTo: FormControl;
+ align: FormControl<'left' | 'right' | 'top' | 'bottom' | undefined>;
+ ratio: FormControl;
+ }>;
+ options: FormGroup<{
+ activate: FormControl;
+ }>;
+ }>>([]),
+ });
+
+ protected MAIN_AREA = MAIN_AREA;
+ protected relativeToList = `relative-to-list-${UUID.randomUUID()}`;
+ protected idList = `id-list-${UUID.randomUUID()}`;
+
+ constructor(private _formBuilder: NonNullableFormBuilder) {
+ this.form.valueChanges
+ .pipe(takeUntilDestroyed())
+ .subscribe(() => {
+ this._cvaChangeFn(this.form.controls.parts.controls.map(partFormGroup => ({
+ id: partFormGroup.controls.id.value,
+ relativeTo: {
+ relativeTo: partFormGroup.controls.relativeTo.controls.relativeTo.value,
+ align: partFormGroup.controls.relativeTo.controls.align.value,
+ ratio: partFormGroup.controls.relativeTo.controls.ratio.value,
+ },
+ options: {
+ activate: partFormGroup.controls.options.controls.activate.value,
+ },
+ })));
+ this._cvaTouchedFn();
+ });
+ }
+
+ protected onAddPart(): void {
+ this.addPart({
+ id: '',
+ relativeTo: {},
+ });
+ }
+
+ protected onRemovePart(index: number): void {
+ this.form.controls.parts.removeAt(index);
+ }
+
+ private addPart(part: PartDescriptor, options?: {emitEvent?: boolean}): void {
+ const isInitialPart = this.requiresInitialPart && this.form.controls.parts.length === 0;
+ this.form.controls.parts.push(
+ this._formBuilder.group({
+ id: this._formBuilder.control(part.id, Validators.required),
+ relativeTo: this._formBuilder.group({
+ relativeTo: this._formBuilder.control({value: isInitialPart ? undefined : part.relativeTo.relativeTo, disabled: isInitialPart}),
+ align: this._formBuilder.control<'left' | 'right' | 'top' | 'bottom' | undefined>({value: isInitialPart ? undefined : part.relativeTo.align, disabled: isInitialPart}, isInitialPart ? Validators.nullValidator : Validators.required),
+ ratio: this._formBuilder.control({value: isInitialPart ? undefined : part.relativeTo.ratio, disabled: isInitialPart}),
+ }),
+ options: this._formBuilder.group({
+ activate: part.options?.activate,
+ }),
+ }), {emitEvent: options?.emitEvent ?? true});
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public writeValue(parts: PartDescriptor[] | undefined | null): void {
+ this.form.controls.parts.clear({emitEvent: false});
+ parts?.forEach(part => this.addPart(part, {emitEvent: false}));
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public registerOnChange(fn: any): void {
+ this._cvaChangeFn = fn;
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public registerOnTouched(fn: any): void {
+ this._cvaTouchedFn = fn;
+ }
+
+ /**
+ * Method implemented as part of `Validator` to work with Angular forms API
+ * @docs-private
+ */
+ public validate(control: AbstractControl): ValidationErrors | null {
+ return this.form.controls.parts.valid ? null : {valid: false};
+ }
+}
+
+export interface PartDescriptor {
+ id: string | MAIN_AREA;
+ relativeTo: {
+ relativeTo?: string;
+ align?: 'left' | 'right' | 'top' | 'bottom';
+ ratio?: number;
+ };
+ options?: {
+ activate?: boolean;
+ };
+}
+
+function arrayAttribute(proposals: string[] | null | undefined): string[] {
+ return proposals ?? [];
+}
diff --git a/apps/workbench-testing-app/src/app/layout-page/tables/add-views/add-views.component.html b/apps/workbench-testing-app/src/app/layout-page/tables/add-views/add-views.component.html
new file mode 100644
index 000000000..c78462b76
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/tables/add-views/add-views.component.html
@@ -0,0 +1,32 @@
+
+
+
diff --git a/apps/workbench-testing-app/src/app/layout-page/tables/add-views/add-views.component.scss b/apps/workbench-testing-app/src/app/layout-page/tables/add-views/add-views.component.scss
new file mode 100644
index 000000000..09725d00b
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/tables/add-views/add-views.component.scss
@@ -0,0 +1,24 @@
+@use '@scion/components.internal/design' as sci-design;
+
+:host {
+ display: grid;
+
+ > form {
+ display: grid;
+ grid-template-columns: 7.5em 1fr 5em 10em min-content min-content auto;
+ gap: .5em .75em;
+ align-items: center;
+
+ > span.checkbox {
+ text-align: center;
+ }
+
+ > sci-checkbox {
+ justify-self: center;
+ }
+
+ > input {
+ @include sci-design.style-input-field();
+ }
+ }
+}
diff --git a/apps/workbench-testing-app/src/app/perspective-page/perspective-page-views/perspective-page-views.component.ts b/apps/workbench-testing-app/src/app/layout-page/tables/add-views/add-views.component.ts
similarity index 50%
rename from apps/workbench-testing-app/src/app/perspective-page/perspective-page-views/perspective-page-views.component.ts
rename to apps/workbench-testing-app/src/app/layout-page/tables/add-views/add-views.component.ts
index b422fd118..9caf41bef 100644
--- a/apps/workbench-testing-app/src/app/perspective-page/perspective-page-views/perspective-page-views.component.ts
+++ b/apps/workbench-testing-app/src/app/layout-page/tables/add-views/add-views.component.ts
@@ -9,89 +9,96 @@
*/
import {Component, forwardRef, Input} from '@angular/core';
-import {CommonModule} from '@angular/common';
import {noop} from 'rxjs';
import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, NonNullableFormBuilder, ReactiveFormsModule, ValidationErrors, Validator, Validators} from '@angular/forms';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
import {SciFormFieldComponent} from '@scion/components.internal/form-field';
-import {PerspectivePagePartEntry} from '../perspective-page-parts/perspective-page-parts.component';
import {SciMaterialIconDirective} from '@scion/components.internal/material-icon';
+import {CssClassComponent} from '../../../css-class/css-class.component';
+import {UUID} from '@scion/toolkit/uuid';
@Component({
- selector: 'app-perspective-page-views',
- templateUrl: './perspective-page-views.component.html',
- styleUrls: ['./perspective-page-views.component.scss'],
+ selector: 'app-add-views',
+ templateUrl: './add-views.component.html',
+ styleUrls: ['./add-views.component.scss'],
standalone: true,
imports: [
- CommonModule,
ReactiveFormsModule,
SciCheckboxComponent,
SciFormFieldComponent,
SciMaterialIconDirective,
+ CssClassComponent,
],
providers: [
- {provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => PerspectivePageViewsComponent)},
- {provide: NG_VALIDATORS, multi: true, useExisting: forwardRef(() => PerspectivePageViewsComponent)},
+ {provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => AddViewsComponent)},
+ {provide: NG_VALIDATORS, multi: true, useExisting: forwardRef(() => AddViewsComponent)},
],
})
-export class PerspectivePageViewsComponent implements ControlValueAccessor, Validator {
+export class AddViewsComponent implements ControlValueAccessor, Validator {
- private _cvaChangeFn: (value: PerspectivePageViewEntry[]) => void = noop;
+ private _cvaChangeFn: (value: ViewDescriptor[]) => void = noop;
private _cvaTouchedFn: () => void = noop;
- @Input()
- public partEntries: PerspectivePagePartEntry[] = [];
+ @Input({transform: arrayAttribute})
+ public partProposals: string[] = [];
- public form = this._formBuilder.group({
+ protected form = this._formBuilder.group({
views: this._formBuilder.array;
- partId: FormControl;
- position: FormControl;
- activateView: FormControl;
- activatePart: FormControl;
+ options: FormGroup<{
+ partId: FormControl;
+ position: FormControl;
+ cssClass: FormControl;
+ activateView: FormControl;
+ activatePart: FormControl;
+ }>;
}>>([]),
});
+ protected partList = `part-list-${UUID.randomUUID()}`;
constructor(private _formBuilder: NonNullableFormBuilder) {
this.form.valueChanges
.pipe(takeUntilDestroyed())
.subscribe(() => {
- const views: PerspectivePageViewEntry[] = this.form.controls.views.controls.map(viewFormGroup => ({
+ this._cvaChangeFn(this.form.controls.views.controls.map(viewFormGroup => ({
id: viewFormGroup.controls.id.value,
- partId: viewFormGroup.controls.partId.value,
- position: viewFormGroup.controls.position.value,
- activateView: viewFormGroup.controls.activateView.value,
- activatePart: viewFormGroup.controls.activatePart.value,
- }));
- this._cvaChangeFn(views);
+ options: {
+ partId: viewFormGroup.controls.options.controls.partId.value,
+ position: viewFormGroup.controls.options.controls.position.value,
+ cssClass: viewFormGroup.controls.options.controls.cssClass.value,
+ activateView: viewFormGroup.controls.options.controls.activateView.value,
+ activatePart: viewFormGroup.controls.options.controls.activatePart.value,
+ },
+ })));
this._cvaTouchedFn();
});
}
protected onAddView(): void {
- this.addViewEntry({
+ this.addView({
id: '',
- partId: '',
+ options: {
+ partId: '',
+ },
});
}
- protected onClearViews(): void {
- this.form.controls.views.clear();
- }
-
protected onRemoveView(index: number): void {
this.form.controls.views.removeAt(index);
}
- private addViewEntry(view: PerspectivePageViewEntry, options?: {emitEvent?: boolean}): void {
+ private addView(view: ViewDescriptor, options?: {emitEvent?: boolean}): void {
this.form.controls.views.push(
this._formBuilder.group({
id: this._formBuilder.control(view.id, Validators.required),
- partId: this._formBuilder.control(view.partId, Validators.required),
- position: this._formBuilder.control(view.position),
- activateView: this._formBuilder.control(view.activateView),
- activatePart: this._formBuilder.control(view.activatePart),
+ options: this._formBuilder.group({
+ partId: this._formBuilder.control(view.options.partId, Validators.required),
+ position: this._formBuilder.control(view.options.position),
+ cssClass: this._formBuilder.control(view.options.cssClass),
+ activateView: this._formBuilder.control(view.options.activateView),
+ activatePart: this._formBuilder.control(view.options.activatePart),
+ }),
}), {emitEvent: options?.emitEvent ?? true});
}
@@ -99,9 +106,9 @@ export class PerspectivePageViewsComponent implements ControlValueAccessor, Vali
* Method implemented as part of `ControlValueAccessor` to work with Angular forms API
* @docs-private
*/
- public writeValue(value: PerspectivePageViewEntry[] | undefined | null): void {
+ public writeValue(views: ViewDescriptor[] | undefined | null): void {
this.form.controls.views.clear({emitEvent: false});
- value?.forEach(view => this.addViewEntry(view, {emitEvent: false}));
+ views?.forEach(view => this.addView(view, {emitEvent: false}));
}
/**
@@ -129,10 +136,17 @@ export class PerspectivePageViewsComponent implements ControlValueAccessor, Vali
}
}
-export type PerspectivePageViewEntry = {
+export interface ViewDescriptor {
id: string;
- partId: string;
- position?: number;
- activateView?: boolean;
- activatePart?: boolean;
-};
+ options: {
+ partId: string;
+ position?: number | 'start' | 'end' | 'before-active-view' | 'after-active-view';
+ activateView?: boolean;
+ activatePart?: boolean;
+ cssClass?: string | string[];
+ };
+}
+
+function arrayAttribute(proposals: string[] | null | undefined): string[] {
+ return proposals ?? [];
+}
diff --git a/apps/workbench-testing-app/src/app/layout-page/tables/navigate-views/navigate-views.component.html b/apps/workbench-testing-app/src/app/layout-page/tables/navigate-views/navigate-views.component.html
new file mode 100644
index 000000000..9d53212ae
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/tables/navigate-views/navigate-views.component.html
@@ -0,0 +1,29 @@
+
+
+
diff --git a/apps/workbench-testing-app/src/app/layout-page/tables/navigate-views/navigate-views.component.scss b/apps/workbench-testing-app/src/app/layout-page/tables/navigate-views/navigate-views.component.scss
new file mode 100644
index 000000000..67b063682
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/tables/navigate-views/navigate-views.component.scss
@@ -0,0 +1,16 @@
+@use '@scion/components.internal/design' as sci-design;
+
+:host {
+ display: grid;
+
+ > form {
+ display: grid;
+ grid-template-columns: 7.5em 1fr 10em 14em 10em auto;
+ gap: .5em .75em;
+ align-items: center;
+
+ > input {
+ @include sci-design.style-input-field();
+ }
+ }
+}
diff --git a/apps/workbench-testing-app/src/app/layout-page/tables/navigate-views/navigate-views.component.ts b/apps/workbench-testing-app/src/app/layout-page/tables/navigate-views/navigate-views.component.ts
new file mode 100644
index 000000000..276c75fce
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/layout-page/tables/navigate-views/navigate-views.component.ts
@@ -0,0 +1,151 @@
+/*
+ * 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, forwardRef, Input} from '@angular/core';
+import {noop} from 'rxjs';
+import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, NonNullableFormBuilder, ReactiveFormsModule, ValidationErrors, Validator, Validators} from '@angular/forms';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
+import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
+import {SciFormFieldComponent} from '@scion/components.internal/form-field';
+import {SciMaterialIconDirective} from '@scion/components.internal/material-icon';
+import {Commands, ViewState} from '@scion/workbench';
+import {RouterCommandsComponent} from '../../../router-commands/router-commands.component';
+import {NavigationStateComponent} from '../../../navigation-state/navigation-state.component';
+import {CssClassComponent} from '../../../css-class/css-class.component';
+import {UUID} from '@scion/toolkit/uuid';
+
+@Component({
+ selector: 'app-navigate-views',
+ templateUrl: './navigate-views.component.html',
+ styleUrls: ['./navigate-views.component.scss'],
+ standalone: true,
+ imports: [
+ ReactiveFormsModule,
+ SciCheckboxComponent,
+ SciFormFieldComponent,
+ SciMaterialIconDirective,
+ RouterCommandsComponent,
+ NavigationStateComponent,
+ CssClassComponent,
+ ],
+ providers: [
+ {provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => NavigateViewsComponent)},
+ {provide: NG_VALIDATORS, multi: true, useExisting: forwardRef(() => NavigateViewsComponent)},
+ ],
+})
+export class NavigateViewsComponent implements ControlValueAccessor, Validator {
+
+ private _cvaChangeFn: (value: NavigationDescriptor[]) => void = noop;
+ private _cvaTouchedFn: () => void = noop;
+
+ @Input({transform: arrayAttribute})
+ public viewProposals: string[] = [];
+
+ protected form = this._formBuilder.group({
+ navigations: this._formBuilder.array;
+ commands: FormControl;
+ extras: FormGroup<{
+ hint: FormControl;
+ state: FormControl;
+ cssClass: FormControl;
+ }>;
+ }>>([]),
+ });
+ protected viewList = `view-list-${UUID.randomUUID()}`;
+
+ constructor(private _formBuilder: NonNullableFormBuilder) {
+ this.form.valueChanges
+ .pipe(takeUntilDestroyed())
+ .subscribe(() => {
+ this._cvaChangeFn(this.form.controls.navigations.controls.map(navigationFormGroup => ({
+ id: navigationFormGroup.controls.id.value,
+ commands: navigationFormGroup.controls.commands.value,
+ extras: ({
+ hint: navigationFormGroup.controls.extras.controls.hint.value || undefined,
+ state: navigationFormGroup.controls.extras.controls.state.value,
+ cssClass: navigationFormGroup.controls.extras.controls.cssClass.value,
+ }),
+ })));
+ this._cvaTouchedFn();
+ });
+ }
+
+ protected onAddNavigation(): void {
+ this.addNavigation({
+ id: '',
+ commands: [],
+ });
+ }
+
+ protected onRemoveNavigation(index: number): void {
+ this.form.controls.navigations.removeAt(index);
+ }
+
+ private addNavigation(navigation: NavigationDescriptor, options?: {emitEvent?: boolean}): void {
+ this.form.controls.navigations.push(
+ this._formBuilder.group({
+ id: this._formBuilder.control(navigation.id, Validators.required),
+ commands: this._formBuilder.control(navigation.commands),
+ extras: this._formBuilder.group({
+ hint: this._formBuilder.control(navigation.extras?.hint),
+ state: this._formBuilder.control(navigation.extras?.state),
+ cssClass: this._formBuilder.control(undefined),
+ }),
+ }), {emitEvent: options?.emitEvent ?? true});
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public writeValue(navigations: NavigationDescriptor[] | undefined | null): void {
+ this.form.controls.navigations.clear({emitEvent: false});
+ navigations?.forEach(navigation => this.addNavigation(navigation, {emitEvent: false}));
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public registerOnChange(fn: any): void {
+ this._cvaChangeFn = fn;
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public registerOnTouched(fn: any): void {
+ this._cvaTouchedFn = fn;
+ }
+
+ /**
+ * Method implemented as part of `Validator` to work with Angular forms API
+ * @docs-private
+ */
+ public validate(control: AbstractControl): ValidationErrors | null {
+ return this.form.controls.navigations.valid ? null : {valid: false};
+ }
+}
+
+export interface NavigationDescriptor {
+ id: string;
+ commands: Commands;
+ extras?: {
+ hint?: string;
+ state?: ViewState;
+ cssClass?: string | string[];
+ };
+}
+
+function arrayAttribute(proposals: string[] | null | undefined): string[] {
+ return proposals ?? [];
+}
diff --git a/apps/workbench-testing-app/src/app/menu/menu-item.ts b/apps/workbench-testing-app/src/app/menu/menu-item.ts
index eb431edc6..bc9f7a497 100644
--- a/apps/workbench-testing-app/src/app/menu/menu-item.ts
+++ b/apps/workbench-testing-app/src/app/menu/menu-item.ts
@@ -29,7 +29,7 @@ export class MenuItem {
*/
public checked?: boolean;
/**
- * Specifies CSS class(es) to be added to the menu item, useful in end-to-end tests for locating the menu item.
+ * Specifies CSS class(es) to add to the menu item, e.g., to locate the menu item in tests.
*/
public cssClass?: string | string[];
diff --git a/apps/workbench-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.html b/apps/workbench-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.html
index a944492d9..298063e64 100644
--- a/apps/workbench-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.html
+++ b/apps/workbench-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.html
@@ -47,7 +47,7 @@
-
+
diff --git a/apps/workbench-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts b/apps/workbench-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts
index 205c4664b..e4ea65f7b 100644
--- a/apps/workbench-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts
+++ b/apps/workbench-testing-app/src/app/message-box-opener-page/message-box-opener-page.component.ts
@@ -17,6 +17,7 @@ import {KeyValueEntry, SciKeyValueFieldComponent} from '@scion/components.intern
import {SciFormFieldComponent} from '@scion/components.internal/form-field';
import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
import {MessageBoxPageComponent} from '../message-box-page/message-box-page.component';
+import {CssClassComponent} from '../css-class/css-class.component';
@Component({
selector: 'app-message-box-opener-page',
@@ -29,6 +30,7 @@ import {MessageBoxPageComponent} from '../message-box-page/message-box-page.comp
SciFormFieldComponent,
SciKeyValueFieldComponent,
SciCheckboxComponent,
+ CssClassComponent,
],
})
export default class MessageBoxOpenerPageComponent {
@@ -43,7 +45,7 @@ export default class MessageBoxOpenerPageComponent {
modality: this._formBuilder.control<'application' | 'view' | ''>(''),
contentSelectable: this._formBuilder.control(false),
inputs: this._formBuilder.array>([]),
- cssClass: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
}),
});
@@ -71,7 +73,7 @@ export default class MessageBoxOpenerPageComponent {
modality: this.form.controls.options.controls.modality.value || undefined,
contentSelectable: this.form.controls.options.controls.contentSelectable.value || undefined,
inputs: SciKeyValueFieldComponent.toDictionary(this.form.controls.options.controls.inputs) ?? undefined,
- cssClass: this.form.controls.options.controls.cssClass.value.split(/\s+/).filter(Boolean),
+ cssClass: this.form.controls.options.controls.cssClass.value,
};
if (this.isUseComponent()) {
diff --git a/apps/workbench-testing-app/src/app/navigation-state/navigation-state.component.html b/apps/workbench-testing-app/src/app/navigation-state/navigation-state.component.html
new file mode 100644
index 000000000..37df287d0
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/navigation-state/navigation-state.component.html
@@ -0,0 +1 @@
+
diff --git a/apps/workbench-testing-app/src/app/navigation-state/navigation-state.component.scss b/apps/workbench-testing-app/src/app/navigation-state/navigation-state.component.scss
new file mode 100644
index 000000000..85980ba98
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/navigation-state/navigation-state.component.scss
@@ -0,0 +1,10 @@
+@use '@scion/components.internal/design' as sci-design;
+
+:host {
+ display: inline-grid;
+
+ > input {
+ @include sci-design.style-input-field();
+ min-width: 0;
+ }
+}
diff --git a/apps/workbench-testing-app/src/app/navigation-state/navigation-state.component.ts b/apps/workbench-testing-app/src/app/navigation-state/navigation-state.component.ts
new file mode 100644
index 000000000..87cc7645e
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/navigation-state/navigation-state.component.ts
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018-2024 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, forwardRef} from '@angular/core';
+import {ControlValueAccessor, NG_VALUE_ACCESSOR, NonNullableFormBuilder, ReactiveFormsModule} from '@angular/forms';
+import {ViewState} from '@scion/workbench';
+import {noop} from 'rxjs';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
+
+@Component({
+ selector: 'app-navigation-state',
+ templateUrl: './navigation-state.component.html',
+ styleUrls: ['./navigation-state.component.scss'],
+ standalone: true,
+ imports: [
+ ReactiveFormsModule,
+ ],
+ providers: [
+ {provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => NavigationStateComponent)},
+ ],
+})
+export class NavigationStateComponent implements ControlValueAccessor {
+
+ private _cvaChangeFn: (state: ViewState | undefined) => void = noop;
+ private _cvaTouchedFn: () => void = noop;
+
+ protected formControl = this._formBuilder.control('');
+
+ constructor(private _formBuilder: NonNullableFormBuilder) {
+ this.formControl.valueChanges
+ .pipe(takeUntilDestroyed())
+ .subscribe(() => {
+ this._cvaChangeFn(this.parse(this.formControl.value));
+ this._cvaTouchedFn();
+ });
+ }
+
+ private parse(stringified: string): ViewState | undefined {
+ if (!stringified.length) {
+ return undefined;
+ }
+ const state: ViewState = {};
+ for (const match of stringified.matchAll(/(?[^=;]+)=(?[^;]+)/g)) {
+ const {key, value} = match.groups!;
+ state[key] = value;
+ }
+ return state;
+ }
+
+ private stringify(state: ViewState | null | undefined): string {
+ return Object.entries(state ?? {}).map(([key, value]) => `${key}=${value}`).join(';');
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public writeValue(state: ViewState | undefined | null): void {
+ this.formControl.setValue(this.stringify(state), {emitEvent: false});
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public registerOnChange(fn: any): void {
+ this._cvaChangeFn = fn;
+ }
+
+ /**
+ * Method implemented as part of `ControlValueAccessor` to work with Angular forms API
+ * @docs-private
+ */
+ public registerOnTouched(fn: any): void {
+ this._cvaTouchedFn = fn;
+ }
+}
diff --git a/apps/workbench-testing-app/src/app/notification-opener-page/notification-opener-page.component.html b/apps/workbench-testing-app/src/app/notification-opener-page/notification-opener-page.component.html
index 5f5026033..29a73674f 100644
--- a/apps/workbench-testing-app/src/app/notification-opener-page/notification-opener-page.component.html
+++ b/apps/workbench-testing-app/src/app/notification-opener-page/notification-opener-page.component.html
@@ -42,7 +42,7 @@
-
+
diff --git a/apps/workbench-testing-app/src/app/notification-opener-page/notification-opener-page.component.ts b/apps/workbench-testing-app/src/app/notification-opener-page/notification-opener-page.component.ts
index 7e8902a4c..881a58930 100644
--- a/apps/workbench-testing-app/src/app/notification-opener-page/notification-opener-page.component.ts
+++ b/apps/workbench-testing-app/src/app/notification-opener-page/notification-opener-page.component.ts
@@ -16,6 +16,7 @@ import {SciCheckboxComponent} from '@scion/components.internal/checkbox';
import {SciFormFieldComponent} from '@scion/components.internal/form-field';
import {stringifyError} from '../common/stringify-error.util';
import {NotificationPageComponent} from '../notification-page/notification-page.component';
+import {CssClassComponent} from '../css-class/css-class.component';
@Component({
selector: 'app-notification-opener-page',
@@ -27,6 +28,7 @@ import {NotificationPageComponent} from '../notification-page/notification-page.
ReactiveFormsModule,
SciFormFieldComponent,
SciCheckboxComponent,
+ CssClassComponent,
],
})
export default class NotificationOpenerPageComponent {
@@ -42,7 +44,7 @@ export default class NotificationOpenerPageComponent {
duration: this._formBuilder.control<'short' | 'medium' | 'long' | 'infinite' | '' | number>(''),
group: this._formBuilder.control(''),
useGroupInputReducer: this._formBuilder.control(false),
- cssClass: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
});
constructor(private _formBuilder: NonNullableFormBuilder, private _notificationService: NotificationService) {
@@ -59,7 +61,7 @@ export default class NotificationOpenerPageComponent {
duration: this.parseDurationFromUI(),
group: this.form.controls.group.value || undefined,
groupInputReduceFn: this.isUseGroupInputReducer() ? concatInput : undefined,
- cssClass: this.form.controls.cssClass.value.split(/\s+/).filter(Boolean),
+ cssClass: this.form.controls.cssClass.value,
});
}
catch (error) {
diff --git a/apps/workbench-testing-app/src/app/notification-page/notification-page.component.html b/apps/workbench-testing-app/src/app/notification-page/notification-page.component.html
index 4558845ef..cbc98094f 100644
--- a/apps/workbench-testing-app/src/app/notification-page/notification-page.component.html
+++ b/apps/workbench-testing-app/src/app/notification-page/notification-page.component.html
@@ -30,6 +30,6 @@
-
+
diff --git a/apps/workbench-testing-app/src/app/notification-page/notification-page.component.ts b/apps/workbench-testing-app/src/app/notification-page/notification-page.component.ts
index c32df828e..5ea97f46a 100644
--- a/apps/workbench-testing-app/src/app/notification-page/notification-page.component.ts
+++ b/apps/workbench-testing-app/src/app/notification-page/notification-page.component.ts
@@ -17,6 +17,7 @@ import {StringifyPipe} from '../common/stringify.pipe';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {filter} from 'rxjs/operators';
import {SciFormFieldComponent} from '@scion/components.internal/form-field';
+import {CssClassComponent} from '../css-class/css-class.component';
@Component({
selector: 'app-notification-page',
@@ -29,6 +30,7 @@ import {SciFormFieldComponent} from '@scion/components.internal/form-field';
StringifyPipe,
SciFormFieldComponent,
SciViewportComponent,
+ CssClassComponent,
],
})
export class NotificationPageComponent {
@@ -37,7 +39,7 @@ export class NotificationPageComponent {
title: this._formBuilder.control(''),
severity: this._formBuilder.control<'info' | 'warn' | 'error' | undefined>(undefined),
duration: this._formBuilder.control<'short' | 'medium' | 'long' | 'infinite' | number | undefined>(undefined),
- cssClass: this._formBuilder.control(''),
+ cssClass: this._formBuilder.control(undefined),
});
constructor(public notification: Notification