From eb801aed436174d6070aadb8df09bf5eb7137bf0 Mon Sep 17 00:00:00 2001 From: Marc LION Date: Sun, 19 Nov 2023 17:03:56 +0100 Subject: [PATCH] [ngssm-toolkit] Add helper to manage opening/closing dialog --- .../ngssm-toolkit/src/lib/mat-dialog/index.ts | 2 + .../mat-dialog-opening.effect.spec.ts | 86 +++++++++++++++++++ .../mat-dialog/mat-dialog-opening.effect.ts | 48 +++++++++++ .../lib/mat-dialog/ngssm-mat-dialog-config.ts | 16 ++++ .../mat-dialog/provide-ngssm-mat-dialog.ts | 11 +++ projects/ngssm-toolkit/src/public-api.ts | 1 + src/app/app.module.ts | 6 +- src/app/toolkit/actions/index.ts | 1 + .../actions/toolkit-demo-action-type.ts | 4 + .../dialog-demo/dialog-demo.component.html | 10 +++ .../dialog-demo/dialog-demo.component.scss | 5 ++ .../dialog-demo/dialog-demo.component.ts | 25 ++++++ .../toolkit-demo/toolkit-demo.component.html | 29 +++++-- .../toolkit-demo/toolkit-demo.component.ts | 5 ++ src/app/toolkit/provide-toolkit-demo.ts | 21 +++++ src/app/toolkit/public-api.ts | 1 + 16 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 projects/ngssm-toolkit/src/lib/mat-dialog/index.ts create mode 100644 projects/ngssm-toolkit/src/lib/mat-dialog/mat-dialog-opening.effect.spec.ts create mode 100644 projects/ngssm-toolkit/src/lib/mat-dialog/mat-dialog-opening.effect.ts create mode 100644 projects/ngssm-toolkit/src/lib/mat-dialog/ngssm-mat-dialog-config.ts create mode 100644 projects/ngssm-toolkit/src/lib/mat-dialog/provide-ngssm-mat-dialog.ts create mode 100644 src/app/toolkit/actions/index.ts create mode 100644 src/app/toolkit/actions/toolkit-demo-action-type.ts create mode 100644 src/app/toolkit/components/dialog-demo/dialog-demo.component.html create mode 100644 src/app/toolkit/components/dialog-demo/dialog-demo.component.scss create mode 100644 src/app/toolkit/components/dialog-demo/dialog-demo.component.ts create mode 100644 src/app/toolkit/provide-toolkit-demo.ts diff --git a/projects/ngssm-toolkit/src/lib/mat-dialog/index.ts b/projects/ngssm-toolkit/src/lib/mat-dialog/index.ts new file mode 100644 index 00000000..d6b3898e --- /dev/null +++ b/projects/ngssm-toolkit/src/lib/mat-dialog/index.ts @@ -0,0 +1,2 @@ +export * from './provide-ngssm-mat-dialog'; +export * from './ngssm-mat-dialog-config'; diff --git a/projects/ngssm-toolkit/src/lib/mat-dialog/mat-dialog-opening.effect.spec.ts b/projects/ngssm-toolkit/src/lib/mat-dialog/mat-dialog-opening.effect.spec.ts new file mode 100644 index 00000000..681ca74d --- /dev/null +++ b/projects/ngssm-toolkit/src/lib/mat-dialog/mat-dialog-opening.effect.spec.ts @@ -0,0 +1,86 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TestBed } from '@angular/core/testing'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; + +import { StoreMock } from 'ngssm-store/testing'; + +import { MatDialogOpeningEffect } from './mat-dialog-opening.effect'; +import { provideNgssmMatDialogConfigs } from './ngssm-mat-dialog-config'; + +enum TestingActionType { + openAction = '[TestingActionType] openAction', + closeAction = '[TestingActionType] closeAction' +} + +@Component({ + selector: 'ngssm-testing', + standalone: true, + imports: [CommonModule], + template: ` nothing `, + styles: [], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DialogDemoComponent {} + +class MatDialogRefMock { + public close(): void { + // nothing to do here + } +} + +describe('MatDialogOpeningEffect', () => { + let effect: MatDialogOpeningEffect; + let matDialog: MatDialog; + let dialog: MatDialogRefMock; + let store: StoreMock; + + beforeEach(() => { + store = new StoreMock({}); + TestBed.configureTestingModule({ + imports: [MatDialogModule], + providers: [ + MatDialogOpeningEffect, + provideNgssmMatDialogConfigs({ + openingAction: TestingActionType.openAction, + closingAction: TestingActionType.closeAction, + component: DialogDemoComponent, + matDialogConfig: { + disableClose: true, + height: '400px', + width: '60vw' + } + }) + ] + }); + effect = TestBed.inject(MatDialogOpeningEffect); + matDialog = TestBed.inject(MatDialog); + dialog = new MatDialogRefMock(); + spyOn(matDialog, 'open').and.returnValue(dialog as any); + spyOn(dialog, 'close'); + }); + + [TestingActionType.openAction, TestingActionType.closeAction].forEach((actionType: string) => { + it(`should process action of type '${actionType}'`, () => { + expect(effect.processedActions).toContain(actionType); + }); + }); + + it(`should open the dialog when calling the action type '${TestingActionType.openAction}'`, () => { + effect.processAction(store as any, store.stateValue, { type: TestingActionType.openAction }); + + expect(matDialog.open).toHaveBeenCalledWith(DialogDemoComponent, { + disableClose: true, + height: '400px', + width: '60vw' + }); + }); + + it(`should close the dialog when calling the action type '${TestingActionType.closeAction}'`, () => { + effect.processAction(store as any, store.stateValue, { type: TestingActionType.openAction }); + + effect.processAction(store as any, store.stateValue, { type: TestingActionType.closeAction }); + + expect(dialog.close).toHaveBeenCalled(); + }); +}); diff --git a/projects/ngssm-toolkit/src/lib/mat-dialog/mat-dialog-opening.effect.ts b/projects/ngssm-toolkit/src/lib/mat-dialog/mat-dialog-opening.effect.ts new file mode 100644 index 00000000..56420b54 --- /dev/null +++ b/projects/ngssm-toolkit/src/lib/mat-dialog/mat-dialog-opening.effect.ts @@ -0,0 +1,48 @@ +import { Inject, Injectable, Optional } from '@angular/core'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; + +import { Effect, Store, State, Action, Logger } from 'ngssm-store'; + +import { NGSSM_MAT_DIALOG_CONFIG, NgssmMatDialogConfig } from './ngssm-mat-dialog-config'; + +interface ExtendedConfig { + config: NgssmMatDialogConfig; + dialog?: MatDialogRef; +} + +@Injectable() +export class MatDialogOpeningEffect implements Effect { + private readonly extendedConfigs: ExtendedConfig[]; + + public readonly processedActions: string[] = []; + + constructor( + @Inject(NGSSM_MAT_DIALOG_CONFIG) @Optional() private configs: NgssmMatDialogConfig[], + private logger: Logger, + private matDialog: MatDialog + ) { + this.processedActions.push(...(this.configs ?? []).flatMap((c) => [c.openingAction, c.closingAction])); + this.extendedConfigs = (this.configs ?? []).map((c) => ({ + config: c + })); + } + + public processAction(store: Store, state: State, action: Action): void { + const extendedConfig = this.extendedConfigs.find( + (c) => c.config.openingAction === action.type || c.config.closingAction === action.type + ); + + if (!extendedConfig) { + this.logger.error(`Need to process action '${action.type}' with no associated config.`); + return; + } + + if (action.type === extendedConfig.config.openingAction) { + extendedConfig.dialog = this.matDialog.open(extendedConfig.config.component, extendedConfig.config.matDialogConfig); + return; + } + + extendedConfig.dialog?.close(); + extendedConfig.dialog = undefined; + } +} diff --git a/projects/ngssm-toolkit/src/lib/mat-dialog/ngssm-mat-dialog-config.ts b/projects/ngssm-toolkit/src/lib/mat-dialog/ngssm-mat-dialog-config.ts new file mode 100644 index 00000000..a42a40ac --- /dev/null +++ b/projects/ngssm-toolkit/src/lib/mat-dialog/ngssm-mat-dialog-config.ts @@ -0,0 +1,16 @@ +import { EnvironmentProviders, InjectionToken, makeEnvironmentProviders } from '@angular/core'; +import { ComponentType } from '@angular/cdk/portal'; +import { MatDialogConfig } from '@angular/material/dialog'; + +export interface NgssmMatDialogConfig { + openingAction: string; + closingAction: string; + component: ComponentType; + matDialogConfig?: MatDialogConfig; +} + +export const NGSSM_MAT_DIALOG_CONFIG = new InjectionToken('NGSSM_MAT_DIALOG_CONFIG'); + +export const provideNgssmMatDialogConfigs = (...configs: NgssmMatDialogConfig[]): EnvironmentProviders => { + return makeEnvironmentProviders(configs.map((config) => ({ provide: NGSSM_MAT_DIALOG_CONFIG, useValue: config, multi: true }))); +}; diff --git a/projects/ngssm-toolkit/src/lib/mat-dialog/provide-ngssm-mat-dialog.ts b/projects/ngssm-toolkit/src/lib/mat-dialog/provide-ngssm-mat-dialog.ts new file mode 100644 index 00000000..57f5c032 --- /dev/null +++ b/projects/ngssm-toolkit/src/lib/mat-dialog/provide-ngssm-mat-dialog.ts @@ -0,0 +1,11 @@ +import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core'; + +import { provideEffects } from 'ngssm-store'; + +import { MatDialogOpeningEffect } from './mat-dialog-opening.effect'; + +export interface ProvideNgssmMatDialog {} + +export const provideNgssmMatDialog = (): EnvironmentProviders => { + return makeEnvironmentProviders([provideEffects(MatDialogOpeningEffect)]); +}; diff --git a/projects/ngssm-toolkit/src/public-api.ts b/projects/ngssm-toolkit/src/public-api.ts index d9ca3cf9..ea3c33b4 100644 --- a/projects/ngssm-toolkit/src/public-api.ts +++ b/projects/ngssm-toolkit/src/public-api.ts @@ -15,3 +15,4 @@ export * from './lib/ngssm-component-display.directive'; export * from './lib/ngssm-help/ngssm-help.component'; export * from './lib/blob-helpers'; export * from './lib/luxon-helpers'; +export * from './lib/mat-dialog'; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d684f16f..55bf91c8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -11,6 +11,7 @@ import { defaultRegexEditorValidator, MaterialImportsModule, NGSSM_REGEX_EDITOR_VALIDATOR, + provideNgssmMatDialog, RegexEditorValidator, useDefaultErrorStateMatcher } from 'ngssm-toolkit'; @@ -29,6 +30,7 @@ import { ShellDemoModule } from './shell-demo/public-api'; import { TreeDataService } from './ngssm-tree-demo/tree-data.service'; import { provideRemoteDataDemo } from './remote-data-demo/public-api'; import { provideJsonBuilder } from './ngssm-expression-tree-demo/json-builder/provide-json-builder'; +import { provideToolkitDemo } from './toolkit/public-api'; const dotnetRegexValidator: RegexEditorValidator = { validatePattern: (pattern: string) => { @@ -97,10 +99,12 @@ const dotnetRegexValidatorFactory = (): RegexEditorValidator => { provideNgssmExpressionTree(), provideNgssmVisibility(), provideNgssmServiceInfo(), + provideNgssmMatDialog(), { provide: NGSSM_TREE_DATA_SERVICE, useClass: TreeDataService, multi: true }, { provide: NGSSM_REGEX_EDITOR_VALIDATOR, useFactory: dotnetRegexValidatorFactory }, provideRemoteDataDemo(), - provideJsonBuilder() + provideJsonBuilder(), + provideToolkitDemo() ], bootstrap: [AppComponent] }) diff --git a/src/app/toolkit/actions/index.ts b/src/app/toolkit/actions/index.ts new file mode 100644 index 00000000..f99a2c29 --- /dev/null +++ b/src/app/toolkit/actions/index.ts @@ -0,0 +1 @@ +export * from './toolkit-demo-action-type'; diff --git a/src/app/toolkit/actions/toolkit-demo-action-type.ts b/src/app/toolkit/actions/toolkit-demo-action-type.ts new file mode 100644 index 00000000..59697e55 --- /dev/null +++ b/src/app/toolkit/actions/toolkit-demo-action-type.ts @@ -0,0 +1,4 @@ +export enum ToolkitDemoActionType { + openDialogDemo = '[ToolkitDemoActionType] openDialogDemo', + closeDialogDemo = '[ToolkitDemoActionType] closeDialogDemo' +} diff --git a/src/app/toolkit/components/dialog-demo/dialog-demo.component.html b/src/app/toolkit/components/dialog-demo/dialog-demo.component.html new file mode 100644 index 00000000..33b116fd --- /dev/null +++ b/src/app/toolkit/components/dialog-demo/dialog-demo.component.html @@ -0,0 +1,10 @@ +

Mat dialog helper demo

+ + Some content to be put here + + + + + diff --git a/src/app/toolkit/components/dialog-demo/dialog-demo.component.scss b/src/app/toolkit/components/dialog-demo/dialog-demo.component.scss new file mode 100644 index 00000000..cbb0c22f --- /dev/null +++ b/src/app/toolkit/components/dialog-demo/dialog-demo.component.scss @@ -0,0 +1,5 @@ +:host { + display: flex; + flex-direction: column; + height: 100%; +} \ No newline at end of file diff --git a/src/app/toolkit/components/dialog-demo/dialog-demo.component.ts b/src/app/toolkit/components/dialog-demo/dialog-demo.component.ts new file mode 100644 index 00000000..2d90444f --- /dev/null +++ b/src/app/toolkit/components/dialog-demo/dialog-demo.component.ts @@ -0,0 +1,25 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; + +import { NgSsmComponent, Store } from 'ngssm-store'; +import { ToolkitDemoActionType } from '../../actions'; + +@Component({ + selector: 'app-dialog-demo', + standalone: true, + imports: [CommonModule, MatDialogModule, MatButtonModule], + templateUrl: './dialog-demo.component.html', + styleUrls: ['./dialog-demo.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DialogDemoComponent extends NgSsmComponent { + constructor(store: Store) { + super(store); + } + + public close(): void { + this.dispatchActionType(ToolkitDemoActionType.closeDialogDemo); + } +} diff --git a/src/app/toolkit/components/toolkit-demo/toolkit-demo.component.html b/src/app/toolkit/components/toolkit-demo/toolkit-demo.component.html index d40f8849..2c20001e 100644 --- a/src/app/toolkit/components/toolkit-demo/toolkit-demo.component.html +++ b/src/app/toolkit/components/toolkit-demo/toolkit-demo.component.html @@ -1,10 +1,10 @@ - + Demo of the components provided by the ngssm-toolkit library - + File Picker @@ -30,7 +30,7 @@ - + Notifier @@ -49,7 +49,7 @@ - + Confirmation dialog @@ -74,7 +74,7 @@ - + Regex Editor @@ -96,7 +96,7 @@ - + Component display directive @@ -119,7 +119,7 @@ - + Help button @@ -140,5 +140,20 @@ + + + + Mat dialog opener + + + + + + + + + \ No newline at end of file diff --git a/src/app/toolkit/components/toolkit-demo/toolkit-demo.component.ts b/src/app/toolkit/components/toolkit-demo/toolkit-demo.component.ts index 2c422049..8f316e88 100644 --- a/src/app/toolkit/components/toolkit-demo/toolkit-demo.component.ts +++ b/src/app/toolkit/components/toolkit-demo/toolkit-demo.component.ts @@ -24,6 +24,7 @@ import { import { OverlayDemoComponent } from '../overlay-demo/overlay-demo.component'; import { Demo1Component } from '../demo1/demo1.component'; import { Demo2Component } from '../demo2/demo2.component'; +import { ToolkitDemoActionType } from '../../actions'; @Injectable({ providedIn: 'root' @@ -147,4 +148,8 @@ export class ToolkitDemoComponent extends NgSsmComponent { this.fileControl.setValue(undefined, { emitEvent: false }); }, 1000); } + + public openDialogDemo(): void { + this.dispatchActionType(ToolkitDemoActionType.openDialogDemo); + } } diff --git a/src/app/toolkit/provide-toolkit-demo.ts b/src/app/toolkit/provide-toolkit-demo.ts new file mode 100644 index 00000000..aa688ec0 --- /dev/null +++ b/src/app/toolkit/provide-toolkit-demo.ts @@ -0,0 +1,21 @@ +import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core'; + +import { provideNgssmMatDialogConfigs } from 'ngssm-toolkit'; + +import { ToolkitDemoActionType } from './actions'; +import { DialogDemoComponent } from './components/dialog-demo/dialog-demo.component'; + +export const provideToolkitDemo = (): EnvironmentProviders => { + return makeEnvironmentProviders([ + provideNgssmMatDialogConfigs({ + openingAction: ToolkitDemoActionType.openDialogDemo, + closingAction: ToolkitDemoActionType.closeDialogDemo, + component: DialogDemoComponent, + matDialogConfig: { + disableClose: true, + height: '400px', + width: '60vw' + } + }) + ]); +}; diff --git a/src/app/toolkit/public-api.ts b/src/app/toolkit/public-api.ts index 8333f2ec..0919f74e 100644 --- a/src/app/toolkit/public-api.ts +++ b/src/app/toolkit/public-api.ts @@ -1 +1,2 @@ export * from './toolkit-routes'; +export * from './provide-toolkit-demo';