Skip to content

Commit

Permalink
feat(workbench): provide workbench dialog
Browse files Browse the repository at this point in the history
A dialog is a visual element for focused interaction with the user, such as prompting the user for input or confirming actions. The user can move or resize a dialog.

Displayed on top of other content, a dialog blocks interaction with other parts of the application. A dialog can be view-modal or application-modal. Multiple dialogs are stacked, and only the topmost dialog in each modality stack can be interacted with.
  • Loading branch information
k-genov authored and danielwiehl committed Nov 17, 2023
1 parent 83e5070 commit 34e5acc
Show file tree
Hide file tree
Showing 55 changed files with 3,060 additions and 301 deletions.
5 changes: 5 additions & 0 deletions apps/workbench-testing-app/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ export const routes: Routes = [
loadComponent: () => import('./message-box-opener-page/message-box-opener-page.component'),
data: {[WorkbenchRouteData.title]: 'Workbench Messagebox', [WorkbenchRouteData.heading]: 'Workbench E2E Testpage', [WorkbenchRouteData.cssClass]: 'e2e-test-message-box-opener', pinToStartPage: true},
},
{
path: 'test-dialog-opener',
loadComponent: () => import('./dialog-opener-page/dialog-opener-page.component'),
data: {[WorkbenchRouteData.title]: 'Workbench Dialog', [WorkbenchRouteData.heading]: 'Workbench E2E Testpage', [WorkbenchRouteData.cssClass]: 'e2e-test-dialog-opener', pinToStartPage: true},
},
{
path: 'test-notification-opener',
loadComponent: () => import('./notification-opener-page/notification-opener-page.component'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<form [formGroup]="form">
<section>
<sci-form-field label="Component">
<select [formControl]="form.controls.component" class="e2e-component">
<option value="dialog-page">DialogPageComponent</option>
<option value="focus-test-page">FocusTestPagePO</option>
<option value="dialog-opener-page">DialogOpenerPageComponent</option>
<option value="blank">BlankTestPageComponent</option>
</select>
</sci-form-field>
</section>

<section>
<sci-form-field label="Input">
<sci-key-value-field [keyValueFormArray]="form.controls.options.controls.inputs" [addable]="true" [removable]="true" class="e2e-inputs"/>
</sci-form-field>

<sci-form-field label="Modality">
<select [formControl]="form.controls.options.controls.modality" class="e2e-modality">
<option value="application">application</option>
<option value="view">view</option>
<option value="">default</option>
</select>
</sci-form-field>

<sci-form-field label="Contextual View ID">
<input [formControl]="form.controls.options.controls.contextualViewId" class="e2e-contextual-view-id">
</sci-form-field>

<sci-form-field label="Animate">
<sci-checkbox [formControl]="form.controls.options.controls.animate"></sci-checkbox>
</sci-form-field>

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

<section>
<sci-form-field label="View Context Active">
<sci-checkbox [formControl]="form.controls.viewContext" class="e2e-view-context"></sci-checkbox>
</sci-form-field>

<sci-form-field label="Count">
<input [formControl]="form.controls.count" class="e2e-count"
placeholder="Number of dialogs to open (1 by default). For every dialog, an 'index-$1' CSS class is added, where $1 is the index of the dialog, starting with 0.">
</sci-form-field>
</section>
</form>

<button (click)="onDialogOpen()" class="e2e-open" [disabled]="form.invalid" sci-primary>
Open dialog
</button>

<output class="return-value e2e-return-value" *ngIf="returnValue">
{{returnValue}}
</output>

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

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

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

> output.return-value {
border: 1px solid var(--sci-color-positive);
background-color: var(--sci-color-background-positive);
color: var(--sci-color-positive);
border-radius: var(--sci-corner);
padding: 1em;
font-family: monospace;
}

> output.dialog-error {
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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 {ApplicationRef, Component, Type} from '@angular/core';
import {FormGroup, NonNullableFormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
import {WorkbenchDialogService} from '@scion/workbench';
import {startWith} from 'rxjs/operators';
import {NgIf} from '@angular/common';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
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 {SciCheckboxComponent} from '@scion/components.internal/checkbox';
import {DialogPageComponent} from '../dialog-page/dialog-page.component';
import BlankTestPageComponent from '../test-pages/blank-test-page/blank-test-page.component';
import {FocusTestPageComponent} from '../test-pages/focus-test-page/focus-test-page.component';

@Component({
selector: 'app-dialog-opener-page',
templateUrl: './dialog-opener-page.component.html',
styleUrls: ['./dialog-opener-page.component.scss'],
standalone: true,
imports: [
NgIf,
ReactiveFormsModule,
SciFormFieldComponent,
SciKeyValueFieldComponent,
SciCheckboxComponent,
],
})
export default class DialogOpenerPageComponent {

public form = this._formBuilder.group({
component: this._formBuilder.control('dialog-page', Validators.required),
options: this._formBuilder.group({
inputs: this._formBuilder.array<FormGroup<KeyValueEntry>>([]),
modality: this._formBuilder.control<'application' | 'view' | ''>(''),
contextualViewId: this._formBuilder.control(''),
cssClass: this._formBuilder.control(''),
animate: this._formBuilder.control(undefined),
}),
count: this._formBuilder.control(''),
viewContext: this._formBuilder.control(true),
});
public dialogError: string | undefined;
public returnValue: string | undefined;

constructor(private _formBuilder: NonNullableFormBuilder,
private _dialogService: WorkbenchDialogService,
private _appRef: ApplicationRef) {
this.installContextualViewIdEnabler();
}

public async onDialogOpen(): Promise<void> {
this.dialogError = undefined;
this.returnValue = undefined;

const unsetViewContext = !this.form.controls.viewContext.value;
const dialogService = unsetViewContext ? this._appRef.injector.get(WorkbenchDialogService) : this._dialogService;

Check warning on line 66 in apps/workbench-testing-app/src/app/dialog-opener-page/dialog-opener-page.component.ts

View workflow job for this annotation

GitHub Actions / Linting

'get' is deprecated. from v4.0.0 use ProviderToken<T>

const dialogs = [];
for (let i = 0; i < Number(this.form.controls.count.value || 1); i++) {
dialogs.push(this.openDialog(dialogService, i));
}
await Promise.all(dialogs);
}

private openDialog(dialogService: WorkbenchDialogService, index: number): Promise<string | undefined> {
const component = this.parseComponentFromUI();
return dialogService.open<string | undefined>(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) || []),
animate: this.form.controls.options.controls.animate.value,
context: {
viewId: this.form.controls.options.controls.contextualViewId.value || undefined,
},
})
.then(result => this.returnValue = result)
.catch(error => this.dialogError = stringifyError(error) || 'Workbench Dialog was closed with an error');
}

private parseComponentFromUI(): Type<DialogPageComponent | BlankTestPageComponent | DialogOpenerPageComponent> {
switch (this.form.controls.component.value) {
case 'dialog-page':
return DialogPageComponent;
case 'dialog-opener-page':
return DialogOpenerPageComponent;
case 'focus-test-page':
return FocusTestPageComponent;
case 'blank':
return BlankTestPageComponent;
default:
throw Error(`[IllegalDialogComponent] Dialog component not supported: ${this.form.controls.component.value}`);
}
}

/**
* Enables the field for setting a contextual view reference when choosing view modality.
*/
private installContextualViewIdEnabler(): void {
this.form.controls.options.controls.modality.valueChanges
.pipe(
startWith(this.form.controls.options.controls.modality.value),
takeUntilDestroyed(),
)
.subscribe(modality => {
if (modality === 'view') {
this.form.controls.options.controls.contextualViewId.enable();
}
else {
this.form.controls.options.controls.contextualViewId.setValue('');
this.form.controls.options.controls.contextualViewId.disable();
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<form [formGroup]="form">
<sci-form-field label="Title" direction="column">
<input [formControl]="form.controls.title" class="e2e-title">
</sci-form-field>

<sci-form-field label="Input" *ngIf="input">
<input class="e2e-input" [value]="input" disabled>
</sci-form-field>

<sci-accordion class="e2e-size" variant="solid">
<ng-template sciAccordionItem [panel]="panel_size">
<header>Size</header>
</ng-template>
<ng-template #panel_size>
<sci-form-field label="Min Height">
<input [formControl]="form.controls.size.controls.minHeight">
</sci-form-field>

<sci-form-field label="Height">
<input [formControl]="form.controls.size.controls.height">
</sci-form-field>

<sci-form-field label="Max Height">
<input [formControl]="form.controls.size.controls.maxHeight">
</sci-form-field>

<sci-form-field label="Min Width">
<input [formControl]="form.controls.size.controls.minWidth">
</sci-form-field>

<sci-form-field label="Width">
<input [formControl]="form.controls.size.controls.width">
</sci-form-field>

<sci-form-field label="Max Width">
<input [formControl]="form.controls.size.controls.maxWidth">
</sci-form-field>
</ng-template>
</sci-accordion>

<sci-accordion class="e2e-miscellaneous" variant="solid">
<ng-template sciAccordionItem [panel]="panel_miscellaneous">
<header>Miscellaneous</header>
</ng-template>
<ng-template #panel_miscellaneous>
<sci-form-field label="Instance ID" title="Unique identifier of this component instance">
<input class="e2e-component-instance-id" [value]="uuid" disabled>
</sci-form-field>

<sci-form-field label="Closable">
<sci-checkbox [formControl]="form.controls.miscellaneous.controls.closable" class="e2e-closable"></sci-checkbox>
</sci-form-field>

<sci-form-field label="Padding">
<input [formControl]="form.controls.miscellaneous.controls.padding">
</sci-form-field>
</ng-template>
</sci-accordion>

<sci-accordion class="return-value e2e-return-value" variant="solid">
<ng-template sciAccordionItem [panel]="panel_return_value">
<header>Return Value</header>
</ng-template>
<ng-template #panel_return_value>
<input class="e2e-return-value" [formControl]="form.controls.result" placeholder="Optional data to return to the dialog opener">
</ng-template>
</sci-accordion>
</form>

<div class="buttons">
<button (click)="onClose()" class="e2e-close" sci-primary>Close</button>
<button (click)="onCloseWithError()" class="e2e-close-with-error">Close (with error)</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@use '@scion/components.internal/design' as sci-design;

:host {
display: flex;
flex-direction: column;

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

> sci-accordion {
header {
font-weight: bold;
}

&.return-value input {
@include sci-design.style-input-field();
}
}
}

> div.buttons {
flex: none;
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: .25em;
margin-top: 1em;
}
}
Loading

0 comments on commit 34e5acc

Please sign in to comment.